Multiple Axis Interpolation | 19.march.1998 | GFX |
by Hin Jang
The classic digital line drawing algorithm developed by Jack Bresenham in 1965 has been subject to several optimisations and extensions. The multiple axis interpolator (MAI) algorithm is one such extension. Its derivation was necessary to find a method the interpolate efficiently four axes (red, green, blue and z) across of polygon, in addition to the x and y values [2]. The algorithm uses integer math for speed and its hardware implementation can yield one pixel per clock cycle. In the following discussion, assume x is the major axis and incremented for each loop interation; xs and ys are the starting values for x and y; xe and ye are the ending values for x and y.
A digital differential analyser (DDA) evaluates the value of y without having to calculate
y = mx + bfor each value of x. The linear differential of the line is m and is a constant. Successive values of the (x, y) pair is the result of incrementing x by one and adding m to y. Adding the initial value of y by 0.5 and truncating each successive result will yield the correct integer values for y. As pseudocode, the algorithm is
DDA(int xs, ys, xe, ye, value) { int n, x float m, y x = xs y = ys + 0.5 m = (ye - ys) / (xe - xe) n = xe - xs PutPixel(x, int(y), value) while (n is not zero) { x++ y += m n-- PutPixel(x, int(y), value) } }The fractional and integer parts of y can be treated independently as long as the integer values generated by adding m to the y-fractional part are transfered to the y-integer part. This is accomplished by subtracting one from the y-fractional part and adding one to the y-integer part when the y-fractional part is greater than one [2]. To avoid the "greater-than-or-equal-to-one" comparison the initial y-fractional value is reduced by one so that a "greater-than-zero" comparison can be used instead. If yi represents the y-integer part and yf represents the y-fractional part, the algorithm becomes
DDA_2(int xs, ys, xe, ye, value) { int n, x, yi float m, y, yf x = xs yi = ys yf = -0.5 m = (ye - ys) / (xe - xe) n = xe - xs PutPixel(x, yi, value) while (n is not zero) { x++ yf += m if (yf is not negative) { yf-- yi++ } n-- PutPixel(x, yi, value) } }If the initial fractional value is scaled by the constant 2(xe - xs), the initial divide is eliminated and the fractional variables become integers. The result is the algorithm that Bresenham presented [1]. The restriction, however, is that m must be within the range 0 and 1. Other slopes are handled by swapping x, y and/or mirroring across the x and/or y axis. Modifying the algorithm DDA( ) above, however, does not have this restriction. Assuming that x still represents the major axis and that z is any other axis, the algorithm becomes
DDA_3(int xs, zs, xe, ze, value) { int mi, zi, n, x float mf, zf x = xs zi = zs zf = -0.5 mi = (ze - zs) DIV (xe - xs) mf = FRACT((ze - zs) / (xe - xs)) n = xe - xs PutPixel(x, zi, value) while (n is not zero) { x++ zi += mi zf += mf if (zf is not negative) { zf-- zi++ } n-- PutPixel(x, zi, value) } }Converting all fractional parts to integers by scaling with the constant 2(xe - xs) yields the multiple axis interpolator (MAI) algorithm where
b MOD a = a * FRACT(b / a) = remainder of (b DIV a)
MAI(int xs, zs, xe, ze, value) { int mi, zi, n, x float mf, zf x = xs zi = zs zf = -(xe - xs) mi = (ze - zs) DIV (xe - xs) mf = 2 * ((ze - zs) MOD (xe - xs)) n = xe - xs PutPixel(x, zi, value) while (n is not zero) { x++ zi += mi zf += mf if (zf is not negative) { zf -= 2*(xe - xs) zi++ } n-- PutPixel(x, zi, value) } }The multiplication within the inner loop can be avoided by precomputing the constant mf2. As such, the final implementation of the MAI algorithm is as follows [2], again written as pseudocode.
MAI_2(int xs, zs, xe, ze, value) { int mi, zi, n, x float mf, mf2, zf x = xs zi = zs n = xe - xs mi = (ze - zs) DIV n mf = 2 * ((ze - zs) MOD n) mf2 = mf - 2 * n zf = mf - n PutPixel(x, zi, value) while (n is not zero) { x++ zi += mi if (zf is not negative) { zf += mf2 zi++ } else { zf += mf } n-- PutPixel(x, zi, value) } }
[1] Bresenham, J.E., "Algorithm for Computer Control of a Digital Plotter," IBM Systems Journal, 4(1):25-30, 1965[2] Swanson, R.W., and L.J. Thayer, "A Fast Shaded-Polygon Renderer," Computer Graphics, SIGGRAPH 1986 Proceedings, 20(4):95-101
.
Level of Detail | 28.april.1998 | GFX |
by Hin Jang
Real-time rendering of dynamic scenes of high complexity require novel algorithms to generate surface models at varying levels of detail. Such techniques must be employed in accomodate the limitations of graphics hardware or to allow such hardware to maintain interactive frame rates. The text herein will discuss briefly a method of reducing the geometric complexity of three-dimensional models. Some of the principles can also be applied to real-time animation of rigid body dynamics [1]. Techniques that are applicable to triangular irregular networks [2] and wavelet-based representations [3] will not be covered.
An ideal level of detail (LOD) algorithm consists of the following features [5]
The premise of one LOD algorithm, developed by Xia et al, involves the edge collapse transformation and its dual, the vertex split transformation. Successive edge collapses result in simplier meshes. For a sequence of k collapses, the original mesh M becomes progressively coarse
Mk --> Mk-1 --> Mk-2 ... M0Higher detail meshes can be retrieved by applying a sequence of k vertex splits.
M0 --> M1 --> Mk-1 ... MkThe edge collapses are stored in a hierarchy resembling a merge tree. Given an edge pc, for example, the vertex c is merged with vertex p as a result of collapsing the edge. During a vertex split, the child vertex c is created from the parent vertex p. The tree, where such child-parent relations are stored, is created as a preprocess and upwards from the high-detail mesh M to a low-detail mesh M0 over the surface of the object. At level h of the tree, those vertices determined to be children remain at h. Parent vertices are promoted to level h + 1. The construction of this tree continues recursively until either there remains a user-specified minimum of vertices or there are no parent-child relationships among the vertices at the given level [4] [6]. A balanced merge tree is produced by considering the region of influence of an edge e. This region is the the union of triangles adjacent to either endpoint of e. The optimal case for a balanced merge tree occurs when there are no common triangles in their respective regions of influence.
The data structure for a merge tree node is
struct Node { struct Vertex *vert, **adjacent_vert, **dependent_vert; struct Node *parent, *child[2]; struct Cone *cone; double upswitch, downswitch; long adjacent_num, dependent_num; };The collapse of an edge e is permitted when all the vertices defining the boundary of the region of influence of the edge exist and are adjacent to the edge [6]. The edge collapse dependencies, restricting the level difference between adjacent vertices are
The pseudocode to build the merge tree is
Build_MergeTree(Mesh *m, Node **roots) { current = InitialiseHeap(m) next = InitialiseHeap(NULL) while (HeapSize(current) > MIN_RESOLUTION) { while (HeapSize(current) > 0) { edge = ExtractMinEdge(current) node = CreateNode(edge) SetDependices(node) SetCone(node) SetSwitchDistances(node) InsertHeap(next, node) } FreeHeap(current) current = next next = InitialiseHeap(NULL) } FlattenHeap(roots, current) }The pseudocode to traverse the merge tree is
Traverse_MergeTree(Node **current, View view, Lights *lights) { for (each node in current) { switch = EvaluateSwitchDistance(node, view, lights) if (switch == REFINE) RefineNode(node) else if (switch == SIMPLIFY) MergeNode(node) } }The primary display vertices are those which passed the occlusion test and either
[1] Carlson, D.A., and J.K. Hodgins, Simulation Levels of Detail for Realtime Animation, TR-96-32, College of Computing and Graphics, Visualisation, and Usability Center, Georgia Institute of Technology, 1996[2] Garland, M., and P.S. Heckbert, Fast Polygonal Approximation of Terrain and Height Fields, Technical Report CMU-CS-95-181, Computer Science Department, Carnegie Mellon University, 1995
[3] Gross, M.H., O.G. Staadt, and R. Gatti, "Efficient Triangular Surface Approximations Using Wavelets and Quadtree Data Structures," IEEE Transactions on Visualisation and Computer Graphics, 2(2):130-143, June 1996
[4] Hoppe, H., T. DeRose, T. Duchamp, J. McDonald, and W. Stuetzle, "Mesh Optimisation," Computer Graphics, SIGGRAPH 1993 Proceedings, 27(4):19-26
[5] Lindstrom, P., D. Koller, W. Ribarsky, L.F. Hodges, N. Faust, G.A. Turner, "Realtime, Continuous Level of Detail Rendering of Height Fields," Computer Graphics, SIGGRAPH 1996 Proceedings, 30(4):109-118
[6] Xia, J.C., J. El-Sana, and A. Varshney, "Adaptive Realtime Level of Detail-Based Rendering for Polygonal Models," IEEE Transactions on Visualisation and Computer Graphics, 3(2):171-183, April 1997
.
Delaunay Tetrahedralisation | 28.april.1998 | GFX |
by Hin Jang
Tetrahedralisation is the process of filling a convex hull of points with tetrahedra. Delaunay tetrahedralisation has the property where the circumsphere of each tetrahedron does not contain any other point inside the sphere, and the vertices of the tetrahedra are those of the data points. Of the several algorithms [3, 4], the one that employs range searching and shelling has been shown to exhibit close to linear time complexity [2].
Given a set of points in R³, these points are placed into a data structure where a fourth point d can be determined efficently such that the Delaunay sphere <a, b, c, d> does not contain any point on the side of <a, b, c> opposite to the search direction. Herein, a three-dimensional cell structure is laid over the set of points. To compute this space, the first step is to calculate the min-max box of the data set. The space is offset by the point coincidence tolerance TOL so that any points that lie of a structure boundary will be handled appropriately.
xmin = xmin - TOL xmax = xmax + TOL ymin = ymin - TOL ymax = ymax + TOL zmin = zmin - TOL zmax = zmax + TOLThe size of the space is the cube root of
(xmax - xmin)(ymax - ymin)(zmax - zmin) ----------------------------------------- nwhere n is the number of points in the data set. The number of grid cells in the x, y and z directions is given by
x_res = floor((xmax - xmin) / size) + 1 y_res = floor((ymax - ymin) / size) + 1 z_res = floor((zmax - zmin) / size) + 1With the cell structure now established, to place each data point into one and only one cell the following steps are required:
cell_x = (xi - xmin) / size cell_y = (yi - ymin) / size cell_z = (zi - zmin) / size i = floor(cell_x) j = floor(cell_y) k = floor(cell_z)
For each cell, there is a data structure that links all points that belong to the same cell.
typedef struct cellnode Cell; struct cellnode { int used, vertex_number; double x, y, z; Cell *next_point; };where used is a flag to indicate whether a point is used to form a tetrahedron, vertex_number is a number between 0 and n. The normalised coordinates of the point are x, y and z. next_node points to the next node in the same cell.
The process to form the first tetrahedron involves three steps [2]
Any point can be the start point but for efficiency the algorithm selects one that is more or less at the center.
(i, j, k) = (x_res/2, y_res/2, z_res/2)
With the start point P1 the algorithm computes the first edge by using the following method
The found point P2 is the closest point to P1 and the edge <P1 , P2> is used to start the triangulation
The first edge is used to form the first triangle
The found point P3 is used to form the first triangle <P1, P2, P3>. The meridian sphere for this triangle does not contain any other point form the dataset inside the sphere.
The range searching algorithm consists of six steps [2]
Tetrahedralise() { p1 = FindFirstPoint(center cell) p2 = FindNearestPoint(p1) current_face = FindFirstTriangle(p1, p2) p4 = FindFourthPoint(current_face) tetrahedron = ConstructTetrahedron(current_face, p4) InitialiseFaceList(tetrahedron) InitialiseEdgeList(p1) InitialiseEdgeList(p2) InitialiseEdgeList(p3) InitialiseEdgeList(p4) while (face_list is not empty) { current_face = RemoveTopElement(face_list) if (fourth point does not exist) p4 = FindFourthPoint(current_face) else p4 = RestoreSearchResult(current_face) if (p4 is found) { if (p4 is used) { CheckTouchCase() UpdateFaceList by either a) moving rear pointer, or b) adding new faces } else { UpdateEdgeList by adding new faces MarkAsUsed(p4) } } else boundary_face = RemoveTopElement(face_list) } }The CheckTouchCase( ) routine ensures that no caves are created. The cases in which a cave may result are [2]
[1] Fang, T., and L. A. Piegl, "Delaunay Triangulation Using a Uniform Grid", IEEE Computer Graphics and Applications, 13(3):36-47, May 1993[2] Fang, T.P., and L.A. Piegl, "Delaunay Triangulation in Three Dimensions," IEEE Computer Graphics and Applications, 15(5):62-69, September 1995
[3] Mitchell, S.A., and S.A. Vavasis, Quality Mesh Generation in Three Dimensions, TR-92-1267, Department of Computer Science, Cornell University, February 7, 1992
[4] Dey, T.K., "Delaunay Triangulations in Three Dimensions with Finite Precision Arithmetic," Computer Aided Geometric Design, 9:457-470, 1992