/// <summary> /// Initializes a new instance of the <see cref="QuadNode" /> class. /// </summary> /// <param name="type">Type of node.</param> /// <param name="size">Width/Height of node (# of vertices across - 1).</param> /// <param name="depth">Depth of current node</param> /// <param name="parent">Parent QuadNode</param> /// <param name="parentTree">Top level Tree.</param> /// <param name="positionIndex">Index of top left Vertex in the parent tree Vertices array</param> public QuadNode( NodeType type, int size, int depth, QuadNode parent, Tanks3DFPP.Terrain.QuadTree parentTree, int positionIndex) { Type = type; this.size = size; this.depth = depth; parentNode = parent; this.parentTree = parentTree; this.positionIndex = positionIndex; AddVertices(); /* * The bounding box is used to determine where the tree * should be split and where to look for nodes that are within view. * * In a production system it would make more sense to use the maximum height value of the tree as a guide to define the bounds of the bounding box. * Even the maximum height within each individual quad may be used to define the bounds which would make culling away invisible quads much more efficient when standing on a mountain. * In fact, a more efficient method would be to use bounding shapes instead of volumes, using a rectangle as the bounding object. * The view frustrum could then be projected onto the terrain as a 2D shape. * Intersection checks for LOD would be much more efficient. */ Bounds = new BoundingBox( this.parentTree.Vertices[VertexTopLeft.Index].Position, this.parentTree.Vertices[VertexBottomRight.Index].Position) { Min = { Y = 0 }, Max = { Y = limY } }; //If node does not belong to the highest LOD if (this.size >= minSizeToContainChildren) { AddChildren(); } // If this is the root node if (this.depth == 1) { /* * Create the neighbors. * This is essentially the bootstrap point for the whole tree. */ AddNeighbors(); VertexTopLeft.Activated = true; VertexTopRight.Activated = true; VertexCenter.Activated = true; VertexBottomLeft.Activated = true; VertexBottomRight.Activated = true; VertexTop.Activated = true; VertexLeft.Activated = true; VertexRight.Activated = true; VertexBottom.Activated = true; } }
/* * Each quad looks like this: * (V - QuadNodeVertex) * * V------V------V * | | | * | | | * V------V------V * | | | * | | | * V------V------V * * Since we are operating on triangles. * Thus the need for 9 vertices per node. * The order of activating vertices is as follows: * 1) Top-left * 2) Top-right * 3) Center * 4) Bottom-left * 5) Bottom-right * Only after splitting are the Top, Left, Right and Bottom (in that order) vertices activated. * * Now let's say that quad has a neighbor to the right. * Since we've activated the "Right" vertex on this node by splitting it * we have to also activate the "Left" node on the neighbor to the right * otherwise we'll have a split node up against an unsplit node which * will create a visible seam. * Having the extra vertices at the quad level allows us to activate * only the necessary vertices without completely splitting * the neighboring quad. */ /// <summary> /// Adds the children quad nodes. /// </summary> private void AddChildren() { int halfSize = size / 2, nextDepth = depth + 1; ChildTopLeft = new QuadNode(NodeType.TopLeft, halfSize, nextDepth, this, parentTree, VertexTopLeft.Index); ChildTopRight = new QuadNode(NodeType.TopRight, halfSize, nextDepth, this, parentTree, VertexTop.Index); ChildBottomLeft = new QuadNode(NodeType.BottomLeft, halfSize, nextDepth, this, parentTree, VertexBottomLeft.Index); ChildBottomRight = new QuadNode(NodeType.BottomRight, halfSize, nextDepth, this, parentTree, VertexCenter.Index); hasChildren = true; }
/// <summary> /// Adds the neighboring quad nodes. /// </summary> private void AddNeighbors() { switch (Type) { case NodeType.TopLeft: #region The direct neighbors that DO NOT belong within the current node's parent: // In case this is not the topmost quad node of the whole tree if (Parent.NeighborTop != null) { // The parent's top neighbor's next-level bottom-left quad node. // This is effectively the adjacent quad node of the same size as this one. NeighborTop = Parent.NeighborTop.ChildBottomLeft; } // In case this is not the leftmost quad node of the whole tree if (Parent.NeighborLeft != null) { // The parent's left neighbor's next-level top-right quad node. // This is effectively the adjacent quad node of the same size as this one. NeighborLeft = Parent.NeighborLeft.ChildTopRight; } #endregion #region The direct neighbors within the same parent: NeighborRight = Parent.ChildTopRight; NeighborBottom = Parent.ChildBottomLeft; #endregion break; case NodeType.TopRight: #region The direct neighbors that DO NOT belong within the current node's parent: if (Parent.NeighborTop != null) { NeighborTop = Parent.NeighborTop.ChildBottomRight; } if (Parent.NeighborRight != null) { NeighborRight = Parent.NeighborRight.ChildTopLeft; } #endregion #region The direct neighbors within the same parent: NeighborLeft = Parent.ChildTopLeft; NeighborBottom = Parent.ChildBottomRight; #endregion break; case NodeType.BottomLeft: #region The direct neighbors that DO NOT belong within the current node's parent: if (Parent.NeighborBottom != null) { NeighborBottom = Parent.NeighborBottom.ChildTopLeft; } if (Parent.NeighborLeft != null) { NeighborLeft = Parent.NeighborLeft.ChildBottomRight; } #endregion #region The direct neighbors within the same parent: NeighborRight = Parent.ChildBottomRight; NeighborTop = Parent.ChildTopLeft; #endregion break; case NodeType.BottomRight: #region The direct neighbors that DO NOT belong within the current node's parent: if (Parent.NeighborBottom != null) { NeighborBottom = Parent.NeighborBottom.ChildTopRight; } if (Parent.NeighborRight != null) { NeighborRight = Parent.NeighborRight.ChildBottomLeft; } #endregion #region The direct neighbors within the same parent: NeighborLeft = Parent.ChildBottomLeft; NeighborTop = Parent.ChildTopRight; #endregion break; } if (hasChildren) { // recursively add neighbors for all children. ChildTopLeft.AddNeighbors(); ChildTopRight.AddNeighbors(); ChildBottomLeft.AddNeighbors(); ChildBottomRight.AddNeighbors(); } }
private static void EnsureNeighborParentSplit(QuadNode neighbor) { if (neighbor != null && neighbor.Parent != null && !neighbor.Parent.IsSplit) { neighbor.Parent.Split(); } }
public void Update(ICamera camera) { // Checking camera position is not enough - terrain has to update also while changing the angle. if (camera.Frustum != lastCameraFrustum) { Effect.Parameters["xView"].SetValue(camera.View); lastCameraFrustum = camera.Frustum; IndexCount = 0; root.Merge(); root.EnforceMinimumDepth(); active = root.DeepestNodeContainingPoint(camera.LookAt); if (active != null) { active.Split(); } root.SetActiveVertices(); buffers.UpdateIndexBuffer(Indices, IndexCount); buffers.SwapBuffer(); } this.RotateLight(.2f); }
public void Initialize(IHeightMap heightMap) { Thread t = new Thread(() => { topNodeSize = heightMap.Width - 1; vertices = new TreeVertexCollection(this.position, heightMap, Game1.GameParameters.MapScale); buffers = new BufferManager(vertices.Vertices, GraphicsDevice); root = new QuadNode(NodeType.FullNode, topNodeSize, 1, null, this, 0); Indices = new int[(heightMap.Width + 1) * (heightMap.Height + 1) * 3]; this.FireReady(this); }); t.Start(); }