public PatchROAM(LandscapeROAM landscape, Camera cam, int heightX, int heightY, int worldX, int worldY, int patchSize, int mapSize) { this.landscape = landscape; this.cam = cam; this.varianceDepth = 8; this.patchSize = patchSize; this.mapSize = mapSize; // Clear all the relationships m_BaseLeft = new TriTreeNode(); m_BaseRight = new TriTreeNode(); m_BaseLeft.setBaseNeighbor(m_BaseRight); m_BaseRight.setBaseNeighbor(m_BaseLeft); // Store Patch offsets for the world and heightmap. this.worldX = worldX; this.worldY = worldY; // Store pointer to first byte of the height data for this patch. this.heightX = heightX; this.heightY = heightY; // Initialize flags m_VarianceDirty = 1; m_isVisible = 0; m_VarianceLeft = new ushort[1 << (varianceDepth)]; // Left variance tree m_VarianceRight = new ushort[1 << (varianceDepth)]; // Right variance tree Vector3 center = new Vector3(worldX + patchSize / 2, 0.0f, -worldY - patchSize / 2); bs = new BoundingSphere(center, patchSize); }
// --------------------------------------------------------------------- // Render the tree. Simple no-fan method. // public void recursRender(TriTreeNode tri, int leftX, int leftY, int rightX, int rightY, int apexX, int apexY) { if (tri != null && tri.getLeftChild() != null) // All non-leaf nodes have both children, so just check for one { int centerX = (leftX + rightX) >> 1; // Compute X coordinate of center of Hypotenuse int centerY = (leftY + rightY) >> 1; // Compute Y coord... recursRender(tri.getLeftChild(), apexX, apexY, leftX, leftY, centerX, centerY); recursRender(tri.getRightChild(), rightX, rightY, apexX, apexY, centerX, centerY); } else // A leaf node! Output a triangle to be rendered. { float leftZ = landscape.getHeightMap(heightX + leftX, heightY + leftY); float rightZ = landscape.getHeightMap(heightX + rightX, heightY + rightY); float apexZ = landscape.getHeightMap(heightX + apexX, heightY + apexY); // Actual number of rendered triangles... landscape.NumTrisRendered++; // Perform lighting calculations. Vector3[] vec = new Vector3[3]; Vector3 normal = new Vector3(); // Create a vertex normal for this triangle. // NOTE: This is an extremely slow operation for illustration purposes only. // You should use a texture map with the lighting pre-applied to the texture. vec[0].X = leftX; vec[0].Y = leftZ; vec[0].Z = leftY; vec[1].X = rightX; vec[1].Y = rightZ; vec[1].Z = rightY; vec[2].X = apexX; vec[2].Y = apexZ; vec[2].Z = apexY; normal = RenderEngine.calcNormal(vec); normal.Normalize(); float u = ((float)leftX + (float)worldX) / (float)mapSize; float v = ((float)leftY + (float)worldY) / (float)mapSize; landscape.Verts[landscape.NumVertsRendered] = new PositionNormalMultiTexture(new Vector3((float)landscape.getPosition().X + worldX + (float)leftX, landscape.getPosition().Y + (float)leftZ, (float)-landscape.getPosition().Z - worldY - (float)leftY), normal, new Vector2(u, v), new Vector2(u, v)); landscape.NumVertsRendered++; u = ((float)rightX + (float)worldX) / (float)mapSize; v = ((float)rightY + (float)worldY) / (float)mapSize; landscape.Verts[landscape.NumVertsRendered] = new PositionNormalMultiTexture(new Vector3((float)landscape.getPosition().X + worldX + (float)rightX, landscape.getPosition().Y + (float)rightZ, (float)-landscape.getPosition().Z - worldY - (float)rightY), normal, new Vector2(u, v), new Vector2(u, v)); landscape.NumVertsRendered++; u = ((float)apexX + (float)worldX) / (float)mapSize; v = ((float)apexY + (float)worldY) / (float)mapSize; landscape.Verts[landscape.NumVertsRendered] = new PositionNormalMultiTexture(new Vector3((float)landscape.getPosition().X + worldX + (float)apexX, landscape.getPosition().Y + (float)apexZ, (float)-landscape.getPosition().Z - worldY - (float)apexY), normal, new Vector2(u, v), new Vector2(u, v)); landscape.NumVertsRendered++; } }
// --------------------------------------------------------------------- // Tessellate a Patch. // Will continue to split until the variance metric is met. // public void recursTessellate(TriTreeNode tri, int leftX, int leftY, int rightX, int rightY, int apexX, int apexY, int node, ushort[] m_CurrentVariance) { float triVariance = 0.0f; int centerX = (leftX + rightX) >> 1; // Compute X coordinate of center of Hypotenuse int centerY = (leftY + rightY) >> 1; // Compute Y coord... if (node < (1 << varianceDepth)) { // Extremely slow distance metric (sqrt is used). // Replace this with a faster one! float distance = 1.0f + (float)Math.Sqrt(Math.Pow(centerX - cam.VEye.X, 2) + Math.Pow(centerY + cam.VEye.Z, 2)); // Egads! A division too? What's this world coming to! // This should also be replaced with a faster operation. triVariance = ((float)m_CurrentVariance[node] * mapSize * 2) / distance; // Take both distance and variance into consideration } if ((node >= (1 << varianceDepth)) || // IF we do not have variance info for this node, then we must have gotten here by splitting, so continue down to the lowest level. (triVariance > landscape.FrameVariance)) // OR if we are not below the variance tree, test for variance. { split(tri); // Split this triangle. if (tri != null && tri.getLeftChild() != null && // If this triangle was split, try to split it's children as well. ((Math.Abs(leftX - rightX) >= 2) || (Math.Abs(leftY - rightY) >= 2))) // Tessellate all the way down to one vertex per height field entry { recursTessellate(tri.getLeftChild(), apexX, apexY, leftX, leftY, centerX, centerY, node << 1, m_CurrentVariance); recursTessellate(tri.getRightChild(), rightX, rightY, apexX, apexY, centerX, centerY, 1 + (node << 1), m_CurrentVariance); } } }
public void setRightNeighbor(TriTreeNode tri) { if (tri == null) { this.rightNeighbor = new TriTreeNode(); } this.rightNeighbor = tri; }
public void setBaseNeighbor(TriTreeNode tri) { if (tri == null) { this.baseNeighbor = new TriTreeNode(); } this.baseNeighbor = tri; }
public void setRightChild(TriTreeNode tri) { if (tri == null) { this.rightChild = new TriTreeNode(); } this.rightChild = tri; }
public void setLeftChild(TriTreeNode tri) { if (tri == null) { this.leftChild = new TriTreeNode(); } this.leftChild = tri; }
public TriTreeNode() { lz = rz = az = 0.0f; this.leftChild = null; this.rightChild = null; this.baseNeighbor = null; this.leftNeighbor = null; this.rightNeighbor = null; }
// --------------------------------------------------------------------- // Allocate a TriTreeNode from the pool. // public TriTreeNode allocateTri() { // If we've run out of TriTreeNodes, just return NULL (this is handled gracefully) if (nextTriNode >= poolSize - 1) { return(null); } nextTriNode++; triPool[nextTriNode] = new TriTreeNode(); return(triPool[nextTriNode]); }
// --------------------------------------------------------------------- // Split a single Triangle and link it into the mesh. // Will correctly force-split diamonds. // public void split(TriTreeNode tri) { if (tri != null) { // We are already split, no need to do it again. if (tri.getLeftChild() != null) { return; } // If this triangle is not in a proper diamond, force split our base neighbor if (tri.getBaseNeighbor() != null && tri.getBaseNeighbor().getBaseNeighbor() != tri) { split(tri.getBaseNeighbor()); } // Create children and link into mesh tri.setLeftChild(landscape.allocateTri()); tri.setRightChild(landscape.allocateTri()); // If creation failed, just exit. if (tri.getLeftChild() == null) { return; } if (tri.getRightChild() == null) { return; } // Fill in the information we can get from the parent (neighbor pointers) tri.getLeftChild().setBaseNeighbor(tri.getLeftNeighbor()); tri.getLeftChild().setLeftNeighbor(tri.getRightChild()); tri.getRightChild().setBaseNeighbor(tri.getRightNeighbor()); tri.getRightChild().setRightNeighbor(tri.getLeftChild()); // Link our Left Neighbor to the new children if (tri.getLeftNeighbor() != null) { if (tri.getLeftNeighbor().getBaseNeighbor() == tri) { tri.getLeftNeighbor().setBaseNeighbor(tri.getLeftChild()); } else if (tri.getLeftNeighbor().getLeftNeighbor() == tri) { tri.getLeftNeighbor().setLeftNeighbor(tri.getLeftChild()); } else if (tri.getLeftNeighbor().getRightNeighbor() == tri) { tri.getLeftNeighbor().setRightNeighbor(tri.getLeftChild()); } else { ;// Illegal Left Neighbor! } } // Link our Right Neighbor to the new children if (tri.getRightNeighbor() != null) { if (tri.getRightNeighbor().getBaseNeighbor() == tri) { tri.getRightNeighbor().setBaseNeighbor(tri.getRightChild()); } else if (tri.getRightNeighbor().getRightNeighbor() == tri) { tri.getRightNeighbor().setRightNeighbor(tri.getRightChild()); } else if (tri.getRightNeighbor().getLeftNeighbor() == tri) { tri.getRightNeighbor().setLeftNeighbor(tri.getRightChild()); } else { ;// Illegal Right Neighbor! } } // Link our Base Neighbor to the new children if (tri.getBaseNeighbor() != null) { if (tri.getBaseNeighbor().getLeftChild() != null) { tri.getBaseNeighbor().getLeftChild().setRightNeighbor(tri.getRightChild()); tri.getBaseNeighbor().getRightChild().setLeftNeighbor(tri.getLeftChild()); tri.getLeftChild().setRightNeighbor(tri.getBaseNeighbor().getRightChild()); tri.getRightChild().setLeftNeighbor(tri.getBaseNeighbor().getLeftChild()); } else { split(tri.getBaseNeighbor()); // Base Neighbor (in a diamond with us) was not split yet, so do that now. } } else { // An edge triangle, trivial case. tri.getLeftChild().setRightNeighbor(null); tri.getRightChild().setLeftNeighbor(null); } } }