public void GenerateTreePrototypeData() { List <string> prefabNames = new List <string>(); List <GameObject> prefabs = new List <GameObject>(); System.Array.ForEach(m_ItemsToExtract, (x) => { prefabNames.Add(x.m_ItemPrefab.name); prefabs.Add(x.m_ItemPrefab); }); if (TerrainUtils.TreeHashCheck(prefabNames.ToArray())) { Debug.LogError("Tree name hash collision, fix!"); return; } GameObject[] proto = prefabs.ToArray(); List <TreeSystemPrototypeData> managed = new List <TreeSystemPrototypeData>(); for (int i = 0; i < proto.Length; i++) { GameObject prefab = proto[i]; if (PrefabUtility.GetPrefabType(prefab) != PrefabType.ModelPrefab || prefab.GetComponent <LODGroup>() == null || prefab.GetComponentInChildren <BillboardRenderer>() == null) { Debug.LogError("Invalid prefab: " + prefab.name + ". Make sure that it is a SpeedTree, that it contains a 'LODGroup' and that it has a 'BillboardRenderer' component."); continue; } TreeSystemPrototypeData data = new TreeSystemPrototypeData(); data.m_TreePrototype = prefab; // Use hash here instead of the old index data.m_TreePrototypeHash = TUtils.GetStableHashCode(prefab.name); // Instantiate LOD data that is going to be populated at runtime LOD[] lods = prefab.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = new TreeSystemLODData[lods.Length]; // Generate some partial LOD data that doesn't have to be calculated at runtime data.m_LODData = lodData; for (int lod = 0; lod < lodData.Length; lod++) { TreeSystemLODData d = new TreeSystemLODData(); lodData[lod] = d; } data.m_MaxLod3DIndex = lodData.Length - 2; managed.Add(data); } m_ManagedPrototypes = managed.ToArray(); // Try and set the prototypes to our tree system TreeSystem t = FindObjectOfType <TreeSystem>(); if (t) { t.m_ManagedPrototypes = m_ManagedPrototypes; } }
/** * Checks whether there is a collision within the tree's hashes that has to be remedied * * @return False in case there is no collision and true if there is a collision */ public static bool TreeHashCheck(string[] prefabNames) { HashSet <int> set = new HashSet <int>(); foreach (string prefab in prefabNames) { if (set.Contains(TUtils.GetStableHashCode(prefab))) { return(true); } set.Add(TUtils.GetStableHashCode(prefab)); } return(false); }
/** * Checks whether there is a collision within the tree's hashes that has to be remedied * * @return False in case there is no collision and true if there is a collision */ public static bool TreeHashCheck(Terrain mainTerrain) { HashSet <int> set = new HashSet <int>(); TreePrototype[] p = mainTerrain.terrainData.treePrototypes; for (int i = 0; i < p.Length; i++) { if (set.Contains(TUtils.GetStableHashCode(p[i].prefab.name))) { return(true); } set.Add(TUtils.GetStableHashCode(p[i].prefab.name)); } return(false); }
public void GenerateTreePrototypeData() { if (TerrainUtils.TreeHashCheck(m_MainManagedTerrain)) { Log.e("Tree name hash collision, fix!"); return; } TreePrototype[] proto = m_MainManagedTerrain.terrainData.treePrototypes; List <TreeSystemPrototypeData> managed = new List <TreeSystemPrototypeData>(); for (int i = 0; i < proto.Length; i++) { if (ShouldUsePrefab(proto[i].prefab) >= 0) { GameObject prefab = proto[i].prefab; TreeSystemPrototypeData data = new TreeSystemPrototypeData(); data.m_TreePrototype = prefab; // Use hash here instead of the old index data.m_TreePrototypeHash = TUtils.GetStableHashCode(proto[i].prefab.name); if (m_UseXMLData) { TextAsset textData = AssetDatabase.LoadAssetAtPath <TextAsset>(m_TreeXMLStorePath + "/" + proto[i].prefab.name + ".xml"); if (textData != null) { data.m_TreeBillboardData = textData; } else { Debug.LogError("Could not find XML data for: " + data.m_TreePrototype.name); } } // Instantiate LOD data that is going to be populated at runtime LOD[] lods = prefab.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = new TreeSystemLODData[lods.Length]; // Generate some partial LOD data that doesn't have to be calculated at runtime data.m_LODData = lodData; for (int lod = 0; lod < lodData.Length; lod++) { TreeSystemLODData d = new TreeSystemLODData(); lodData[lod] = d; } data.m_MaxLodIndex = lodData.Length - 1; data.m_MaxLod3DIndex = lodData.Length - 2; managed.Add(data); } } m_ManagedPrototypes = managed.ToArray(); // Try and set the prototypes to our tree system TreeSystem t = FindObjectOfType <TreeSystem>(); if (t) { t.m_ManagedPrototypes = m_ManagedPrototypes; } }
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); }
public void GenerateTreePrototypeData() { if (TerrainUtils.TreeHashCheck(m_MainManagedTerrain)) { Debug.LogError("Tree name hash collision, fix!"); return; } GameObject[] proto = m_TreeToExtractPrefabs; List <TreeSystemPrototypeData> managed = new List <TreeSystemPrototypeData>(); for (int i = 0; i < proto.Length; i++) { GameObject prefab = proto[i]; if (PrefabUtility.GetPrefabType(prefab) != PrefabType.ModelPrefab || prefab.GetComponent <LODGroup>() == null || prefab.GetComponentInChildren <BillboardRenderer>() == null) { Debug.LogError("Invalid prefab: " + prefab.name + ". Make sure that it is a SpeedTree, that it contains a 'LODGroup' and that it has a 'BillboardRenderer' component."); continue; } TreeSystemPrototypeData data = new TreeSystemPrototypeData(); data.m_TreePrototype = prefab; // Use hash here instead of the old index data.m_TreePrototypeHash = TUtils.GetStableHashCode(prefab.name); if (m_UseXMLData) { TextAsset textData = AssetDatabase.LoadAssetAtPath <TextAsset>(m_TreeXMLStorePath + "/" + prefab.name + ".xml"); if (textData != null) { data.m_TreeBillboardData = textData; } else { Debug.LogError("Could not find XML data for: " + data.m_TreePrototype.name); } } // Instantiate LOD data that is going to be populated at runtime LOD[] lods = prefab.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = new TreeSystemLODData[lods.Length]; // Generate some partial LOD data that doesn't have to be calculated at runtime data.m_LODData = lodData; for (int lod = 0; lod < lodData.Length; lod++) { TreeSystemLODData d = new TreeSystemLODData(); lodData[lod] = d; } data.m_MaxLodIndex = lodData.Length - 1; data.m_MaxLod3DIndex = lodData.Length - 2; managed.Add(data); } m_ManagedPrototypes = managed.ToArray(); // Try and set the prototypes to our tree system TreeSystem t = FindObjectOfType <TreeSystem>(); if (t) { t.m_ManagedPrototypes = m_ManagedPrototypes; } }
// 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); }