// create quadtree buffer void AllocateNodeBuffer() { int gridNum = hmSize - 1; long nodeCnt = 1; if (gridNum > terrain.leafGridSize) { for (int i = 0; gridNum > terrain.leafGridSize; i++) { nodeCnt += 4 << (i * 2); gridNum >>= 1; } } // just to ensure there isn't too many nodes Debug.Assert(nodeCnt < uint.MaxValue); nodeBuf = new QtreeNode[nodeCnt]; nodeNum = (int)nodeCnt; for (int i = 0; i < nodeNum; i++) { nodeBuf[i] = new QtreeNode(); } }
public Quadtree(QuadtreeTerrain _trn, int _hmSize) { Debug.Assert(Misc.Is2Power(_hmSize - 1)); terrain = _trn; hmSize = _hmSize; // create node buffer AllocateNodeBuffer(); // Initialize root node root = nodeBuf[0]; root.index = 0; root.hmLeft = 0; root.hmTop = 0; root.hmAreaSize = (ushort)(_hmSize - 1); // Generate tree recursively int nodeTempCnt = 1; BuildNode_r(0, (sbyte)eChildPos.POS_INVALID, ref nodeTempCnt); // check node number Debug.Assert(nodeTempCnt == (int)nodeNum); // Build neighbours info BuildNeighbours_r(0); }
QuadtreeMesh CreateNodeMesh(int nodeIndex) { try { QtreeNode node = terrain.qtree.GetQtreeNode(nodeIndex); int rowVertNum = terrain.vertexNumInNodeRow; int numVert = rowVertNum * rowVertNum; // Prepare temporary buffers if (tempVertNum != numVert) { // In fact, except the first time, the code should never run into here, // for all quadtree nodes should have same number of vertex in their inborn LOD mesh tempVerts = new VertexBuffer(numVert); tempVertNum = numVert; } int gridSize = terrain.gridSize; float miny = 0.0f; float maxy = 0.0f; // Get mesh from height blender if (!terrain.gemBuilder.BuildQTreeNodeMesh(node.hmLeft, node.hmTop, node.gridStep, rowVertNum, gridSize, tempVerts, out miny, out maxy)) { Debug.Assert(false); return(null); } // calculate mesh's aabb in local space Rect rcLocal = node.CalcLocalArea(terrain.gridSize); Vector3 mins = new Vector3(rcLocal.xMin, miny, rcLocal.yMin); Vector3 maxs = new Vector3(rcLocal.xMax, maxy, rcLocal.yMax); // Create mesh object QuadtreeMesh nodeMesh = new QuadtreeMesh(terrain, nodeIndex, mins, maxs) { ts = Time.realtimeSinceStartup, }; // Create height map nodeMesh.heights = new float[numVert]; for (int i = 0; i < numVert; i++) { nodeMesh.heights[i] = tempVerts.verts[i].pos.y; } // Create vertex buffer nodeMesh.CreateVertCB(tempVerts); return(nodeMesh); } catch { Debug.LogFormat("Failed to create node mesh {0}", nodeIndex); return(null); } }
void CollectRenderNodes_r(int nodeIndex) { QtreeNode node = nodeBuf[nodeIndex]; // Only render node whose info is up-to-date if (node.updateCnt != updateCnt) { // If a node isn't up-to-date, all it's children must not be. return; } // do camera cull with node's AABB at first, but node may not have min-y and max-y // at this moment if it's mesh wasn't built, so now we only do a conservative check // with x and z axis. // NOTE: we ONLY consider tranlation of terrain, but not rotation and scale Vector3 offset = terrain.transform.position; Rect rcLocal = node.CalcLocalArea(terrain.gridSize); Vector3 mins = new Vector3(rcLocal.xMin + offset.x, -10000.0f, rcLocal.yMin + offset.z); Vector3 maxs = new Vector3(rcLocal.xMax + offset.x, 10000.0f, rcLocal.yMax + offset.z); Bounds aabb = new Bounds(); aabb.SetMinMax(mins, maxs); if (!GeometryUtility.TestPlanesAABB(camPlanes, aabb)) { return; // The whole node isn't visible } if (node.meshState == (byte)eNodeMeshState.LOAD_INBORN || node.meshState == (byte)eNodeMeshState.LOD_GRADEUP) { // Get index buffer according to neighbour's LOD grade int sideMask = 0; for (int i = 0; i < 4; i++) { if (node.neighbour[i] < 0) { continue; } QtreeNode neighbour = nodeBuf[node.neighbour[i]]; if (neighbour.updateCnt == updateCnt && neighbour.areaLOD > node.areaLOD) { sideMask |= (1 << i); } } // This node's may be drawn ComputeBuffer _vertCB = null; ComputeBuffer _indexCB = null; QuadtreeMesh nodeMesh = null; if (node.meshState == (byte)eNodeMeshState.LOAD_INBORN) { nodeMesh = terrain.meshMan.GetNodeMesh(nodeIndex); if (nodeMesh != null) { _vertCB = nodeMesh.vertCB; _indexCB = terrain.trnRes.GetNodeIndexCB(sideMask, -1); } } else if (node.meshState == (byte)eNodeMeshState.LOD_GRADEUP) { // LOD grade-up rendering, use parent's mesh Debug.Assert(node.parent >= 0); nodeMesh = terrain.meshMan.GetNodeMesh(node.parent); if (nodeMesh != null) { _vertCB = nodeMesh.vertCB; _indexCB = terrain.trnRes.GetNodeIndexCB(sideMask, node.childPos); } } if (_vertCB != null && _indexCB != null) { // do camera cull again with more precise aabb mins.y = nodeMesh.aabb.min.y + offset.y; maxs.y = nodeMesh.aabb.max.y + offset.y; aabb.SetMinMax(mins, maxs); if (!GeometryUtility.TestPlanesAABB(camPlanes, aabb)) { return; // The whole node isn't visible } // Push node to rendering collector DRAWDATA drawData = new DRAWDATA() { node = node, nodeAABB = aabb, vertCB = _vertCB, indexCB = _indexCB, }; curRenderer.PushDrawData(drawData); } } if (!node.IsLeaf()) { // Go on for children for (int i = 0; i < 4; i++) { CollectRenderNodes_r(node.children[i]); } } }
void UpdateNodeLOD_r(int nodeIndex, sbyte areaLOD) { QtreeNode node = nodeBuf[nodeIndex]; // Sync node's update counter node.updateCnt = updateCnt; node.areaLOD = areaLOD; node.outRange = false; // node's area in terrain's local space Rect rcLocal = node.CalcLocalArea(terrain.gridSize); // Calculate the distance from m_vUpdateCenter to node's border float distx = Mathf.Abs((rcLocal.xMin + rcLocal.xMax) * 0.5f - updateCenter.x); float distz = Mathf.Abs((rcLocal.yMin + rcLocal.yMax) * 0.5f - updateCenter.z); float min_distx = distx - rcLocal.width * 0.5f; float min_distz = distz - rcLocal.height * 0.5f; float check_dist = min_distx > min_distz ? min_distx : min_distz; if (check_dist > terrain.viewDistance) { // this node is out of view distance node.outRange = true; // we stop the recursively process only when this node and it's parent // are both out of view distance. this is a tip to avoid node flashing // on the border of view distance. if (node.parent >= 0 && nodeBuf[node.parent].outRange) { node.meshState = (byte)eNodeMeshState.TO_UNLOAD; return; } } if (node.inbornLOD < 0) { // the node hasn't mesh at all node.meshState = (byte)eNodeMeshState.TO_UNLOAD; Debug.Assert(!node.IsLeaf()); // Go on for children for (int i = 0; i < 4; i++) { UpdateNodeLOD_r(node.children[i], areaLOD); } } else if (areaLOD >= 0) { // parent's mesh is to be loaded, so this node needn't mesh any more. node.meshState = (byte)eNodeMeshState.TO_UNLOAD; // we can stop the recursive process here now (not go into children anymore), // however we can't stop at parent node early! because this node's neighbours need // its areaLOD to decide whether their border mesh should do LOD grade-up or not. // if (!node.IsLeaf()) // { // for (int i = 0; i < 4; i++) // UpdateNodeLOD_r(node.children[i], areaLOD); // } } else { // Check which LOD grade this node locates in int lod_grade; for (lod_grade = lodGradeNum - 1; lod_grade >= 0; lod_grade--) { if (check_dist > lodDists[lod_grade]) { break; } } Debug.Assert(lod_grade >= 0); if (lod_grade < node.inbornLOD) { // This node totally or partly locates in lower LOD grade area, // we should render its children node.meshState = (byte)eNodeMeshState.TO_UNLOAD; // this shouldn't be a leaf Debug.Assert(!node.IsLeaf()); // go on for children for (int i = 0; i < 4; i++) { UpdateNodeLOD_r(node.children[i], areaLOD); } // If only part of this node in lower LOD grade area, some // children may need our mesh to do LOD grade-up render. for (int i = 0; i < 4; i++) { if (nodeBuf[node.children[i]].meshState == (byte)eNodeMeshState.LOD_GRADEUP) { node.meshState = (byte)eNodeMeshState.LOAD_FOR_CHILD; break; } } } else if (lod_grade == node.inbornLOD) { // use node's inborn LOD node.meshState = (byte)eNodeMeshState.LOAD_INBORN; node.areaLOD = node.inbornLOD; // Go on to tell all children that they needn't loading mesh if (!node.IsLeaf()) { for (int i = 0; i < 4; i++) { UpdateNodeLOD_r(node.children[i], node.areaLOD); } } } else if (lod_grade == nodeBuf[node.parent].inbornLOD) { // LOD grade up, use parent's LOD node.meshState = (byte)eNodeMeshState.LOD_GRADEUP; node.areaLOD = nodeBuf[node.parent].inbornLOD; // Go on to tell all children that they needn't loading mesh if (!node.IsLeaf()) { for (int i = 0; i < 4; i++) { UpdateNodeLOD_r(node.children[i], node.areaLOD); } } } else { // Shouldn't go here ! Debug.Assert(false); } } // Stream in/out node mesh according to it's rendering flag if (node.meshState == (byte)eNodeMeshState.LOAD_INBORN || node.meshState == (byte)eNodeMeshState.LOAD_FOR_CHILD) { terrain.meshMan.RequestNodeMesh(nodeIndex); } }
void BuildNeighbours_r(int nodeIndex) { QtreeNode node = nodeBuf[nodeIndex]; // set our parent's neighbour's child as our neighbour ... Action <QtreeNode, int, int, int> SetParentNeighbourChildAsNeighbour = (QtreeNode theNode, int target, int neighbour, int child) => { int parentNbr = nodeBuf[theNode.parent].neighbour[neighbour]; if (parentNbr >= 0) { theNode.neighbour[target] = nodeBuf[parentNbr].children[child]; } else { theNode.neighbour[target] = -1; } }; if (node.parent < 0) { // root node node.neighbour[(int)eNeighbour.LEFT] = -1; node.neighbour[(int)eNeighbour.TOP] = -1; node.neighbour[(int)eNeighbour.RIGHT] = -1; node.neighbour[(int)eNeighbour.BOTTOM] = -1; } else { QtreeNode parentNode = nodeBuf[node.parent]; switch ((eChildPos)node.childPos) { case eChildPos.POS_LT: { SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.LEFT, (int)eNeighbour.LEFT, (int)eChildPos.POS_RT); SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.TOP, (int)eNeighbour.TOP, (int)eChildPos.POS_LB); node.neighbour[(int)eNeighbour.RIGHT] = parentNode.children[(int)eChildPos.POS_RT]; node.neighbour[(int)eNeighbour.BOTTOM] = parentNode.children[(int)eChildPos.POS_LB]; break; } case eChildPos.POS_RT: { node.neighbour[(int)eNeighbour.LEFT] = parentNode.children[(int)eChildPos.POS_LT]; SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.TOP, (int)eNeighbour.TOP, (int)eChildPos.POS_RB); SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.RIGHT, (int)eNeighbour.RIGHT, (int)eChildPos.POS_LT); node.neighbour[(int)eNeighbour.BOTTOM] = parentNode.children[(int)eChildPos.POS_RB]; break; } case eChildPos.POS_LB: { SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.LEFT, (int)eNeighbour.LEFT, (int)eChildPos.POS_RB); node.neighbour[(int)eNeighbour.TOP] = parentNode.children[(int)eChildPos.POS_LT]; node.neighbour[(int)eNeighbour.RIGHT] = parentNode.children[(int)eChildPos.POS_RB]; SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.BOTTOM, (int)eNeighbour.BOTTOM, (int)eChildPos.POS_LT); break; } case eChildPos.POS_RB: { node.neighbour[(int)eNeighbour.LEFT] = parentNode.children[(int)eChildPos.POS_LB]; node.neighbour[(int)eNeighbour.TOP] = parentNode.children[(int)eChildPos.POS_RT]; SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.RIGHT, (int)eNeighbour.RIGHT, (int)eChildPos.POS_LB); SetParentNeighbourChildAsNeighbour(node, (int)eNeighbour.BOTTOM, (int)eNeighbour.BOTTOM, (int)eChildPos.POS_RT); break; } default: { // Invalid child position Debug.Assert(false); return; } } } if (!node.IsLeaf()) { // Go on for children for (int i = 0; i < 4; i++) { BuildNeighbours_r(node.children[i]); } } }
bool BuildNode_r(int nodeIndex, sbyte childPos, ref int nodeCnt) { QtreeNode node = nodeBuf[nodeIndex]; int gridSize = terrain.gridSize; node.childPos = childPos; // Check if this node meets leaf condition if (node.hmAreaSize <= terrain.leafGridSize) { // This is a leaf. // In fact, they should be equal ! Debug.Assert(node.hmAreaSize == terrain.leafGridSize); node.inbornLOD = 0; node.gridStep = 1; return(true); } int halfSize = node.hmAreaSize / 2; // Split node and generate 4 children for (int i = 0; i < 4; i++) { QtreeNode newNode = nodeBuf[nodeCnt]; newNode.index = nodeCnt++; newNode.parent = node.index; node.children[i] = newNode.index; if ((i & 0x01) != 0) // On right side ? { newNode.hmLeft = node.hmLeft + halfSize; } else { newNode.hmLeft = node.hmLeft; } if ((i & 0x02) != 0) // On bottom side ? { newNode.hmTop = node.hmTop + halfSize; } else { newNode.hmTop = node.hmTop; } newNode.hmAreaSize = halfSize; } // Go on for children for (int i = 0; i < 4; i++) { if (!BuildNode_r(node.children[i], (sbyte)i, ref nodeCnt)) { return(false); } } // Check if this node needs generating mesh ? if (node.hmAreaSize <= terrain.maxDrawnNodeGridSize) { // Our LOD level should be our childs +1 node.inbornLOD = (sbyte)(nodeBuf[node.children[0]].inbornLOD + 1); node.gridStep = (ushort)(1 << node.inbornLOD); Debug.Assert(terrain.leafGridSize == (node.hmAreaSize / node.gridStep)); } return(true); }