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; } }
private void UpdateLODDataDistances(TreeSystemPrototypeData data) { LOD[] lods = data.m_TreePrototype.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = data.m_LODData; for (int i = 0; i < lodData.Length; i++) { // If it's the billboard move on if (i == lods.Length - 1) { continue; } TreeSystemLODData d = lodData[i]; if (i == 0) { // If it's first 3D LOD d.m_StartDistance = 0; d.m_EndDistance = ((1.0f - lods[i].screenRelativeTransitionHeight) * m_Settings.m_MaxTreeDistance); } else if (i == lodData.Length - 2) { // If it's last 3D LOD d.m_StartDistance = ((1.0f - lods[i - 1].screenRelativeTransitionHeight) * m_Settings.m_MaxTreeDistance); d.m_EndDistance = m_Settings.m_MaxTreeDistance; } else { // If it's a LOD in between d.m_StartDistance = ((1.0f - lods[i - 1].screenRelativeTransitionHeight) * m_Settings.m_MaxTreeDistance); d.m_EndDistance = ((1.0f - lods[i].screenRelativeTransitionHeight) * m_Settings.m_MaxTreeDistance); } } }
private GameObject GetColliderForPrototype(int hash) { TreeSystemPrototypeData data = m_OwnerSystem.m_ManagedPrototypesIndexed[hash]; if (m_Cache.ContainsKey(hash) == false) { // If we don't contain the key create and add it // If we don't have a tree with a collider, like a bush or something, just add a null mapping if (data.m_TreePrototype.GetComponentInChildren <Collider>() == null) { m_Cache.Add(hash, null); return(null); } else { // Create the collider prototype and remove all it's mesh renderers and stuff GameObject colliderPrototype = Instantiate(data.m_TreePrototype, m_ColliderHolder.transform); colliderPrototype.name = "ColliderPrototype_" + data.m_TreePrototype.name; // Clear the lod group LODGroup lod = colliderPrototype.GetComponent <LODGroup>(); if (lod) { DestroyImmediate(lod); } // Clear any owned GObjects that don't have colliders for (int i = colliderPrototype.transform.childCount - 1; i >= 0; i--) { GameObject owned = colliderPrototype.transform.GetChild(i).gameObject; if (owned.GetComponent <Collider>() == null) { DestroyImmediate(owned); } } // Deactivate it colliderPrototype.SetActive(false); // Create the cache entry TreeCollisionCache cache = new TreeCollisionCache(colliderPrototype, m_ColliderHolder); // Add the collision cache to our dictionary m_Cache.Add(hash, cache); return(cache.RetrieveInstance()); } } else if (m_Cache[hash] != null) { // We contain the cache, just retrieve an object return(m_Cache[hash].RetrieveInstance()); } else { // If we contain the hash but it doesn't have anything in it, it means than it's a tree without collisions, like a bush or something return(null); } }
public void ExtractTreeLODPrototypeData() { TreeSystemPrototypeData[] proto = m_ManagedPrototypes; for (int i = 0; i < proto.Length; i++) { GameObject prefab = proto[i].m_TreePrototype; TreeSystemPrototypeData data = proto[i]; // 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; // Populate some data if (lod == lods.Length - 1) { // It must be a billboard renderer tread it specially d.m_IsBillboard = true; d.m_Mesh = Instantiate(m_SystemQuad); AssetDatabase.CreateAsset(d.m_Mesh, m_DataStorePath + "/" + prefab.name + "_Master_Billboard_Mesh_0.asset"); d.m_Materials = new Material[] { data.m_BillboardMasterMaterial }; } else { MeshRenderer rend = lods[lod].renderers[0].gameObject.GetComponent <MeshRenderer>(); MeshFilter filter = lods[lod].renderers[0].gameObject.GetComponent <MeshFilter>(); // Set the mesh we are drawing to the shared mesh d.m_Mesh = Instantiate(filter.sharedMesh); AssetDatabase.CreateAsset(d.m_Mesh, m_DataStorePath + "/" + prefab.name + "_Master_Tree_Mesh_LOD_" + lod + ".asset"); // Get the materials, create instances and set our SpeedTree master shader d.m_Materials = rend.sharedMaterials; for (int mat = 0; mat < d.m_Materials.Length; mat++) { d.m_Materials[mat] = new Material(d.m_Materials[mat]); d.m_Materials[mat].shader = m_TreeShaderMaster; AssetDatabase.CreateAsset(d.m_Materials[mat], m_DataStorePath + "/" + prefab.name + "_Master_Tree_Material_LOD" + lod + "_" + mat + ".mat"); } } } data.m_MaxLodIndex = lodData.Length - 1; data.m_MaxLod3DIndex = lodData.Length - 2; } }
void Start() { system = FindObjectOfType <TreeSystem>(); protoData = system.m_ManagedPrototypes[0]; THRESHOLD = system.GetTreeTanzitionThreshold(); procDistance = system.GetTreeDistance() - THRESHOLD; }
private void GenerateRuntimePrototypeData(TreeSystemPrototypeData data) { // All stuff that we draw must have billboard renderers, that's why it's trees LOD[] lods = data.m_TreePrototype.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = data.m_LODData; // Set material's UV at runtime SetMaterialBillProps(data, data.m_BillboardBatchMaterial); SetMaterialBillProps(data, data.m_BillboardMasterMaterial); // Get the same tree stuff as the grass for (int i = 0; i < lodData.Length; i++) { if (lods[i].renderers.Length != 1) { Debug.LogError("Renderer length != 1! Problem!"); } // Assume only one renderer TreeSystemLODData d = lodData[i]; // If it's last element if (i == lods.Length - 1) { d.m_Block = new MaterialPropertyBlock(); } else { d.m_Block = new MaterialPropertyBlock(); GameObject weirdTree = Instantiate(lods[i].renderers[0].gameObject); weirdTree.hideFlags = HideFlags.HideAndDontSave; // Get the tree renderer d.m_TreeRenderer = weirdTree.GetComponent <MeshRenderer>(); d.m_TreeRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; d.m_TreeRenderer.transform.SetParent(Camera.main.transform); d.m_TreeRenderer.transform.localPosition = new Vector3(0, 0, -2); d.m_TreeRenderer.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f); MeshFilter mf = d.m_TreeRenderer.GetComponent <MeshFilter>(); // Get an instance mesh from the weird tree Mesh m = mf.mesh; // Set the new bounds so that it's always drawn Bounds b = m.bounds; b.Expand(50.0f); m.bounds = b; mf.mesh = m; } } // Update the LOD distances UpdateLODDataDistances(data); }
public void ExtractTreeLODPrototypeData() { TreeSystemPrototypeData[] proto = m_ManagedPrototypes; for (int i = 0; i < proto.Length; i++) { GameObject prefab = proto[i].m_TreePrototype; TreeSystemPrototypeData data = proto[i]; if (prefab.GetComponent <LODGroup>() == null) { Debug.LogError("Prototype: " + prefab.name + " does not have a LOD group! Please fix and regenerate, or the system will break!"); continue; } // 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; if (lods[lod].renderers[0].gameObject.GetComponent <BillboardRenderer>()) { continue; } MeshRenderer rend = lods[lod].renderers[0].gameObject.GetComponent <MeshRenderer>(); MeshFilter filter = lods[lod].renderers[0].gameObject.GetComponent <MeshFilter>(); // Set the mesh we are drawing to the shared mesh d.m_Mesh = Instantiate(filter.sharedMesh); AssetDatabase.CreateAsset(d.m_Mesh, m_DataStorePath + "/" + prefab.name + "_Master_Tree_Mesh_LOD_" + lod + ".asset"); // Get the materials, create instances and set our SpeedTree master shader d.m_Materials = rend.sharedMaterials; for (int mat = 0; mat < d.m_Materials.Length; mat++) { d.m_Materials[mat] = new Material(d.m_Materials[mat]); d.m_Materials[mat].shader = m_TreeShaderMaster; AssetDatabase.CreateAsset(d.m_Materials[mat], m_DataStorePath + "/" + prefab.name + "_Master_Tree_Material_LOD" + lod + "_" + mat + ".mat"); } } data.m_MaxLod3DIndex = lodData.Length - 2; } }
public static void SetMaterialBillProps(TreeSystemPrototypeData data, Material m) { Vector2[] uv = data.m_VertBillboardUVs; Vector4[] UV_U = new Vector4[8]; Vector4[] UV_V = new Vector4[8]; for (int i = 0; i < 8; i++) { // 4 by 4 elements UV_U[i].x = uv[4 * i + 0].x; UV_U[i].y = uv[4 * i + 1].x; UV_U[i].z = uv[4 * i + 2].x; UV_U[i].w = uv[4 * i + 3].x; UV_V[i].x = uv[4 * i + 0].y; UV_V[i].y = uv[4 * i + 1].y; UV_V[i].z = uv[4 * i + 2].y; UV_V[i].w = uv[4 * i + 3].y; } m.SetVectorArray("_UVVert_U", UV_U); m.SetVectorArray("_UVVert_V", UV_V); uv = data.m_HorzBillboardUVs; for (int i = 0; i < 1; i++) { // 4 by 4 elements UV_U[i].x = uv[4 * i + 0].x; UV_U[i].y = uv[4 * i + 1].x; UV_U[i].z = uv[4 * i + 2].x; UV_U[i].w = uv[4 * i + 3].x; UV_V[i].x = uv[4 * i + 0].y; UV_V[i].y = uv[4 * i + 1].y; UV_V[i].z = uv[4 * i + 2].y; UV_V[i].w = uv[4 * i + 3].y; } m.SetVector("_UVHorz_U", UV_U[0]); m.SetVector("_UVHorz_V", UV_V[0]); }
public void ExtractXMLTreePrototypeData() { TreeSystemPrototypeData[] data = m_ManagedPrototypes; for (int i = 0; i < data.Length; i++) { TreeSystemPrototypeData d = data[i]; if (d.m_TreePrototype == null) { Debug.LogError("Nothing set for data at index: " + i); continue; } // Get the protorype's billboard asset BillboardRenderer bill = d.m_TreePrototype.GetComponentInChildren <BillboardRenderer>(); if (bill == null) { Debug.LogError("Prototype: " + d.m_TreePrototype.name + " does not contain a billboard renderer! Items without billboard renderers are not supported at the moment!"); continue; } BillboardAsset billAsset = bill.billboard; // Set sizes d.m_Size = new Vector3(billAsset.width, billAsset.height, billAsset.bottom); Vector4[] uvs = billAsset.GetImageTexCoords(); // Ussualy 16 d.m_VertBillboardUVs = new Vector2[uvs.Length * 4]; // Just set the first UV's just to have something d.m_HorzBillboardUVs = new Vector2[4]; for (int uvIdx = 0, billUv = 0; uvIdx < uvs.Length; uvIdx++, billUv += 4) { Vector4 extract = uvs[uvIdx]; if (uvIdx == 0) { d.m_HorzBillboardUVs[0] = new Vector2(extract.x, extract.y); d.m_HorzBillboardUVs[1] = new Vector2(extract.x, extract.y) + new Vector2(0, Mathf.Abs(extract.w)); d.m_HorzBillboardUVs[2] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, Mathf.Abs(extract.w)); d.m_HorzBillboardUVs[3] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, 0); } // We are rotated if (extract.w < 0) { d.m_VertBillboardUVs[billUv + 0] = new Vector2(extract.x, extract.y); d.m_VertBillboardUVs[billUv + 1] = new Vector2(extract.x, extract.y) + new Vector2(0, Mathf.Abs(extract.w)); d.m_VertBillboardUVs[billUv + 2] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, Mathf.Abs(extract.w)); d.m_VertBillboardUVs[billUv + 3] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, 0); } else { d.m_VertBillboardUVs[billUv + 0] = new Vector2(extract.x, extract.y); d.m_VertBillboardUVs[billUv + 1] = new Vector2(extract.x, extract.y) + new Vector2(extract.z, 0); d.m_VertBillboardUVs[billUv + 2] = new Vector2(extract.x, extract.y) + new Vector2(extract.z, extract.w); d.m_VertBillboardUVs[billUv + 3] = new Vector2(extract.x, extract.y) + new Vector2(0, extract.w); } } Vector4 size = d.m_Size; size.w = 1; // Create the material with the texture references Material billboardMaterialBatch = new Material(m_BillboardShaderBatch); billboardMaterialBatch.SetTexture("_MainTex", bill.billboard.material.GetTexture("_MainTex")); billboardMaterialBatch.SetTexture("_BumpMap", bill.billboard.material.GetTexture("_BumpMap")); billboardMaterialBatch.SetVector("_Size", size); // Replace, don't delete // AssetDatabase.DeleteAsset(m_DataStorePath + "/" + d.m_TreePrototype.name + "_Mat.mat"); AssetDatabase.CreateAsset(billboardMaterialBatch, m_DataStorePath + "/" + d.m_TreePrototype.name + "_Bill_Batch_Mat.mat"); // Set the material d.m_BillboardBatchMaterial = billboardMaterialBatch; // Set billboard data TreeSystem.SetMaterialBillProps(d, d.m_BillboardBatchMaterial); } AssetDatabase.Refresh(); }
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 GenerateRuntimePrototypeData(TreeSystemPrototypeData data) { // All stuff that we draw must have billboard renderers, that's why it's trees LOD[] lods = data.m_TreePrototype.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = data.m_LODData; // Set material's UV at runtime SetMaterialBillProps(data, data.m_BillboardBatchMaterial); // Instantiate the last 3D lod GameObject weirdTree = Instantiate(lods[lods.Length - 2].renderers[0].gameObject); // weirdTree.hideFlags = HideFlags.HideAndDontSave; // Stick it to the camera weirdTree.transform.SetParent(Camera.main.transform); weirdTree.transform.localPosition = new Vector3(0, 0, -2); weirdTree.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f); // Expand the bounds MeshRenderer mr = weirdTree.GetComponent <MeshRenderer>(); MeshFilter mf = weirdTree.GetComponent <MeshFilter>(); // Get an instance mesh from the weird tree Mesh m = mf.mesh; // Set the new bounds so that it's always drawn Bounds b = m.bounds; b.Expand(50.0f); m.bounds = b; mf.mesh = m; // Don't cast shadows mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; // Get the same tree stuff as the grass for (int i = 0; i < lodData.Length; i++) { if (lods[i].renderers.Length != 1) { Debug.LogError("Renderer length != 1! Problem!"); } // Assume only one renderer TreeSystemLODData d = lodData[i]; // Add a new MBP and set the renderer from which we are going to copy the wind data from d.m_Block = new MaterialPropertyBlock(); d.m_TreeRenderer = mr; // Set the full array here because based on the docs, i'm not sure when is this going to be refreshed // Quote: // "The array length can't be changed once it has been added to the block. If you subsequently try // to set a longer array into the same property, the length will be capped to the original length and // the extra items you tried to assign will be ignored." // So, we're making sure that we have those MAX_BATCH count of floats d.m_Block.SetFloatArray(m_ShaderIDFadeLODDetail, new float[MAX_BATCH]); d.m_Block.SetFloatArray(m_ShaderIDFadeLODFull, new float[MAX_BATCH]); } // Update the LOD distances UpdateLODDataDistances(data); }
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 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); }
public void ExtractXMLTreePrototypeData() { TreeSystemPrototypeData[] data = m_ManagedPrototypes; for (int i = 0; i < data.Length; i++) { TreeSystemPrototypeData d = data[i]; if (d.m_TreePrototype == null) { Log.e("Nothing set for data at index: " + i); continue; } // Get the protorype's billboard asset BillboardRenderer bill = d.m_TreePrototype.GetComponentInChildren <BillboardRenderer>(); BillboardAsset billAsset = bill.billboard; // Set sizes d.m_Size = new Vector3(billAsset.width, billAsset.height, billAsset.bottom); // Parse the XML if (!d.m_TreeBillboardData && m_UseXMLData) { Debug.LogError("We are using XML data and we don't have any custom XML data! Switch 'UseXMLData' off!"); continue; } if (m_UseXMLData) { XmlDocument doc = new XmlDocument(); doc.LoadXml(d.m_TreeBillboardData.text); if (doc["SpeedTreeRaw"]["Billboards"]["Vertical"] == null || doc["SpeedTreeRaw"]["Billboards"]["Horizontal"] == null) { Debug.Log("Missing tree XML data for: " + d.m_TreePrototype.name); } else { // Should be multiple of 4 d.m_VertBillboardUVs = ExtractBillboards(doc["SpeedTreeRaw"]["Billboards"]["Vertical"], true).ToArray(); d.m_HorzBillboardUVs = ExtractBillboards(doc["SpeedTreeRaw"]["Billboards"]["Horizontal"], false).ToArray(); } } else { // TODO: support for non-XML Vector4[] uvs = billAsset.GetImageTexCoords(); // Ussualy 16 d.m_VertBillboardUVs = new Vector2[uvs.Length * 4]; // Just set the first UV's just to have something d.m_HorzBillboardUVs = new Vector2[4]; for (int uvIdx = 0, billUv = 0; uvIdx < uvs.Length; uvIdx++, billUv += 4) { Vector4 extract = uvs[uvIdx]; if (uvIdx == 0) { d.m_HorzBillboardUVs[0] = new Vector2(extract.x, extract.y); d.m_HorzBillboardUVs[1] = new Vector2(extract.x, extract.y) + new Vector2(0, Mathf.Abs(extract.w)); d.m_HorzBillboardUVs[2] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, Mathf.Abs(extract.w)); d.m_HorzBillboardUVs[3] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, 0); } // We are rotated if (extract.w < 0) { d.m_VertBillboardUVs[billUv + 0] = new Vector2(extract.x, extract.y); d.m_VertBillboardUVs[billUv + 1] = new Vector2(extract.x, extract.y) + new Vector2(0, Mathf.Abs(extract.w)); d.m_VertBillboardUVs[billUv + 2] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, Mathf.Abs(extract.w)); d.m_VertBillboardUVs[billUv + 3] = new Vector2(extract.x, extract.y) + new Vector2(-extract.z, 0); } else { d.m_VertBillboardUVs[billUv + 0] = new Vector2(extract.x, extract.y); d.m_VertBillboardUVs[billUv + 1] = new Vector2(extract.x, extract.y) + new Vector2(extract.z, 0); d.m_VertBillboardUVs[billUv + 2] = new Vector2(extract.x, extract.y) + new Vector2(extract.z, extract.w); d.m_VertBillboardUVs[billUv + 3] = new Vector2(extract.x, extract.y) + new Vector2(0, extract.w); } } } Vector4 size = d.m_Size; size.w = 1; // Create the material with the texture references Material billboardMaterialBatch = new Material(m_BillboardShaderBatch); billboardMaterialBatch.SetTexture("_MainTex", bill.billboard.material.GetTexture("_MainTex")); billboardMaterialBatch.SetTexture("_BumpMap", bill.billboard.material.GetTexture("_BumpMap")); billboardMaterialBatch.SetVector("_Size", size); Material billboardMaterialMaster = new Material(m_BillboardShaderMaster); billboardMaterialMaster.SetTexture("_MainTex", bill.billboard.material.GetTexture("_MainTex")); billboardMaterialMaster.SetTexture("_BumpMap", bill.billboard.material.GetTexture("_BumpMap")); billboardMaterialMaster.SetVector("_Size", size); // Replace, don't delete // AssetDatabase.DeleteAsset(m_DataStorePath + "/" + d.m_TreePrototype.name + "_Mat.mat"); AssetDatabase.CreateAsset(billboardMaterialBatch, m_DataStorePath + "/" + d.m_TreePrototype.name + "_Bill_Batch_Mat.mat"); AssetDatabase.CreateAsset(billboardMaterialMaster, m_DataStorePath + "/" + d.m_TreePrototype.name + "_Bill_Master_Mat.mat"); // Set the material d.m_BillboardBatchMaterial = billboardMaterialBatch; d.m_BillboardMasterMaterial = billboardMaterialMaster; // Set billboard data TreeSystem.SetMaterialBillProps(d, d.m_BillboardBatchMaterial); TreeSystem.SetMaterialBillProps(d, d.m_BillboardMasterMaterial); } AssetDatabase.Refresh(); }
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); }
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; } }