void quadsquare::CreateChild(int index, const quadcornerdata& cd) // Creates a child square at the specified index. { if (Child[index] == 0) { quadcornerdata q; SetupCornerData(&q, cd, index); Child[index] = new quadsquare(&q); } }
quadsquare* quadsquare::EnableDescendant(int count, int path[], const quadcornerdata& cd) // This function enables the descendant node 'count' generations below // us, located by following the list of child indices in path[]. // Creates the node if necessary, and returns a pointer to it. { count--; int ChildIndex = path[count]; if ((EnabledFlags & (16 << ChildIndex)) == 0) { EnableChild(ChildIndex, cd); } if (count > 0) { quadcornerdata q; SetupCornerData(&q, cd, ChildIndex); return Child[ChildIndex]->EnableDescendant(count, path, q); } else { return Child[ChildIndex]; } }
void quadsquare::RenderAux( const quadcornerdata &cd, CLIPSTATE vis ) { //Does the work of rendering this square. Uses the enabled vertices only. //Recurses as necessary. unsigned int whole = 2<<cd.Level; SphereTransformRenderlevel++; //If this square is outside the frustum, then don't render it. if (vis != GFX_TOTALLY_VISIBLE) { Vector min, max; min.i = cd.xorg; min.j = MinY; min.k = cd.zorg; max.i = cd.xorg+whole; max.j = MaxY; max.k = cd.zorg+whole; vis = nonlinear_trans->BoxInFrustum( min, max, quadsquare::camerapos ); if (vis == GFX_NOT_VISIBLE) { SphereTransformRenderlevel--; //This square is completely outside the view frustum. return; } } int i; int flags = 0; int mask = 1; quadcornerdata q; for (i = 0; i < 4; i++, mask <<= 1) { if ( EnabledFlags&(16<<i) ) { SetupCornerData( &q, cd, i ); Child[i]->RenderAux( q, vis ); } else { flags |= mask; } } SphereTransformRenderlevel--; if (flags == 0) return; //Local macro to make the triangle logic shorter & hopefully clearer. //#define tri(aa,ta,bb,tb,cc,tc) (indices[ta].q.push_back (aa), indices[ta].q.push_back (bb), indices[ta].q.push_back (cc)) #define V0 (Vertex[0].vertindex) #define T0 ( Vertex[0].GetTex() ) #define V1 (Vertex[1].vertindex) #define T1 ( Vertex[1].GetTex() ) #define V2 (cd.Verts[0].vertindex) #define T2 ( cd.Verts[0].GetTex() ) #define V3 (Vertex[2].vertindex) #define T3 ( Vertex[2].GetTex() ) #define V4 (cd.Verts[1].vertindex) #define T4 ( cd.Verts[1].GetTex() ) #define V5 (Vertex[3].vertindex) #define T5 ( Vertex[3].GetTex() ) #define V6 (cd.Verts[2].vertindex) #define T6 ( cd.Verts[2].GetTex() ) #define V7 (Vertex[4].vertindex) #define T7 ( Vertex[4].GetTex() ) #define V8 (cd.Verts[3].vertindex) #define T8 ( cd.Verts[3].GetTex() ) //Make the list of triangles to draw. if ( (EnabledFlags&1) == 0 ) {tri( V0, T0, V8, T8, V2, T2 ); } else { if (flags&8) tri( V0, T0, V8, T8, V1, T1 ); if (flags&1) tri( V0, T0, V1, T1, V2, T2 ); } if ( (EnabledFlags&2) == 0 ) {tri( V0, T0, V2, T2, V4, T4 ); } else { if (flags&1) tri( V0, T0, V2, T2, V3, T3 ); if (flags&2) tri( V0, T0, V3, T3, V4, T4 ); } if ( (EnabledFlags&4) == 0 ) {tri( V0, T0, V4, T4, V6, T6 ); } else { if (flags&2) tri( V0, T0, V4, T4, V5, T5 ); if (flags&4) tri( V0, T0, V5, T5, V6, T6 ); } if ( (EnabledFlags&8) == 0 ) {tri( V0, T0, V6, T6, V8, T8 ); } else { if (flags&4) tri( V0, T0, V6, T6, V7, T7 ); if (flags&8) tri( V0, T0, V7, T7, V8, T8 ); } #undef V1 #undef V2 #undef V3 #undef V4 #undef V5 #undef V6 #undef V7 #undef V8 #undef T1 #undef T2 #undef T3 #undef T4 #undef T5 #undef t6 #undef T7 #undef T8 }
void quadsquare::UpdateAux(const quadcornerdata& cd, const float ViewerLocation[3], float CenterError, clip_result_t vis ) // Does the actual work of updating enabled states and tree growing/shrinking. { BlockUpdateCount++; //xxxxx check_assertion( vis != NotVisible, "Invalid visibility value" ); if ( vis != NoClip ) { vis = ClipSquare( cd ); if ( vis == NotVisible ) { return; } } // Make sure error values are current. if (Dirty) { RecomputeError(cd); } int half = 1 << cd.Level; int whole = half << 1; // See about enabling child verts. // East vert. if ( (EnabledFlags & 1) == 0 && VertexTest(cd.xorg + whole, Vertex[1].Y, cd.zorg + half, Error[0], ViewerLocation, cd.Level, East) == true ) { EnableEdgeVertex(0, false, cd); } // South vert. if ( (EnabledFlags & 8) == 0 && VertexTest(cd.xorg + half, Vertex[4].Y, cd.zorg + whole, Error[1], ViewerLocation, cd.Level, South) == true ) { EnableEdgeVertex(3, false, cd); } if (cd.Level > 0) { if ((EnabledFlags & 32) == 0) { if (BoxTest(cd.xorg, cd.zorg, half, MinY, MaxY, Error[3], ViewerLocation) == true) EnableChild(1, cd); // nw child.er } if ((EnabledFlags & 16) == 0) { if (BoxTest(cd.xorg + half, cd.zorg, half, MinY, MaxY, Error[2], ViewerLocation) == true) EnableChild(0, cd); // ne child. } if ((EnabledFlags & 64) == 0) { if (BoxTest(cd.xorg, cd.zorg + half, half, MinY, MaxY, Error[4], ViewerLocation) == true) EnableChild(2, cd); // sw child. } if ((EnabledFlags & 128) == 0) { if (BoxTest(cd.xorg + half, cd.zorg + half, half, MinY, MaxY, Error[5], ViewerLocation) == true) EnableChild(3, cd); // se child. } // Recurse into child quadrants as necessary. quadcornerdata q; if (EnabledFlags & 32) { SetupCornerData(&q, cd, 1); Child[1]->UpdateAux(q, ViewerLocation, Error[3], vis); } if (EnabledFlags & 16) { SetupCornerData(&q, cd, 0); Child[0]->UpdateAux(q, ViewerLocation, Error[2], vis); } if (EnabledFlags & 64) { SetupCornerData(&q, cd, 2); Child[2]->UpdateAux(q, ViewerLocation, Error[4], vis); } if (EnabledFlags & 128) { SetupCornerData(&q, cd, 3); Child[3]->UpdateAux(q, ViewerLocation, Error[5], vis); } } // Test for disabling. East, South, and center. if ( (EnabledFlags & 1) && SubEnabledCount[0] == 0 && VertexTest(cd.xorg + whole, Vertex[1].Y, cd.zorg + half, Error[0], ViewerLocation, cd.Level, East) == false) { EnabledFlags &= ~1; quadsquare* s = GetNeighbor(0, cd); if (s) s->EnabledFlags &= ~4; } if ( (EnabledFlags & 8) && SubEnabledCount[1] == 0 && VertexTest(cd.xorg + half, Vertex[4].Y, cd.zorg + whole, Error[1], ViewerLocation, cd.Level, South) == false) { EnabledFlags &= ~8; quadsquare* s = GetNeighbor(3, cd); if (s) s->EnabledFlags &= ~2; } if (EnabledFlags == 0 && cd.Parent != NULL && BoxTest(cd.xorg, cd.zorg, whole, MinY, MaxY, CenterError, ViewerLocation) == false) { // Disable ourself. cd.Parent->Square->NotifyChildDisable(*cd.Parent, cd.ChildIndex); // nb: possibly deletes 'this'. } }
void quadsquare::StaticCullAux(const quadcornerdata& cd, float ThresholdDetail, int TargetLevel) // Check this node and its descendents, and remove nodes which don't contain // necessary detail. { int i, j; quadcornerdata q; if (cd.Level > TargetLevel) { // Just recurse to child nodes. for (j = 0; j < 4; j++) { if (j < 2) i = 1 - j; else i = j; if (Child[i]) { SetupCornerData(&q, cd, i); Child[i]->StaticCullAux(q, ThresholdDetail, TargetLevel); } } return; } // We're at the target level. Check this node to see if it's OK to delete it. // Check edge vertices to see if they're necessary. float size = 2 << cd.Level; // Edge length. if (Child[0] == NULL && Child[3] == NULL && Error[0] * ThresholdDetail < size) { quadsquare* s = GetNeighbor(0, cd); if (s == NULL || (s->Child[1] == NULL && s->Child[2] == NULL)) { // Force vertex height to the edge value. float y = (cd.Verts[0].Y + cd.Verts[3].Y) * 0.5; Vertex[1].Y = y; Error[0] = 0; // Force alias vertex to match. if (s) s->Vertex[3].Y = y; Dirty = true; } } if (Child[2] == NULL && Child[3] == NULL && Error[1] * ThresholdDetail < size) { quadsquare* s = GetNeighbor(3, cd); if (s == NULL || (s->Child[0] == NULL && s->Child[1] == NULL)) { float y = (cd.Verts[2].Y + cd.Verts[3].Y) * 0.5; Vertex[4].Y = y; Error[1] = 0; if (s) s->Vertex[2].Y = y; Dirty = true; } } // See if we have child nodes. bool StaticChildren = false; for (i = 0; i < 4; i++) { if (Child[i]) { StaticChildren = true; if (Child[i]->Dirty) Dirty = true; } } // If we have no children and no necessary edges, then see if we can delete ourself. if (StaticChildren == false && cd.Parent != NULL) { bool NecessaryEdges = false; for (i = 0; i < 4; i++) { // See if vertex deviates from edge between corners. float diff = fabs(Vertex[i+1].Y - (cd.Verts[i].Y + cd.Verts[(i+3)&3].Y) * 0.5); if (diff > 0.00001) { NecessaryEdges = true; } } if (!NecessaryEdges) { size *= 1.414213562; // sqrt(2), because diagonal is longer than side. if (cd.Parent->Square->Error[2 + cd.ChildIndex] * ThresholdDetail < size) { delete cd.Parent->Square->Child[cd.ChildIndex]; // Delete this. cd.Parent->Square->Child[cd.ChildIndex] = 0; // Clear the pointer. } } } }
float quadsquare::RecomputeError(const quadcornerdata& cd) // Recomputes the error values for this tree. Returns the // max error. // Also updates MinY & MaxY. { int i; int j; int t; int half = 1 << cd.Level; int whole = half << 1; float terrain_error; // Measure error of center and edge vertices. float maxerror = 0; // Compute error of center vert. float e; if (cd.ChildIndex & 1) { e = fabs(Vertex[0].Y - (cd.Verts[1].Y + cd.Verts[3].Y) * 0.5); } else { e = fabs(Vertex[0].Y - (cd.Verts[0].Y + cd.Verts[2].Y) * 0.5); } if (e > maxerror) maxerror = e; // Initial min/max. MaxY = Vertex[0].Y; MinY = Vertex[0].Y; // Check min/max of corners. for (i = 0; i < 4; i++) { float y = cd.Verts[i].Y; if (y < MinY) MinY = y; if (y > MaxY) MaxY = y; } // Edge verts. e = fabs(Vertex[1].Y - (cd.Verts[0].Y + cd.Verts[3].Y) * 0.5); if (e > maxerror) maxerror = e; Error[0] = e; e = fabs(Vertex[4].Y - (cd.Verts[2].Y + cd.Verts[3].Y) * 0.5); if (e > maxerror) maxerror = e; Error[1] = e; // Terrain edge checks if ( cd.Level == 0 && cd.xorg <= RowSize-1 && cd.zorg <= NumRows-1 ) { // Check South vertex int x = cd.xorg + half; int z = cd.zorg + whole; int idx = x + z * RowSize; bool different_terrains = false; terrain_error = 0.f; check_assertion( x >= 0, "x coordinate is negative" ); check_assertion( z >= 0, "z coordinate is negative" ); if ( x < RowSize && z < NumRows ) { if ( x < RowSize - 1 ) { if ( Terrain[idx] != Terrain[idx+1] ) { different_terrains = true; } } if ( z >= 1 ) { idx -= RowSize; if ( Terrain[idx] != Terrain[idx+1] ) { different_terrains = true; } } if ( different_terrains ) { ForceSouthVert = true; terrain_error = TERRAIN_ERROR_SCALE * whole * whole; } else { ForceSouthVert = false; } if ( terrain_error > Error[0] ) { Error[0] = terrain_error; } if ( Error[0] > maxerror ) { maxerror = Error[0]; } } // Check East vertex x = cd.xorg + whole; z = cd.zorg + half; idx = x + z * RowSize; terrain_error = 0; different_terrains = false; if ( x < RowSize && z < NumRows ) { if ( z >= 1 ) { if ( Terrain[idx] != Terrain[idx-RowSize] ) { different_terrains = true; } } if ( z >= 1 && x < RowSize - 1 ) { idx += 1; if ( Terrain[idx] != Terrain[idx-RowSize] ) { different_terrains = true; } } if ( different_terrains ) { ForceEastVert = true; terrain_error = TERRAIN_ERROR_SCALE * whole * whole; } else { ForceEastVert = false; } if ( terrain_error > Error[1] ) { Error[1] = terrain_error; } if ( Error[1] > maxerror ) { maxerror = Error[1]; } } } // Min/max of edge verts. for (i = 0; i < 4; i++) { float y = Vertex[1 + i].Y; if (y < MinY) MinY = y; if (y > MaxY) MaxY = y; } // Check child squares. for (i = 0; i < 4; i++) { quadcornerdata q; if (Child[i]) { SetupCornerData(&q, cd, i); Error[i+2] = Child[i]->RecomputeError(q); if (Child[i]->MinY < MinY) MinY = Child[i]->MinY; if (Child[i]->MaxY > MaxY) MaxY = Child[i]->MaxY; } else { // Compute difference between bilinear average at child center, and diagonal edge approximation. Error[i+2] = fabs((Vertex[0].Y + cd.Verts[i].Y) - (Vertex[i+1].Y + Vertex[((i+1)&3) + 1].Y)) * 0.25; } if (Error[i+2] > maxerror) maxerror = Error[i+2]; } // // Compute terrain_error // int terrain; int *terrain_count = new int[(int)NumTerrains]; for (t=0; t<NumTerrains; t++) { terrain_count[t] = 0; } for (i=cd.xorg; i<=cd.xorg+whole; i++) { for (j=cd.zorg; j<=cd.zorg+whole; j++) { if ( i < 0 || i >= RowSize || j < 0 || j >= NumRows ) { continue; } terrain = (int) Terrain[ i + RowSize*j ]; check_assertion( terrain >= 0 && terrain < NumTerrains, "Invalid terrain type" ); terrain_count[ terrain ] += 1; } } int max_count = 0; int max_type = 0; int total = 0; for (t=0; t<NumTerrains; t++) { if ( terrain_count[t] > max_count ) { max_count = terrain_count[t]; max_type = t; } total += terrain_count[t]; } delete [] terrain_count; /* Calculate a terrain error that varies between 0 and 1 */ if ( total > 0 ) { terrain_error = (1.0 - max_count / total); if ( NumTerrains > 1 ) { terrain_error *= NumTerrains / ( NumTerrains - 1.0 ); } } else { terrain_error = 0; } /* and scale it by the square area */ terrain_error *= whole * whole; /* and finally scale it so that it's comparable to height error */ terrain_error *= TERRAIN_ERROR_SCALE; if ( terrain_error > maxerror ) { maxerror = terrain_error; } if ( terrain_error > Error[0] ) { Error[0] = terrain_error; } if ( terrain_error > Error[1] ) { Error[1] = terrain_error; } // The error and MinY/MaxY values for this node and descendants are correct now. Dirty = false; return maxerror; }
float quadsquare::GetHeight(const quadcornerdata& cd, float x, float z) // Returns the height of the heightfield at the specified x,z coordinates. { int half = 1 << cd.Level; float lx = (x - cd.xorg) / float(half); float lz = (z - cd.zorg) / float(half); int ix = (int) floor(lx); int iz = (int) floor(lz); // Clamp. if (ix < 0) ix = 0; if (ix > 1) ix = 1; if (iz < 0) iz = 0; if (iz > 1) iz = 1; int index = ix ^ (iz ^ 1) + (iz << 1); if (Child[index] && Child[index]->Static) { // Pass the query down to the child which contains it. quadcornerdata q; SetupCornerData(&q, cd, index); return Child[index]->GetHeight(q, x, z); } // Bilinear interpolation. lx -= ix; if (lx < 0) lx = 0; if (lx > 1) lx = 1; lz -= iz; if (lx < 0) lz = 0; if (lz > 1) lz = 1; float s00, s01, s10, s11; switch (index) { default: case 0: s00 = Vertex[2].Y; s01 = cd.Verts[0].Y; s10 = Vertex[0].Y; s11 = Vertex[1].Y; break; case 1: s00 = cd.Verts[1].Y; s01 = Vertex[2].Y; s10 = Vertex[3].Y; s11 = Vertex[0].Y; break; case 2: s00 = Vertex[3].Y; s01 = Vertex[0].Y; s10 = cd.Verts[2].Y; s11 = Vertex[4].Y; break; case 3: s00 = Vertex[0].Y; s01 = Vertex[1].Y; s10 = Vertex[4].Y; s11 = cd.Verts[3].Y; break; } return (s00 * (1-lx) + s01 * lx) * (1 - lz) + (s10 * (1-lx) + s11 * lx) * lz; }
void quadsquare::AddHeightMap(const quadcornerdata& cd, const HeightMapInfo& hm) // Sets the height of all samples within the specified rectangular // region using the given array of floats. Extends the tree to the // level of detail defined by (1 << hm.Scale) as necessary. { RowSize = hm.RowWidth; NumRows = hm.ZSize; if ( cd.Parent == NULL ) { if ( VertexArrayIndices != NULL ) { delete VertexArrayIndices; } /* Maximum number of triangles is 2 * RowSize * NumRows This uses up a lot of space but it is a *big* performance gain. */ VertexArrayIndices = new GLuint[6 * RowSize * NumRows]; } // If block is outside rectangle, then don't bother. int BlockSize = 2 << cd.Level; if (cd.xorg > hm.XOrigin + ((hm.XSize + 2) << hm.Scale) || cd.xorg + BlockSize < hm.XOrigin - (1 << hm.Scale) || cd.zorg > hm.ZOrigin + ((hm.ZSize + 2) << hm.Scale) || cd.zorg + BlockSize < hm.ZOrigin - (1 << hm.Scale)) { // This square does not touch the given height array area; no need to modify this square or descendants. return; } if (cd.Parent && cd.Parent->Square) { cd.Parent->Square->EnableChild(cd.ChildIndex, *cd.Parent); // causes parent edge verts to be enabled, possibly causing neighbor blocks to be created. } int i; int half = 1 << cd.Level; // Create and update child nodes. for (i = 0; i < 4; i++) { quadcornerdata q; SetupCornerData(&q, cd, i); if (Child[i] == NULL && cd.Level > hm.Scale) { // Create child node w/ current (unmodified) values for corner verts. Child[i] = new quadsquare(&q); } // Recurse. if (Child[i]) { Child[i]->AddHeightMap(q, hm); } } // Deviate vertex heights based on data sampled from heightmap. float s[5]; s[0] = hm.Sample(cd.xorg + half, cd.zorg + half); s[1] = hm.Sample(cd.xorg + half*2, cd.zorg + half); s[2] = hm.Sample(cd.xorg + half, cd.zorg); s[3] = hm.Sample(cd.xorg, cd.zorg + half); s[4] = hm.Sample(cd.xorg + half, cd.zorg + half*2); // Modify the vertex heights if necessary, and set the dirty // flag if any modifications occur, so that we know we need to // recompute error data later. for (i = 0; i < 5; i++) { if (s[i] != 0) { Dirty = true; Vertex[i].Y += s[i]; } } if (!Dirty) { // Check to see if any child nodes are dirty, and set the dirty flag if so. for (i = 0; i < 4; i++) { if (Child[i] && Child[i]->Dirty) { Dirty = true; break; } } } if (Dirty) SetStatic(cd); }
void quadsquare::RenderAux(const quadcornerdata& cd, clip_result_t vis, int terrain) // Does the work of rendering this square. Uses the enabled vertices only. // Recurses as necessary. { int half = 1 << cd.Level; int whole = 2 << cd.Level; // If this square is outside the frustum, then don't render it. if (vis != NoClip) { vis = ClipSquare( cd ); if (vis == NotVisible ) { // This square is completely outside the view frustum. return; } // else vis is either NoClip or SomeClip. If it's NoClip, then child // squares won't have to bother with the frustum check. } int i; int flags = 0; int mask = 1; quadcornerdata q; for (i = 0; i < 4; i++, mask <<= 1) { if (EnabledFlags & (16 << i)) { SetupCornerData(&q, cd, i); Child[i]->RenderAux(q, vis, terrain); } else { flags |= mask; } } if (flags == 0) return; // Init vertex data. Here's a diagram of what's going on. // Yes, this diagram is f****d, but that's because things are mirrored // in the z axis for us (z coords are actually -z coords). // // (top of course) // N // +---+---+ (xorg, zorg) // 2| 3 4| // |(1) (2)| // E + + + W // 1| 0 5| // |(8) (4)| // +---+---+ // 8 7 6 // S // (bottom of course) // // Values in parens are bitmask values for the corresponding child squares // InitVert(0, cd.xorg + half, cd.zorg + half); InitVert(1, cd.xorg + whole, cd.zorg + half); InitVert(2, cd.xorg + whole, cd.zorg); InitVert(3, cd.xorg + half, cd.zorg); InitVert(4, cd.xorg, cd.zorg); InitVert(5, cd.xorg, cd.zorg + half); InitVert(6, cd.xorg, cd.zorg + whole); InitVert(7, cd.xorg + half, cd.zorg + whole); InitVert(8, cd.xorg + whole, cd.zorg + whole); // Make the list of triangles to draw. #define make_tri_list(tri_func) \ if ((EnabledFlags & 1) == 0 ) { \ tri_func(0, 2, 8, terrain); \ } else { \ if (flags & 8) tri_func(0, 1, 8, terrain); \ if (flags & 1) tri_func(0, 2, 1, terrain); \ } \ if ((EnabledFlags & 2) == 0 ) { \ tri_func(0, 4, 2, terrain); \ } else { \ if (flags & 1) tri_func(0, 3, 2, terrain); \ if (flags & 2) tri_func(0, 4, 3, terrain); \ } \ if ((EnabledFlags & 4) == 0 ) { \ tri_func(0, 6, 4, terrain); \ } else { \ if (flags & 2) tri_func(0, 5, 4, terrain); \ if (flags & 4) tri_func(0, 6, 5, terrain); \ } \ if ((EnabledFlags & 8) == 0 ) { \ tri_func(0, 8, 6, terrain); \ } else { \ if (flags & 4) tri_func(0, 7, 6, terrain); \ if (flags & 8) tri_func(0, 8, 7, terrain); \ } if ( terrain == -1 ) { make_tri_list(MakeSpecialTri); } else if ( getparam_terrain_blending() ) { make_tri_list(MakeTri); } else { make_tri_list(MakeNoBlendTri); } }