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 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 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]); } } }