Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
EXTREME GRAPHICAL SIMPLIFICATION
An Major Qualifying ProjectSubmitted to the faculty of
Worcester Polytechnic Institutein partial fulfillment of the requirements for the
Degree of Bachelor of Science
by
John Reynolds
Paul Fydenkeves
Matt Gage
Professor Emmanuel Agu
Advisor
Date: May 18, 2023
2
ABSTRACT
Complex 3D models usually require more processing power
than the average mobile device can provide. A popular tech-
nique used in games and real-time rendering is to substitute
polygonal representations and meshes with image-based repre-
sentations such as billboards, sprites, and imposters. Using Bill-
board Clouds, a recently proposed image-based 3D model simpli-
fication algorithm, it is possible for a high-end server to simplify
a complex 3D mesh that suits a mobile client’s processing capa-
bilities. This MQP implements the Billboard Cloud algorithm in
Java3D.
3
Table of Contents1 Introduction.............................................................................82 Literature...............................................................................10
2.1 Image-Based Rendering.................................................102.1.1 Sprites....................................................................102.1.2 Billboarding...........................................................132.1.3 Imposters...............................................................152.1.4 Lens Flare..............................................................172.1.5 Particle Systems.....................................................192.1.6 Depth Sprites.........................................................192.1.7 Hierarchical Image Caching..................................202.1.8 Full-Screen Billboarding........................................212.1.9 Skyboxes................................................................212.1.10 Fixed-View Effects...............................................22
2.2 Billboard Clouds............................................................232.2.1 What is a Billboard Cloud?.....................................242.2.2 Setting up a Billboard Cloud..................................262.2.3 Greedy Optimization..............................................262.2.4 Discretization of Plane Space................................272.2.5 View-Dependent Optimization...............................28
2.2.5.1 Validity...........................................................282.2.6 Implementation......................................................29
2.2.6.1 Texture Usage................................................302.3 Java3D............................................................................30
2.3.1 Introduction...........................................................312.3.2 Architecture of Java3D...........................................312.3.3 OpenGL vs. DirectX in Java3D...............................322.3.4 Java3D vs. Other Graphical APIs............................33
2.4 Mobile Devices...............................................................332.4.1 Introduction...........................................................332.4.2 Mobile Phones........................................................34
2.4.2.1 System Specifications....................................342.4.3 Personal Digital Assistants (PDAs).........................35
2.4.3.1 System Specifications....................................352.4.3.2 Display...........................................................37
2.4.4 Mobile Devices and Graphics.................................373 Methodology..........................................................................38
3.1 Helper Programs............................................................383.1.1 Model Displayer.....................................................38
3.2 Algorithms.....................................................................393.2.1 3-D Hough Transforms...........................................393.2.2 Projection onto a plane..........................................40
4
3.2.3 Validity Check........................................................413.2.4 Best-Fit Projection Rectangle................................433.2.5 Spherical Equidistant Separation..........................45
4 Implementation......................................................................484.1 Helper Programs............................................................50
4.1.1 Model Displayer.....................................................504.1.1.1 Function: loadMesh( )....................................51
4.2 Algorithms.....................................................................554.2.1 3-D Hough Transforms...........................................55
4.2.1.1 Function: funcHoughTransform( ) – 3 Dimen-sional.................................................................................55
4.2.1.2 Function: funcHoughTransform( ) – 2 Dimen-sional.................................................................................57
4.2.2 Projection onto a plane..........................................574.2.2.1 Function: orthoProject( )................................58
4.2.3 Validity Check........................................................594.2.3.1 Function: isValid( ).........................................59
4.2.4 Best-Fit Projection Rectangle for Set of Points in Plane Space..........................................................................60
4.2.4.1 Function: brGetRotations( )...........................614.2.4.2 Function: brRotationX/Z( ).............................624.2.4.3 Function: brGetXYCoords( )...........................634.2.4.4 Function: brAreaFromAngle( ).......................644.2.4.5 Function: brGetBestArea( )............................65
4.2.5 Spherical Equidistant Separation..........................694.2.5.1 Function: makeSphere( )................................69
5 Results...................................................................................735.1 Screenshots...................................................................735.2 Running Time of Algorithm............................................785.3 Final State of Implementation.......................................79
5.3.1 Shortcomings.........................................................795.3.1.1 Shooting Textures..........................................805.3.1.2 Java/Java3D only allocates 82 Megabytes of
Memory.............................................................................815.3.1.3 Image File Compression.................................835.3.1.4 Choosing Billboards.......................................835.3.1.5 Liberal use of ArrayList..................................845.3.1.6 Two Coordinate Systems................................845.3.1.7 Better Model (.ply) Files................................85
5.3.2 Achievements.........................................................855.3.2.1 Get Best-Fit Rectangle Algorithm..................855.3.2.2 Everything Else..............................................86
5
6 Conclusion.............................................................................877 References.............................................................................898 Appendix................................................................................90
8.1 Instructions on Running Code.......................................908.1.1 Setting up the Environment...................................908.1.2 Program Descriptions............................................90
8.2 Code...............................................................................93
6
Table of FiguresFigure 1: Sprites.......................................................................11Figure 2: Mobile Sprite............................................................11Figure 3: Animated Sprite........................................................12Figure 4: Schaufler's worst case angular discrepancy metric [7]
...................................................................................................16Figure 5: Lens Flare.................................................................18Figure 6: Lens Flare off-center.................................................18Figure 7: Original 3D Model [6]...............................................25Figure 8: Optimal Set of Billboards [6].....................................25Figure 9: Texture & Transparency Maps for Billboards [6].....25Figure 10: Billboard Clouds Representation [6].......................25Figure 11: N-Gage Mobile Phone.............................................35Figure 12: PDA running PalmOS..............................................36Figure 13: Projection................................................................40Figure 14: Validity Diagram.....................................................42Figure 15: Points on a plane.....................................................43Figure 16: Finding Rectangle Width........................................44Figure 17: Finding Rectangle Height.......................................44Figure 18: Best-Fit Rectangle...................................................44Figure 19: Random points on a sphere.....................................45Figure 20: Sum of all magnitudes and directions of every other
point...........................................................................................46Figure 21: New position of P....................................................46Figure 22: High-Level Diagram of Implementation..................48Figure 23: Detailed Calculation Diagram.................................49Figure 24: Function: loadMesh() (1 of 3)..................................51Figure 25: Function: loadMesh() (2 of 3)..................................53Figure 26: Function: loadMesh() (3 of 3)..................................54Figure 27: Function: funcHoughTransform() – 3D...................55Figure 28: Function: funcHoughTransform() - 2D....................57Figure 29: Function: orthoProject()..........................................58Figure 30: Function: isValid()...................................................59Figure 31: Function: brGetRotations().....................................61Figure 32: Function: brRotationX/Z().......................................62Figure 33: Function: brGetXYCoords().....................................63Figure 34: Function: brAreaFromAngle().................................64Figure 35: Function: brGetBestArea() (1 of 5).........................65Figure 36: Function: brGetBestArea() (2 of 5).........................66Figure 37: Function: brGetBestArea() (3 of 5).........................67Figure 38: Function: brGetBestArea() (4 of 5).........................68Figure 39: Function: brGetBestArea() (5 of 5).........................69
7
Figure 40: Function: makeSphere() (1 of 4).............................69Figure 41: Function: makeSphere() (2 of 4).............................70Figure 42: Function: makeSphere() (3 of 4).............................71Figure 43: Function: makeSphere() (4 of 4).............................71Figure 44: Original 3D Bunny...................................................73Figure 45: Original 3D Dragon.................................................73Figure 46: Original 3D Bunny with Optimal Billboard Set.......74Figure 47: Original 3D Dragon with Optimal Billboard Set.....74Figure 48: Original 3D Bunny with Affected Faces..................74Figure 49: Original 3D Dragon with Affected Faces.................74Figure 50: Billboard Representation Bunny with Low Error
(with Lighting)............................................................................75Figure 51: Billboard Representation Dragon with Low Error
(with Lighting)............................................................................75Figure 52: Billboard Representation Bunny with High Error
(with Lighting)............................................................................76Figure 53: Billboard Representation Dragon with High Error
(with Lighting)............................................................................76Figure 54: Billboard Representation Bunny with Low Error
(without Lighting).......................................................................77Figure 55: Billboard Representation Dragon with Low Error
(without Lighting).......................................................................77Figure 56: Billboard Representation Bunny with High Error
(without Lighting).......................................................................77Figure 57: Billboard Representation Dragon with High Error
(without Lighting).......................................................................77Figure 58: Running Time of Algorithm.....................................78Figure 59: 3 Programs to Run..................................................91
8
Table of EquationsEquation 1: Theta Screen.........................................................16Equation 2: 3D Hough Transform.............................................40Equation 3: Projection Formula................................................41Equation 4: Constant................................................................41Equation 5: New position of P..................................................46Equation 6: Error Bound..........................................................47
9
1 INTRODUCTION
“There are some things in this world that will never
change…some things do change.” ~Morpheus
20 years ago when the video game industry was just born,
gaming system users jumped at all the new games that were
coming out. During that time, video games were restricted to
the consoles, systems dedicated to one purpose—to play games.
Because the industry was brand new, the hardware in these con-
soles was very primitive. However, the 2-dimensional games
available were a new concept, and a huge hit nonetheless.
20 years later, some things did not change, and some
things did. Games are still plentiful, and people never stopped
playing them. However, games did evolve—and with this evolu-
tion, came the third dimension. Very, very few games that are
created in the present day are rendered in two dimensions. Ev-
ery new game that comes to the market has a huge library of
polygons that make up the characters and objects. Of course,
games are not the only medium restricted to 3-Dimensional poly-
10
gons. The movie, business, and other industries also benefit
from graphics rendered in 3D space.
Moreover, in the past 20 years, the personal computer has
come into existence. While having the ability to replace the
gaming console, the personal computer is less bound to the pa-
rameters of just games. In fact, it can be applied to any indus-
try.
With such a powerful tool as the personal computer, it only
makes sense that the next step in the evolution of technology is
to be able to take your personal computer with you anywhere—
making it mobile. Laptops of course, have grown quite powerful
in that it is possible for a laptop to replace a desktop personal
computer. However, other mobile devices have appeared in the
last few years that are not meant to replace the personal com-
puter, but to compliment it. Devices such as Personal Digital As-
sistants (PDAs) were created to enable someone to do small
tasks previously done on a personal computer, in an extremely
mobile way. The pocket-sized PDAs have extreme mobility, but
come at a price of pure computing power.
Since PDAs are relatively new to the market, they have
something in common with the console gaming systems of 20
11
years ago—they can only really handle 2D renderings. So the
question is: what if someone wants to view a complex 3D model
on their mobile device? One answer is billboard clouds.
12
2 LITERATUREIn order to better understand 3-Dimensional graphics ren-
dering, this section will detail the basic ideas behind 3-Dimen-
sional graphics rendering and a few other methods that relate to
this topic. Other possible methods to substitute for rendering a
3-Dimensional mesh model are discussed as to how the help with
the rendering of objects, as well as how they relate to the Bill-
board Clouds algorithm.
2.1 Image-Based Rendering
Image-Based Rendering (IBR) is a technique that displays
objects using images rather than polygons. Images are a much
more efficient way to render complex models because only the
visible pixels in view need to be rendered, rather than all the
vertices of a polygon. There is also another advantage of using
IBR techniques. Intricate objects such as clouds and fire, are
much easier to depict using images whereas it is almost impossi-
ble to render these non-solid objects as polygons. [1]
2.1.1 Sprites
A sprite is simply a 2-dimensional object on a screen. It
usually represents an object within the program for which it was
13
designed. For instance, a sprite could be used to represent a
spaceship in a computer game. Figure 1 shows a few examples
of sprites used in games:
A sprite can be stationary, mobile, or animated. A station-
ary sprite typically sits motionless on the screen. Figure 1-A and
C below are sprites that are currently stationary on the screen.
The seven sprites in Figure 1-B (the six players and the ball) are
also stationary.
The sprite, which is just a picture, can be also be moved
around the screen—becoming a mobile sprite. Figure 2 below
shows an example of a mobile sprite.
Figure 1: Sprites
14
(A) (B) (C)
In Figure 2-A, the sprite representing the person is on the
left side of the screen. In Figure 2-B, the sprite representing the
person is more to the right side of the screen. This is an exam-
ple of a mobile sprite because the same image is represented in
different parts of the screen.
A sprite can also be animated. This means that the same
object represented in the game, can be switched with another
sprite while keeping the position of the sprite the same with the
sprite it just replaced.
Figure 2: Mobile Sprite
15
(A
)
(B
)
Figure 3 shows sprites representing a walking character.
Figure 3-A shows a sprite of the character stepping forward.
Figure 3-B represents the character in mid-walking motion or
even standing still. Figure 3-C shows the character stepping for-
ward now, with the other foot (as apparent because the charac-
ter’s arms are now swinging opposite from before). All these
sprites are switched in and out to simulate that the character is
walking.
Sprites are very efficient, take up very little memory, and
are easy to manipulate. Sprites are used in almost all of 2-di-
mensional graphical applications. However, the sprite has be-
gun to be pushed aside in favor of polygonal meshes. Polygonal
meshes are 3-dimensional objects that can be rendered once and
represent all angles of the object. Whereas a sprite, just being
2-dimensional, would have to represent a different side of the
object by switching in a new sprite with every angle change. [1]
Figure 3: Animated Sprite
16
(A
)
(B
)
(C
)
Layering a scene using sprites can be an effective tool that
saves resources. Each sprite layer is accompanied with a depth.
Thus, Z-buffering is eliminated when rendering in a back-to-front
order. [1]
QuickTime VR is a technique where a photo-realistic image
is rendered on the inside of a cylinder in which the camera is
placed in the center. Therefore, when the viewer rotates the
camera, the image within view is retrieved and displayed. Using
this technique, the viewer has control over how to view the
scene. [1]
Lumigraph is similar to QuickTime VR in that instead of
rendering a scene, it renders, by storing an image array of al-
most every possible angle, thus giving the viewer a full represen-
tation of the object. [1]
2.1.2 Billboarding
Billboarding is a technique where polygons are oriented
based on the view direction. As the view changes, the polygon's
orientation changes in a few different ways, depending upon
which type of billboard it is. When combined with alpha textur-
ing and animation, billboarding can be used for effects such as
smoke, fire, fog and explosions among others. For each bill-
17
board, two vectors are needed: a surface normal and an up di-
rection. Usually, the normal vector is fixed, but in the case of axi-
ally-aligned billboards, the up vector remains fixed while the
normal vector is what needs to be moved. Using these two vec-
tors, you can create a rotation matrix for the billboard. [1]
Screen-aligned billboards are the simplest types of bill-
boards to create. For all billboards of this type, the surface nor-
mal is the negation of the view plane's normal. The up vector is
the same as that of the camera itself. Billboards that use this
kind of orientation will always have the same look to the camera.
This is useful for things such as annotation text and particles (or
other circular sprites) but is pretty much limited to that for real-
ism purposes. [1]
World-Oriented billboards are oriented with a global up
vector (i.e. the normal to the "ground" usually). If the normal
and up vectors are used to form a rotation matrix that are used
for all of the billboards in a scene, you end up with a problem of
distortion with billboards that are further away from the view-
axis. If the billboards are nearby there can be a problem with re-
alism, so something called viewpoint-oriented billboards are
used. To do this, you need to set the billboard's normal vector as
18
a vector from the billboard's position to the viewpoint's position,
thus having a unique rotation matrix for every billboard on the
scene. [1]
In axial billboards, the up vector of an object is specified,
and un-modifiable, while all other vectors are to rotate towards
the viewer. The most commonly used example of an axial bill-
board is that of a tree. While flying around the tree looks normal
(although the tree always faces towards the viewer, which might
be a bit odd in reality), flying above it and looking down would
show artifacts, resembling a cardboard-like cutout. Although
trees are the primary cited example of axial billboards, essen-
tially any cylindrically shaped object can easily be represented
by an axial billboard, as long as the up vector runs the length of
the cylindrical axis. [1]
2.1.3 Imposters
An imposter is basically a form of a billboard. The imposter
is created by rendering a complex object from the current view-
point into an image texture, and then mapping the texture onto
the billboard. This creation of the object is proportional to the
number of pixels the imposter covers on the screen. This is im-
portant in helping to create images much faster than by creating
19
the object and modifying the object each time the viewpoint
changes or the object moves, because an imposter should be
faster to draw than the object it represents, it should resemble
the object, and it should be reused for several viewpoints. [1]
Imposters can be used in a few instances of the object or a
few frames. They are good for using with collections of small
static objects and for rendering distant objects rapidly, which
have a few key advantages. An imposter can create a low Level
of Detail (LoD) model that does not lose shape or color informa-
tion and it can also create an out of focus image by using a low-
pass filter to help with a depth of field effect. This is an easier
method for drawing because the farther the object is away from
the viewer, the closer the viewpoints are together, creating a
slow moving object. One more way to use imposters is when an
object is close to the viewer and tends to show the same side of
the object to the viewer. [1]
One problem with imposters is the degree of error in creat-
ing them. When creating the imposter the viewer is set to view
the center of the bounding box of the object and the imposter
polygon is chosen so it points directly at the viewpoint. The im-
poster size is the smallest rectangle to contain the projected
20
bounding box of the object. The texture of the imposter is then
mapped onto the polygon. If the angle created by the viewer
moving sideways (ßtrans) becomes too large, the imposter needs
to be updated. Also, if the viewer moves too close to the im-
poster, it needs to be redrawn (ßtrans, ßtex, ßsize). Basically, the im-
poster needs to be redrawn every time ßtrans, ßsize, or ßtex is
greater than ßscr. This is shown in Figure 4 below:
where:
Another method is to determine the largest distance any
vertex moves during the entire animation. This distance is then
Figure 4: Schaufler's worst case angular discrepancy metric [7]
Equation 1: Theta Screen
21
divided by the number of time steps in the animation. If this
value multiplied by the number of frames the imposter has been
used for is greater than a threshold set by the user, then the im-
poster needs to be regenerated. [1]
Imposters are important in increasing the speed to create a
scene by replacing the creation of the actual objects with im-
posters. It is good for using with distant objects or collections of
dynamic objects. The ability to create and edit these images
faster than creating the actual object and redrawing it are a
great advantage for use in graphics. [1]
2.1.4 Lens Flare
When direct light hits the lens of a camera, the light is sup-
posed to pass through the glass lens; however, this is not always
the case. Light can reflect off the surface of the glass, and then
may bounce around on the inside of the camera. This is called a
lens flare. Lens flares are usually referred to as “mistakes” in
the photography world. However, they are regularly used in the
computer graphics community due to their aesthetically pleasing
effect. [1]
The lens flare is composed of two elements: flare and
bloom. The flare consists of a lenticular halo and a ciliary
22
corona. The halo is made up of colored concentric rings where
the inside ring is violet and the outside ring is red. The corona is
made up of bright rays emitted from the central point of the light
source. The bloom is the brightness of the light source. Having
a large bloom is usually used to simulate the sun, since the light
intensity of a computer monitor has difficulty displaying bright-
ness that powerful. [1]
The image on the left of Figure 5 shows a lens flare with a
small bloom while the image on the right has a very large bloom.
Figure 5: Lens Flare
23
Lenticular
Halo
Ciliary
Corona
Bloo
m
Bloo
m
The lens flare is usually implemented using a set of sprites
affixed to a polygon that is oriented to face the camera, thus cre-
ating a billboard. Unlike the flares in Figure 5 where the light
source is dead center of the camera, the lens flare is more realis-
tic when it moves with the camera as in Figure 6. The billboards
are arranged in a line drawn from the light source through the
center of the screen. [1]
2.1.5 Particle Systems
A particle system is a collection of individual particles or
small objects where each holds any number of attributes that de-
termine the behavior and movement of the particle. Particle sys-
tems are created to model things such as fireworks, trees, or
even birds. [1]
Figure 6: Lens Flare off-center
24
Implementations of particle systems vary among applica-
tions. Billboards and points are the most common. A particle
can most obviously be represented as a point on the screen from
which attributes such as speed, direction, and color are manipu-
lated through an algorithm. Lines are another popular form of
particle representation. In a loop, a line segment can be drawn
from the initial location of the particle to its final location. Parti-
cles can also be represented as billboards. Each particle is
placed on a polygon that is then oriented to the screen. How-
ever, if the particle is a point, then there is no need to manage
the polygon’s up-vector, rather only its position in space. [1]
2.1.6 Depth Sprites
Depth sprites (or nailboards) are born when the texture of
an imposter comes together with a depth attribute. Therefore,
depth sprites are RGB images with a depth. The advantage of a
depth sprite over an imposter is an avoidance of visibility bugs
inherent in object collisions. [1]
The nailboard rendering algorithm is accomplished in soft-
ware because current hardware architecture does not support
rendering nailboards at run-time. [1]
25
2.1.7 Hierarchical Image Caching
Hierarchical image caching is an algorithm that arranges
imposters in a hierarchy for better performance. Essentially, an
entire scene is separated into a Binary Space Paritioning (BSP)
tree, where the dividing planes form a series of main-axis-
aligned cubes and each cube is then made into an imposter. The
algorithms that power the image caching must keep in mind two
things: it must try to keep the number of primitives in each box
the approximately the same, and it must try to minimize the
number of interactions between the dividing planes and objects
inside. If the number of boxes that the algorithm produces is too
high, then the costs related to creating the imposters will out-
weigh the benefits of having them. Also, if an object is split into
multiple boxes, then cracks in the image may occur due to
rounding errors. To avoid this effect, an easy solution is to just
make each BSP box slightly larger. [1]
To render a scene using hierarchical image caching, two
steps are in order. First off, all objects outside the viewable area
(view frustum) are culled out, and any invalid imposters are ren-
dered and propagated to the root of the hierarchy. Secondly, the
imposters that are inside of the view frustum are all rendered
26
from back to front. Because this process requires such a large
amount of pre-calculation for the BSP tree, it is infeasible to
have dynamically changing objects rendered using this method.
One possible idea for a solution however is to use depth sprites
to avoid visibility and depth problems. [1]
2.1.8 Full-Screen Billboarding
Full-screen billboarding is often used for different effects
such as night-vision goggles, and flash effects. Essentially, this
technique involves setting a billboard facing the viewer at all
times, and taking up the entire view screen. For the two above
effects to work for example, you could have a green-alpha-
blended billboard for the night-vision, and you could have a
white billboard that increases and decreases rapidly in opacity
for a flash effect. Also, to simulate an environment, billboards
can be placed behind all the objects in a scene. For this to work
correctly though, the 'u' and 'v' coordinates of the environment
texture need to rotate with respect to the camera, so that the il-
lusion of animation is maintained. [1]
27
2.1.9 Skyboxes
Skyboxes are an interesting concept similar to that of envi-
ronmental billboarding. Essentially a cube is used to enclose all
objects in the scene and the cube is centered on the viewer in
the scene. This is especially useful in cases such as star fields,
and sky textures, because they will never change appearance no
matter where a person moves in relation to the scene. Although
this technique has many benefits, there are occasionally prob-
lems with seams appearing in the cubic map. One way of avoid-
ing the problem of seams is to create the cube from six slightly
larger squares that overlap each other. However, the problem
with this technique is that the textures cannot easily be used as
a cubic environment map, due to the extra pixels on the edges.
[1]
2.1.10 Fixed-View Effects
When drawing a scene, the view is in a fixed position, giv-
ing only one location and orientation of the environment. This is
common in some types of games and training software. This can
be a benefit when trying to increase the speed of rendering a
scene, in that a few techniques can be applied to draw images in
different ways instead of rendering each object for every frame.
28
For instance, a static scene can be photographed, drawn, or
computed in advance. In order not to lose any quality, depth
mapping can be used. This involves storing the depth at which
an object is currently set at, and then comparing this value to
other objects in the scene to give the correct perception. Also,
this scene can be panned, which means that it can be made
larger than the visible screen itself (i.e. zooming). Varying the
shading of the pixel can help with the illusion of giving the ob-
ject depth within the image. Simply by editing the lighting the lo-
cal lighting, the rendering speed can be sped up by up to 95
times faster than re-rendering the objects with each frame. Fi-
nally, the last idea to help this situation is a concept called the
golden thread or adaptive refinement, which in a static scene
can enable the computer to produce a better image as time goes
on. The images can be swapped abruptly or blended into the
scene over time. [1]
The ability to use pixel shading has helped tremendously to
speed up the rendering of images. They allow a user to sample a
texture and blur it, remap it to grayscale or heat signatures, per-
form edge detection, and many other options. It works by map-
ping an image to a screen-aligned quadrilateral with a size so
29
that it will render each texel on a pixel. Using a pixel shader, it
is sampled multiple times and combined to create an average
and result in a better looking image, instead of just sampling it
once. Some examples of combined pixel shaders are the images
of smoke, fire, and rippling water. [1]
Another form of rendering is volume rendering, which is
data represented by voxels, or volumetric pixels. They are repre-
sentations of a regular volume of space. An example of this is an
MRI image. It takes x by y by z amount of voxels to create a
three dimensional image. This method is used in medicine, oil
prospecting, and can also create photo realistic images. Voxels
can be used as a set of two dimensional image slices and then
shearing and warping them to create the resulting image, or it
can be used in a method called splatting. Splatting is when a
surface or volume can be represented by screen aligned geome-
try or sprites rendered together to form a surface. Other meth-
ods are rendering volume slices as textured quadrilaterals and
volumetric textures. These are volume descriptions represented
by layers of two dimensional, semitransparent textures. Which
are good for creating complex surfaces, such as landscape de-
tails, organic tissues, and fuzzy or hairy objects. [1]
30
2.2 Billboard Clouds
As time passes, the human need for expansion across all
forms of technology causes a constant demand for new and bet-
ter applications. Each time a new groundbreaking product or
application reaches the market, very shortly after do the de-
mands for even more ground-breaking emerge. This is apparent
across all forms of technology, and is not any different with com-
puter graphics. Once the eye-popping graphics and special ef-
fects of a new game or movie wears off, the public then demands
even more realism in the next generation of entertainment plat-
forms. [2]
One of the main factors that play an important role in the
advancement of graphics is hardware. Hardware assistance
greatly improves the rendering speed of graphics engines.
Presently, graphics software performance—and the human imag-
ination—exceed that of hardware performance. Therefore, in or-
der to pacify the ever increasing human thirst for realism, soft-
ware must find shortcuts in order to keep the requirements of
the software on the same level as hardware technology. [2]
The aspirations for creating a realistic environment require
rendering a number of virtual polygons that would surpass the
31
capacity of today’s hardware technology. The billboard clouds
application is a method of creating that realistic environment
without the cost of rendering a huge amount of polygons. [2]
2.2.1 What is a Billboard Cloud?
A billboard cloud is “a set of textured, partially transparent
polygons, with independent size, orientation and texture resolu-
tion” [2]. Basically, a billboard is a set of pictures that are ori-
ented in such as way as to create the appearance of a 3-dimen-
sional object. Therefore, since the object is made up of pictures,
the visual representation of the object can be complex or simple
with a negligible change in rendering cost. [2]
32
Figure 7: Original 3D Model [6] Figure 8: Optimal Set of Billboards [6]
Figure 9: Texture & Transparency Maps for Billboards [6]
Figure 10: Billboard Clouds Representa-tion [6]
2.2.2 Setting up a Billboard Cloud
The protocol of setting up a billboard cloud is simply taking
a textured 3-dimensional virtual model and transforming it into a
billboard cloud. There are two functions for a billboard cloud:
an error function and a cost function. [2]
In most approaches to new graphics rendering techniques,
there are two methods to rendering simplification: budget-based
simplification and error-based simplification. In Budget-based
simplification, a maximum cost is given that represents hard-
ware demands for rendering—such as number of billboards in an
object, and the number of textures used—and the billboard cloud
is constructed that minimizes error while not exceeding the max-
imum cost. In error-based simplification, a maximum error is set
and a billboard cloud is constructed that minimizes rendering
cost and does not exceed the error limit. [2]
2.2.3 Greedy Optimization
The Billboard Clouds algorithm takes as input the specified
error metric value and automatically generates an optimal set of
billboards planes. One option is a greedy algorithm. Because
greedy algorithms are so focused on the local optimal values,
they can often miss values that are will cause problems down the
line. In the case of the Billboard Clouds algorithm, that means
that it might pick a certain group of planes, and leave out ones
that are near because they do not quite have the same tangent
as the billboard. Thus, a penalty system was created that ac-
counted for near-tangency issues in the billboard selection algo-
rithm. The Billboard Clouds algorithm uses penalty, contribu-
tion, and validity. The penalty is similar in formula to that of the
contribution, but it is weighed more strongly. Instead of taking a
group valid(P), the penalty formula sums the area of a miss(P)
group defined as the group of planes that fall within the area
that is one away from the billboard normal plane in question.
[2]
2.2.4 Discretization of Plane Space
Using something known as a Hough transform, a plane can
be converted into a 3D function in plane space. The coordinates
for this are (,,) which if represented as a 3D graph would rep-
resent the (x, y, z) dimensions respectively. In plane space, each
plane is defined as a sheet following a function = f(,). To dis-
cretize this plane space, we divide it into “bins” (the respective
equivalent to a “cube” in 3D space). Each bin will have a sepa-
35
rate value, depending upon how many faces intersect at all with
the bin. [2]
After giving each bin in space a discrete value, the model is
now optimized with a greedy algorithm. To do so, the bin with
the highest density is found, and a place in the bin that can col-
lapse the set of faces contained within. Because a bin’s density
does not necessarily mean that all of the planes inside intercept,
the algorithm has to refine the bin until it reaches a state where
all of the planes inside of the bin intercept. To help choose which
bin has the highest density, it and all of its neighbors (imagine
the 27 sub-cubes of a rubix-cube-type setup) are subdivided into
8 pieces each (i.e. split ½ way on each axis x, y, z). After a sub-
bin has been chosen, the process is repeated until a bin is found
with a full plane intersected inside, which then has its faces col-
lapsed, and the algorithm restarts. [2]
During the greedy collapse phase, each plane P is assigned
a set of faces that have collapsed into it. To shoot a texture onto
the plane, the minimum bounding rectangle is first found, and
then the texture is shot by rendering the faces using ortho-
graphic projection. During this process, it is important to also
36
keep the alpha and mip-mapping data consistent throughout the
whole process. [2]
2.2.5 View-Dependent Optimization
The billboard cloud construction can be applied to a view-
dependent case to minimize the error in the volumetric view-cell.
This useful image-based acceleration uses a reference viewpoint
in the view-cell to compute the textures and to define the valid-
ity. A billboard is built so the view of the cloud is only correct
from the reference viewpoint. This yields a billboard cloud with
better consistency and one that is less likely to crack. [2]
2.2.5.1 Validity
The validity is calculated from an algorithm that uses the
reference viewpoint V, a vertex M, a point P on the line created
with VM, and another viewpoint T. V is selected as the center of
the view-cell, as this minimizes the reprojection error, and the
viewpoint M chosen some distance from the view-cell. When M is
simplified along the line VM to a point P, the reprojection error
for another viewpoint T in the view-cell can be defined as the an-
gle under which the line MP can be seen from T. This error is at
a maximum when the line MT is tangential to the view-cell. This
37
algorithm defines the distance where M can be translated along
the line P- to P+ for an error less than max. To get the pixel dis-
tance in the image space of this angle max another algorithm is
used. This algorithm uses the distance of the near plane of the
camera n, the width of the screen w (in world units), and the
width of the viewport W (in pixels). The final portion of this is
the contribution, which is defined as the projected area in a ref-
erence view from V. [2]
2.2.6 Implementation
These validity domains are calculated and then use the
greedy optimization to find a set of planes. Then the textures are
shot from the reference viewpoint. This texture is then stored
with the transformation matrix and will be used as a texture ma-
trix to render the billboard cloud. [2]
The only inputs for the implementation of this algorithm
are the error threshold and the maximum texture resolution in
object space. This always renders a billboard cloud with all origi-
nal faces collapsed on a billboard in a very robust manner in re-
gards to such things as the resolution of the discrete plane
space. This method also has a great benefit since the notion of
texture compacity and texture compression can usually reduce
38
memory requirements by a factor of four to eight. The complex-
ity of this method is basically O(kn) where n is the size of the in-
put and k is the number of planes in the billboard cloud, ob-
tained from the density computation being O(n) and each itera-
tion costing O(n) as well. Using a random subset of faces could
accelerate the density computation; although the density is only
a guide for plane selection therefore it should not affect the be-
havior of this method. [2]
2.2.6.1 Texture Usage
These previous methods did not take into account texture
size. One way to do this is to include a compactness term in the
density definition and to restrict the primitives collapsed on a
plane to maximal compact sets in the greedy construction algo-
rithm. In order to do this the bounding box of valid(i) is stored
with each bin Bi of plane space in order to compute an irregular-
ity term proportional to the squared perimeter divided by the
area. The greedy algorithm is modified and restricts the valid()
to a maximal compact subset. The issues with compactness are
mostly caused by different parts of the scene being collapsed on
the same plane. This can be overcome by analyzing the validity
just mentioned into clusters and only use the densest cluster.
39
“An aid in this task is to use the bucket-like partitioning algo-
rithm by Cazals et al. [8], with an appropriate measure of com-
pactness. The rest of the algorithm is left unchanged, but this
can lead to one plane being selected many times to handle differ-
ent clusters.” [2]
2.3 Java3D
The Billboard Clouds algorithm will be implemented in Ja-
va3D. This API was chosen due to Java’s ability to be portable on
many platforms. The use of the Java Virtual Machine allows for
this ability which will lead to the use of the algorithm on differ-
ent devices such as PDAs, cell phones, and other mobile devices.
2.3.1 Introduction
Java 3D is an API for developing three dimensional graph-
ics applications in the Java programming language. It was de-
signed with the intention of having high performance 3D graph-
ics. This new cross platform API allows for quick development of
complex 3D objects. It also permits fast and efficient implemen-
tations over a variety of platforms. 3D content can be loaded
from VRML, OBJ, and other external files to aid in the creation of
scenes. A rich feature set is included for building shapes, com-
40
posing behavior, interacting with the user, and controlling ren-
dering details. Although, even with the benefits of using this API,
the future of Java3D is uncertain and has been put on hold as of
now.
2.3.2 Architecture of Java3D
Java3D has implemented its own thread scheduler, as the
java thread model does not allow for enough control of thread
scheduling. This new scheduler allows for total control over all
of the threads used, so thread priorities are not needed. Mes-
sages are used in the system to generate scene graph changes
into structures that optimize a particular function.
Geometry objects have two structures that are used. One
structure organizes the geometry spacially, used for special
queries, in the scene. Each virtual universe has a geometry
structure. The other is the render bin. A render bin is associated
with every view. The renderer threads use this by state sorting
all geometries for rendering.
Behavior structures spacially organize behavior nodes.
These structures are used by the behavior scheduler threads.
The scheduler threads execute the behaviors that need to be
completed.
41
Rendering is the most important part of this system. It is
generally the most expensive part of graphics, the goal of this
system is to be rendering at all times from a thread scheduling
point of view. These structures fit together through the thread
scheduler. This scheduler is basically an infinite loop with each
iteration running each thread it wishes to once. This creates one
frame and runs one behavior scheduler for each loop. The sched-
uler waits for each thread to complete in the iteration before
moving on to the next iteration.
A message is created each time the scene is changed. This
message contains a time value as well as a state value if needed
to associate with the scene change. The message is then queued
by the thread scheduler and at each iteration the messages are
processed and the necessary structures are updated. Most of
these messages are easy to process requiring very little work,
but occasionally there will be a difficult process that requires
more time to process. With this ability to start processing the
messages and rendering the frames right away, Java3D can hit
native graphics speeds in most cases. The difficult messages, re-
quiring a great deal of change or complex changes in the scene,
slow the process, and start to degrade the rendering time.
42
2.3.3 OpenGL vs. DirectX in Java3D
Java3D can be used with either OpenGL or DirectX. When
running on an operating system, DirectX can only be used on
Windows. OpenGL is a more feature rich implementation of Ja-
va3D than the version using DirectX. The newer version of Ja-
va3D now uses DirectX version 8.0. With OpenGL being platform
independent, Java has put a little more effort into the implemen-
tation and therefore produces about thirty to fifty percent better
frame rates. Although OpenGL seems to be the better choice due
to the amount of effort Java put into its implementation, the Di-
rectX version seems to be a little more stable. The DirectX ver-
sion seems to have fewer bugs in testing a major application.
2.3.4 Java3D vs. Other Graphical APIs
Java3D is similar to the other graphics programming APIs,
like OpenGL, Direct3D, and PHIGS. Java3D is designed to use
hardware acceleration when available, as much as possible
based on the underlying graphics architecture of the operating
system. This API supplies Java with the ability to render three di-
mensional objects, but at the same time use OpenGL or DirectX
indirectly interface to the hardware. Java3D does not require di-
43
rect hardware device driver support, as it can rely on other APIs
for this.
2.4 Mobile Devices
2.4.1 Introduction
As in many computing devices, mobile devices have a cen-
tral processor, memory, display, and other components similar
to desktop computers. However, the size of these components,
both in physical size and power, are greatly reduced. These de-
vices are designed for extreme mobility, and therefore must sac-
rifice size and power. Mobile phones are mainly used for making
phone calls, but also have many of the same basic features as a
computer, including a display—and with a display, comes an
urge to display graphics on it. Personal Digital Assitants (PDAs)
are not meant to replace computers, but rather be a companion
or “assistant” to a desktop computer. With the invention of
these devices comes the desire to do more with them—this paper
outlines a start to the journey of displaying complex 3D models
on a mobile device.
44
2.4.2 Mobile Phones
2.4.2.1 System Specifications
Mobile phones are mostly used for making phone calls
while on the move. Early mobile phones came with just enough
processing power to control simple calling functions and the
monotone display. Today, many mobile phones now have full
color displays, as well as handling more complicated functions
such as calendars, notepads, and even surf the internet. Most
recently, phones have even appeared that can handle some 3D
models, such as the N-Gage (no relation to one of the members
of this project).
The N-Gage phone is actually a mobile device not exactly
intended for the implementation of this project. Being already
able to render complex 3D models, it actually has enough power
to accomplish what it wants too. Granted the N-Gage cannot
Figure 11: N-Gage Mobile Phone
45
render an extremely large number of polygons, but it can handle
way more than the average mobile phone or PDA. The Billboard
Clouds algorithm is mostly intended for devices that have almost
no ability to render a 3D model. However, for the N-Gage, the
algorithm could still be applied if the number of polygons were
very high.
2.4.3 Personal Digital Assistants (PDAs)
2.4.3.1 System Specifications
As in many computing systems, Personal Digital Assistants
also have a central processor that carry out tasks and processes.
However, the speed of the processor on the average PDA is
much slower than that of an average personal computer. The
fastest PDAs only have a couple hundred MHz of processing
power—compared to thousands of MHz on desktop computers.
This makes sense, since a PDA is not meant to take the place of a
desktop or even laptop computer. Extreme mobility and size of
the device take precedence over functionality in this case. A
PDA is meant to be a companion, or “assistant” to a personal
computer. PDAs could be divided into two groups, one group
have a faster processor (and other components) than the other.
The faster PDA could run a Microsoft Windows’ based operating 46
system, where as the slower PDA is most likely running a Palm
operating system specifically designed for PDAs. More detail
about PDA operating systems now follows.
There are mainly two distinct types of operating systems
for PDAs: Palm OS and PocketPC. Palm OS runs on PDAs cre-
ated by Palm and require less memory than PocketPC. Palm OS
is designed to be smooth, efficient, fast, and easy to use—de-
signed specifically for PDAs. PocketPC, which was formally
called WindowsCE, is created by Microsoft as an OS for PDAs.
This OS is made more to act like the OS of a desktop computer.
It requires more memory and can be a little more cumbersome
to use. PocketPC supports more of the entertainment features
such as color, sound, and graphics. It would therefore be easier
Figure 12: PDA running PalmOS
47
to display graphics on a PDA running PocketPC both because of
the better support by the OS, and also because the PDA running
PocketPC is more likely faster.
Memory works a little differently in a PDA than in a desk-
top computer. A PDA does not have any of the complicated mov-
ing parts of a pc hard drive, but rather, stores all of its applica-
tions on two types of main memory. The default applications
such as the operating system and address book are stored on a
read-only, non-volatile chip. Any user created/installed applica-
tions or data is stored on the PDA’s volatile chip that can be lost
if the batteries run out. Because all of the applications are
stored on main memory, all of them are accessible all the time—
with no need for the OS to fetch the data off a secondary storage
drive. Some slower PDAs (again, many running PalmOS) may
only have an average of 8 MB to store data where as the faster
PDAs (running PocketPC), can have as much as 32 MB of space.
2.4.3.2 Display
PDAs have become successful because of their small size
and extreme mobility. Therefore, the viewing display of the de-
vice cannot be large enough as to defeat the purpose of being
able to fit the PDA into a pocket. PDAs have a very small screen
48
to work with and thus, make it difficult to show extensive graph-
ics. Pixel resolutions barely exceed 320 pixels. Colors can
range from grayscale (most likely on the Palm devices) to thou-
sands of colors (on the PocketPC supporting devices).
2.4.4 Mobile Devices and Graphics
Displaying graphics on mobile devices are very difficult to
accomplish given the limited resources these devices have ac-
cess to. However as time passes, engineers will only find new
ways to make PDA components better—making it much easier to
display graphics. Until then, it is extremely difficult to ade-
quately represent complex graphics such as 3D models on a PDA
without some sort of extreme simplification algorithm.
49
3 METHODOLOGYThis section will outline the steps in order to implement the
Billboard Clouds algorithm in Java3D. Each of the following sec-
tions will describe a portion of the algorithm itself to be imple-
mented or some needed functionality to add to the algorithm in
order to complete the implementation of the algorithm itself.
3.1 Helper Programs
There are certain things that are not present in the bill-
board clouds algorithm that are necessary in order to re-imple-
ment the algorithm. The algorithm itself only executes the
needed equations to create the billboards for the objects, but
does not do much else on its own. There is some work that needs
to be done to give the algorithm the necessary information. This
is why there are several other functions that needed to be added
to the program in order to use the algorithm.
3.1.1 Model Displayer
The first step in the re-implementation of the billboard
clouds algorithm is to be able to load an object into the program
to change to billboard clouds. The file format of a polygonal ob-
ject to be drawn with Java3D is a ply file. This file consists of a
50
portion of header information, a list of vertices, and a list of
faces. The list of vertices contains the values for the x-coordi-
nate, y-coordinate, z-coordinate, the confidence, and the inten-
sity. The list of faces contains the amount of vertices in the face
and a reference to each of these values in the list of vertices
from the file itself. Obtaining these values are the first and most
basic step in trying to implement the billboard clouds algorithm.
This function needs to be able to read in ply file and extract
this data for the algorithm to use. This step is not included in the
actual algorithm itself. This will be implemented as an added
pre-process helper function in order to get the necessary data to
the algorithm for execution.
3.2 Algorithms
This section describes the different portions of the Bill-
board Clouds algorithm that will be implemented. Each smaller
algorithm that makes up the entire Billboard Clouds algorithm is
detailed here as to how each one works and how it will be imple-
mented.
51
3.2.1 3-D Hough Transforms
3-D Hough Transforms are used in the billboard clouds al-
gorithm to help with the checking for validity. In order for this to
be useful for checking the validity, the values for the validity for-
mula need to be found. This is where the 3-D Hough Transform
comes into play. The validity formula needs a value r, which rep-
resents the distance from a point to a plane. The Hough Trans-
form finds this value. If a 3-D space is defined by the parameters
r, θ, and φ, than any plane in x, y, z space corresponds to a point
in this space defined by r, θ, and φ. As a result of this, the 3-D
Hough Transform of a plane in x, y, z space is a point in that
space defined by r, θ, and φ. The values r, θ, and φ define a vec-
tor form the origin to the nearest point on the plane. This vector
is also perpendicular to the plane.
Now, given a point, x, y, z, there are many different planes
that intersect this point. All of these planes are expressed using
the following equation:
r = xcos(θ) ∙ cos(φ) + ysin(θ) ∙ cos(φ) + zsin(θ)
Equation 2: 3D Hough Transform
52
All of these planes can be expressed with a curved surface
using the values in r, θ, φ space. These values can be calculated
for a series of 3-D points. The results of these calculations can
be plotted in a 3-D histogram to find the best fitting plane for the
given point.
3.2.2 Projection onto a plane
Given a point and a plane, there is a simple formula that
will project, or cast an image of that point onto the plane shown
in Figure 13 below,
Projecting a point onto a plane is a result of the following
equation:
Figure 13: Projection
P' = P − CNEquation 3: Projection
Formula
53
P is the original position of the point that is to be projected.
N is the normal of the plane, and C is a constant. C corresponds
to the distance P must travel in order to intersect itself with the
plane. C is a constant that is calculated in the following equa-
tion:
3.2.3 Validity Check
As validity has already been defined within the billboard
clouds algorithm (see section 2.2.5.1 ), the methods to imple-
ment this will now be discussed. As mentioned, to calculate va-
lidity an error metric needs to be set as well as the values for the
formula. These values will be obtained using the 3-D Hough
transform. Each value from the Hough transform, r, will be
found and then the calculation for validity will be conducted. As
mentioned in the description of the 3-D Hough transform, the
values for r will be plotted in a histogram format.
This process to find the best plane for the given set of
points will be done by checking each set of points’ values for r
against each other to find the smallest difference between the
Equation 4: Con-stant
54
maximum value of r and the minimum value of r. The values of r
will be calculated for every degree to find the best values for the
plane to use. There will be a list of values of r for each point
ranging from being calculated at zero degrees all the way to
ninety degrees. When this is completed for every set of points,
the values of r for the x component, the y component, and the z
component having the highest and lowest values will give a
value to compare to the error metric. The lowest value will be
subtracted from the highest value of r, no matter which compo-
nent it is, and this value will be compared to the error metric to
see if it is valid. Once a fit is found, this will not be the end of the
test for validity. The test will continue to iterate to see if there is
a better fit value. Therefore, all of the values that have been cal-
culated using the 3-D Hough transform will be tested. If a better
fit is found, this selection will be saved until there is another
plane that is a better fit, or the test ends. Once this is finished,
the best fit plane to create the billboard for the given face will be
known. Figure 14 below illustrates this further:
55
3.2.4 Best-Fit Projection Rectangle
The purpose of this algorithm is to find the best-fit rectan-
gle that encompasses all the points on the plane. Given a plane
of points, the goal is to calculate the dimensions of a rectangle
that contain every point with the smallest area.
Figure 14: Validity Diagram
x
y
56
Figure 15 shows a sphere of points aligned at the origin.
The next step is to increment an angle, θ, originating from the
origin and along the x-axis. Along this line, a perpendicular line
is drawn that intersects with both the furthest point and the
closest point from the origin as shown in Figure 16. This will de-
termine the width of the rectangle. Then, the same technique is
used for the angle θ + 90° which would in turn, find the limits of
the height of the rectangle as shown in Figure 17. Therefore,
Figure 18 shows that the smallest rectangle has been found.
Figure 15: Points on a plane
57
Figure 16: Finding Rectangle Width Figure 17: Finding Rectangle Height
Then, θ is incremented and the steps are repeated for this
new angle. The angle, θ, is incremented from 0° to 90° at which
point the rectangle with the smallest possible area will be deter-
mined.
Figure 18: Best-Fit Rectangle
x
y
x
y
x
y
58
3.2.5 Spherical Equidistant Separation
An algorithm was developed to create the equidistant sepa-
ration among points on a spherical surface. The most common
use of this algorithm is in the placing of dimples on the surface
of a golf ball. The task is for every point to be equal distances
from each point closest to it. Contrary to the algorithm used in
the original implementation of this algorithm by Décoret, et al.
[2], this procedure produces an equal sampling rate in all direc-
tions.
The algorithm is given a set of points scattered randomly
around the surface of a sphere as in Figure 19:
The algorithm begins by choosing any point P on the surface of
the sphere. For this point, the sum of the magnitude and direc-
Figure 19: Random points on a sphere
59
tion of every other point (P1, P2, …, Pn) on the sphere is calcu-
lated to create a total magnitude and direction, M, as shown in
Figure 20.
P is then translated to a new position that is the result of the
normalization of its current position, and the total magnitude
and direction previously calculated. This is shown in Equation 5
and depicted graphically in Figure 21 below:
Figure 20: Sum of all magnitudes and directions of ev-ery other point
Pnew = normalize(Pold + M)
Equation 5: New position of P
60
The algorithm then chooses another point, and loops
through again until all points have been translated. It must be
taken into account, however, that with the translation of each
point, its new location then affects every other point that once
used it in their own calculations. So, once each point has tra-
versed through the loop once, they must go through yet again,
making calculations on their positions due to the change in the
magnitude and direction of all the other points on the sphere.
The loop is traversed as many times as needed until the er-
ror bound is reached. The error bound is a value at which if the
distance of the point at its new position, Pnew, is subtracted from
its original position, Pold, does not exceed the error bound, e,
then the point does not need to traverse the loop again:
Figure 21: New position of P
61
4
Pnew − Pold > e Traverse P through the loop
Pnew − Pold ≤ e P may exit the loop
Equation 6: Error Bound
62
5 IMPLEMENTATIONThe actual implementation of the code will be explained in
this section. Each section mentioned in the methodology (Sec-
tion 3 starting on page 50) will be discussed in detail as to how it
was coded in Java3D.
Figure 22 is a very general diagram of the structure of our
code. The algorithm takes in a 3D mesh model and runs it
through a rigorous set of calculations that determine the optimal
Figure 22: High-Level Diagram of Implementa-tion
63
set of billboards to represent the model. With this, the final out-
put is to compile the billboards and images of the model to-
gether to create a simplified version of the original 3D mesh
model.
Figure 23: Detailed Calculation Diagram
64
Figure 23 above is a demonstration of how the calculation
of the billboards actually works broken down into steps. The al-
gorithm tests an equal number of points created from the
equidistant spherical separation function in the algorithm. This
assures an equal distribution of points to be tested in the algo-
rithm. Then, the best angle for each billboard is calculated and
the valid faces for each billboard are calculated using the valid-
ity test and the contribution test. This is to ensure that the best
selections of faces are chosen for the billboards to contain in the
final product. These faces that are chosen to be projected onto
the billboard from the calculation are used to shape the billboard
and create the best fit for the set faces on the billboard. The
faces that have been selected for the billboard are then put into
image files by taking snapshots of only these faces. Finally, the
billboard and the image files are combined to create the final
billboard to be used. The image files containing the faces to be
used are projected onto the billboard and this is the final prod-
uct. This is an example of one step of the algorithm to be looped
for each billboard in order to create the entire 3-dimensional
simplified object.
65
5.1 Helper Programs
The creation of the helper function to help display the
model was an original creation that was not included in the Bill-
board Clouds algorithm. The implementation behind this func-
tion will be discussed in the following section.
5.1.1 Model Displayer
The first part of actually starting to implement the bill-
board clouds algorithm began with creating a helper function to
give the algorithm the necessary data to execute all of the calcu-
lations. The most important of this needed information is to load
the data from the polygonal object file or ply file.
5.1.1.1 Function: loadMesh( )
66
1
3456789101112131415161718
192121222324
252627
public Group loadMesh(String loc, Color3f clr, float scale) {...
while((str=fl.readLine()) != null) {switch(state) {case 0:if(str.indexOf("ply") != 0) {
state=-1;} else {
state++;}break;
case 1:if( str.indexOf("end_header") == 0) {
state++;fl.mark(500);
}if( str.indexOf("element") == 0) {
str2 = str.substring(str.lastIndexOf(' ')+1);
if(str.indexOf("vertex") >= 0) {num_ver = Integer.parseInt(str2);
}if(str.indexOf("face") >= 0) {
num_face = Intger.parseInt(str2);theFaces =
new TrangleAray(num_face*3, TriangleArray.COORDINATES | TriangleArray.NORMALS);
}}break;
...
ParametersString loc : A string that represents the path and file-name to the .ply file.Color3f clr : What the color of the model will be.Float scale : The rate of resizing the points in the model—this is used to reduce error when using very small numbers.
ReturnsTransformGroup tg : A group for holding the final model object that can be applied to various transforms.
Figure 24: Function: loadMesh() (1 of 3)
67
The basic idea behind the loadMesh function shown in Fig-
ure 24 through Figure 26 is to read in the ply file and extract the
needed data. The steps to complete this process consist of pars-
ing through the data in the file, and then storing the list of ver-
tices and the list of faces for the object. This also entails obtain-
ing the important header information from the file, such as the
number of vertices and faces as well as checking to see if it is
the correct type of file.
This first block of code shown in Figure 24 conducts the
initial steps of the helper function. This code checks to see if the
file be read in is in fact a .ply file, as can be seen in line 6. The
file is read in line by line, and a series of if statements and a case
statement are used to check the line that is being read in. When
the type of file is checked, the case statement breaks if it is the
wrong type of file (lines 6,7). Otherwise the program proceeds to
the next step. At this point, the amount of vertices and faces are
extracted from the header information. Lines 19-21 check for the
keywords for the vertex count and stores this number into the
variable num_ver. Lines 22-25 search for the keywords for the
count of the faces and stores this into the variable num_face and
68
creates a new triangle array with this total amount of faces mul-
tiplied three times, as each face needs three vertices.
Figure 25 is a block of code that parses the list of informa-
tion and stores the values for each vertex in the file. A for loop is
used to cycle through each vertex and extract its values using
282930313233343536373839
40
41
4243
44
...case 2:
theVers = new Point3f[num_ver];
fl.reset();for(i=0; i<num_ver; i++) {
str=fl.readLine();if(str == null) break;
int j=0;float x,y,z;
x = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
y = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
z = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
theVers[i] = new Point3f(x*scale,y*scale,z*scale);
}...
Figure 25: Function: loadMesh() (2 of 3)
69
the information already taken from the header as to how many
vertices there are. Each line is read in, and the first value is
stored as the x-coordinate, the second value as the y-coordinate,
and the third value as the z-coordinate. Next these values are
then stored in the array theVers[] in order to use this information
later on in the program.
70
45464748495051525354
55565758
59
6061626364
65666768697071
...case 3:
fl.reset();for(i=0; i<num_face; i++) {
str=fl.readLine();if(str == null) break;
int j=0, k=0;int n,p;
n = Integer.parseInt(str.substring(j, (j=str.indexOf(' ', j+1))));
int list[] = new int[n+1];
for(k=0; k<3; k++) {p = Inger.parseInt(str.substring(
j+1, (j=str.indexOf(' ', j+1))));theFaces.setCoordinate(i*3+k,
theVers[p]);list[k] = p;
}list[3] = i;faceList.add(list[n], list[0],
list[1], list[2]);...
theShape.addGeometry(theFaces);Transform3D ts = new Transform3D();TransformGroup tg = new TransformGroup(ts);tg.addChild(theShape);
return tg;}
Figure 26: Function: loadMesh() (3 of 3)
71
Figure 26 is a block of code to store the list of faces from
the file. In the same manner as the earlier parts of this function,
each line is read in and parsed for important information. The
first thing to be stored is the number of vertices of each face.
This value is stored in the variable n as can be seen in line 54.
Line 55 creates an array that will store this amount of values
plus one, as the normal to each face will be stored in the array
as well. The “for” loop in lines 57-61 extracts the faces and the
vertices to be used for each face and stores them in the array
list[]. These values are then added to the faceList variable in or-
der to be used later on in the program. The normal to each face
is calculated just after this portion of the code and is also stored
using these variables.
5.2 Algorithms
The following sections will discuss the implementation of
the functions included in the actual Billboard Clouds algorithm
itself. Each section will display a section of code along with a de-
scription of how each function works.
72
5.2.1 3-D Hough Transforms
When the time came to implement the calculation of the 3-
D Hough transform, it was a somewhat easy process. Please see
section 3.2.1 on page 52 for more details about Hough Trans-
forms.
5.2.1.1 Function: funcHoughTransform( ) – 3 Dimensional
A simple function (Figure 27) was created that returned
the value for r in floating point format. The function takes in the
3D point as well as the theta and phi values as parameters for
the calculation. The point comes in as a Point3f value, so it con-
tains the three values for its x, y, and z coordinates. It then exe-
cutes the same equation as the written Hough transform. The
1
2
3
public static float funcHoughTransform(Point3f pt, float theta, float phi) {return (float)(pt.x*Math.cos(theta)*Math.cos(phi) +
pt.y*Math.sin(theta)*Math.cos(phi) +pt.z*Math.sin(phi));
}
ParametersPoint3f pt : The point where its distance from the plane must be determined.float theta : The angle from the origin of the plane to the point along the x axis.float phi : The angle from the origin of the plane to the point along the y axis.
Returnsfloat : The distance from the point to the plane.
Figure 27: Function: funcHoughTransform() – 3D
73
math libraries are used for the trigonometric functions involving
the angles.
As can be seen in the code, the parameters are taken into
the function, in line 1, using the 3D floating point Point3f format
for the point, pt, to use for the calculation and the floating point
format for the angles to use in the calculation. The actual for-
mula is then calculated in line 2 in the return value of the func-
tion. Due to the fact that this is a simple function to only return
a value of a calculation, the entire equation is executed in the re-
turn statement. The different values of the 3D point are ac-
cessed through the pt.x, pt.y, or the pt.z portions of the code as
they are of Point3f format. The math libraries are used in the
Math.cos or Math.sin portions of the code, which can be seen within
the equation in line 2.
5.2.1.2 Function: funcHoughTransform( ) – 2 Dimensional
74
As an added bonus, another use of Hough transforms was implemented into the program shown in Figure 28. This formula was also used to find the best possible fitting rectangle (see sec-tion 3.2.4 Best-Fit Projection Rectangle) for the given face or set of faces. This version of the formula only uses 2D as the bill-boards are created from rectangles and one angle. This version of the function takes in a Point2d value, pt, for the parameter of the point and a double value, theta, for the parameter of the an-gle. The entire equation is again included as the return state-ment as it is merely a simple calculation from the given equa-tion.
The parameters taken into the function can be seen in line
1, using the Point2d format for the point to use in the formula
and the double format for the angle to be used in the formula for
the calculation of r. The entire formula is executed in line 2, and
then is simply returned as a double value. As in the previous ver-
sion of the function, this version uses the math library.
1
23
public static double funcHoughTransform(Point2d pt, double theta) {return pt.x*Math.cos(theta) + pt.y*Math.sin(theta);
}
ParametersPoint2d pt : The point where its distance from the plane must be determined.double theta : The angle from the origin of the plane to the point.
Returnsdouble : The distance from the point to the plane.
Figure 28: Function: funcHoughTransform() - 2D
75
5.2.2 Projection onto a plane
Projection onto a plane, described in detail in section 3.2.2,
is simply taking a point, and casting it onto a plane. The projec-
tion formula (Equation 3 on page 53) is the calculation of the
projected point, given the location of the original point, a con-
stant, and the normal of the plane.
5.2.2.1 Function: orthoProject( )
The function orthoProject() (Figure 29) takes in a point, pt,
and the normal of the plane that the point is to be projected to,
1234567891011121314
public static Point3f orthoProject(Point3f pt, Vector3f norm) {Vector3f ptVec, ret;float k;
ptVec = new Vector3f(pt);k = ptVec.dot(norm) / norm.lengthSquared() - 1.0f;
ret = new Vector3f();ret.scale(k, norm);ret.sub(ptVec);ret.negate();
return new Point3f(ret);}
ParametersPoint3f pt : The point to be projected onto the plane.Vector3f norm : The normal of the plane that the point is to be projected onto.
ReturnsPoint3f : The new projected point on the plane.
Figure 29: Function: orthoProject()
76
norm. Line 5 creates the vector3f variable, ptVec, by using the co-
ordinates from the pt parameter. Line 6 does the math de-
scribed in Equation 4 on page 54. Lines 9,10 do the math de-
scribed in Equation 3. Finally, the new projected point is re-
turned in the form of a point3f variable, ret.
5.2.3 Validity Check
As it has already been stated, for a billboard to be valid, it
must be within the error metric. The function to determine this
portion of the billboard clouds algorithm was also a relatively
simple portion to implement. See section 3.2.3 on page 54 for
more details about the validity check algorithm.
77
5.2.3.1 Function: isValid( )
The function shown in Figure 30 needs the points to check
if they are valid for a given billboard and the angles for the
points. The function will then need the r value, which it receives
from a function call to the Hough transform function. In order to
check for validity, every point will need to be checked. There-
1
23456
789101112
public static boolean isValid(float theta, float phi, float rad, Point3f pts[], float ERROR) {int i;float height;
for(i=0; i<Array.getLength(pts); i++) {height = MQPFunctions.funcHoughTransform(pts[i],
theta, phi);
if(Math.abs(height-rad) > ERROR) return false;}
return true;}
Parametersfloat theta : The angle from the origin of the plane to the point along the x axis.float phi : The angle from the origin of the plane to the point along the y axis.float rad : Point3f pts[] : The list of points of the object in 3D space.float ERROR : The standard value for error metric that de-termines if the distance of the points is valid.
Returnsboolean : Valid if the points are within the error metric.
Figure 30: Function: isValid()
78
fore, this function needs an array for all of the points to check.
The function just simply returns a true or false for the billboard.
Line 1 shows the function header with the needed parame-
ters to actually calculate the validity. All of the values to be
taken in are of the floating point type. The angles, theta and phi,
are needed to make the function call to the Hough transform
function, as well as the array of 3D points in order to check each
set of points for validity. It also takes in the error value, ERROR, for
the metric to compare to the value returned from the Hough
transform. The actual methods for checking the validity are in
lines 5-9 as the function cycles through every point in the array
to check for validity. The “for” loop cycles through every point
(line 5), and calls the function to get the r value from the Hough
transform using the point and the angles, and then stores the
value in the height variable, height (line 6). The absolute distance
is then calculated and compared to the error metric to see if this
is valid (line 8). If this is outside of the error metric, the function
returns a false value and that point is not included for the given
billboard. Of course, if it is within the error metric, it returns a
true value and the point is used for the billboard.
79
5.2.4 Best-Fit Projection Rectangle for Set of Points in Plane Space
The algorithm for calculating best-fit rectangle among a set
of random placed points is outlined in section 3.2.4. However,
more steps are used in the implementation that involve rotation
of the plane to the x,y origin, and then back to its original posi-
tion.
5.2.4.1 Function: brGetRotations( )
80
Figure 31 is a function that takes in two axis’ that repre-
sent the x and y axis’ of the arbitrary plane in space. The easiest
way to make calculations on this plane, is to rotate it to the x,y
origin. In whatever the orientation of the plane is to the origin,
three rotations are needed to align it with the origin. Line 10
makes the calculation of the x-axis angle, and then lines 13,14
1
234567891011121314
15
public static double[] brGetRotations(Vector3d xo_ax,Vector3d yo_ax) {Vector3d vec, x_ax, y_ax;double x_a, z_a, x2_a;
x_ax = new Vector3d(xo_ax);y_ax = new Vector3d(yo_ax);
vec = new Vector3d(x_ax); vec.x = 0.0f;
x_a = Math.acos(vec.y / vec.length());if(x_ax.z < 0) x_a = -x_a;
x_ax = brRotationX(x_ax, x_a); y_ax = brRotationX(y_ax, x_a);
...return angles;
ParametersVector3d xo_ax : The user defined x-axis that needs to be rotated to the x,y origin.Vector3d yo_ax : The user defined x-axis that needs to be rotated to the x,y origin.
Returnsdouble angles[] : Three angles that represent the amount the plane would need to be rotated to make it aligned with the x,y origin.
Figure 31: Function: brGetRotations()
81
actually make the rotations with calls to brRotationX(). What is
not shown in the excerpt above is the code that makes the calcu-
lations and rotations for the other two angles. After rotating
once around the x-axis as shown above, the plane is then rotated
around the z-axis with a call to brRotationZ(). Then, after the sec-
ond angle is calculated, another call is made to the brRotationX()
function. This is because when the plane is rotated about two
axis’, it is then aligned along two completely different axis’.
Therefore, in order to make its third rotation, it happens to be
making that rotation about an axis that it was rotated around
previously before. This function returns an array of doubles that
represent the three angles needed for the plane to be rotated
and aligned with the x,y origin—two x-axis rotations and one z-
axis rotation.
82
5.2.4.2 Function: brRotationX/Z( )
Figure 32 is a helper function that is used to rotate the
plane of points around an axis. With this function, brRotationX(),
there is also another function called brRotationZ() which creates
the same result about the Z axis.
5.2.4.3 Function: brGetXYCoords( )
12345678910111213
public static Vector3d brRotationX(Vector3d vec, double ang) {Vector3d vec2 = new Vector3d();double c, s;
c = Math.cos(ang);s = Math.sin(ang);
vec2.x = vec.x;vec2.y = vec.y*c + vec.z*s;vec2.z = vec.z*c - vec.y*s;
return vec2;}
ParametersVector3d vec : The vector at which to rotate the plane.double ang : The amount of degrees to rotate
ReturnsVector3d vec2: The new vector about the x,y origin.
Figure 32: Function: brRotationX/Z()
83
1
2345678910111213141516171819202122232425
public static ArrayList brGetXYCoords(ArrayList pts, Vector3d off, double rots[]) {
ArrayList list = new ArrayList();Vector3d vec, vec2;Point3d pt;int i, j;
for(i=0; i<pts.size(); i++) {vec = new Vector3d((Point3f)pts.get(i)); vec.sub(off);
vec2 = brRotationX(vec, rots[0]);vec2 = brRotationZ(vec2, rots[1]);vec2 = brRotationX(vec2, rots[2]);
list.add(vec2);}
ArrayList list2 = new ArrayList();for(i=0; i<list.size(); i++) {
vec = (Vector3d)list.get(i);pt = new Point3d(vec.x, vec.y, vec.z);list2.add(pt);
}
return list2;}
ParametersArrayList pts : An array of all the 3D points.Vector3d off : The offset of the plane relative to the x,y ori-gin.double rots[] : A list of three angles that specify the rota-tions needed for the plane to be aligned with the x,y ori-gin.
ReturnsArrayList list2 : A list of all the 2D points that have been rotated to the x,y origin.
Figure 33: Function: brGetXYCoords()84
The purpose of this function (Figure 33) is simply to take
the plane of points in 3D space, and rotated them all down to the
x,y origin. The parameters include all the points to be rotated,
the offset, and the list of rotations needed for the plane to be
aligned with the x,y axis. All the tools exist for this function to
take every point, and rotate them accordingly to line up with the
x,y origin plane. With the plane of points somewhere in space,
the three rotations, 1 rotation per axis, have been determined
from the function brGetRotations() described in section 5.2.4.1 .
Therefore, each of the three angles (from rots[0], rots[1], and
rots[2]), are passed to the rotation helper functions brRotationX()
and brRotationZ() as shown in lines 10-12. The function ends by
returning a list of points, list2, where each point is now along
the x,y origin.
85
5.2.4.4 Function: brAreaFromAngle( )
This function shown in Figure 34, when called standalone,
finds two sides of a rectangle that are the closest to each other
1234567891011121314151617181920
Public static double[] brAreaFromAngle(ArrayList pts, double theta) double min=10000, max=-10000, r;Point3d pt;Point2d pt2;int i;
for(i=0; i<pts.size(); i++) {pt = (Point3d)pts.get(i);pt2 = new Point2d(pt.x, pt.y);r = funcHoughTransform(pt2, theta);if(r < min) min=r;if(r > max) max=r;
}
double arr[] = new double[2];arr[0] = max-min;arr[1] = (max+min)/2.0f;
return arr;}
ParametersArrayList pts : An array of 2D points already transformed to the x,y origin.double theta : Current angle being evaluated.
Returnsdouble arr[0] : The angle of the length of the rectangle segment.double arr[1] : The length from the origin to the midpoint of the rectangle segment.
Figure 34: Function: brAreaFromAngle()
86
while also encompassing all the points on the plane. This is de-
picted in Figure 16: Finding Rectangle Width on page 58. If the
function is called when 90 degrees is added to theta, brAreaFromAn-
gle() returns the other two segments of the rectangle as shown
in Figure 17: Finding Rectangle Height. Together, this function
returns the smallest area rectangle possible that encompasses
all points on the plane shown in Figure 18: Best-Fit Rectangle.
The “for” loop in lines 7-13 determine which points are the
farthest and the closest from the origin. When a perpendicular
line is drawn from the current angle through these points, the
line represents the far and near boundaries of the rectangle.
When 90 degrees are added to theta, it can be said that the func-
tion returns the height of the rectangle, while theta would return
the width.
The array arr[], holds two values that will be used in deter-
mining the corner points of the rectangle. This is described in
section 5.2.4.5 Function: brGetBestArea( ) below.
87
5.2.4.5 Function: brGetBestArea( )
This is the main function used to determine the best-fit pro-
jection rectangle. The entire function is shown in Figure 35
through Figure 39. brGetBestArea() works with a list of points that
need to be enclosed within a rectangle that has the smallest pos-
sible area. The list of points lie on a single plane, for which both
the normal vector and the distance to the origin are known. The
distance from the plane to the origin is determined by using the
2D Hough Transform algorithm described in section 3.2.1.
1 Public static ArrayList brGetBestArea(ArrayList pts, Vector3f plane, float rad) {
...
ParametersArrayList pts : An array of 3D points on a plane that has not yet been rotated to the x,y axis.Vector3f plane : Normal vector of the plane.Float rad : Hough-transform distance from the plane to the origin.
ReturnsArrayList pts3 : The angle of the length of the rectangle seg-ment.
Figure 35: Function: brGetBestArea() (1 of 5)
88
The initial hurdle in this function is to take the plane, and
rotate it down to the x,y axis—shown in Figure 36. First step is
to choose a point and treat it as the origin of the plane (line 2).
This point does not need to be anywhere special, therefore, the
first point in the array is chosen. Next, another point is chosen
where once a line is drawn through the chosen origin point, and
this second point, it will create a user defined x-axis (line 3).
Two vectors now exist that the function has access too: the user
defined x-axis, and the normal of the plane. To determine the y-
axis, it is simply just the cross product of the two vectors (lines
6,7). A call to brGetRotations() (section 5.2.4.1 ) is made that will
calculate the angles needed to rotate the plane to the x,y axis.
These three angles are stored in the variable rots[] (line 10).
rots[] will be used later to rotate the plane back to its original
23456789101112
...off = new Vector3d((Point3f)pts.get(0));x_ax = new Vector3d((Point3f)pts.get(1)); x_ax.sub(off);x_ax.normalize();
y_ax = new Vector3d(x_ax);y_ax.cross(y_ax,new Vector3d(plane));y_ax.normalize();
double rots[] = brGetRotations(x_ax, y_ax);
pts2 = brGetXYCoords(pts, off, rots);...
Figure 36: Function: brGetBestArea() (2 of 5)
89
position. To complete the process of rotating the plane, a call is
made to brGetXYCoords() (section 5.2.4.3 ) in which the plane, with
all the points, will now be aligned with the x,y origin (line 12).
90
1314151617181920212223242526272829303132333435363738394041
...for(i=0; i<180; i++) {
theta = degToRad(i/2.0);
arr = brAreaFromAngle(pts2,theta);w = arr[0];x = arr[1]; y=theta;v1 = new Vector2d(x*Math.cos(y), x*Math.sin(y));
arr = brAreaFromAngle(pts2,theta+degToRad(90.0f));h = arr[0];x = arr[1]; y=theta+degToRad(90.0f);v2 = new Vector2d(x*Math.cos(y), x*Math.sin(y));
v1.add(v2);x = v1.x;y = v1.y;
area=w*h;if(area < min) {
min=area;min_x=x;min_y=y;min_w=w;min_h=h;minv1 = new Vector2d(v1);minv2 = new Vector2d(v2);min_t=theta;
}}
...
Figure 37: Function: brGetBestArea() (3 of 5)
91
This giant “for” loop in Figure 37 determines the best-fit
rectangle. In this algorithm, the angles used to calculate the
rectangles are in 0.5 degree increments from 0 degrees to 90 de-
grees (lines 13,14). Using the current angle, a call is made to
brAreaFromAngle() which returns the width of the rectangle, and
also the length of the distance of the origin to the midpoint of
the width (line 16). Adding 90 degrees to the current angle will
now determine the height of the rectangle with a call back to
brAreaFromAngle() (line 21). The variables v1 and v2, hold the val-
ues of the actual midpoints of the rectangle segments after using
the calculations provided by the midpoint length data stored in
arr[1] (lines 19,24). Then, a simple calculation is made to deter-
mine the midpoint of the entire rectangle (lines 26-28). Now
that the calculations for the smallest area rectangle at the cur-
rent angle are complete, it is now time to determine if this is the
smallest possible area rectangle out of all angles. If the current
rectangle is currently the smallest, record all its data and dimen-
sions (lines 31-40).
92
At this stage (Figure 38), the function now has access to
the height, width, midpoint, and angle of the rectangle. It is now
time to create the actual rectangle, and rotate the points within
it back to their original position. Lines 13-16 create the height
and width vectors using the midpoint and theta from the rectan-
gle. Next, the plane of points can be rotated back to their origi-
nal position. Earlier in the algorithm, line 12, the offset was
used to rotate the plane to the x,y origin. Now that the plane
131415161718192021222324252627
...tv = circToCart(min_w/2.0f,min_t+degToRad(0.0f));wv = new Vector3d(tv.x, tv.y, 0.0f);tv = circToCart(min_h/2.0f,min_t+degToRad(90.0f));hv = new Vector3d(tv.x, tv.y, 0.0f);
vec = new Vector3d(plane);vec.scale(Math.abs(rad)-1.0);off.add(vec);
vecd = new Vector3d(ov); vecd.sub(wv); vecd.sub(hv);vecd = brRotationX(vecd, -rots[2]);vecd = brRotationZ(vecd, -rots[1]);vecd = brRotationX(vecd, -rots[0]);vecd.add(off);pts3.add(swapPoint(new Point3f(vecd)));
...
Figure 38: Function: brGetBestArea() (4 of 5)
93
has been normalized, a call to scale() is now used to get the
plane back (lines 18-20). Finally, using the height and width
vectors, the corner points of the rectangle can be determined.
Lines22-27 show the code used to calculate the first corner.
Similar code would be used to create the remaining corners.
This is the smallest area rectangle to encompass all the points
on the plane.
The function returns the points from the four corners of the
rectangle (line 28 of Figure 39).
5.2.5 Spherical Equidistant Separation
The calculation of this function takes very little time com-
pared to the time it takes to implement the rest of the algorithm.
5.2.5.1 Function: makeSphere( )
2829
...return pts3;
}
Figure 39: Function: brGetBestArea() (5 of 5)
94
The parameter N, is the number of angles in space to be
sampled. For instance, if N held the value of three, then there
would only be three angles in space that the final billboards
would be able to utilize. So of course, N should be a decent num-
ber to accommodate as many angles in space as possible. How-
ever, if N is too high, it would create many more, and probably
unnecessary, calculations. The parameter deg, holds the value
for the maximum number of iterations run through the repulsion
algorithm. This is just a safety method implemented just incase
an infinite loop is reached—however, very little cases would
need to rely on the value of deg.
1 public Structs.SphereList makeSphere(int N, int deg) {...
Parametersint N : The number of angles in space to be sampled.int deg : The maximum number of iterations for the repul-sion algorithm.
ReturnsSphereList spl : The angle of the length of the rectangle seg-ment.
Figure 40: Function: makeSphere() (1 of 4)
95
The “for” loop, lines 2-5 of Figure 41, generates a random
position for each and every one of the spheres and then normal-
izes that point to the sphere. The next step is to find a point and
anchor it to the sphere. This point will never move, but will re-
main the only constant in the algorithm (line 7). It does not mat-
ter which point is chosen, however, for simplicity, the easiest
point to chose is the first one in the array.
23
4567
...for(i=0; i<N; i++) {
pt[i] = new Vector3f((float)Math.random()*2.0f-1.0f, (float)Math.random()*2.0f-1.0f, (float)Math.ran-dom()*2.0f-1.0f);
pt[i].normalize();}
pt[0] = new Vector3f(0.0f, 0.0f, 1.0f);...
Figure 41: Function: makeSphere() (2 of 4)
96
This “for” loop in Figure 42 takes a point on the sphere,
and sums up the forces of every other point acting on it. This
891011121314151617
181920212223
25
262728293031
...sp=0;for(it=0; it<deg; it++) {
ptn[0] = new Vector3f(pt[0]);for(i=1; i<N; i++) {
ptn[i] = new Vector3f();for(j=0; j<N; j++) {
if(i != j) {v = new Vector3f(pt[i]);v.sub(pt[j]);f = (float)Math.exp(-
Math.pow(v.length(), 3));v.scale(f);
ptn[i].add(v);}
}}
...ptn[i].normalize();
...if(max < 0.02f) {
System.out.println("exiting due to max beneath min ("+max+")\n");
break;}
}...
Figure 42: Function: makeSphere() (3 of 4)
97
sum is added to the point which pushes it way out into space. It
must be noted, that every point is moved except the first one in
the array—this is the anchor point that is static (line 11). Line
25 normalizes all the points on the sphere. Lines 26-30 are a
check to see if there is movement greater than 0.02. If there is
no more movement, the loop is exited and the algorithm has fin-
ished moving the points on the surface of the sphere.
Finally, the function exists after having completed the
equidistant spherical separation of points, and returns angle in-
formation used in the programs (Figure 43).
3233
...return spl;
}
Figure 43: Function: makeSphere() (4 of 4)
98
6 RESULTS
6.1 Screenshots
The following figures are screenshots of our project.
Figure 44: Original 3D Bunny Figure 45: Original 3D Dragon
Figure 44 and Figure 45 are the original 3D mesh models gener-
ated from the .ply files.
99
Figure 46: Original 3D Bunny with Optimal Billboard Set
Figure 47: Original 3D Dragon with Optimal Billboard Set
After the models are submitted to the program, the optimal set
of billboards are calculated for each model—these are depicted
in Figure 46 and Figure 47.
100
Figure 48: Original 3D Bunny with Affected Faces
Figure 49: Original 3D Dragon with Affected Faces
Figure 48 and Figure 49 show the models with one bill-
board each. The red area on the models represents the faces of
those models that are affected by those specific billboards.
101
Figure 50: Billboard Representation Bunny with Low
Error (with Lighting)
Figure 51: Billboard Representation Dragon with Low
Error (with Lighting)
Figure 50 and Figure 51 show the models in their final bill-
board clouds representation. These representations have a
small error metric which increase the number of billboards and
create more detail. However, due to a memory restriction in
Java (see section 6.3.1.2 ), only 50 billboards can be shown at a
time. Therefore, the extensive holes generated in this represen-
tation are simply because not all the billboards can be shown at
once.
102
Figure 52: Billboard Representation Bunny with High
Error (with Lighting)Figure 53: Billboard Representation Dragon
with High Error (with Lighting)
Figure 52 and Figure 53 show the models in their final billboard
clouds representations with a larger error metric, resulting in
fewer billboards.
103
Figure 54: Billboard Representation Bunny with Low
Error (without Lighting)
Figure 55: Billboard Representation Dragon with Low
Error (without Lighting)
Figure 54 and Figure 55 show the same models as in Figure 50
and Figure 51 but with light shading disabled. This creates a
more uniform look to the models, but not necessary more realis-
tic.
104
Figure 56: Billboard Representation Bunny with High Error (without
Lighting)
Figure 57: Billboard Representation Dragon with High Error (without
Lighting)
Figure 56 and Figure 57 show the same models as in Figure 52
and Figure 53 but with light shading disabled.
6.2 Running Time of Algorithm
105
As can be seen in Figure 58 below, this implementation of
the Billboard Clouds algorithm leads to a much more simplified
version of the object to be created. These values show that the
number of polygons needed to create the object is severely less-
ened upon completion of the algorithm. The number of polygons
to be used can also be reduced with the increase in the size of
the error metric. As these values decrease, the time needed to
execute the algorithm also decreases.
This version of the algorithm finds the best fitting plane for
use creating an optimal set of billboards. As this is good for cre-
ating the billboard clouds representation of the object, it does
Model Faces Error Bill-boards
Time (s)
Time (m)
Bun
ny
16301
0.05 162 1341.672 22.3612
0.1 46 983.438 16.39063
0.15 29 720.187 12.00312
0.2 20 548.079 9.13465
Dra
gon
47794
0.05 179 5612.187
93.53645
0.1 45 2326.781
38.77968
0.15 21 1527.234 25.4539
0.2 14 1301.563
21.69272
Figure 58: Running Time of Algorithm
106
require longer time intervals to execute the algorithm. These
values are a bit higher than those of the original algorithm; how-
ever this implementation finds the best set of billboards.
The values for the final execution of the algorithm can be
seen in the chart in for the bunny and for the dragon models. As
can be seen in the two sections, the amount of time needed and
the number of planes needed to create the billboard clouds rep-
resentation decrease as the error metric increases. The billboard
column depict the amount of billboards needed for the bunny
and the dragon, respectively, using different levels of the error
metric. The time columns display the times needed to execute
the algorithm for the models using different levels of the error
metric. For each of these models, there is a drastic drop in the
number of polygons needed when the error metric is changed
from a rather small value of 0.05 to a value of 0.1. This is also
true in the amount of time needed for the algorithm. This change
in the error metric produces the largest changes in the outcome
of the algorithm. While further change in the error metric does
produce additional reduction, the changes are not nearly as
large.
107
6.3 Final State of Implementation
6.3.1 Shortcomings
This section lists some of the shortcomings we had in our
project. Some shortcomings were bugs from the core network of
Java, others were just algorithms being one step away from com-
pletely optimal and were left as is because of time constraints.
6.3.1.1 Shooting Textures
The largest problem we had with our implementation of the
Billboard Clouds algorithm actually aided us in not successfully
100% completing the algorithm. The problem was with shooting
textures from the model and creating the image files that will
later be displayed on the billboards. We could not actually get
Java3D to successfully render the images. The outcome was
usually that of an image being all black or all white. We believe
with confidence, that this was a bug within Java3D itself and that
our algorithm works perfectly. The Java website has a page ti-
tled “Java3D 1.3.1 API Known Issues/Bugs” (http://
java.sun.com/products/java-media/3D/java3d-bugs.html) and un-
der the “Core Graphics and Vecmath Bugs” section, there is this
entry:
108
4674146 Background texture fail to render for RenderedImage and byref ImageCom-
ponent2D
We believe this is the cause of the textures shooting improperly.
However, this simple line is not detailed enough to say that it is
the problem for sure, and therefore could still be a bug, despite
our best attempts to solve the problem.
Many tries have been made to get around this bug, but un-
fortunately, Java3D refuses to capture the images. Therefore, in
order to get successful looking output, we have manually cre-
ated the image files ourselves. With the image displayed on
screen, we take a manual screenshot (on a PC this is done by
pressing the PrtScr button on the keyboard. Once we have cap-
tured the image, we then paste it into a picture editing program.
We crop the image to our standard of 400 by 400 pixels, and
then we replace any black in the image, with the transparency
color. The image is then saved as an image file with the .gif ex-
tension since the .gif supports transparency. We do this for ev-
ery image that will be projected onto a billboard.
With this technique, we are able to create the final output
pictures of the model in the billboard clouds representation.
109
6.3.1.2 Java/Java3D only allocates 82 Megabytes of Memory
This is a “security” limitation within the Java or Java3D en-
vironment. Only 82 Mb can be allocated for our programs. Of
course when loading complex 3D models or many image files, 82
Mb can be used up very quickly. This was a problem for us as it
inhibited us from producing the results we desired with our algo-
rithm.
This limitation dominates our algorithm in two places. The
first is when we load the model information from the .ply file into
main memory. If the model has too many faces, we exceed our
82 Mb limit and the program will crash with an “out of memory”
error. We were able to load a model with 202 thousand poly-
gons with our memory usage closely approaching the 82 MB
limit, but not going over. Therefore, we have concluded than
any model with slightly higher than 202 thousand faces will run
the risk of not being able to load due to the Java/Java3D memory
usage limitation.
The second place for which the Java limitation dominates
our algorithm is when loading the images that are projected onto
the billboards. Each .gif file must be loaded into memory in or-
der to be projected onto the billboard. However, only about 50
110
images can be loaded before the program receives an “out of
memory” error. Therefore, we can choose to have two results.
One result is that we can only choose to create the billboard
clouds representation with only 50 billboards. Here, we would
not run out of memory, however, the final representation might
not look as well as it could since the complex model is being rep-
resented with only 50 billboards. The final image will definitely
not look as good as it could. The second result we can create is
to actually do the calculations for the model with as many bill-
boards as we want, but in the end, we could only project images
onto 50 of those billboards—the rest of the billboards would
have no images on them at all. This would create a more de-
tailed representation of the model with one serious drawback,
not all the images will be there. This results in a final represen-
tation having many holes where billboards would normally be—
but because we can only have 50 images, the holes cannot be
filled. These representations are shown in our screenshots the
figures from section 6.1.
There are not a whole lot of fixes to get around this Java
limitation. Basically, the only way we could fix this problem is to
not use up as much memory. This means that we could make
111
our algorithm more efficient, but as you will see in section
6.3.2.2 below, we feel that any further optimization of our algo-
rithm will probably yield negligible results. One way we could
definitely decrease memory usage would be to save the image
files in a smaller format. This is discussed more thoroughly in
section 6.3.1.3 below. However, smaller image files would only
go so far. For instance, the image sizes only apply to the final
step of the program, and would not help to alleviate the problem
of a large faced model being loaded into memory in the begin-
ning of the program.
6.3.1.3 Image File Compression
When saving the images to image files, many of the files
are larger than they have to be. Every billboard image is saved
as a 400 pixel by 400 pixel image. This means that if a billboard
consists of one triangle, it will take the up the same amount of
space that a billboard of 100 triangles will take. This is unneces-
sary and could be corrected. Time constraints inhibited us from
this implementation. With smaller image files to be imported
into main memory, this would help out the Java limitation de-
scribed in section 6.3.1.2 above.
112
6.3.1.4 Choosing Billboards
In the scope of the billboard clouds algorithm in general
(not just our project), there are three main methods to calculat-
ing the optimal set of billboards. One way is to use the method
implemented by Décoret et al. [2], where their algorithm can cal-
culate the necessary billboards in a matter of a few minutes.
However, their algorithm, while fast, does not represent the
most optimal placement of the billboards. A second way is to
use the method implemented in our algorithm. Our method finds
the most optimal/accurate set of billboards for the model. How-
ever, our algorithm, while accurate, takes much more time to
calculate. Can we have our cake and eat it too? The answer is
yes. A third way can be implemented that can find the most ac-
curate set of billboards in a matter of minutes. Time constraints
held us from implementing this optimal algorithm.
6.3.1.5 Liberal use of ArrayList
This section describes a programming imperfection
throughout our code. Since functions can only return one value,
Java’s ArrayList was used to put multiple values of different data
types into one variable that could be passed between functions.
Therefore, the individual values (which could have no relation
113
with each other) would have to be extracted from the ArrayList
in a structured way. The programmer must know the exact posi-
tion of each entity in the list to be able to use the values in other
calculations.
The better alternative to using ArrayLists’ is to use Java’s
struct class. This is the more correct way of achieving the same
goal. ArrayLists were used in this way during the early stages of
development. Gradually, a shift into the usage of structs oc-
curred.
This unique use of ArrayLists in no way affected the perfor-
mance of our algorithm, it just makes the code a little bit more
difficult to interpret.
6.3.1.6 Two Coordinate Systems
For some reason, the graphics world uses a different coor-
dinate system than the mathematics world. For whatever rea-
son, that difference managed to make it’s way into our code. All
pre-development calculations were done using the standard co-
ordinate system used in the mathematics world (where the y-axis
points up). So instead of re-calculating the formulas, a function
was created called swapPoint() that simply converts a point into
the graphics coordinate system (where the z-axis points up).
114
Again, this implementation in no way affects the perfor-
mance of our algorithm, it just makes the code more messy.
6.3.1.7 Better Model (.ply) Files
We would love more models to work with. The models we
got from the Standford 3D Scanning Repository [9] were good,
but lacked variety. Many of the models were too large for us to
use due to the Java main memory limit (see section 6.3.1.2 ).
What were left were a lot of models that all shared the same
quality, they were very rounded. While our algorithm does work
with rounded models, it would work even better with models
that were squarer and we would have liked to see the outcome
of those.
6.3.2 Achievements
This section lists our achievements. The Best-Fit Rectangle
algorithm was our most notable achievement, but on the whole—
just about everything in our program worked just as we had in-
tended it to.
6.3.2.1 Get Best-Fit Rectangle Algorithm
We consider this algorithm—described in detail in section
3.2.4 and 5.2.4—our most notable achievement in the implemen-
115
tation of the billboard clouds algorithm. While this algorithm
could have been implemented elsewhere, we had no awareness
of any such existence. This algorithm was created indepen-
dently by us and we believe it is the most optimal for finding the
smallest area rectangle given a set of 2D points.
6.3.2.2 Everything Else
Basically, if something is not listed in section 6.3.1 Short-
comings above, then it has worked exactly as we intended it to.
We are very proud of the efficiency and originality of all our al-
gorithms.
116
7 CONCLUSIONUpon completion of re-implementing the Billboard Clouds
algorithm in Java3D, a few things have been determined about
the algorithm and its results. The results showed that it does re-
duce the time needed to render a 3-Dimensional object and that
it is very portable to other systems or platforms. The algorithm
entirely works, but due to the fact that there is a bug in Java3D,
as was mentioned in section 6.3.1.1 , some modifications had to
be made in order to reach a final state.
The view independent version of the Billboard Clouds algo-
rithm has been implemented entirely as it is the more general of
the subclasses of the algorithm and can be more widely used. It
is complete and creates the billboards for a given 3D mesh
model. As an example, it allows for a client to run this algorithm
to find the best set of billboards that have already been calcu-
lated and given a set of technical specifications for the client, the
optimal set of billboards are returned to the client.
With the algorithm being implemented in Java3D it allows
for portability to different types of systems or platforms. The Ja-
va3D API uses the Java Virtual Machine to allow the algorithm to
run on different systems. Especially with the ever growing and
117
changing technology, the algorithm will be perfect for use with
different types of mobile devices. The Java Virtual Machine al-
lows for the algorithm to be used on these devices.
With respect to the code, it is felt that both the implemen-
tation of the algorithms and the algorithms themselves are ex-
tremely efficient and original. The wheel was reinvented many
times throughout the program even though outside sources
could have been consulted. Java3D in general was difficult to
use. In many areas it has been easier to use than OpenGL or Di-
rectX, however, it does not allow access for many low level func-
tions. These restrictions have showed up in the implementation
of this project.
There were some aspects to these areas that could have
been expanded upon given more time within the project, al-
though the requirements were met for the algorithm. The algo-
rithm does take a considerable amount of time, which could be
cut down given more time to allow for research into a better
sorting method within the algorithm. As time constraints were
reached, some of this research was not able to be reached. Also,
testing with mobile devices was not able to be obtained due to
118
time constraints. This would be a great area for further research
for a future project.
119
8 REFERENCES[1] Muller and Haines. Real Time Rendering, 2nd Edition.
[2] X. Décoret, F. Durand, F. Sillion, and J. Dorsey. Billboard
Clouds. In Rapport de recherche n° 4485, June 2002.
[3] Sun Microsystems, Inc (2004). The Source for Developers.
Retreived Janurary 25, 2004 from http://java.sun.com
[4] Nokia (2004). Wallpapers. Retreived March 25, 2004 from
http://www.n-gage.com/downloads/extras/wallpapers/
ngage_1280x1024.jpg
[5] PalmOne, Inc (2004). Tungsten|T3. Retreived April 5, 2004
from http://www.palmone.com/us/products/handhelds/tung-
sten-t3
[6] X. Décoret, F. Durand, F. Sillion, and J. Dorsey. Billboard
Clouds for Extreme Model Simplification. Proceedings of the
ACM Siggraph 2003.
[7] Watt, Alan H. 3D Computer Graphics 3rd Edition. Pearson
Addison Wesley, December 6, 1999.
[8] F. Cazals, G. Drettakis, and C. Puech. Filtering, clustering
and hierarcy construction: a new solution for ray-tracing com-
plex scenes. Computer Graphics Forum, Proc. Eurographics,
1995
120
[9] The Stanford 3D Scanning Repository. Stanford Computer
Graphics Labroatory. 1994-2004. 26 April 2004 <http://
graphics.stanford.edu/data/3Dscanrep/>
121
9 APPENDIX
9.1 Instructions on Running Code
9.1.1 Setting up the Environment
In order to compile and run the code, the computer will
need to have the latest versions of Java and Java3D. At the time
of this writing, these are Java 1.4 (J2SE v1.4.2_04 SDK) and Ja-
va3D 1.3 (Java3D v1.3.1 API). These are available for download
from the Java website (java.sun.com).
An option to make coding easier, is to set up a development
environment that makes coding and debugging much easier.
The environment used for this project was Eclipse available for
free from the Eclipse website (www.eclipse.org).
Once you are set up, you may import the billboard clouds
packages implemented for this project. To run a model, please
refer to section 9.1.2 below.
9.1.2 Program Descriptions
There are three programs that when run one after another,
will convert a 3D model into a Billboard Clouds set. The names
of the programs and the order they need to be run is described
in Figure 59 below:
122
BillboardCalc is the first program. This program reads in
the .ply file from disk. There are three parameters used to run
this program. The first parameter is an input file that points to
the .ply file. The .ply file is the 3D mesh model that will be run
through the Billboard Clouds algorithm. The second parameter
is a scale value. This value will scale the model to itself propor-
tionally. The third parameter is the name of the output file for
which the program will dump the data. The best format to write
this in would be a .csv file, though any will do.
BillboardProject is the second program to be run. As an ar-
gument, this program takes in the .csv file generated from the
first program. The filename for this input file will need to be put
Figure 59: 3 Programs to Run
123
in as a parameter. Also within this program, you can set the out-
put directory of where the image files will be stored.
BillboardDisplay is the third and final program. This pro-
gram takes in two parameters. The first parameter is same .csv
file used in the second program. The second parameter is the di-
rectory that points to the location of the image files created by
the second program.
124
9.2 Code
BillboardCalc.javapackage src.BillboardCalc;
import java.applet.Applet;import java.awt.BorderLayout;import java.awt.GraphicsConfiguration;import java.io.FileWriter;import java.util.ArrayList;
import javax.media.j3d.Alpha;import javax.media.j3d.AmbientLight;import javax.media.j3d.Appearance;import javax.media.j3d.Background;import javax.media.j3d.BoundingSphere;import javax.media.j3d.BranchGroup;import javax.media.j3d.Canvas3D;import javax.media.j3d.Group;import javax.media.j3d.LineAttributes;import javax.media.j3d.Material;import javax.media.j3d.PointLight;import javax.media.j3d.PolygonAttributes;import javax.media.j3d.QuadArray;import javax.media.j3d.RotationInterpolator;import javax.media.j3d.Shape3D;import javax.media.j3d.Transform3D;import javax.media.j3d.TransformGroup;import javax.vecmath.Color3f;import javax.vecmath.Point3d;import javax.vecmath.Point3f;import javax.vecmath.Vector3d;import javax.vecmath.Vector3f;
import com.sun.j3d.utils.applet.MainFrame;import com.sun.j3d.utils.geometry.Box;import com.sun.j3d.utils.universe.SimpleUniverse;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * This class comprises the entirety of the actual billboard calculation algorithm. * The projection / re-Displaying are in seperate class files. */
public class BillboardCalc extends Applet {
/* * This function basically takes in an angle, and converts it to Radian * * [in] float ang: The angle to convert * [return]: The converted value */public static float degToRad(float ang) {
return (float)(ang*Math.PI/180.0f);}
/* * Your basic main function. Set the window's default size to 400x400. */public static void main(String[] args) {
new MainFrame(new BillboardCalc(args), 400, 400);}
/* * Because we did all of our formulas in the coordinate system where Y was up, * we had to keep swapping the coordinate systems as we did our calculations.
125
* Very messy, and will be fixed in future versions. * * [in] Point3f pt: A point to switch * [return]: The swapped coordinate-system point */public static Point3f swapPoint(Point3f pt) {
return new Point3f(pt.x, pt.z, pt.y);}
/* * Same as above, except with Vectors instead * * [in] Vector3f pt: Vector to swap coordinates * [return]: Swapped Vector */public static Vector3f swapPoint(Vector3f pt) {
return new Vector3f(pt.x, pt.z, pt.y);}
/* * Some color definitions */private Color3f bgColor = new Color3f(0.13f, 0.51f, 0.13f);private Color3f clrBlack = new Color3f(0.0f, 0.0f, 0.0f);private Color3f clrBlue = new Color3f(0.0f, 0.0f, 1.0f);private Color3f clrCyan = new Color3f(0.0f, 1.0f, 1.0f);private Color3f clrGreen = new Color3f(0.0f, 1.0f, 0.0f);private Color3f clrPurple = new Color3f(1.0f, 0.0f, 1.0f);private Color3f clrRed = new Color3f(1.0f, 0.0f, 0.0f);private Color3f clrWhite = new Color3f(1.0f, 1.0f, 1.0f);private Color3f clrYellow = new Color3f(1.0f, 1.0f, 0.0f);
private SimpleUniverse u = null;
/* * Some basic directional vectors */private Vector3f vecAll = new Vector3f(1.0f, 1.0f, 1.0f);private Vector3f vecNull = new Vector3f(0.0f, 0.0f, 0.0f);private Vector3f vecX = new Vector3f(1.0f, 0.0f, 0.0f);private Vector3f vecY = new Vector3f(0.0f, 1.0f, 0.0f);private Vector3f vecZ = new Vector3f(0.0f, 0.0f, 1.0f);
String fileName;String outFile;float fileScale;float errorMetric;
/* * Set the arguments up from the program's running. */public BillboardCalc(String args[]) {
if(args.length != 4) {System.out.println("Invalid arguments. The correct way to run this program
is:\n");System.out.println("BillboardCalc <input model> <scale> <error metric> <output
file>");System.out.println(" <input model> - This is the input file for your model.
(ex. 'Models/dragon.ply')");System.out.println(" <scale> - This is how much to scale the model
internally. Default is 1.0.");System.out.println(" <error metric> - This is the error metric to determine how
many billboards will be");System.out.println(" outputted. This is model-dependant, and
is sort of a tweak factor.");System.out.println(" <output file> - This is where your billboard information
will be stored.");System.exit(1);
}
fileName = args[0];
126
fileScale = Float.parseFloat(args[1]);errorMetric = Float.parseFloat(args[2]);outFile = args[3];
}
/* * This is the meat of the code. It takes an error metric, and outputs a series of * billboards to a file provided in the startup arguments. */public TransformGroup calculateBillboards(float allow_err) {
Group ret;
TransformGroup tg2;
//Load the model from the file.PlyModelExtend theHouse = new PlyModelExtend();Shape3D shape = theHouse.loadMesh(fileName, clrGreen, fileScale, false);tg2 = new TransformGroup(); tg2.addChild(shape);
BillboardBestPlane bestPlane = new BillboardBestPlane();Structs.BBPlaneList contribList = new Structs.BBPlaneList();long start,end;
//Loop through each billboard [maximum of 100,000]start = System.currentTimeMillis();for(int i=0; i<100000; i++) {
if(theHouse.getCurFaces() == 0) {/* * All faces were found. Everything is good. */System.out.println("Breaking due to no more faces!");break;
}
theHouse.update();
//find the best billboard planeStructs.FaceContribList contrib;contrib = bestPlane.getBestPlane(theHouse, allow_err);
if(contrib.size() <= 0) {/* * If this error occurs, then it means that there were * faces that weren't caught by the algorithm. Suggestions * are to increase the spherical size [in BillboardBestPlane.java]. */System.out.println("Breaking due to no more contributables!! Oh no!");break;
}
//Record the information, and cut the used faces out of the model.contribList.add(contrib);theHouse.trimFaces(contrib);
System.out.println((i+1)+" billboards, "+theHouse.getCurFaces()+" faces left");}
System.out.println("");
/* * Now to output the billboards to the outputFile */try {
FileWriter fw = new FileWriter(outFile);
System.out.println("Outputting billboards to "+outFile);fw.write(fileName+","+fileScale+"\n");
//For each billboard...for(int i=0; i<contribList.size(); i++) {
Structs.FaceContribList contrib;
127
ArrayList arr = new ArrayList();
contrib = contribList.get(i);
//get its Normal planefloat flist[] = contrib.getPlane();Vector3f plane = Functions.getNormPlane(flist[0], flist[1], flist[2]);plane = swapPoint(plane);
String str;
/* * Get the Valid faces information and add them to the display list. */str = "";for(int j=0; j<contrib.size(); j++) {
int face = contrib.getRealInd(j);str += "," + face;
for(int k=0; k<3; k++) {Point3f pt, pt2;pt = new Point3f();
theHouse.theFaces.getCoordinate(face*3+k,pt);pt2 = new Point3f(pt);pt = swapPoint(Functions.orthoProject(pt, plane));
arr.add(pt);}
}/* * Do the same for the penalty faces; add those to the list as well. */for(int j=0; j<contrib.psize(); j++) {
int face = contrib.getPRealInd(j);str += "," + face;
for(int k=0; k<3; k++) {Point3f pt, pt2;pt = new Point3f();
theHouse.theFaces.getCoordinate(face*3+k,pt);pt2 = new Point3f(pt);pt = swapPoint(Functions.orthoProject(pt, plane));
arr.add(pt);}
}
plane.normalize();
//calculate the best-fit-rectangle of the billboardarr = Functions.brGetBestArea(arr,swapPoint(plane),flist[0]);
//Just a visual representation so we can see where the billboards are.tg2.addChild(createPlane(arr, plane, clrRed));
//Create the axis-orientation information for aligning the//billboards in the next program [BillboardProject]Vector3d off, x_ax, y_ax;off = new Vector3d((Point3f)arr.get(0));x_ax = new Vector3d((Point3f)arr.get(1)); x_ax.sub(off);x_ax.normalize();x_ax = new Vector3d(x_ax.x, x_ax.z, x_ax.y);
y_ax = new Vector3d(plane);y_ax.normalize();y_ax = new Vector3d(y_ax.x, y_ax.z, y_ax.y);
//Get the angles needed.double angles[] = Functions.brGetRotations(x_ax, y_ax);str = "," + Functions.radToDeg(angles[0]) + "," +
Functions.radToDeg(angles[1]) + "," +
128
Functions.radToDeg(angles[2]) + str;
//Extra infostr = (String)arr.get(5) + str + "\n";
fw.write(str);}
fw.close();} catch (Exception e) { e.printStackTrace(); System.exit(1); }
end = System.currentTimeMillis();
float total;
total = (float)(end-start)/1000.0f;
System.out.println("\nTotal time taken: "+total+" s");
return tg2;}
/* * This function takes the information for a billboard, and returns a * billboard shape to display. * * [in] ArrayList pts: These are the points that will be added to the shape * [in] Vector3f norm: This is the normal vector for the plane. Needed for the Normals. * [in] Color3f clr: This is what the color of the billboard rectangle will be. * [return]: Will return a Group containing a billboard Shape3D */public Group createPlane(ArrayList pts, Vector3f norm, Color3f clr) {
Appearance a = new Appearance();Material m = new Material();PolygonAttributes p = new PolygonAttributes();Shape3D shape = new Shape3D();QuadArray face;Vector3f norm2;int i;
//setup the attributes [make it a series of lines, instead of a face]LineAttributes latt = new LineAttributes();latt.setLineAntialiasingEnable(true);a.setLineAttributes(latt);p.setPolygonMode(PolygonAttributes.POLYGON_LINE);m.setDiffuseColor(clr);a.setMaterial(m);a.setPolygonAttributes(p);
face = new QuadArray(8, QuadArray.COORDINATES | QuadArray.NORMALS);
//Set the coordinatesPoint3f pt;for(i=0; i<4; i++) {
pt = (Point3f)pts.get(i);face.setCoordinate(i, pt); face.setNormal(i,norm);face.setCoordinate(7-i, pt); face.setNormal(7-i,norm);
}
//Add the shapeGroup gr = new Group();gr.addChild(shape);shape.setAppearance(a);shape.addGeometry(face);
return gr;}
/* * Creates a very simple scenegraph to hold the model. */
129
public BranchGroup createSceneGraph(SimpleUniverse u) {TransformGroup tg, tg2;Vector3f vec, pos, rot;Transform3D t, t2;Appearance a;Material m;int i;
// Create the root of the branch graphBranchGroup objRoot = new BranchGroup();TransformGroup objScene = new TransformGroup();TransformGroup objInit = new TransformGroup();
//scale, translate, and rotate it// 0.5, {0,0,-10}, {20,-10,0}t = new Transform3D();t2 = new Transform3D(); t2.setScale(0.5); t.mul(t2);t2 = new Transform3D(); t2.setTranslation(new Vector3f(0.0f, 0.0f, -10.0f)); t.mul(t2);t2 = new Transform3D(); t2.rotX(degToRad(20.0f)); t.mul(t2);t2 = new Transform3D(); t2.rotY(degToRad(-10.0f)); t.mul(t2);objInit.setTransform(t);objRoot.addChild(objInit);
objScene = new TransformGroup();objScene.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);objInit.addChild(objScene);
Alpha alpha = new Alpha(-1,10000);RotationInterpolator rotInt = new RotationInterpolator(alpha, objScene);rotInt.setSchedulingBounds(new BoundingSphere());objScene.addChild(rotInt);
// Create a bounds for the background and lightsBoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
// Set up the backgroundBackground bg = new Background(bgColor);bg.setApplicationBounds(bounds);objScene.addChild(bg);
LineAttributes latt = new LineAttributes();latt.setLineAntialiasingEnable(true);a = new Appearance();a.setLineAttributes(latt);m = new Material();m.setDiffuseColor(clrYellow);a.setMaterial(m);Box ground = new Box(5.0f, 0.0f, 5.0f, Box.GENERATE_NORMALS, a);objScene.addChild(ground);
//Add the model & billboard rectanglesobjScene.addChild(calculateBillboards(errorMetric));
Point3f pt = new Point3f();tg = new TransformGroup();
//Light the scene a bit...Point3f lPos = new Point3f(10.0f, 10.0f, 10.0f);
PointLight l1 = new PointLight();l1.setAttenuation(1.0f, 0.0f, 0.0f);l1.setInfluencingBounds(bounds);l1.setPosition(lPos);
AmbientLight l2 = new AmbientLight();l2.setInfluencingBounds(bounds);l2.setColor(clrWhite);
130
tg = new TransformGroup();tg.addChild(l1);tg.addChild(l2);objScene.addChild(tg);
// Let Java 3D perform optimizations on this scene graph.objRoot.compile();
return objRoot;}
/* * (non-Javadoc) * @see java.applet.Applet#destroy() */public void destroy() {
u.cleanup();}
/* * This sets up the basic info for the universe. * * @see java.applet.Applet#init() */public void init() {
setLayout(new BorderLayout());GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
Canvas3D c = new Canvas3D(config);add("Center", c);
u = new SimpleUniverse(c);BranchGroup scene = createSceneGraph(u);
u.getViewingPlatform().setNominalViewingTransform();
u.addBranchGraph(scene);}
}
131
BillboardBestPlane.javapackage src.BillboardCalc;
import java.lang.reflect.Array;import java.util.ArrayList;
import javax.vecmath.Point3f;import javax.vecmath.Vector3f;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * This class calculates which billboards are best to display, out of all planes. */class BillboardBestPlane {
/* * This is a simple test to determine if all N [3] sides of a triangle are within ERROR * distance, i.e. are simply valid for a certian angle. * * [in] float theta: The theta angle to check. * [in] float phi: The phi angle to check. * [in] Point3f pts[]: A series of points that represent the face. * [in] float ERROR: The error metric to check. * [return]: The average distance for the face. */public static float isSimplyValid(float theta, float phi, Point3f pts[], float ERROR) {
float height, max=-100000.0f, min=100000.0f;int i;
//For each, set the max/min values appropriatelyfor(i=0; i<Array.getLength(pts); i++) {
height = Functions.funcHoughTransform(pts[i], theta, phi);
if(height > max) max=height;if(height < min) min=height;
}
//if the max-min range is > ERROR, we've got a problem.if(max-min > ERROR) return 0.0f;
//return the average heightreturn (max+min)/2;
}
/* * Almost the same as above, it checks validity for a certian height. * * [in] float theta: The theta angle to check. * [in] float phi: The phi angle to check. * [in] float rad: How far away we're checking against. * [in] Point3f pts[]: A series of points that represent the face. * [in] float ERROR: The error metric to check. * [return]: True or false, depending on if it's valid. */public static boolean isValid(float theta, float phi, float rad, Point3f pts[], float ERROR) {
int i;float height;
//For each, make sure that they're within ERROR from the rad valuefor(i=0; i<Array.getLength(pts); i++) {
height = Functions.funcHoughTransform(pts[i], theta, phi);
if(Math.abs(height-rad) > ERROR) return false;}
return true;}
132
/* * This checks to see whether a face lies within the penalty region. * * [in] float theta: The theta angle to check. * [in] float phi: The phi angle to check. * [in] float rad: How far away we're checking against. * [in] Point3f pts[]: A series of points that represent the face. * [in] float ERROR: The error metric to check. * [return]: True or false, depending on if it's valid. */public static boolean isPenalty(float theta, float phi, float rad, Point3f pts[], float ERROR) {
int i;float height;
for(i=0; i<Array.getLength(pts); i++) {height = Functions.funcHoughTransform(pts[i], theta, phi);
if( Math.abs(height-rad) > 2*ERROR ||Math.abs(height-rad) <= ERROR) return false;
}
return true;}
/* * This formula calculates the projected area of a face at a certian angle. * * [in] float theta: The theta angle to check. * [in] float phi: The phi angle to check. * [in] float rad: <ignore, or pass 1.0f> * [in] Point3f pts[]: A series of points that represent the face. * [return]: The projected area of the face. */public static float projectedArea(float theta, float phi, float rad, Point3f pts[]) {
Point3f proj[] = new Point3f[3];int i;
Vector3f planeNorm = Functions.getNormPlane(rad, theta, phi);
for(i=0; i<Array.getLength(pts); i++) {proj[i] = Functions.orthoProject(pts[i], planeNorm);
}return Functions.triangleArea(proj[0], proj[1], proj[2]);
}
int included_faces;boolean made_sphere=false;private Structs.SphereList sphere = new Structs.SphereList();
/* * This is the core algorithm that finds the best billboard for each major iteration. * * [in] PlyModelExtend model: Model file for calculations. * [in] float ERROR: Error metric. * [return]: A FaceContribList of all the contributing valid/penalty faces. */public Structs.FaceContribList getBestPlane(PlyModelExtend model, float ERROR) {
float phi, theta, rad;int i, j, k, l, m, min, max, minp, maxp;int hdiv = 0;Structs.SphereList.CoordPair c;float val;float errordiv = 1.0f;Point3f pts[];float flist[], h, maxv;int ct, ct2;Structs.SphereList.CoordPair.FaceInfo arr1[], arr2[], finfo;int div = 500; //Change this if you want more accuracy in your angle calculations
//Warning: time increases linearly as this increases
133
ArrayList ranges;float mh[];float[] heights[] = new float[div][];Structs.FaceContribList contrib = new Structs.FaceContribList();
//Create the angle listStructs.SphereList spl;sphere = spl = makeSphere(div, 10000);
//Start generating the contributing listsfor(i=0,ct=0,maxv=0; i<spl.size(); i++) {
c = spl.getCoords(i);
//Find all of the simply valid faces, and put them into an arrayarr1 = new Structs.SphereList.CoordPair.FaceInfo[model.getCurFaces()];for(j=0,ct2=0; j<model.getCurFaces(); j++) {
pts = model.getVers(j);h = isSimplyValid(c.theta, c.phi, pts, ERROR);if(h != 0.0f) {
ct++;finfo = new Structs.SphereList.CoordPair.FaceInfo();finfo.fnum = j;finfo.height = h;finfo.contrib = projectedArea(c.theta, c.phi, h, pts);arr1[ct2++] = finfo;
}}
//Sort the faces, so they're in order based on Hough-heightQuickSortFaces(arr1, 0, ct2-1);
arr2 = new Structs.SphereList.CoordPair.FaceInfo[ct2];System.arraycopy(arr1, 0, arr2, 0, ct2);
/* * I use a sliding window to calculate the highest value. Basically * slide the min/max values up the list as they change, and add/subtract * the contributions from each face as they go in/out of the window. * Hard to explain really... will be sped up in the future. */arr1 = null;for(val=0,min=0,max=0,minp=0,maxp=0,j=0; j<ct2; j++) {
//increment the Minimum Valid sliding windowwhile(true) {
if(min >= ct2) break;pts = model.getVers(arr2[min].fnum);if(!isValid(c.theta, c.phi, arr2[j].height, pts, ERROR)) {
val -= arr2[min].contrib;} else {
break;}min++;
}//increment the Maximum Valid sliding windowwhile(true) {
if(max >= ct2) break;pts = model.getVers(arr2[max].fnum);if(isValid(c.theta, c.phi, arr2[j].height, pts, ERROR)) {
val += arr2[max].contrib;} else {
break;}max++;if(maxp < max) maxp=max;
}//increment the Maximum Penalty sliding windowwhile(true) {
if(maxp >= ct2) break;pts = model.getVers(arr2[maxp].fnum);if(!isPenalty(c.theta, c.phi, arr2[j].height, pts, ERROR)) break;maxp++;
134
}//increment the Minimum Penalty sliding windowwhile(true) {
if(minp >= ct2) break;pts = model.getVers(arr2[minp].fnum);if(isPenalty(c.theta, c.phi, arr2[j].height, pts, ERROR)) break;minp++;if(minp >= min) {
minp = min;break;
}}
finfo = arr2[j];finfo.min = min;finfo.max = max;finfo.tcontrib = val;
//If there's a new max, set all the appropriate informationif(finfo.tcontrib > maxv) {
System.out.println("New Max: "+finfo.tcontrib+", num="+(maxp-minp)+", ["+Functions.radToDeg(c.theta)+", "+Functions.radToDeg(c.phi)+", "+finfo.height+"]");
maxv = finfo.tcontrib;
//now that we've got the indices for the sliding windows, just //iterate over all of them//to find the contributing faces.Structs.FaceContribList.FaceInfo contriblist[] = new
Structs.FaceContribList.FaceInfo[max-min];Structs.FaceContribList.FaceInfo plist[] = new
Structs.FaceContribList.FaceInfo[(maxp-max)+(min-minp)];for(k=min; k<max; k++) {
Structs.FaceContribList.FaceInfo finf = new Structs.FaceContribList.FaceInfo();
finf.set(arr2[k].fnum, model.faceList.getInd(arr2[k].fnum));
contriblist[k-min] = finf;}int nc=0;for(k=minp; k<min; k++,nc++) {
Structs.FaceContribList.FaceInfo finf = new Structs.FaceContribList.FaceInfo();
finf.set(arr2[k].fnum, model.faceList.getInd(arr2[k].fnum));
plist[nc] = finf;}for(k=max; k<maxp; k++,nc++) {
Structs.FaceContribList.FaceInfo finf = new Structs.FaceContribList.FaceInfo();
finf.set(arr2[k].fnum, model.faceList.getInd(arr2[k].fnum));
plist[nc] = finf;}
//Sort the listsQuickSortContrib(contriblist, 0, max-min-1);QuickSortContrib(plist, 0, (maxp-max)+(min-minp)-1);
contrib = new Structs.FaceContribList();contrib.setPlane(finfo.height,c.theta,c.phi);contrib.setList(contriblist, max-min);contrib.setPList(plist, (maxp-max)+(min-minp));
}}arr2 = null;
}
135
//return the max informationSystem.out.println("");return contrib;
}
/* * Use the equal-distance algorithm to create a set of equally distant theta/phi angles. * * [in] int N: How many angles we want to create [will be sliced approx. in half]. * [in] int deg: Used only in dire emergencies when the algorithm -doesn't- converge. * Highly unlikely though. * [return]: SphereList containing the angle information. */public Structs.SphereList makeSphere(int N, int deg) {
Vector3f pt[] = new Vector3f[N];Vector3f ptn[] = new Vector3f[N];Structs.SphereList spl = new Structs.SphereList();int i,j,it,sp;float f, max;Vector3f v;
if(made_sphere) return sphere;
System.out.println("Generating spherical coverage for "+N+" points");
//randomize all the pointsfor(i=0; i<N; i++) {
pt[i] = new Vector3f((float)Math.random()*2.0f-1.0f, (float)Math.random()*2.0f-1.0f, (float)Math.random()*2.0f-1.0f);
pt[i].normalize();}
//set the first point to a constantpt[0] = new Vector3f(0.0f, 0.0f, 1.0f);
sp=0;for(it=0; it<deg; it++) {
//Calculate the forces on each point from every other point//Edit: Fascinating... it appears to converge a lot quicker//when there -are- no force magnitudes, just directionsptn[0] = new Vector3f(pt[0]);for(i=1; i<N; i++) {
ptn[i] = new Vector3f();for(j=0; j<N; j++) {
if(i != j) {v = new Vector3f(pt[i]);v.sub(pt[j]);
// f = (float)Math.exp(-Math.pow(v.length(), 3));// v.normalize();// v.scale(f);
v.normalize();
ptn[i].add(v);}
}}
//Calculate the maximum change in angle [trig time!]max=0.0f;for(i=0; i<N; i++) {
ptn[i].normalize();v = new Vector3f(ptn[i]);v.sub(pt[i]);
f = (float)Math.acos(1.0f-v.lengthSquared()/2.0f);f *= 180.0f / Math.PI;
if(f > max) max=f;pt[i] = new Vector3f(ptn[i]);
}
136
if(it%20 == 0) {System.out.println((it/(float)deg*100.0f)+"% done with sphere creation
("+max+")");}
//if the points are still, it's converged enoughif(max < 0.02f) {
System.out.println("exiting due to max beneath min ("+max+")\n");break;
}}
//Now that everything's settled, let's record the informationfor(i=0; i<N; i++) {
float theta, phi, r;float arr[] = new float[2];
theta = (float)Math.atan2(pt[i].z, pt[i].x);r = (float)Math.sqrt(pt[i].x*pt[i].x + pt[i].z*pt[i].z);phi = (float)Math.atan2(pt[i].y,r);
//we can literally cut off the bottom half of the sphere,//because negative angles can be represented by positive//phi angles, just with a negative radiusif(phi >= 0.0f) { spl.add(theta, phi); }
}//cache it for this sessionmade_sphere = true;
System.out.println("Sphere created with "+spl.size()+" points");
return spl;}
/* * Your vanilla Quick Sort function. This one sorts by FaceInfo's rind [real-index] field. * * [in] Array * [in] Low Index * [in] Hi Index */
void QuickSortContrib(Structs.FaceContribList.FaceInfo a[], int lo0, int hi0) {int lo = lo0;int hi = hi0;Structs.FaceContribList.FaceInfo T, pivot;
if (lo >= hi) return;else if( lo == hi - 1 ) {
if (a[lo].rind > a[hi].rind) { T = a[lo]; a[lo] = a[hi]; a[hi] = T; } return;
}
pivot = a[(lo + hi) / 2]; a[(lo + hi) / 2] = a[hi]; a[hi] = pivot;
while( lo < hi ) { while (a[lo].rind <= pivot.rind && lo < hi) lo++;
while (pivot.rind <= a[hi].rind && lo < hi ) hi--;
if( lo < hi ) { T = a[lo]; a[lo] = a[hi]; a[hi] = T; }}
a[hi0] = a[hi]; a[hi] = pivot;
QuickSortContrib(a, lo0, lo-1);QuickSortContrib(a, hi+1, hi0);
}
137
/* * Your vanilla Quick Sort function. This one sorts by FaceInfo's height field. * * [in] Array * [in] Low Index * [in] Hi Index */
void QuickSortFaces(Structs.SphereList.CoordPair.FaceInfo a[], int lo0, int hi0) {int lo = lo0;int hi = hi0;Structs.SphereList.CoordPair.FaceInfo T, pivot;
if (lo >= hi) return;else if( lo == hi - 1 ) {
if (a[lo].height > a[hi].height) { T = a[lo]; a[lo] = a[hi]; a[hi] = T; } return;
}
pivot = a[(lo + hi) / 2]; a[(lo + hi) / 2] = a[hi]; a[hi] = pivot;
while( lo < hi ) { while (a[lo].height <= pivot.height && lo < hi) lo++;
while (pivot.height <= a[hi].height && lo < hi ) hi--;
if( lo < hi ) { T = a[lo]; a[lo] = a[hi]; a[hi] = T; }}
a[hi0] = a[hi]; a[hi] = pivot;
QuickSortFaces(a, lo0, lo-1);QuickSortFaces(a, hi+1, hi0);
}}
138
Functions.javapackage src.BillboardCalc;
import java.util.ArrayList;
import javax.vecmath.Point2d;import javax.vecmath.Point3d;import javax.vecmath.Point3f;import javax.vecmath.Vector2d;import javax.vecmath.Vector3d;import javax.vecmath.Vector3f;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * This is a multitude of helper functions that make the billboard clouds algorithm work. */public class Functions {
/* * The following are various Radian-to-Degree or vice-versa functions. */public static float degToRad(float ang) {
return (float)(ang*Math.PI/180.0f);}
public static float radToDeg(float ang) {return (float)(ang*180.0f/Math.PI);
}
public static double degToRad(double ang) {return ang*Math.PI/180.0f;
}
public static double radToDeg(double ang) {return ang*180.0f/Math.PI;
}
/* * Because of the incorrect coordinate system we used, we had to use this function to * switch between them. Basically swaps the Z & Y coordinates. */public static Point3f swapPoint(Point3f pt) {
return new Point3f(pt.x, pt.z, pt.y);}
/* * Takes in a spherical coordinate, and returns a Cartesian point equivalent. * * [in] float rad: Radius of the point * [in] float theta: Theta angle * [in] float phi: Phi angle * [return]: Returns a Point3f value equivalent to the spherical coordinates */public static Point3f sphereToCart(float rad, float theta, float phi) {
Point3f pt = new Point3f();
pt.x = (float)(rad*Math.cos(theta)*Math.cos(phi));pt.y = (float)(rad*Math.sin(theta)*Math.cos(phi));pt.z = (float)(rad*Math.sin(phi));
return pt;}
/* * Same as above, but circular coordinates -> cartesian 2D * * [in] double rad: Radius of the point * [in] double theta: Theta angle * [return]: Returns a Point2d value equivalent to the circ. coords
139
*/public static Point2d circToCart(double rad, double theta) {
Point2d pt = new Point2d();
pt.x = rad*Math.cos(theta);pt.y = rad*Math.sin(theta);
return pt;}
/* * Returns the 3D Hough Transformation height of a point at an angle. * Distance is from the origin. * * [in] Point3f pt: The actual point * [in] float theta: Theta angle * [in] float phi: Phi angle * [return]: Returns the distance a point is from origin * when perpendicular to a plane. */public static float funcHoughTransform(Point3f pt, float theta, float phi) {
return (float)(pt.x*Math.cos(theta)*Math.cos(phi) + pt.y*Math.sin(theta)*Math.cos(phi) + pt.z*Math.sin(phi));
}
/* * Returns the 2D Hough Transformation height of a point at an angle. * Distance is from the origin. * * [in] Point2d pt: The actual point * [in] double theta: Theta angle * [return]: Returns the distance a point is from origin * when perpendicular to a plane. */public static double funcHoughTransform(Point2d pt, double theta) {
return pt.x*Math.cos(theta) +pt.y*Math.sin(theta);
}
/* * Gets a normal plane for a [theta,phi] angle. * * [in] float rad: <realistically ignored> * [in] float theta: Theta angle * [in] float phi: Phi angle * [return]: A vector3f that is the normal vector for the angle */public static Vector3f getNormPlane(float rad, float theta, float phi) {
float r;r = (rad < 0)? -1.0f : 1.0f;return new Vector3f(sphereToCart(r, theta, phi));
}
/* * Gets a normal plane for a series of three points. * * [in] Point3f pt1: Point #1 * [in] Point3f pt2: Point #2 * [in] Point3f pt3: Point #3 * [return]: A vector3f that is the normal vector for the triangle */public static Vector3f getNormPlane(Point3f pt1, Point3f pt2, Point3f pt3) {
Vector3f AB, AC;
AB = new Vector3f(pt2); AB.sub(pt1);AC = new Vector3f(pt3); AC.sub(pt1);
AB.cross(AB,AC);AB.normalize();return AB;
140
}
/* * Equation for Orthographic Projection of a point P onto a plane with * surface normal N: * P' = P - [(P.N)/(|N|^2) - 1]*N * * [in] Point3f pt: Point to project * [in] Vector3f norm: Normal vector for the plane * [return]: A projected point onto the plane */public static Point3f orthoProject(Point3f pt, Vector3f norm) {
Vector3f ptVec, ret;float k;
ptVec = new Vector3f(pt);k = ptVec.dot(norm) / norm.lengthSquared() - 1.0f;
ret = new Vector3f();ret.scale(k, norm);ret.sub(ptVec);ret.negate();
return new Point3f(ret);}
/* * This function calculates the area of a triangle. * * [in] Point3f p0: Point #1 * [in] Point3f p1: Point #2 * [in] Point3f p2: Point #3 * [return]: The area of the triangle */public static float triangleArea(Point3f p0, Point3f p1, Point3f p2) {
Vector3f v0 = new Vector3f(p1.x - p0.x, p1.y - p0.y, p1.z - p0.z);Vector3f v1 = new Vector3f(p2.x - p0.x, p2.y - p0.y, p2.z - p0.z);Vector3f v2 = new Vector3f(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
float a = v0.length();float b = v1.length();float c = v2.length();
float s = (a + b + c) / 2.0f;
return (float)Math.sqrt(s * (s-a) * (s-b) * (s-c));}
/* * The following are just utility functions to print Vector/Point information. */public static String prVec(Vector3f vec) {
return "["+vec.x+","+vec.y+","+vec.z+"]";}
public static String prVec(Vector3d vec) {return "["+vec.x+","+vec.y+","+vec.z+"]";
}
public static String prVec2(Point3f vec) {return vec.x+","+vec.y+","+vec.z;
}
/* * Basic equation to rotate something on the X axis * * [in] Vector3d vec: Input point/vector * [in] double ang: Angle to rotate [Radian] * [return]: A new vector that's rotated */public static Vector3d brRotationX(Vector3d vec, double ang) {
141
Vector3d vec2 = new Vector3d();double c, s;
c = Math.cos(ang);s = Math.sin(ang);
vec2.x = vec.x;vec2.y = vec.y*c + vec.z*s;vec2.z = vec.z*c - vec.y*s;
return vec2;}
/* * Basic equation to rotate something on the Z axis * * [in] Vector3d vec: Input point/vector * [in] double ang: Angle to rotate [Radian] * [return]: A new vector that's rotated */public static Vector3d brRotationZ(Vector3d vec, double ang) {
Vector3d vec2 = new Vector3d();double c, s;
c = Math.cos(ang);s = Math.sin(ang);
vec2.x = vec.x*c + vec.y*s;vec2.y = vec.y*c - vec.x*s;vec2.z = vec.z;
return vec2;}
/* * This function calculates the rotations that are needed to get a plane from * an arbitrary angle to its own X-Y coordinate system [i.e. all Z points are 0.0f]. * What follows is some heavy trig. * * [in] Vector3d x_ax: X axis for calculations * [in] Vector3d y_ax: Y axis for calculations * [return]: A small array containing the: * [0] - X rotation * [1] - Z rotation * [2] - X2 rotation */public static double[] brGetRotations(Vector3d xo_ax, Vector3d yo_ax) {
Vector3d vec, x_ax, y_ax;double x_a, z_a, x2_a;
x_ax = new Vector3d(xo_ax);y_ax = new Vector3d(yo_ax);
vec = new Vector3d(x_ax); vec.x = 0.0f;x_a = Math.acos(vec.y / vec.length());
if(x_ax.z < 0) x_a = -x_a;
x_ax = brRotationX(x_ax, x_a); y_ax = brRotationX(y_ax, x_a);
z_a = Math.acos(x_ax.x);if(x_ax.y < 0) z_a = -z_a;
x_ax = brRotationZ(x_ax, z_a); y_ax = brRotationZ(y_ax, z_a);
x2_a = Math.acos(y_ax.y);if(y_ax.z < 0) x2_a = -x2_a;
x_ax = brRotationX(x_ax, x2_a); y_ax = brRotationX(y_ax, x2_a);
double angles[] = new double[3];
142
angles[0] = x_a;angles[1] = z_a;angles[2] = x2_a;
return angles;}
/* * This function changes a whole array of 3D points into a transformed * series of points that are on the "2D" system [i.e. X,Y,0] * * [in] ArrayList pts: A list of Point3f values to transform * [in] Vector3d off: Every point must be subtracted by an offset so * that it's relative to origin * [in] double rots[]: The series of three rotations calculated above * [return]: A new series of points that have been transformed */public static ArrayList brGetXYCoords(ArrayList pts, Vector3d off, double rots[]) {
ArrayList list = new ArrayList();Vector3d vec, vec2;Point3d pt;int i, j;
for(i=0; i<pts.size(); i++) {vec = new Vector3d((Point3f)pts.get(i)); vec.sub(off);
vec2 = brRotationX(vec, rots[0]);vec2 = brRotationZ(vec2, rots[1]);vec2 = brRotationX(vec2, rots[2]);
list.add(vec2);}
ArrayList list2 = new ArrayList();for(i=0; i<list.size(); i++) {
vec = (Vector3d)list.get(i);pt = new Point3d(vec.x, vec.y, vec.z);list2.add(pt);
}
return list2;}
/* * Takes in a list of points, an angle and finds the area for it. * See the research paper for a description of the Best-Fit Rectangle * algorithm, and how it works [pictures are so much more descriptive]. * * [in] ArrayList pts: A "2D" set of Point3d points * [in] double theta: Angle to find for * [return]: A set of doubles that contains width & average value. */public static double[] brAreaFromAngle(ArrayList pts, double theta) {
double min=100000, max=-100000, r;Point3d pt;Point2d pt2;int i;
for(i=0; i<pts.size(); i++) {pt = (Point3d)pts.get(i);pt2 = new Point2d(pt.x, pt.y);r = funcHoughTransform(pt2, theta);if(r < min) min=r;if(r > max) max=r;
}
double arr[] = new double[2];arr[0] = max-min;arr[1] = (max+min)/2.0f;
return arr;
143
}
/* * This is the mother function for finding Best-Fit rectangle. * * [in] ArrayList pts: A set of Point3f points that all lie on an arbitrary plane * [in] Vector3f plane: Normal for the plane * [in] float rad: Distance of the plane from origin * [return]: An amalgamation of several values... see the code. */public static ArrayList brGetBestArea(ArrayList pts, Vector3f plane, float rad) {
double w=0.0f,h=0.0f,min=10000,min_w=0.0f,min_h=0.0f;double min_x=0.0f,min_y=0.0f,min_t=0.0f;double arr[],x,y,area,theta;Vector3d off, vec, vec2, x_ax, y_ax;ArrayList pts2, pts3;int i,j;
pts3 = new ArrayList();
//Get the axesoff = new Vector3d((Point3f)pts.get(0));x_ax = new Vector3d((Point3f)pts.get(1)); x_ax.sub(off);x_ax.normalize();
y_ax = new Vector3d(x_ax);y_ax.cross(y_ax,new Vector3d(plane));y_ax.normalize();
//get the rotationsdouble rots[] = brGetRotations(x_ax, y_ax);
//transform the coordinatespts2 = brGetXYCoords(pts, off, rots);
Vector2d v1, v2, minv1, minv2;v1 = v2 = minv1 = minv2 = new Vector2d();
//Find the angle with the smallest areafor(i=0; i<180; i++) {
theta = degToRad(i/2.0);
arr = brAreaFromAngle(pts2,theta);w = arr[0];x = arr[1]; y=theta;v1 = new Vector2d(x*Math.cos(y), x*Math.sin(y));
arr = brAreaFromAngle(pts2,theta+degToRad(90.0f));h = arr[0];x = arr[1]; y=theta+degToRad(90.0f);v2 = new Vector2d(x*Math.cos(y), x*Math.sin(y));
v1.add(v2);x = v1.x;y = v1.y;
area=w*h;if(area < min) {
min=area;min_x=x;min_y=y;min_w=w;min_h=h;minv1 = new Vector2d(v1);minv2 = new Vector2d(v2);min_t=theta;
}}
Vector3d vecd, ov, offd, wv, hv;Point2d tv;
144
ov = new Vector3d(min_x, min_y, 0.0f);
tv = circToCart(min_w/2.0f,min_t+degToRad(0.0f));wv = new Vector3d(tv.x, tv.y, 0.0f);tv = circToCart(min_h/2.0f,min_t+degToRad(90.0f));hv = new Vector3d(tv.x, tv.y, 0.0f);
vec = new Vector3d(plane);vec.scale(Math.abs(rad)-1.0);off.add(vec);
//Calculate the bottom-left corner of the rectanglevecd = new Vector3d(ov); vecd.sub(wv); vecd.sub(hv);vecd = brRotationX(vecd, -rots[2]);vecd = brRotationZ(vecd, -rots[1]);vecd = brRotationX(vecd, -rots[0]);vecd.add(off);pts3.add(swapPoint(new Point3f(vecd)));
//Calculate the upper-left corner of the rectanglevecd = new Vector3d(ov); vecd.sub(wv); vecd.add(hv);vecd = brRotationX(vecd, -rots[2]);vecd = brRotationZ(vecd, -rots[1]);vecd = brRotationX(vecd, -rots[0]);vecd.add(off);pts3.add(swapPoint(new Point3f(vecd)));
//Calculate the upper-right corner of the rectanglevecd = new Vector3d(ov); vecd.add(wv); vecd.add(hv);vecd = brRotationX(vecd, -rots[2]);vecd = brRotationZ(vecd, -rots[1]);vecd = brRotationX(vecd, -rots[0]);vecd.add(off);pts3.add(swapPoint(new Point3f(vecd)));
//Calculate the bottom-right corner of the rectanglevecd = new Vector3d(ov); vecd.add(wv); vecd.sub(hv);vecd = brRotationX(vecd, -rots[2]);vecd = brRotationZ(vecd, -rots[1]);vecd = brRotationX(vecd, -rots[0]);vecd.add(off);pts3.add(swapPoint(new Point3f(vecd)));
//Calculate the middle of the rectanglevecd = new Vector3d(ov);vecd = brRotationX(vecd, -rots[2]);vecd = brRotationZ(vecd, -rots[1]);vecd = brRotationX(vecd, -rots[0]);vecd.add(off);pts3.add(swapPoint(new Point3f(vecd)));
//Add misc information for the BillboardProject program to usepts3.add(prVec2((Point3f)pts3.get(0))+","+prVec2((Point3f)pts3.get(1))+","+
prVec2((Point3f)pts3.get(2))+","+prVec2((Point3f)pts3.get(3))+","+ min_w+","+min_h);
return pts3;}
};
145
PlyModel.javapackage src.BillboardCalc;
import java.io.FileReader;import java.io.IOException;import java.io.LineNumberReader;import java.util.ArrayList;
import javax.media.j3d.Appearance;import javax.media.j3d.LineAttributes;import javax.media.j3d.Material;import javax.media.j3d.Shape3D;import javax.media.j3d.TriangleArray;import javax.vecmath.Color3f;import javax.vecmath.Point3f;import javax.vecmath.Vector3f;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * The class is solely concerned with handling PLY file interactions. */public class PlyModel {
public Shape3D theShape = null;public Point3f theVers[] = null;public TriangleArray theFaces = null;public Structs.FacesList faceList;
//how many faces/vertices the model haspublic int num_ver = 0;public int num_face = 0;
/* * This loads a PLY file into the program, and returns a Shape3D object. * Oh yeah, added normalization to it as well. * * [in] String loc: This it the file location for the model * [in] Color3f clr: Since ply files aren't colored, I added the option here * [in] float scale: If you want to scale up/down the model size * [in] boolean ambient: Whether or not it will have an ambient color set * [return]: Returns a Shape3D object for the model */public Shape3D loadMesh(String loc, Color3f clr, float scale, boolean ambient) {
String str, str2;int state=0, i=0;Appearance a = new Appearance();Material m = new Material();
LineAttributes latt = new LineAttributes();latt.setLineAntialiasingEnable(true);a.setLineAttributes(latt);if(ambient) m.setAmbientColor(clr);m.setDiffuseColor(clr);a.setMaterial(m);theShape.setAppearance(a);
faceList = new Structs.FacesList();
try {str = null;str = loc;System.out.println("Model file location: " + str);LineNumberReader fl = new LineNumberReader(new FileReader(str));
while((str=fl.readLine()) != null) {switch(state) {case 0: //Initial - has to be a PLY file
if(str.indexOf("ply") != 0) {state=-1;
146
} else {state++;
}break;
case 1: //Checks the header stuff (limited)if( str.indexOf("end_header") == 0) {
state++;fl.mark(500);
}if( str.indexOf("element") == 0) {
str2 = str.substring(str.lastIndexOf(' ')+1);if(str.indexOf("vertex") >= 0) {
num_ver = Integer.parseInt(str2);}if(str.indexOf("face") >= 0) {
num_face = Integer.parseInt(str2);theFaces = new TriangleArray(num_face*3,
TriangleArray.COORDINATES | TriangleArray.NORMALS);
}}break;
case 2: //Vertex InformationtheVers = new Point3f[num_ver];
fl.reset();for(i=0; i<num_ver; i++) {
str=fl.readLine();if(str == null) break;
int j=0;float x,y,z;
x = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
y = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
z = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
theVers[i] = new Point3f(x*scale,y*scale,z*scale);}fl.mark(500);state++;break;
case 3: //Face informationfl.reset();for(i=0; i<num_face; i++) {
str=fl.readLine();if(str == null) break;
int j=0, k=0;int n,p;
n = Integer.parseInt(str.substring(j, (j=str.indexOf(' ', j+1))));
int list[] = new int[n+1];
for(k=0; k<3; k++) {p = Integer.parseInt(str.substring(j+1,
(j=str.indexOf(' ', j+1))));theFaces.setCoordinate(i*3+k, theVers[p]);list[k] = p;
}list[3] = i;faceList.add(list[n], list[0], list[1], list[2]);
//Calculate the normal for the face (assuming triangular)//Keep in mind, this may or may not be correct, I was //just winging it
147
//with some math I scribbled down.//Edit: I'm pretty sure it's correct
Vector3f v01, v10, v02, v20, v12, v21, vr;Point3f pt, pt0, pt1, pt2;
pt0 = new Point3f();theFaces.getCoordinate(i*3+0,pt0);pt1 = new Point3f();theFaces.getCoordinate(i*3+1,pt1);pt2 = new Point3f();theFaces.getCoordinate(i*3+2,pt2);
v01 = new Vector3f(pt1);v01.sub(pt0);v02 = new Vector3f(pt2);v02.sub(pt0);v12 = new Vector3f(pt2);v12.sub(pt1);v10 = new Vector3f(pt0);v10.sub(pt1);v20 = new Vector3f(pt0);v20.sub(pt2);v21 = new Vector3f(pt1);v21.sub(pt2);
vr = new Vector3f();vr.cross(v01, v02);vr.normalize();theFaces.setNormal(i*3+0,vr);
vr = new Vector3f();vr.cross(v12, v10);vr.normalize();theFaces.setNormal(i*3+1,vr);
vr = new Vector3f();vr.cross(v20, v21);vr.normalize();theFaces.setNormal(i*3+2,vr);
}state++;break;
default:}
if(state == -1) break;}
} catch(IOException e) {System.out.println(e);System.exit(1);
};
theShape.setGeometry(theFaces);
return theShape;}
/* * Ok, this is almost the exact same as above, except that we're only * loading a smaller subset of faces, not every one of them. * (Note: Assumes ambient true, because this is only used in BillboardProject). * * [in] ArrayList toLoad: This is a list of Integers for face indices that will be loaded * [in] String loc: This it the file location for the model * [in] Color3f clr: Since ply files aren't colored, I added the option here * [in] float scale: If you want to scale up/down the model size * [return]: Returns a Shape3D object for the model */public Shape3D loadPartialMesh(ArrayList toLoad, String loc, Color3f clr, float scale) {
String str, str2;int state=0, i=0;Appearance a = new Appearance();Material m = new Material();
LineAttributes latt = new LineAttributes();latt.setLineAntialiasingEnable(true);a.setLineAttributes(latt);m.setDiffuseColor(clr);m.setAmbientColor(clr);a.setMaterial(m);theShape.setAppearance(a);
try {str = null;str = loc;
148
//System.out.println("Model file location: " + str);LineNumberReader fl = new LineNumberReader(new FileReader(str));
while((str=fl.readLine()) != null) {switch(state) {case 0: //Initial - has to be a PLY file
if(str.indexOf("ply") != 0) {state=-1;
} else {state++;
}break;
case 1: //Checks the header stuff (limited)if( str.indexOf("end_header") == 0) {
state++;fl.mark(500);
}if( str.indexOf("element") == 0) {
str2 = str.substring(str.lastIndexOf(' ')+1);if(str.indexOf("vertex") >= 0) {
num_ver = Integer.parseInt(str2);}if(str.indexOf("face") >= 0) {
num_face = Integer.parseInt(str2);theFaces = new TriangleArray(toLoad.size()*6,
TriangleArray.COORDINATES | TriangleArray.NORMALS);
}}break;
case 2: //Vertex InformationtheVers = new Point3f[num_ver];
fl.reset();for(i=0; i<num_ver; i++) {
str=fl.readLine();if(str == null) break;
int j=0;float x,y,z;
x = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
y = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
z = Float.parseFloat(str.substring(j, (j=str.indexOf(' ', j+1))));
theVers[i] = new Point3f(x*scale,y*scale,z*scale);}fl.mark(500);state++;break;
case 3: //Face informationfl.reset();int ct=0;for(i=0,ct=-1; i<num_face; i++) {
str=fl.readLine();if(str == null) break;
if(!toLoad.contains(new Integer(i))) continue;ct++;
int j=0, k=0;int n,p;
n = Integer.parseInt(str.substring(j, (j=str.indexOf(' ', j+1))));
int list[] = new int[n+1];
for(k=0; k<3; k++) {
149
p = Integer.parseInt(str.substring(j+1, (j=str.indexOf(' ', j+1))));
theFaces.setCoordinate(ct*6+k, theVers[p]);theFaces.setCoordinate(ct*6+5-k, theVers[p]);
}
//Calculate the normal for the face (assuming triangular)
Vector3f v01, v10, v02, v20, v12, v21, vr;Point3f pt, pt0, pt1, pt2;
pt0 = new Point3f();theFaces.getCoordinate(ct*6+0,pt0);pt1 = new Point3f();theFaces.getCoordinate(ct*6+1,pt1);pt2 = new Point3f();theFaces.getCoordinate(ct*6+2,pt2);
v01 = new Vector3f(pt1);v01.sub(pt0);v02 = new Vector3f(pt2);v02.sub(pt0);v12 = new Vector3f(pt2);v12.sub(pt1);v10 = new Vector3f(pt0);v10.sub(pt1);v20 = new Vector3f(pt0);v20.sub(pt2);v21 = new Vector3f(pt1);v21.sub(pt2);
vr = new Vector3f();vr.cross(v01, v02);vr.normalize();theFaces.setNormal(ct*6+0,vr); vr.negate();theFaces.setNormal(ct*6+3,vr);
vr = new Vector3f();vr.cross(v12, v10);vr.normalize();theFaces.setNormal(ct*6+1,vr); vr.negate();theFaces.setNormal(ct*6+4,vr);
vr = new Vector3f();vr.cross(v20, v21);vr.normalize();theFaces.setNormal(ct*6+2,vr); vr.negate();theFaces.setNormal(ct*6+5,vr);
}state++;break;
default:}
if(state == -1) break;}
} catch(IOException e) {e.printStackTrace();System.exit(1);
};
num_face = toLoad.size();theShape.setGeometry(theFaces);
return theShape;}
/* * Initialization */public PlyModel() {
theShape = new Shape3D();}
}
150
PlyModelExtend.javapackage src.BillboardCalc;
import javax.vecmath.Point3f;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * This is a helper subclass to PlyModel, and has additional functions * regarding the algorithm. */public class PlyModelExtend extends PlyModel {
/* * The ever-present swapPoint function. Swaps the Z & Y coordinates. */public static Point3f swapPoint(Point3f pt) {
return new Point3f(pt.x, pt.z, pt.y);}
/* * Empty initialization */public PlyModelExtend() {}
/* * Returns the current number of faces left */public int getCurFaces() {
return faceList.size();}
/* * Gets the three vertices of a face from the current set of faces avalible. * * [in] int i: Current-set-based index * [return] Three Point3f values for each vertex */Point3f[] getVers(int i) {
Point3f pointArray[] = new Point3f[3];Point3f point = new Point3f();int list[] = faceList.getList(i);
pointArray[0] = new Point3f(swapPoint(theVers[list[0]]));pointArray[1] = new Point3f(swapPoint(theVers[list[1]]));pointArray[2] = new Point3f(swapPoint(theVers[list[2]]));
return pointArray;}
/* * Deletes the contributing faces from the model. */void trimFaces(Structs.FaceContribList contrib) {
int i,j;
for(i=0; i<contrib.size(); i++) {faceList.remove(contrib.getInd(i)-i);
}}
}
151
Structs.javapackage src.BillboardCalc;
import java.lang.reflect.Array;import java.util.ArrayList;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * All of these classes pretty much work in the way that * an ArrayList works. */public class Structs {
/* * This class stores a list of all contributing sets for the model */public static class BBPlaneList {
ArrayList flist;
BBPlaneList() {flist = new ArrayList();
}
public void add(FaceContribList f) {flist.add(f);
}
public FaceContribList get(int i) {return (FaceContribList)flist.get(i);
}
public int size() {return flist.size();
}}
/* * This class keeps track of a number of things about the faces * that contribute to a billboard. The index, real-index [in the model], * and the spherical coordinates for the billboard. */public static class FaceContribList {
public static class FaceInfo {public int ind, rind;
FaceInfo() {ind=rind=-1;
}
void set(int i, int ri) {ind=i;rind=ri;
}}
private ArrayList flist;private ArrayList plist;float rad, theta, phi;
FaceContribList() {flist = new ArrayList();plist = new ArrayList();
}
public void add(int i, int ri) {FaceInfo f = new FaceInfo();
f.ind = i;
152
f.rind = ri;
InsertFace(flist, f);}
public int getInd(int i) {return ((FaceInfo)flist.get(i)).ind;
}
public float[] getPlane() {float f[] = new float[3];
f[0] = rad;f[1] = theta;f[2] = phi;return f;
}
public int getRealInd(int i) {return ((FaceInfo)flist.get(i)).rind;
}
public int getPRealInd(int i) {return ((FaceInfo)plist.get(i)).rind;
}
public void InsertFace(ArrayList arr, FaceInfo face) {FaceInfo f;int min, cur, max;int i;
for(i=0; i<size(); i++) {f = (FaceInfo)arr.get(i);if(f.rind > face.rind) {
arr.add(i, face);return;
}}arr.add(face);
}
public void setList(FaceInfo arr[], int sz) {for(int i=0; i<sz; i++) {
flist.add(arr[i]);}
}
public void setPList(FaceInfo arr[], int sz) {for(int i=0; i<sz; i++) {
plist.add(arr[i]);}
}
public void setPlane(float nrad, float ntheta, float nphi) {rad = nrad;theta = ntheta;phi = nphi;
}
public int size() {return flist.size();
}
public int psize() {return plist.size();
}}
/* * This class is a list of the faces in a model. */
153
public static class FacesList {public static class FaceInfo {
int ind;int v[];
FaceInfo() {ind=-1;v = null;
}}
private ArrayList flist;
FacesList() {flist = new ArrayList();
}
public void add(int nind, int nv1, int nv2, int nv3) {FaceInfo f = new FaceInfo();
f.ind = nind;f.v = new int[3];f.v[0] = nv1;f.v[1] = nv2;f.v[2] = nv3;
flist.add(f);}
public int getInd(int i) {return ((FaceInfo)flist.get(i)).ind;
}
public int[] getList(int i) {return ((FaceInfo)flist.get(i)).v;
}
public void remove(int i) {flist.remove(i);
}
public int size() {return flist.size();
}}
/* * This class is a list angles for the sphere algorithm. Also doubles as * information in the core algorithm. (Note: Changed the list from an * ArrayList, as for some reason that was taking up a lot more memory * than it should. This uses a lot less memory now.) */public static class SphereList {
public static class CoordPair {public static class FaceInfo {
public float contrib;public int fnum;public float height;public int min, max;public float tcontrib;
FaceInfo() {fnum=min=max=0;height=contrib=tcontrib=0;
}}public int fcount;public FaceInfo flist[];public float phi;
public float theta;
154
CoordPair() {theta = phi = 0.0f;flist = null;
}
CoordPair(CoordPair c) {flist = new FaceInfo[c.size()];fcount = c.size();theta = c.theta;phi = c.phi;for(int i=0; i<c.size(); i++) {
flist[i] = c.getFace(i);}
}
public void add(FaceInfo i) {flist[fcount++] = i;
}
public FaceInfo getFace(int i) {return flist[i];
}
public void init(int c) {flist = new FaceInfo[c];fcount = 0;
}
public void set(FaceInfo arr[]) {flist = arr;fcount = Array.getLength(arr);
}
public int size() {return fcount;
}}
private ArrayList clist;
SphereList() {clist = new ArrayList();
}
SphereList(SphereList sp) {CoordPair c;
clist = new ArrayList();for(int i=0; i<sp.size(); i++) {
c = new CoordPair(sp.getCoords(i));clist.add(c);
}}
public void add(float theta, float phi) {CoordPair c = new CoordPair();
c.theta = theta;c.phi = phi;clist.add(c);
}
public void addFace(int index, CoordPair.FaceInfo face) {CoordPair c = (CoordPair)clist.get(index);
c.add(face);}
public CoordPair getCoords(int i) {return (CoordPair)clist.get(i);
155
}
public void set(int i, CoordPair.FaceInfo arr[]) {CoordPair c = (CoordPair)clist.get(i);
c.set(arr);}
public int size() {return clist.size();
}}
}
156
BillboardDisplay.java/* * Created on Apr 23, 2004 * * To change the template for this generated file go to * Window>Preferences>Java>Code Generation>Code and Comments */package src.BillboardDisplay;
import java.applet.Applet;import java.awt.BorderLayout;import java.awt.GraphicsConfiguration;import java.io.FileReader;import java.io.LineNumberReader;import java.util.ArrayList;import java.util.StringTokenizer;
import javax.media.j3d.Alpha;import javax.media.j3d.AmbientLight;import javax.media.j3d.Appearance;import javax.media.j3d.Background;import javax.media.j3d.BoundingSphere;import javax.media.j3d.BranchGroup;import javax.media.j3d.Canvas3D;import javax.media.j3d.Group;import javax.media.j3d.LineAttributes;import javax.media.j3d.Material;import javax.media.j3d.PointLight;import javax.media.j3d.QuadArray;import javax.media.j3d.RotationInterpolator;import javax.media.j3d.Shape3D;import javax.media.j3d.Texture;import javax.media.j3d.TextureAttributes;import javax.media.j3d.Transform3D;import javax.media.j3d.TransformGroup;import javax.media.j3d.TransparencyAttributes;import javax.media.j3d.View;import javax.vecmath.Color3f;import javax.vecmath.Point3d;import javax.vecmath.Point3f;import javax.vecmath.TexCoord2f;import javax.vecmath.Vector3f;
import com.sun.j3d.utils.applet.MainFrame;import com.sun.j3d.utils.geometry.Box;import com.sun.j3d.utils.image.TextureLoader;import com.sun.j3d.utils.universe.SimpleUniverse;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * Ok, this is almost a direct copy of the BillboardCalc class, * just added a few small things for billboard reading. */public class BillboardDisplay extends Applet {
/* * This function basically takes in an angle, and converts it to Radian * * [in] float ang: The angle to convert * [return]: The converted value */public static float degToRad(float ang) {
return (float)(ang*Math.PI/180.0f);}
/* * Your basic main function. Set the window's default size to 400x400. */public static void main(String[] args) {
157
new MainFrame(new BillboardDisplay(args), 400, 400);}
/* * Some color definitions */private Color3f bgColor = new Color3f(0.13f, 0.51f, 0.13f);private Color3f clrWhite = new Color3f(1.0f, 1.0f, 1.0f);private Color3f clrYellow = new Color3f(1.0f, 1.0f, 0.0f);
private SimpleUniverse u = null;
/* * Some basic directional vectors */private Vector3f vecAll = new Vector3f(1.0f, 1.0f, 1.0f);private Vector3f vecNull = new Vector3f(0.0f, 0.0f, 0.0f);private Vector3f vecX = new Vector3f(1.0f, 0.0f, 0.0f);private Vector3f vecY = new Vector3f(0.0f, 1.0f, 0.0f);private Vector3f vecZ = new Vector3f(0.0f, 0.0f, 1.0f);
private String inputFile;private String billboardDir;
public BillboardDisplay(String args[]) {if(args.length != 2) {
System.out.println("Invalid arguments. The correct way to run this program is:\n");
System.out.println("BillboardDisplay <input file> <billboard dir>");System.out.println(" <input file> - This is where your billboard information
was stored.");System.out.println(" <billboard dir> - This is where the billboard files were
saved.");System.out.println(" - (i.e. 'pics/'");System.exit(1);
}
inputFile = args[0];billboardDir = args[1];
}
/* * This function takes a billboard information and creates a shape for it, * containing the transparent gif file. * * [in] int line: Which billboard number we'll be loading * [in] ArrayList bInfo: The corners of the billboard */public Shape3D loadBillboard(int line, ArrayList bInfo) {
Appearance a = new Appearance();Material m = new Material();Shape3D shape = new Shape3D();QuadArray face;int i;boolean diffuse = false;
TransparencyAttributes ta = new TransparencyAttributes();ta.setTransparencyMode(TransparencyAttributes.BLENDED);a.setTransparencyAttributes(ta);
TextureAttributes texAttr = new TextureAttributes();texAttr.setTextureMode(TextureAttributes.MODULATE);a.setTextureAttributes(texAttr);
m.setAmbientColor(new Color3f(0.0f, 0.0f, 0.0f));m.setLightingEnable(true);if(!diffuse)
a.setMaterial(m);
String fname = billboardDir+"billboard"+line+".gif";Texture texImage = new TextureLoader(fname, this).getTexture();
158
a.setTexture(texImage);
if(!diffuse) {face = new QuadArray(8, QuadArray.COORDINATES | QuadArray.NORMALS | QuadAr
ray.TEXTURE_COORDINATE_2);} else {
face = new QuadArray(8, QuadArray.COORDINATES | QuadArray.TEXTURE_COORDINATE_2);}
Point3f pt;Vector3f norm,norm2,va,vb;va = new Vector3f((Point3f)bInfo.get(1));va.sub(new Vector3f((Point3f)bInfo.get(0)));vb = new Vector3f((Point3f)bInfo.get(3));vb.sub(new Vector3f((Point3f)bInfo.get(0)));norm = new Vector3f();norm.cross(va,vb);norm.normalize();norm2 = new Vector3f(norm);norm2.negate();
for(i=0; i<4; i++) {pt = (Point3f)bInfo.get(i);face.setCoordinate(i, pt);face.setCoordinate(7-i, pt);if(!diffuse) {
face.setNormal(i,norm);face.setNormal(7-i,norm2);
}}
float f[] = new float[2];
face.setTextureCoordinate(0, 0, new TexCoord2f(0.0f, 1.0f));face.setTextureCoordinate(0, 1, new TexCoord2f(1.0f, 1.0f));face.setTextureCoordinate(0, 2, new TexCoord2f(1.0f, 0.0f));face.setTextureCoordinate(0, 3, new TexCoord2f(0.0f, 0.0f));face.setTextureCoordinate(0, 7, new TexCoord2f(0.0f, 1.0f));face.setTextureCoordinate(0, 6, new TexCoord2f(1.0f, 1.0f));face.setTextureCoordinate(0, 5, new TexCoord2f(1.0f, 0.0f));face.setTextureCoordinate(0, 4, new TexCoord2f(0.0f, 0.0f));
shape.setAppearance(a);shape.setGeometry(face);
return shape;}
/* * This function reads in the billboard information from the input file. * * [return]: Returns a Group containing the billboards */public Group readBillboards() {
int line;Shape3D retShape = new Shape3D();Group group = new Group();
try {
String str = inputFile;LineNumberReader fl = new LineNumberReader(new FileReader(str));
for(line=0; (str=fl.readLine()) != null; line++) {StringTokenizer stok = new StringTokenizer(str, ",");if(line != 0) { //Ignore the first line
for(int i=1; stok.hasMoreTokens(); i++) {String str2 = stok.nextToken();if(i<=12){
ArrayList barr2 = new ArrayList();
//The first 12 float values are the 4 corners of
159
//the billboard//grouped into X,Y,Z valuesfor(int j=0; j<4; j++) {
float f1,f2,f3;if(j!=0) str2=stok.nextToken();f1 = Float.parseFloat(str2);str2 = stok.nextToken(); f2 =
Float.parseFloat(str2);str2 = stok.nextToken(); f3 =
Float.parseFloat(str2);barr2.add(new Point3f(f1,f2,f3));
}i += 11;
group.addChild(loadBillboard(line, barr2));}
}}
}} catch(Exception e) { e.printStackTrace(); System.exit(1); }
return group;
}
/* * Creates a very simple scenegraph to hold the billboards. */public BranchGroup createSceneGraph(SimpleUniverse u) {
TransformGroup tg, tg2;Vector3f vec, pos, rot;Transform3D t, t2;Appearance a;Material m;int i;
// Create the root of the branch graphBranchGroup objRoot = new BranchGroup();TransformGroup objScene = new TransformGroup();TransformGroup objInit = new TransformGroup();
//scale, translate, and rotate it// 0.4, {0,0,-10}, {20,-10,0}t = new Transform3D();t2 = new Transform3D(); t2.setScale(0.5); t.mul(t2);t2 = new Transform3D(); t2.setTranslation(new Vector3f(0.0f, 0.0f, -10.0f)); t.mul(t2);t2 = new Transform3D(); t2.rotX(degToRad(20.0f)); t.mul(t2);t2 = new Transform3D(); t2.rotY(degToRad(-10.0f)); t.mul(t2);objInit.setTransform(t);objRoot.addChild(objInit);
objScene = new TransformGroup();objScene.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);objInit.addChild(objScene);
Alpha alpha = new Alpha(-1,10000);RotationInterpolator rotInt = new RotationInterpolator(alpha, objScene);rotInt.setSchedulingBounds(new BoundingSphere());objScene.addChild(rotInt);
// Create a bounds for the background and lightsBoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
// Set up the backgroundBackground bg = new Background(bgColor);bg.setApplicationBounds(bounds);objScene.addChild(bg);
LineAttributes latt = new LineAttributes();latt.setLineAntialiasingEnable(true);a = new Appearance();a.setLineAttributes(latt);
160
m = new Material();m.setDiffuseColor(clrYellow);a.setMaterial(m);Box ground = new Box(5.0f, 0.0f, 5.0f, Box.GENERATE_NORMALS, a);objScene.addChild(ground);
objScene.addChild(readBillboards());
Point3f pt = new Point3f();tg = new TransformGroup();
//Light the scene a bit...Point3f lPos = new Point3f(10.0f, 10.0f, 10.0f);
PointLight l1 = new PointLight();l1.setAttenuation(1.0f, 0.0f, 0.0f);l1.setInfluencingBounds(bounds);l1.setPosition(lPos);
AmbientLight l2 = new AmbientLight();l2.setInfluencingBounds(bounds);l2.setColor(clrWhite);
tg = new TransformGroup();tg.addChild(l1);tg.addChild(l2);objScene.addChild(tg);
// Let Java 3D perform optimizations on this scene graph.objRoot.compile();
return objRoot;}
/* * (non-Javadoc) * @see java.applet.Applet#destroy() */public void destroy() {
u.cleanup();}
/* * This sets up the basic info for the universe. * * @see java.applet.Applet#init() */public void init() {
setLayout(new BorderLayout());GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
Canvas3D c = new Canvas3D(config);add("Center", c);
u = new SimpleUniverse(c);BranchGroup scene = createSceneGraph(u);
u.getViewingPlatform().setNominalViewingTransform();
View view = u.getViewer().getView();view.setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY);
u.addBranchGraph(scene);}
}
161
BillboardProject.javapackage src.BillboardProject;
import java.applet.Applet;import java.awt.BorderLayout;import java.awt.GraphicsConfiguration;
import com.sun.j3d.utils.applet.MainFrame;import com.sun.j3d.utils.universe.SimpleUniverse;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * This is an -extremely- simple class that just creates the ImmediateCanvas3D object * for immediate-mode rendering. */public class BillboardProject extends Applet {
private SimpleUniverse u;private String inputFile, outputDir;
/* * Initializing the class & program */
public static void main(String[] args) {new MainFrame(new BillboardProject(args), 400, 400);
}
BillboardProject(String args[]) {if(args.length != 2) {
System.out.println("Invalid arguments. The correct way to run this program is:\n");
System.out.println("BillboardProject <input file> <billboard dir>");System.out.println(" <input file> - This is where your billboard information
was stored.");System.out.println(" <billboard dir> - This is where the billboard files are to
be saved.");System.out.println(" - (i.e. 'pics/'");System.exit(1);
}
inputFile = args[0]; outputDir = args[1]; } /* * Create the ImmediateCanvas3D object for displaying */
protected ImmediateCanvas3D createCanvas3D(GraphicsConfiguration config) {ImmediateCanvas3D c3d = new ImmediateCanvas3D( config, inputFile, outputDir);c3d.setSize( 400, 400 );
return c3d;}
/* * * @see java.applet.Applet#destroy() */public void destroy() {
u.cleanup();}
/* * This sets up the basic info for the universe. * * @see java.applet.Applet#init() */public void init() {
setLayout(new BorderLayout());
162
GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
ImmediateCanvas3D c = createCanvas3D(config);add("Center", c);
u = new SimpleUniverse(c);
u.getViewingPlatform().setNominalViewingTransform();c.repaint();
}}
163
ImmediateCanvas3D.javapackage src.BillboardProject;
import java.io.FileReader;import java.io.LineNumberReader;import java.util.ArrayList;import java.util.StringTokenizer;
import javax.media.j3d.AmbientLight;import javax.media.j3d.Appearance;import javax.media.j3d.GraphicsContext3D;import javax.media.j3d.Light;import javax.media.j3d.LineAttributes;import javax.media.j3d.Material;import javax.media.j3d.PolygonAttributes;import javax.media.j3d.QuadArray;import javax.media.j3d.Shape3D;import javax.media.j3d.Transform3D;import javax.vecmath.Color3f;import javax.vecmath.Point3f;import javax.vecmath.Vector3f;
import org.j3d.ui.ImageCaptureCanvas3D;import org.j3d.ui.image.JPEGSequenceObserver;
import src.BillboardCalc.PlyModel;
/* * @author John Reynolds, Matt Gage, Paul Fydenkeves * * This class is a sub-class of a freely available image-capturing * program from www.j3d.org. However, either a) there's a bug in Java3d, * b) there's a bug in their code, or c) there's a bug in our code, as * for some reason we can't get anything to capture from the Canvas * to the raster. Very frustrating. When we can, this file will be updated. */public class ImmediateCanvas3D extends ImageCaptureCanvas3D {
private long m_nRender = 0;private long m_StartTime = 0;JPEGSequenceObserver iobs = new JPEGSequenceObserver();private ArrayList billboardArray = new ArrayList();private ArrayList billboardArrayInfo = new ArrayList();private Shape3D curShape = new Shape3D();private Shape3D curBillboard = new Shape3D();private Shape3D oShape = new Shape3D();private String fileLoc = "";private float fileScale = 1.0f;private int mcount=-1;
public String inputFile, outputDir;
/* * This basically initializes the class, etc. Also sets it ready for capturing. */ImmediateCanvas3D( java.awt.GraphicsConfiguration graphicsConfiguration , String iF, String oD) {
super( graphicsConfiguration );
super.setDoubleBufferEnable(true);this.setDoubleBufferEnable(true);
inputFile = iF;outputDir = oD;
iobs.setFileDetails(outputDir, "billboard");
super.addCaptureObserver(iobs);super.setBounds(this.getBounds());
164
iobs.setEnabled(true);System.out.println("Capturing!");
}
/* * Call the parent function */
public void postSwap() { super.postSwap(); }
/* * This function reads in the billboard information from the input file. * * [return]: Returns an ArrayList of an ArrayList of faces for each billboard. */
public ArrayList readBillboards() { int line; ArrayList marr = new ArrayList();
try {String str = inputFile;LineNumberReader fl = new LineNumberReader(new FileReader(str));
for(line=0; (str=fl.readLine()) != null; line++) {ArrayList sarr = new ArrayList();ArrayList barr = new ArrayList();
StringTokenizer stok = new StringTokenizer(str, ",");if(line == 0) {
//First line contains file information (name & scaling used)for(int i=1; stok.hasMoreTokens(); i++) {
String str2 = stok.nextToken();
switch(i) {case 1:
fileLoc = str2;break;
case 2:fileScale = Float.parseFloat(str2);break;
default:}
}} else {
//All other lines contain billboard informationfor(int i=1; stok.hasMoreTokens(); i++) {
String str2 = stok.nextToken();if(i>5+12) {
//After the first 17 bits of info, we reach the //face indicessarr.add(new Integer(str2));
} else {//first 12 items are the billboard coordinates//[arranged in groups of XYZ coordinates]if(i<=12){
ArrayList barr2 = new ArrayList();
for(int j=0; j<4; j++) {float f1,f2,f3;if(j!=0) str2=stok.nextToken();f1 = Float.parseFloat(str2);str2 = stok.nextToken(); f2 =
Float.parseFloat(str2);str2 = stok.nextToken(); f3 =
Float.parseFloat(str2);barr2.add(new Point3f(f1,f2,f3));
}i += 11;
barr.add(loadBillboard(barr2, new
165
Color3f(1.0f,0.0f,0.0f)));} else {
//the 5 remaining items are billboard in//formation//(i.e. rotations, size, etc.)barr.add(new Float(str2));
}}
}if(sarr.size() > 0) marr.add(sarr);if(barr.size() > 0) billboardArrayInfo.add(barr);
}}
} catch(Exception e) { e.printStackTrace(); System.exit(1); }
return marr; } /* * Loads a shape in memory of the billboard. No longer displayed, but used for calculations. */ public Shape3D loadBillboard(ArrayList bInfo, Color3f clr) {
Appearance a = new Appearance();Material m = new Material();PolygonAttributes p = new PolygonAttributes();Shape3D shape = new Shape3D();QuadArray face;int i;
LineAttributes latt = new LineAttributes();latt.setLineAntialiasingEnable(true);a.setLineAttributes(latt);p.setPolygonMode(PolygonAttributes.POLYGON_LINE);m.setDiffuseColor(clr);m.setAmbientColor(clr);a.setMaterial(m);a.setPolygonAttributes(p);
face = new QuadArray(8, QuadArray.COORDINATES);
Point3f pt;for(i=0; i<4; i++) {
pt = (Point3f)bInfo.get(i);face.setCoordinate(i, pt);face.setCoordinate(7-i, pt);
}
shape.setAppearance(a);shape.setGeometry(face);
return shape; }
/* * Overwritten method that allows us to manually display what we want, when we want. * * @see javax.media.j3d.Canvas3D#renderField(int) */public void renderField( int fieldDesc ) {
super.renderField( fieldDesc );
GraphicsContext3D g = super.getGraphicsContext3D( );
// first time initializationif( m_nRender == 0 ) {
// set the start timem_StartTime = System.currentTimeMillis( )-60000;
// add a light to the graphics contextAmbientLight light = new AmbientLight( );light.setEnable( true );
166
Point3f lPos = new Point3f(0.0f, 0.0f, 10.0f);g.addLight( (Light) light );
billboardArray = readBillboards();PlyModel model = new PlyModel();oShape = model.loadMesh(fileLoc, new Color3f(0.0f, 1.0f, 0.0f), fileScale,
true);}
float rotX,rotZ,rotX2;Point3f off = new Point3f();
//new frame, load different set of facesif(System.currentTimeMillis() - m_StartTime >= 1) {
m_StartTime = System.currentTimeMillis();if(mcount < billboardArray.size()-1) {
mcount++;mcount %= billboardArray.size();PlyModel model = new PlyModel();ArrayList arr2 = (ArrayList)billboardArray.get(mcount);System.out.println("Loading billboard #"+mcount+" ("+arr2.size()+"
faces)");curShape = new Shape3D();curShape = model.loadPartialMesh(arr2, fileLoc, new Color3f(1.0f, 0.0f,
0.0f), fileScale+0.0f);}
}
// set the current transformation for the graphics contextTransform3D m_t3d = new Transform3D(), t3;
//Get the rotations needed to display only the faces we wantArrayList arr = (ArrayList)billboardArrayInfo.get(mcount);rotX = ((Float)arr.get(4+1)).floatValue();rotZ = ((Float)arr.get(3+1)).floatValue();rotX2 = ((Float)arr.get(2+1)).floatValue();
//Get the width & heightfloat mx,my;my = ((Float)arr.get(0+1)).floatValue();mx = ((Float)arr.get(1+1)).floatValue();
QuadArray face = (QuadArray)((Shape3D)arr.get(0)).getGeometry(0);face.getCoordinate(0, off);off.negate();
//Rotate everything so we're orthographically facing the facest3 = new Transform3D(); t3.ortho(0.0, mx, -my, 0.0, -100.0, 100.0); m_t3d.mul(t3);t3 = new Transform3D(); t3.rotX(rotX*Math.PI/180.0f); m_t3d.mul(t3);t3 = new Transform3D(); t3.rotY(rotZ*Math.PI/180.0f); m_t3d.mul(t3);t3 = new Transform3D(); t3.rotX(rotX2*Math.PI/180.0f); m_t3d.mul(t3);t3 = new Transform3D(); t3.setTranslation(new Vector3f(off)); m_t3d.mul(t3);
g.setModelTransform( m_t3d );
// finally render the Shapeg.draw(curShape);
if(m_nRender == billboardArray.size()) {iobs.setEnabled(false);System.out.println("Finished Capturing!");
}
m_nRender++;
}
/* * @see javax.media.j3d.Canvas3D#preRender() */public void preRender( )
167
{super.preRender( );
// force a paint (will call renderField)//repaint();paint( getGraphics( ) );
}}
168