private void ProcessTerrainCell(TreeSystemStructuredTrees cell, ref float colliderDistSqr) { TreeSystemStoredInstance[] treeInstances = cell.m_Instances; float x, y, z; for (int treeIndex = 0; treeIndex < treeInstances.Length; treeIndex++) { x = treeInstances[treeIndex].m_WorldPosition.x - m_CameraPosTemp.x; y = treeInstances[treeIndex].m_WorldPosition.y - m_CameraPosTemp.y; z = treeInstances[treeIndex].m_WorldPosition.z - m_CameraPosTemp.z; float distToTree = x * x + y * y + z * z; if (distToTree <= colliderDistSqr) { int hash = treeInstances[treeIndex].m_TreeHash; // Get a collider for the hash GameObject collider = GetColliderForPrototype(hash); if (collider != null) { // Update it's transform values collider.transform.position = treeInstances[treeIndex].m_WorldPosition; collider.transform.rotation = Quaternion.Euler(0, treeInstances[treeIndex].m_WorldRotation * Mathf.Rad2Deg, 0); collider.transform.localScale = treeInstances[treeIndex].m_WorldScale; // Increment the active collider count m_DataIssuedActiveColliders++; } } } }
private void ProcessTerrain(TreeSystemTerrain terrain, ref float treeDistSqr, ref float shadowDistSqr) { TreeSystemStructuredTrees[] cells = terrain.m_Cells; CullingGroup culling = terrain.m_CullingGroup; float x, y, z; // TODO: calculate based on bounds index the cells that we'll iterate like the grass system // We won't require to iterate all the cells but the neighbors of the current cell only // TODO: only get the data from the culling group API at the moment // Go bounds by bounds for (int cellIdx = 0; cellIdx < cells.Length; cellIdx++) { TreeSystemStructuredTrees cell = cells[cellIdx]; // If we don't have any tree skip this cell if (cell.m_Instances.Length <= 0) { continue; } // And also check if the bounds are visible if (culling.IsVisible(cellIdx) == false) { continue; } // Get closest point to cell Vector3 pt = cell.m_BoundsBox.ClosestPoint(m_CameraPosTemp); x = pt.x - m_CameraPosTemp.x; y = pt.y - m_CameraPosTemp.y; z = pt.z - m_CameraPosTemp.z; float distToCell = x * x + y * y + z * z; if (distToCell < treeDistSqr && GeometryUtility.TestPlanesAABB(m_PlanesTemp, cell.m_BoundsBox)) { // TODO: the same process when we are going to have terrain sub-cells ProcessTerrainCell(cell, ref treeDistSqr, ref shadowDistSqr); // If it's visible m_DataIssuedTerrainCells++; } // TODO: See for the case in which we are at the intersection of 4 cells and the cells are not visible to the frustum // BUT they have trees that 'might' cast shadows inside the frustum. Do that with a special thing that only checks the trees // for the shadow distance, and that's all // else if (m_ApplyShadowPoppingCorrection && distToCell < shadowDistSqr) { } } }
private void ProcessTerrain(TreeSystemTerrain terrain, ref float treeDistSqr) { TreeSystemStructuredTrees[] cells = terrain.m_Cells; float x, y, z; // TODO: calculate based on bounds index the cells that we'll iterate like the grass system // We won't require to iterate all the cells but the neighbors of the current cell only // Go bounds by bounds for (int cellIdx = 0; cellIdx < cells.Length; cellIdx++) { TreeSystemStructuredTrees cell = cells[cellIdx]; // If we don't have any tree skip this cell if (cell.m_Instances.Length <= 0) { continue; } // Get closest point to cell Vector3 pt = cell.m_Bounds.ClosestPoint(m_CameraPosTemp); x = pt.x - m_CameraPosTemp.x; y = pt.y - m_CameraPosTemp.y; z = pt.z - m_CameraPosTemp.z; float distToCell = x * x + y * y + z * z; if (distToCell < treeDistSqr && GeometryUtility.TestPlanesAABB(m_PlanesTemp, cell.m_Bounds)) { // TODO: the same process when we are going to have terrain sub-cells ProcessTerrainCell(cell, ref treeDistSqr); // If it's visible m_DataIssuedTerrainCells++; } } }
private void ProcessTerrain(TreeSystemTerrain terrain, ref float colliderDistSqr) { TreeSystemStructuredTrees[] cells = terrain.m_Cells; float x, y, z; for (int cellIdx = 0; cellIdx < cells.Length; cellIdx++) { TreeSystemStructuredTrees cell = cells[cellIdx]; // Get closest point to cell Vector3 pt = cell.m_BoundsBox.ClosestPoint(m_CameraPosTemp); x = pt.x - m_CameraPosTemp.x; y = pt.y - m_CameraPosTemp.y; z = pt.z - m_CameraPosTemp.z; float distToCell = x * x + y * y + z * z; if (distToCell < colliderDistSqr) { ProcessTerrainCell(cell, ref colliderDistSqr); } } }
private void IssueDrawTrees(TreeSystemPrototypeData data, TreeSystemStructuredTrees trees, int[] indices, float[] dist, int count, bool shadowsOnly) { for (int i = 0; i < MAX_LOD_COUNT; i++) { m_MtxLODTempCount[i] = 0; } TreeSystemLODData[] lodData = data.m_LODData; TreeSystemStoredInstance[] treeInstances = trees.m_Instances; TreeSystemLODInstance[] lodInstanceData = trees.m_InstanceData; int maxLod3D = data.m_MaxLod3DIndex; // Build the batched stuff. We want to batch the same LOD and draw them using instancing for (int i = 0; i < count; i++) { ProcessLOD(ref lodData, ref maxLod3D, ref lodInstanceData[indices[i]], ref dist[i]); int idx = indices[i]; int currentLODLevel = lodInstanceData[idx].m_LODLevel; // Collect that LOD level if (currentLODLevel == 0) { m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m00 = treeInstances[idx].m_PositionMtx.m00; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m01 = treeInstances[idx].m_PositionMtx.m01; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m02 = treeInstances[idx].m_PositionMtx.m02; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m03 = treeInstances[idx].m_PositionMtx.m03; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m10 = treeInstances[idx].m_PositionMtx.m10; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m11 = treeInstances[idx].m_PositionMtx.m11; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m12 = treeInstances[idx].m_PositionMtx.m12; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m13 = treeInstances[idx].m_PositionMtx.m13; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m20 = treeInstances[idx].m_PositionMtx.m20; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m21 = treeInstances[idx].m_PositionMtx.m21; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m22 = treeInstances[idx].m_PositionMtx.m22; m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]].m23 = treeInstances[idx].m_PositionMtx.m23; m_MtxLODTranzDetail_0[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_0[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 1) { m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m00 = treeInstances[idx].m_PositionMtx.m00; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m01 = treeInstances[idx].m_PositionMtx.m01; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m02 = treeInstances[idx].m_PositionMtx.m02; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m03 = treeInstances[idx].m_PositionMtx.m03; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m10 = treeInstances[idx].m_PositionMtx.m10; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m11 = treeInstances[idx].m_PositionMtx.m11; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m12 = treeInstances[idx].m_PositionMtx.m12; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m13 = treeInstances[idx].m_PositionMtx.m13; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m20 = treeInstances[idx].m_PositionMtx.m20; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m21 = treeInstances[idx].m_PositionMtx.m21; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m22 = treeInstances[idx].m_PositionMtx.m22; m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]].m23 = treeInstances[idx].m_PositionMtx.m23; m_MtxLODTranzDetail_1[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_1[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 2) { m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m00 = treeInstances[idx].m_PositionMtx.m00; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m01 = treeInstances[idx].m_PositionMtx.m01; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m02 = treeInstances[idx].m_PositionMtx.m02; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m03 = treeInstances[idx].m_PositionMtx.m03; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m10 = treeInstances[idx].m_PositionMtx.m10; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m11 = treeInstances[idx].m_PositionMtx.m11; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m12 = treeInstances[idx].m_PositionMtx.m12; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m13 = treeInstances[idx].m_PositionMtx.m13; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m20 = treeInstances[idx].m_PositionMtx.m20; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m21 = treeInstances[idx].m_PositionMtx.m21; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m22 = treeInstances[idx].m_PositionMtx.m22; m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]].m23 = treeInstances[idx].m_PositionMtx.m23; m_MtxLODTranzDetail_2[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_2[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 3) { m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m00 = treeInstances[idx].m_PositionMtx.m00; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m01 = treeInstances[idx].m_PositionMtx.m01; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m02 = treeInstances[idx].m_PositionMtx.m02; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m03 = treeInstances[idx].m_PositionMtx.m03; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m10 = treeInstances[idx].m_PositionMtx.m10; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m11 = treeInstances[idx].m_PositionMtx.m11; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m12 = treeInstances[idx].m_PositionMtx.m12; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m13 = treeInstances[idx].m_PositionMtx.m13; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m20 = treeInstances[idx].m_PositionMtx.m20; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m21 = treeInstances[idx].m_PositionMtx.m21; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m22 = treeInstances[idx].m_PositionMtx.m22; m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]].m23 = treeInstances[idx].m_PositionMtx.m23; m_MtxLODTranzDetail_3[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_3[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 4) { m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m00 = treeInstances[idx].m_PositionMtx.m00; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m01 = treeInstances[idx].m_PositionMtx.m01; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m02 = treeInstances[idx].m_PositionMtx.m02; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m03 = treeInstances[idx].m_PositionMtx.m03; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m10 = treeInstances[idx].m_PositionMtx.m10; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m11 = treeInstances[idx].m_PositionMtx.m11; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m12 = treeInstances[idx].m_PositionMtx.m12; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m13 = treeInstances[idx].m_PositionMtx.m13; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m20 = treeInstances[idx].m_PositionMtx.m20; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m21 = treeInstances[idx].m_PositionMtx.m21; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m22 = treeInstances[idx].m_PositionMtx.m22; m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]].m23 = treeInstances[idx].m_PositionMtx.m23; m_MtxLODTranzDetail_4[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_4[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } m_MtxLODTempCount[currentLODLevel]++; } // Now that the data is built, issue it for (int i = 0; i <= maxLod3D; i++) { if (i == 0) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_0, ref m_MtxLODTranzDetail_0, ref m_MtxLODTranzFull_0, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 1) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_1, ref m_MtxLODTranzDetail_1, ref m_MtxLODTranzFull_1, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 2) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_2, ref m_MtxLODTranzDetail_2, ref m_MtxLODTranzFull_2, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 3) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_3, ref m_MtxLODTranzDetail_3, ref m_MtxLODTranzFull_3, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 4) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_4, ref m_MtxLODTranzDetail_4, ref m_MtxLODTranzFull_4, ref m_MtxLODTempCount[i], ref shadowsOnly); } } }
private void ProcessTerrainCell(TreeSystemStructuredTrees cell, ref float treeDistSqr, ref float shadowDistSqr) { // Draw all trees instanced in MAX_BATCH chunks int tempIndexTree = 0; int tempIndexShadow = 0; float x, y, z; // If we are completely inside frustum we don't need to AABB test each tree bool insideFrustum = TUtils.IsCompletelyInsideFrustum(m_PlanesTemp, TUtils.BoundsCorners(ref cell.m_BoundsBox, ref m_TempCorners)); // If it is completely inside the frustum notify us if (insideFrustum) { m_DataIssuedTerrainCellsFull++; } // Tree instances TreeSystemStoredInstance[] treeInstances = cell.m_Instances; // TODO: Hm... if we take that hash it doesn't mean that it's the first visible one... int treeHash = treeInstances[0].m_TreeHash; int currentTreeHash = treeHash; int shadowHash = treeHash; int currentShadowHash = shadowHash; for (int treeIndex = 0; treeIndex < treeInstances.Length; treeIndex++) { // This is an order of magnitude faster than (treeInstances[treeIndex].m_WorldPosition - pos).sqrMagnitude // since it does not initiate with a ctor an extra vector during the computation x = treeInstances[treeIndex].m_WorldPosition.x - m_CameraPosTemp.x; y = treeInstances[treeIndex].m_WorldPosition.y - m_CameraPosTemp.y; z = treeInstances[treeIndex].m_WorldPosition.z - m_CameraPosTemp.z; float distToTree = x * x + y * y + z * z; if (insideFrustum) { // If we are completely inside the frustum we don't need to check each individual tree's bounds if (distToTree <= treeDistSqr) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndexTree >= MAX_BATCH || treeHash != currentTreeHash) { // We're sure that they will not be shadows only IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTempMesh, m_DstTempMesh, tempIndexTree, false); tempIndexTree = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTempMesh[tempIndexTree] = treeIndex; m_DstTempMesh[tempIndexTree] = Mathf.Sqrt(distToTree); tempIndexTree++; m_DataIssuedMeshTrees++; } } else { // TODO: In the future instead of 'GeometryUtility.TestPlanesAABB' have our own function that // checks if the object is within the shadow volume, that is if it casts shadow inside our frustum // Just generate our own set of planes that we'll use... // If we are not completely inside the frustum we need to check the bounds of each individual tree if (distToTree <= treeDistSqr && GeometryUtility.TestPlanesAABB(m_PlanesTemp, treeInstances[treeIndex].m_WorldBounds)) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndexTree >= MAX_BATCH || treeHash != currentTreeHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTempMesh, m_DstTempMesh, tempIndexTree, false); tempIndexTree = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTempMesh[tempIndexTree] = treeIndex; m_DstTempMesh[tempIndexTree] = Mathf.Sqrt(distToTree); tempIndexTree++; m_DataIssuedMeshTrees++; } // Same operation as above but with different matrices else if (m_Settings.m_ApplyShadowPoppingCorrection && distToTree < shadowDistSqr) { // Even if we are invisible but within the shadow sitance still cast shadows... // (not the most efficient method though, waiting for the complete one) currentShadowHash = treeInstances[treeIndex].m_TreeHash; if (tempIndexShadow >= MAX_BATCH || shadowHash != currentShadowHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[shadowHash], cell, m_IdxTempShadow, m_DstTempShadow, tempIndexShadow, true); tempIndexShadow = 0; // Update the shadow hash shadowHash = currentShadowHash; } m_IdxTempShadow[tempIndexShadow] = treeIndex; m_DstTempShadow[tempIndexShadow] = Mathf.Sqrt(distToTree); tempIndexShadow++; m_DataIssuedShadows++; } } } // End cell tree iteration if (tempIndexTree > 0) { // Get a tree hash from the first element of the array so that we know for sure that we use the correct prototype data IssueDrawTrees(m_ManagedPrototypesIndexed[treeInstances[m_IdxTempMesh[0]].m_TreeHash], cell, m_IdxTempMesh, m_DstTempMesh, tempIndexTree, false); tempIndexTree = 0; } if (tempIndexShadow > 0) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeInstances[m_IdxTempShadow[0]].m_TreeHash], cell, m_IdxTempShadow, m_DstTempShadow, tempIndexShadow, true); tempIndexShadow = 0; } }
private void ProcessTerrain(TreeSystemTerrain terrain, ref float treeDistSqr, ref float shadowDistSqr) { // Get only the visible cells around the player int cellsCount = terrain.m_CellCount; Vector3 terrainLocal = terrain.m_ManagedTerrainWorldToLocal.MultiplyPoint3x4(m_CameraPosTemp); Vector3 terrSize = terrain.m_ManagedTerrainSizes; RowCol gridPos; GetTerrainGridIndex(out gridPos, terrainLocal, cellsCount, terrSize); // Get neighbors GetNeightbors(m_TempNeighbors, gridPos.m_Row, gridPos.m_Col, terrain.m_CellsStructured, ref cellsCount); if (m_TempNeighbors.Count <= 0) { return; } CullingGroup culling = terrain.m_CullingGroup; bool useOcclusion = m_Settings.m_UseOcclusion; // float x, y, z; // TODO: calculate based on bounds index the cells that we'll iterate like the grass system // We won't require to iterate all the cells but the neighbors of the current cell only // TODO: only get the data from the culling group API at the moment // Go bounds by bounds for (int cellIdx = 0; cellIdx < m_TempNeighbors.Count; cellIdx++) { TreeSystemStructuredTrees cell = m_TempNeighbors[cellIdx]; // If we don't have any tree skip this cell if (cell.m_Instances.Length <= 0) { continue; } // And also check if the bounds are visible if (useOcclusion && culling.IsVisible(cellIdx) == false) { continue; } // Get closest point to cell /* * Vector3 pt = cell.m_BoundsBox.ClosestPoint(m_CameraPosTemp); * * x = pt.x - m_CameraPosTemp.x; * y = pt.y - m_CameraPosTemp.y; * z = pt.z - m_CameraPosTemp.z; * * float distToCell = x * x + y * y + z * z; */ float distToCell = cell.m_BoundsBox.SqrDistance(m_CameraPosTemp); if (distToCell < treeDistSqr && GeometryUtility.TestPlanesAABB(m_PlanesTemp, cell.m_BoundsBox)) { // TODO: the same process when we are going to have terrain sub-cells ProcessTerrainCell(cell, ref treeDistSqr, ref shadowDistSqr); // If it's visible m_DataIssuedTerrainCells++; } // TODO: See for the case in which we are at the intersection of 4 cells and the cells are not visible to the frustum // BUT they have trees that 'might' cast shadows inside the frustum. Do that with a special thing that only checks the trees // for the shadow distance, and that's all // else if (m_ApplyShadowPoppingCorrection && distToCell < shadowDistSqr) { } } }
private void BuildTreeTypeCellMesh(GameObject owner, TreeSystemStructuredTrees cell, List <TreeSystemStoredInstance> trees, TreeSystemPrototypeData data) { int[] originalTriangles = m_SystemQuad.triangles; RowCol pos = cell.m_Position; GameObject mesh = new GameObject(); // Mark object as static GameObjectUtility.SetStaticEditorFlags(mesh, StaticEditorFlags.OccludeeStatic | StaticEditorFlags.ReflectionProbeStatic); mesh.transform.SetParent(owner.transform); mesh.name = "MeshCell[" + pos.m_Row + "_" + pos.m_Col + "_" + data.m_TreePrototype.name + "]"; Vector3 worldScale = new Vector3(data.m_Size.x, data.m_Size.y, data.m_Size.x); // Set material MeshRenderer rend = mesh.AddComponent <MeshRenderer>(); rend.sharedMaterial = data.m_BillboardBatchMaterial; MeshFilter filter = mesh.AddComponent <MeshFilter>(); Mesh treeMesh = new Mesh(); treeMesh.name = "TreeCell[" + pos.m_Row + "_" + pos.m_Col + "_" + data.m_TreePrototype.name + "]"; List <Vector4> m_TempWorldPositions = new List <Vector4>(); List <Vector3> m_TempWorldScales = new List <Vector3>(); List <Vector3> m_TempQuadVertices = new List <Vector3>(); List <Vector4> m_TempQuadTangents = new List <Vector4>(); List <Vector3> m_TempQuadNormals = new List <Vector3>(); List <int> m_TempQuadIndices = new List <int>(); Bounds newBounds = new Bounds(); newBounds.center = cell.m_BoundsBox.center; // TODO: populate mesh data for (int treeIndex = 0; treeIndex < trees.Count; treeIndex++) { Vector3 position = trees[treeIndex].m_WorldPosition; Vector3 scale = trees[treeIndex].m_WorldScale; float rot = trees[treeIndex].m_WorldRotation; // Offset world position, by the grounding factor Vector3 instancePos = position; instancePos.y += data.m_Size.z; // Scale by the world scale too so that we don't have to do an extra multip Vector3 instanceScale = scale; instanceScale.Scale(worldScale); // Encapsulate bottom and top also newBounds.Encapsulate(instancePos); newBounds.Encapsulate(instancePos + new Vector3(0, data.m_Size.y, 0)); // Add the world and scale data for (int index = 0; index < 4; index++) { Vector4 posAndRot = instancePos; posAndRot.w = rot; m_TempWorldPositions.Add(posAndRot); m_TempWorldScales.Add(instanceScale); } // Add stanard quad data m_TempQuadVertices.AddRange(m_SystemQuad.vertices); m_TempQuadTangents.AddRange(m_SystemQuad.tangents); m_TempQuadNormals.AddRange(m_SystemQuad.normals); // Calculate triangle indixes m_TempQuadIndices.AddRange(originalTriangles); for (int triIndex = 0; triIndex < 6; triIndex++) { // Just add to the triangles the existing triangles + the new indices m_TempQuadIndices[triIndex + 6 * treeIndex] = originalTriangles[triIndex] + 4 * treeIndex; } } treeMesh.Clear(); // Set standard data treeMesh.SetVertices(m_TempQuadVertices); treeMesh.SetNormals(m_TempQuadNormals); treeMesh.SetTangents(m_TempQuadTangents); // Set the custom data treeMesh.SetUVs(1, m_TempWorldPositions); treeMesh.SetUVs(2, m_TempWorldScales); // Set triangles and do not calculate bounds treeMesh.SetTriangles(m_TempQuadIndices, 0, false); // Set the manually calculated bounds treeMesh.bounds = newBounds; treeMesh.UploadMeshData(true); // Set the mesh filter.mesh = treeMesh; }
private TreeSystemTerrain ProcessTerrain(Terrain terrain, int cellSize, GameObject cellHolder) { TreeSystemTerrain systemTerrain = new TreeSystemTerrain(); // Set system terrain data systemTerrain.m_ManagedTerrain = terrain; systemTerrain.m_ManagedTerrainBounds = terrain.GetComponent <TerrainCollider>().bounds; systemTerrain.m_CellCount = TerrainUtils.GetCellCount(terrain, cellSize); systemTerrain.m_CellSize = cellSize; int cellCount; BoxCollider[,] collidersBox; SphereCollider[,] collidersSphere; // Gridify terrain TerrainUtils.Gridify(terrain, cellSize, out cellCount, out collidersBox, out collidersSphere, cellHolder, null); // Temporary structured data TreeSystemStructuredTrees[,] str = new TreeSystemStructuredTrees[cellCount, cellCount]; List <TreeSystemStoredInstance>[,] strInst = new List <TreeSystemStoredInstance> [cellCount, cellCount]; List <TreeSystemStructuredTrees> list = new List <TreeSystemStructuredTrees>(); // Insantiate the required data for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { TreeSystemStructuredTrees s = new TreeSystemStructuredTrees(); // Set the bounds, all in world space s.m_BoundsBox = collidersBox[r, c].bounds; s.m_BoundsSphere = new TreeSystemBoundingSphere(s.m_BoundsBox.center, collidersSphere[r, c].radius); // Set it's new position s.m_Position = new RowCol(r, c); str[r, c] = s; strInst[r, c] = new List <TreeSystemStoredInstance>(); list.Add(s); } } TreeInstance[] terrainTreeInstances = terrain.terrainData.treeInstances; TreePrototype[] terrainTreeProto = terrain.terrainData.treePrototypes; Vector3 sizes = terrain.terrainData.size; for (int i = 0; i < terrainTreeInstances.Length; i++) { GameObject proto = terrainTreeProto[terrainTreeInstances[i].prototypeIndex].prefab; if (ShouldUsePrefab(proto) < 0) { continue; } // Get bounds for that mesh Bounds b = proto.transform.Find(proto.name + "_LOD0").gameObject.GetComponent <MeshFilter>().sharedMesh.bounds; // Calculate this from normalized terrain space to terrain's local space so that our row/col info are correct. // Do the same when testing for cell row/col in which the player is, transform to terrain local space Vector3 pos = TerrainUtils.TerrainToTerrainPos(terrainTreeInstances[i].position, terrain); int row = Mathf.Clamp(Mathf.FloorToInt(pos.x / sizes.x * cellCount), 0, cellCount - 1); int col = Mathf.Clamp(Mathf.FloorToInt(pos.z / sizes.z * cellCount), 0, cellCount - 1); pos = TerrainUtils.TerrainToWorldPos(terrainTreeInstances[i].position, terrain); Vector3 scale = new Vector3(terrainTreeInstances[i].widthScale, terrainTreeInstances[i].heightScale, terrainTreeInstances[i].widthScale); float rot = terrainTreeInstances[i].rotation; int hash = TUtils.GetStableHashCode(proto.name); Matrix4x4 mtx = Matrix4x4.TRS(pos, Quaternion.Euler(0, rot * Mathf.Rad2Deg, 0), scale); TreeSystemStoredInstance inst = new TreeSystemStoredInstance(); inst.m_TreeHash = hash; inst.m_PositionMtx = mtx; inst.m_WorldPosition = pos; inst.m_WorldScale = scale; inst.m_WorldRotation = rot; inst.m_WorldBounds = TUtils.LocalToWorld(ref b, ref mtx); strInst[row, col].Add(inst); } // Generate the mesh that contain all the billboards for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { if (strInst[r, c].Count <= 0) { continue; } // Sort based on the tree hash so that we don't have to do many dictionary look-ups strInst[r, c].Sort((x, y) => x.m_TreeHash.CompareTo(y.m_TreeHash)); // Set the new instances str[r, c].m_Instances = strInst[r, c].ToArray(); // Build the meshes for each cell based on tree type List <TreeSystemStoredInstance> singleType = new List <TreeSystemStoredInstance>(); int lastHash = strInst[r, c][0].m_TreeHash; foreach (TreeSystemStoredInstance inst in strInst[r, c]) { // If we have a new hash, consume all the existing instances if (inst.m_TreeHash != lastHash) { TreeSystemPrototypeData data = GetPrototypeWithHash(lastHash); BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); singleType.Clear(); // Update the hash lastHash = inst.m_TreeHash; } // Add them to a list and when the hash changes begin the next generation singleType.Add(inst); } if (singleType.Count > 0) { TreeSystemPrototypeData data = GetPrototypeWithHash(singleType[0].m_TreeHash); BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); singleType.Clear(); } } } // Set the cells that contain the trees to the system terrain systemTerrain.m_Cells = list.ToArray(); // Return it return(systemTerrain); }
private void IssueDrawTrees(TreeSystemPrototypeData data, TreeSystemStructuredTrees trees, int[] indices, float[] dist, int count, bool shadowsOnly) { for (int i = 0; i < MAX_LOD_COUNT; i++) { m_MtxLODTempCount[i] = 0; } TreeSystemLODData[] lodData = data.m_LODData; TreeSystemStoredInstance[] treeInstances = trees.m_Instances; TreeSystemLODInstance[] lodInstanceData = trees.m_InstanceData; int maxLod3D = data.m_MaxLod3DIndex; // Process LOD so that we know what to batch for (int i = 0; i < count; i++) { ProcessLOD(ref lodData, ref maxLod3D, ref lodInstanceData[indices[i]], ref dist[i]); } if (m_Settings.m_UseInstancing) { // Build the batched stuff. We want to batch the same LOD and draw them using instancing for (int i = 0; i < count; i++) { int idx = indices[i]; int currentLODLevel = lodInstanceData[idx].m_LODLevel; // Collect that LOD level if (currentLODLevel == 0) { m_MtxLODTemp_0[m_MtxLODTempCount[currentLODLevel]] = treeInstances[idx].m_PositionMtx; m_MtxLODTranzDetail_0[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_0[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 1) { m_MtxLODTemp_1[m_MtxLODTempCount[currentLODLevel]] = treeInstances[idx].m_PositionMtx; m_MtxLODTranzDetail_1[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_1[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 2) { m_MtxLODTemp_2[m_MtxLODTempCount[currentLODLevel]] = treeInstances[idx].m_PositionMtx; m_MtxLODTranzDetail_2[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_2[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 3) { m_MtxLODTemp_3[m_MtxLODTempCount[currentLODLevel]] = treeInstances[idx].m_PositionMtx; m_MtxLODTranzDetail_3[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_3[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } else if (currentLODLevel == 4) { m_MtxLODTemp_4[m_MtxLODTempCount[currentLODLevel]] = treeInstances[idx].m_PositionMtx; m_MtxLODTranzDetail_4[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODTransition; m_MtxLODTranzFull_4[m_MtxLODTempCount[currentLODLevel]] = lodInstanceData[idx].m_LODFullFade; } m_MtxLODTempCount[currentLODLevel]++; // We don't instantiate the trees that are in a transition since they should be an exception if (currentLODLevel == maxLod3D && lodInstanceData[idx].m_LODFullFade < 1) { DrawBillboardLOD(ref lodData[currentLODLevel + 1], ref lodInstanceData[idx], ref treeInstances[idx]); } } // Now that the data is built, issue it for (int i = 0; i <= maxLod3D; i++) { if (i == 0) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_0, ref m_MtxLODTranzDetail_0, ref m_MtxLODTranzFull_0, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 1) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_1, ref m_MtxLODTranzDetail_1, ref m_MtxLODTranzFull_1, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 2) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_2, ref m_MtxLODTranzDetail_2, ref m_MtxLODTranzFull_2, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 3) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_3, ref m_MtxLODTranzDetail_3, ref m_MtxLODTranzFull_3, ref m_MtxLODTempCount[i], ref shadowsOnly); } else if (i == 4) { Draw3DLODInstanced(ref lodData[i], ref m_MtxLODTemp_4, ref m_MtxLODTranzDetail_4, ref m_MtxLODTranzFull_4, ref m_MtxLODTempCount[i], ref shadowsOnly); } } } else { // Draw the processed lod for (int i = 0; i < count; i++) { int idx = indices[i]; DrawProcessedLOD(ref lodData, ref maxLod3D, ref lodInstanceData[idx], ref treeInstances[idx], ref shadowsOnly); } } }
// TODO: see from the list which trees fit in here private TreeSystemTerrain ProcessTerrain(Terrain terrain, int cellSize, GameObject cellHolder, List <GameObject> extraTrees) { TreeSystemTerrain systemTerrain = new TreeSystemTerrain(); // Set system terrain data systemTerrain.m_ManagedTerrain = terrain; systemTerrain.m_ManagedTerrainBounds = terrain.GetComponent <TerrainCollider>().bounds; systemTerrain.m_ManagedTerrainLocalToWorld = terrain.transform.localToWorldMatrix; systemTerrain.m_ManagedTerrainWorldToLocal = terrain.transform.worldToLocalMatrix; systemTerrain.m_ManagedTerrainSizes = terrain.terrainData.size; systemTerrain.m_CellCount = TerrainUtils.GetCellCount(terrain, cellSize); systemTerrain.m_CellSize = cellSize; int cellCount; BoxCollider[,] collidersBox; SphereCollider[,] collidersSphere; // Gridify terrain TerrainUtils.Gridify(terrain, cellSize, out cellCount, out collidersBox, out collidersSphere, cellHolder, null); // Temporary structured data TreeSystemStructuredTrees[,] str = new TreeSystemStructuredTrees[cellCount, cellCount]; List <TreeSystemStoredInstance>[,] strInst = new List <TreeSystemStoredInstance> [cellCount, cellCount]; List <TreeSystemStructuredTrees> list = new List <TreeSystemStructuredTrees>(); // Insantiate the required data for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { TreeSystemStructuredTrees s = new TreeSystemStructuredTrees(); // Set the bounds, all in world space s.m_BoundsBox = collidersBox[r, c].bounds; s.m_BoundsSphere = new TreeSystemBoundingSphere(s.m_BoundsBox.center, collidersSphere[r, c].radius); // Set it's new position s.m_Position = new RowCol(r, c); str[r, c] = s; strInst[r, c] = new List <TreeSystemStoredInstance>(); list.Add(s); } } // Delete cells since they might cause physics problems if (m_DeleteCellsAfterGridify) { for (int i = 0; i < cellCount; i++) { for (int j = 0; j < cellCount; j++) { DestroyImmediate(collidersBox[i, j].gameObject); } } } int treeInstancesCount = 0, treeExtraCount = 0; TreeInstance[] terrainTreeInstances = terrain.terrainData.treeInstances; TreePrototype[] terrainTreeProto = terrain.terrainData.treePrototypes; Vector3 sizes = terrain.terrainData.size; for (int i = 0; i < terrainTreeInstances.Length; i++) { GameObject proto = terrainTreeProto[terrainTreeInstances[i].prototypeIndex].prefab; if (ShouldUsePrefab(proto) < 0) { continue; } treeInstancesCount++; // Get bounds for that mesh Bounds b = proto.transform.Find(proto.name + "_LOD0").gameObject.GetComponent <MeshFilter>().sharedMesh.bounds; // Calculate this from normalized terrain space to terrain's local space so that our row/col info are correct. // Do the same when testing for cell row/col in which the player is, transform to terrain local space Vector3 pos = TerrainUtils.TerrainToTerrainPos(terrainTreeInstances[i].position, terrain); int row = Mathf.Clamp(Mathf.FloorToInt(pos.x / sizes.x * cellCount), 0, cellCount - 1); int col = Mathf.Clamp(Mathf.FloorToInt(pos.z / sizes.z * cellCount), 0, cellCount - 1); pos = TerrainUtils.TerrainToWorldPos(terrainTreeInstances[i].position, terrain); Vector3 scale = new Vector3(terrainTreeInstances[i].widthScale, terrainTreeInstances[i].heightScale, terrainTreeInstances[i].widthScale); float rot = terrainTreeInstances[i].rotation; int hash = TUtils.GetStableHashCode(proto.name); Matrix4x4 mtx = Matrix4x4.TRS(pos, Quaternion.Euler(0, rot * Mathf.Rad2Deg, 0), scale); TreeSystemStoredInstance inst = new TreeSystemStoredInstance(); inst.m_TreeHash = hash; inst.m_PositionMtx = mtx; inst.m_WorldPosition = pos; inst.m_WorldScale = scale; inst.m_WorldRotation = rot; inst.m_WorldBounds = TUtils.LocalToWorld(ref b, ref mtx); strInst[row, col].Add(inst); } List <GameObject> containedTrees = new List <GameObject>(); // Change if we're going to use something diff than 50 for max extent Bounds terrainExtendedBounds = systemTerrain.m_ManagedTerrainBounds; terrainExtendedBounds.Expand(new Vector3(0, 50, 0)); // Same as a instance with minor diferences for (int i = 0; i < extraTrees.Count; i++) { GameObject treeInstance = extraTrees[i]; // If the terrain contains the stuff if (terrainExtendedBounds.Contains(treeInstance.transform.position) == false) { continue; } treeExtraCount++; // Add the tree to the list of trees for removal containedTrees.Add(treeInstance); // Owner GameObject proto = GetPrefabOwner(treeInstance); // Get bounds for that mesh Bounds b = proto.transform.Find(proto.name + "_LOD0").gameObject.GetComponent <MeshFilter>().sharedMesh.bounds; // Calculate this from normalized terrain space to terrain's local space so that our row/col info are correct. // Do the same when testing for cell row/col in which the player is, transform to terrain local space Vector3 pos = TerrainUtils.TerrainToTerrainPos(TerrainUtils.WorldPosToTerrain(treeInstance.transform.position, terrain), terrain); int row = Mathf.Clamp(Mathf.FloorToInt(pos.x / sizes.x * cellCount), 0, cellCount - 1); int col = Mathf.Clamp(Mathf.FloorToInt(pos.z / sizes.z * cellCount), 0, cellCount - 1); pos = treeInstance.transform.position; Vector3 scale = treeInstance.transform.localScale; float rot = treeInstance.transform.rotation.eulerAngles.y * Mathf.Deg2Rad; // Set the hash int hash = TUtils.GetStableHashCode(proto.name); // Set the mtx Matrix4x4 mtx = Matrix4x4.TRS(pos, Quaternion.Euler(0, rot * Mathf.Rad2Deg, 0), scale); TreeSystemStoredInstance inst = new TreeSystemStoredInstance(); inst.m_TreeHash = hash; inst.m_PositionMtx = mtx; inst.m_WorldPosition = pos; inst.m_WorldScale = scale; inst.m_WorldRotation = rot; inst.m_WorldBounds = TUtils.LocalToWorld(ref b, ref mtx); strInst[row, col].Add(inst); } // Remove the items from the extra trees foreach (GameObject tree in containedTrees) { extraTrees.Remove(tree); } // Generate the mesh that contain all the billboards for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { if (strInst[r, c].Count <= 0) { continue; } // Sort based on the tree hash so that we don't have to do many dictionary look-ups strInst[r, c].Sort((x, y) => x.m_TreeHash.CompareTo(y.m_TreeHash)); // Set the new instances str[r, c].m_Instances = strInst[r, c].ToArray(); // Build the meshes for each cell based on tree type List <TreeSystemStoredInstance> singleType = new List <TreeSystemStoredInstance>(); int lastHash = strInst[r, c][0].m_TreeHash; foreach (TreeSystemStoredInstance inst in strInst[r, c]) { // If we have a new hash, consume all the existing instances if (inst.m_TreeHash != lastHash) { TreeSystemPrototypeData data = GetPrototypeWithHash(lastHash); if (ShouldBuildBillboardBatch(data.m_TreePrototype)) { BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); } singleType.Clear(); // Update the hash lastHash = inst.m_TreeHash; } // Add them to a list and when the hash changes begin the next generation singleType.Add(inst); } if (singleType.Count > 0) { TreeSystemPrototypeData data = GetPrototypeWithHash(singleType[0].m_TreeHash); if (ShouldBuildBillboardBatch(data.m_TreePrototype)) { BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); } singleType.Clear(); } } } // Set the cells that contain the trees to the system terrain systemTerrain.m_Cells = list.ToArray(); // Print extraction data Debug.Log("Extracted for terrain: " + terrain.name + " instance trees: " + treeInstancesCount + " extra trees: " + treeExtraCount); // Return it return(systemTerrain); }
private void ProcessTerrainCell(TreeSystemStructuredTrees cell, ref float treeDistSqr) { // Draw all trees instanced in MAX_BATCH chunks int tempIndex = 0; float x, y, z; // If we are completely inside frustum we don't need to AABB test each tree bool insideFrustum = TUtils.IsCompletelyInsideFrustum(m_PlanesTemp, TUtils.BoundsCorners(ref cell.m_Bounds, ref m_TempCorners)); // Tree instances TreeSystemStoredInstance[] treeInstances = cell.m_Instances; // TODO: Hm... if we take that hash it doesn't mean that it's the first visible one... int treeHash = treeInstances[0].m_TreeHash; int currentTreeHash = treeHash; for (int treeIndex = 0; treeIndex < treeInstances.Length; treeIndex++) { // 1.33 ms for 110k trees // This is an order of magnitude faster than (treeInstances[treeIndex].m_WorldPosition - pos).sqrMagnitude // since it does not initiate with a ctor an extra vector during the computation x = treeInstances[treeIndex].m_WorldPosition.x - m_CameraPosTemp.x; y = treeInstances[treeIndex].m_WorldPosition.y - m_CameraPosTemp.y; z = treeInstances[treeIndex].m_WorldPosition.z - m_CameraPosTemp.z; float distToTree = x * x + y * y + z * z; // 17 ms for 110k trees // float distToTree = (treeInstances[treeIndex].m_WorldPosition - pos).sqrMagnitude; if (insideFrustum) { // If we are completely inside the frustum we don't need to check each individual tree's bounds if (distToTree <= treeDistSqr) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndex >= MAX_BATCH || treeHash != currentTreeHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTemp, m_DstTemp, tempIndex); tempIndex = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTemp[tempIndex] = treeIndex; m_DstTemp[tempIndex] = Mathf.Sqrt(distToTree); tempIndex++; m_DataIssuedMeshTrees++; } } else { // If we are not completely inside the frustum we need to check the bounds of each individual tree if (distToTree <= treeDistSqr && GeometryUtility.TestPlanesAABB(m_PlanesTemp, treeInstances[treeIndex].m_WorldBounds)) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndex >= MAX_BATCH || treeHash != currentTreeHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTemp, m_DstTemp, tempIndex); tempIndex = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTemp[tempIndex] = treeIndex; m_DstTemp[tempIndex] = Mathf.Sqrt(distToTree); tempIndex++; m_DataIssuedMeshTrees++; } } } // End cell tree iteration if (tempIndex > 0) { // Get a tree hash from the first element of the array so that we know for sure that we use the correc prototype data IssueDrawTrees(m_ManagedPrototypesIndexed[treeInstances[m_IdxTemp[0]].m_TreeHash], cell, m_IdxTemp, m_DstTemp, tempIndex); tempIndex = 0; } }