private static void ExtractFromRootObject(FoliagePainterEditTime painter, GameObject root, bool disable, bool delete) { FoliageLog.Assert(root.transform.parent == null, "Must have root object!"); List <GameObject> foliageInstances = new List <GameObject>(); RecursivelyExtract(root, foliageInstances); int extracted = 0; // Process them and add them for (int i = foliageInstances.Count - 1; i >= 0; i--) { GameObject proto = foliageInstances[i]; GameObject protoPrefab = PrefabUtility.GetPrefabParent(proto) as GameObject; if (painter.HasFoliageType(protoPrefab) == false) { continue; } int hash = painter.GetFoliageTypeHash(protoPrefab); FoliageInstance instance = new FoliageInstance(); // Populate the data // Get the world data Vector3 worldPosition = proto.transform.position; Vector3 worldScale = proto.transform.localScale; Quaternion worldRotation = proto.transform.rotation; instance.m_Position = worldPosition; instance.m_Scale = worldScale; instance.m_Rotation = worldRotation; // Add the foliage instance painter.AddFoliageInstance(hash, instance, root.name); extracted++; // Auto disable if (disable) { proto.SetActive(false); } // Auto delete if (delete) { GameObject.DestroyImmediate(proto); } } FoliageLog.i("Extracted objects: " + extracted + " from: " + root.name); }
private static void WriteFoliageInstance(BinaryWriter a, FoliageInstance i, bool tree) { if (tree) { WriteBounds(a, i.m_Bounds); WriteVector3(a, i.m_Position); WriteQuaternion(a, i.m_Rotation); WriteVector3(a, i.m_Scale); WriteGuid(a, i.m_UniqueId); } else { WriteVector3(a, i.m_Position); WriteQuaternion(a, i.m_Rotation); WriteVector3(a, i.m_Scale); } }
/** * Runs basic code to add a tree at runtime. You should implement your own logic and not call this each frame * since it will cause a severe FPS drop. */ void Update() { if (Input.GetMouseButtonDown(0) == false) { return; } CritiasFoliage.FoliagePainterRuntime runtime = FindObjectOfType <CritiasFoliage.FoliagePainter>().GetRuntime; var types = runtime.GetFoliageTypes(); CritiasFoliage.FoliageTypeRuntime treeType = default(CritiasFoliage.FoliageTypeRuntime); bool foundTree = false; for (int i = 0; i < types.Count; i++) { if (types[i].m_IsGrassType == false) { treeType = types[i]; foundTree = true; } } if (foundTree == false) { Debug.LogError("Could not find a tree type! Please add it in the inspector!"); return; } RaycastHit hit; if (Physics.Raycast(transform.position, transform.forward, out hit, 100, ~0)) { if (hit.collider) { CritiasFoliage.FoliageInstance inst = new CritiasFoliage.FoliageInstance(); // All the data that we need to set inst.m_Position = hit.point; inst.m_Scale = Vector3.one; inst.m_Rotation = Quaternion.Euler(0, Random.Range(0, 360), 0); runtime.AddFoliageInstance(treeType.m_Hash, inst); } } }
/** Add foliage instance. Only works for trees at runtime. */ public void AddFoliageInstance(int typeHash, FoliageInstance instance) { int keyHash = FoliageCell.MakeHash(instance.m_Position); FoliageCellDataRuntime cell; if (m_FoliageData.ContainsKey(keyHash) == false) { cell = new FoliageCellDataRuntime(); FoliageCell fCell = new FoliageCell(instance.m_Position, false); // Set the standard data cell.m_Bounds = fCell.GetBounds(); cell.m_Position = fCell; // Empty subdivided data cell.m_FoliageDataSubdivided = new FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime> [0]; // Empty type data cell.m_TypeHashLocationsRuntime = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> > [0]; // Add the data m_FoliageData.Add(keyHash, cell); } cell = m_FoliageData[keyHash]; // Check if we have all the data int idx = System.Array.FindIndex(cell.m_TypeHashLocationsRuntime, (x) => x.Key == keyHash); if (idx < 0) { System.Array.Resize(ref cell.m_TypeHashLocationsRuntime, cell.m_TypeHashLocationsRuntime.Length + 1); idx = cell.m_TypeHashLocationsRuntime.Length - 1; // Add the type cell.m_TypeHashLocationsRuntime[idx] = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> >(typeHash, new FoliageTuple <FoliageInstance[]>(new FoliageInstance[0])); } // We have the stuff System.Array.Resize(ref cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime, cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime.Length + 1); cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime[cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime.Length - 1] = instance; }
private static FoliageInstance ReadFoliageInstance(BinaryReader a, bool tree) { FoliageInstance instance = new FoliageInstance(); if (tree) { instance.m_Bounds = ReadBounds(a); instance.m_Position = ReadVector3(a); instance.m_Rotation = ReadQuaternion(a); instance.m_Scale = ReadVector3(a); instance.m_UniqueId = ReadGuid(a); } else { instance.m_Position = ReadVector3(a); instance.m_Rotation = ReadQuaternion(a); instance.m_Scale = ReadVector3(a); } return(instance); }
/** * Runs basic code to remove a tree at runtime. You should implement your own logic and not call this each frame * since it will cause a severe FPS drop. * * NOTE: Even if the maximum distance is set to '1000' MAKE SURE that you also set the maximum distance of the collision data in the * 'Foliage Colliders' script to something larger than the default 7! Else colliders will only have 7m of colliders and therefore you * can remove only trees at 7m distance. */ void Update() { if (Input.GetMouseButtonDown(0) == false) { return; } CritiasFoliage.FoliagePainterRuntime runtime = FindObjectOfType <CritiasFoliage.FoliagePainter>().GetRuntime; // Change this to whatever layer you use for trees const string queryLayer = "Default"; RaycastHit hit; if (Physics.Raycast(transform.position, transform.forward, out hit, 1000, LayerMask.GetMask(queryLayer))) { if (hit.collider) { // Try and get the value if the collider is sticked here var data = hit.collider.gameObject.GetComponent <CritiasFoliage.FoliageColliderData>(); if (data) { CritiasFoliage.FoliageInstance instance = data.m_FoliageInstance; runtime.RemoveFoliageInstance(data.m_FoliageType, instance.m_UniqueId, instance.m_Position); } // Try and get the value if maybe it is in the owner in case we are an owned collider data = hit.collider.gameObject.GetComponentInParent <CritiasFoliage.FoliageColliderData>(); if (data) { CritiasFoliage.FoliageInstance instance = data.m_FoliageInstance; runtime.RemoveFoliageInstance(data.m_FoliageType, instance.m_UniqueId, instance.m_Position); } } } }
/** * Add a new foliage instance to the underlaying data. * * The painter should decide if our data is added to the subdivisions or if it is a tree type and must not be added to the subdivision. */ public void AddInstance(int typeHash, FoliageInstance instance, bool subdivision, string label = FoliageGlobals.LABEL_PAINTED) { int hash = FoliageCell.MakeHash(instance.m_Position); if (m_FoliageData.ContainsKey(hash) == false) { FoliageCellData data = new FoliageCellData(); data.m_Position = new FoliageCell(); data.m_Position.Set(instance.m_Position); data.m_Bounds = data.m_Position.GetBounds(); data.m_BoundsExtended = data.m_Bounds; // Add the foliage cell m_FoliageData.Add(hash, data); } FoliageCellData cellData = m_FoliageData[hash]; if (subdivision == false) { if (cellData.m_TypeHashLocationsEditor.ContainsKey(typeHash) == false) { cellData.m_TypeHashLocationsEditor.Add(typeHash, new Dictionary <string, List <FoliageInstance> >()); } var labeled = cellData.m_TypeHashLocationsEditor[typeHash]; if (labeled.ContainsKey(label) == false) { labeled.Add(label, new List <FoliageInstance>()); } // Add the foliage data labeled[label].Add(instance); // Make the extended bounds larger. Encapsulate anything that might make the bounds larger. Only applied to trees m_FoliageData[hash].m_BoundsExtended.Encapsulate(instance.m_Bounds); } else { var foliageSubdividedData = cellData.m_FoliageDataSubdivided; Vector3 localPosition = GetLocalInCell(instance.m_Position, cellData); int hashSubdivided = FoliageCell.MakeHashSubdivided(localPosition); if (foliageSubdividedData.ContainsKey(hashSubdivided) == false) { FoliageCellSubdividedData data = new FoliageCellSubdividedData(); data.m_Position = new FoliageCell(); data.m_Position.SetSubdivided(localPosition); // Get bounds in world space data.m_Bounds = data.m_Position.GetBoundsSubdivided(); data.m_Bounds.center = GetWorldInCell(data.m_Bounds.center, cellData); foliageSubdividedData.Add(hashSubdivided, data); } FoliageCellSubdividedData cellDataSubdivided = foliageSubdividedData[hashSubdivided]; if (cellDataSubdivided.m_TypeHashLocationsEditor.ContainsKey(typeHash) == false) { cellDataSubdivided.m_TypeHashLocationsEditor.Add(typeHash, new Dictionary <string, List <FoliageInstance> >()); } var labeled = cellDataSubdivided.m_TypeHashLocationsEditor[typeHash]; if (labeled.ContainsKey(label) == false) { labeled.Add(label, new List <FoliageInstance>()); } labeled[label].Add(instance); } }
/** * Load from file the runtime version of the data */ public static FoliageDataRuntime LoadFromFileRuntime(string filename) { FoliageData data = LoadFromFileEditTime(filename); // Build the runtime data from the edit time data FoliageDataRuntime runtime = new FoliageDataRuntime(); foreach (var hashedCell in data.m_FoliageData) { FoliageCellData editCell = hashedCell.Value; FoliageCellDataRuntime runtimeCell = new FoliageCellDataRuntime(); // Set the data. Note that we only need the extended bounds runtimeCell.m_Bounds = editCell.m_BoundsExtended; runtimeCell.m_Position = editCell.m_Position; // Build the tree instance data int idx = -1; runtimeCell.m_TypeHashLocationsRuntime = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> > [editCell.m_TypeHashLocationsEditor.Count]; foreach (var instances in editCell.m_TypeHashLocationsEditor) { idx++; List <FoliageInstance> allTreeInstances = new List <FoliageInstance>(); var labeledInstances = instances.Value; // Build all the data from the labeled data foreach (List <FoliageInstance> inst in labeledInstances.Values) { allTreeInstances.AddRange(inst); } // We will build the world matrix for trees for (int i = 0; i < allTreeInstances.Count; i++) { FoliageInstance inst = allTreeInstances[i]; inst.BuildWorldMatrix(); allTreeInstances[i] = inst; } // Don't forget to trim all excess instances! allTreeInstances.TrimExcess(); #if UNITY_EDITOR if (allTreeInstances.Count == 0) { Debug.Assert(false, "Count 0!"); } #endif runtimeCell.m_TypeHashLocationsRuntime[idx] = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> >(instances.Key, new FoliageTuple <FoliageInstance[]>(allTreeInstances.ToArray())); } // Build the grass instance data from the subdivided cells List <FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime> > foliageCellDataSubdivided = new List <FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime> >(editCell.m_FoliageDataSubdivided.Count); foreach (var hashedSubdividedCell in editCell.m_FoliageDataSubdivided) { FoliageCellSubdividedData editSubdividedCell = hashedSubdividedCell.Value; FoliageCellSubdividedDataRuntime runtimeSubdividedCell = new FoliageCellSubdividedDataRuntime(); // Set the data runtimeSubdividedCell.m_Bounds = editSubdividedCell.m_Bounds; runtimeSubdividedCell.m_Position = editSubdividedCell.m_Position; idx = -1; runtimeSubdividedCell.m_TypeHashLocationsRuntime = new FoliageKeyValuePair <int, FoliageTuple <Matrix4x4[][]> > [editSubdividedCell.m_TypeHashLocationsEditor.Count]; foreach (var instances in editSubdividedCell.m_TypeHashLocationsEditor) { idx++; List <FoliageInstance> allGrassInstances = new List <FoliageInstance>(); var labeledInstances = instances.Value; foreach (List <FoliageInstance> inst in labeledInstances.Values) { allGrassInstances.AddRange(inst); } #if UNITY_EDITOR if (allGrassInstances.Count == 0) { Debug.Assert(false, "Count 0!"); } #endif // Build the multi-array data int ranges = Mathf.CeilToInt(allGrassInstances.Count / (float)FoliageGlobals.RENDER_BATCH_SIZE); Matrix4x4[][] batches = new Matrix4x4[ranges][]; for (int i = 0; i < ranges; i++) { List <FoliageInstance> range = allGrassInstances.GetRange(i * FoliageGlobals.RENDER_BATCH_SIZE, i * FoliageGlobals.RENDER_BATCH_SIZE + FoliageGlobals.RENDER_BATCH_SIZE > allGrassInstances.Count ? allGrassInstances.Count - i * FoliageGlobals.RENDER_BATCH_SIZE : FoliageGlobals.RENDER_BATCH_SIZE); batches[i] = range.ConvertAll <Matrix4x4>((x) => x.GetWorldTransform()).ToArray(); } // Set the data runtimeSubdividedCell.m_TypeHashLocationsRuntime[idx] = new FoliageKeyValuePair <int, FoliageTuple <Matrix4x4[][]> >(instances.Key, new FoliageTuple <Matrix4x4[][]>(batches)); } // Add the subdivided runtime cell foliageCellDataSubdivided.Add(new FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime>(hashedSubdividedCell.Key, runtimeSubdividedCell)); } // Build the subdivided data runtimeCell.m_FoliageDataSubdivided = foliageCellDataSubdivided.ToArray(); // Add the runtime cell runtime.m_FoliageData.Add(hashedCell.Key, runtimeCell); } // Good for GC data = null; return(runtime); }
/** * Adds a new instance to the runtime data. Adding a foliage instance requires a 'typeHash'. One can be * retrieved either by 'AddFoliageType' at runtime, or by querying the type hash from the existing list * of added types at edit time, using 'GetFoliageTypes'. * * @param typeHash * Foliage type hash that will be used for adding the foliage. Must exist in order to take effect * @param instance * Foliage data. All the data this has to have set is the position, rotation and scale, the other is auto-set */ public void AddFoliageInstance(int typeHash, FoliageInstance instance) { m_Painter.AddFoliageInstanceRuntime(typeHash, instance); }
private static void ExtractDetailsFromTerrain(FoliagePainter painter, Terrain terrain, List <FoliageDetailExtracterMapping> mappings, bool disable, bool delete) { string label = FoliageGlobals.LABEL_TERRAIN_DETAILS_EXTRACTED + terrain.name; FoliagePainterEditTime edit = painter.GetEditTime; int detailMapSizeW = terrain.terrainData.detailWidth; int detailMapSizeH = terrain.terrainData.detailHeight; float patchSizeW = terrain.terrainData.size.x / detailMapSizeW; float patchSizeH = terrain.terrainData.size.z / detailMapSizeH; int extracted = 0; // Extract the types for all the mapping for (int m = 0; m < mappings.Count; m++) { int layer = mappings[m].m_DetailLayer; int mapping = mappings[m].m_FoliageTypeHash; float density = mappings[m].m_ExtractedDensity; FoliageLog.Assert(painter.HasFoliageType(mapping), "Must have foliage hash!!"); // Get the foliage type FoliageType type = painter.GetFoliageTypeByHash(mapping); DetailPrototype proto = terrain.terrainData.detailPrototypes[layer]; // If we should align to the surface bool followTerrainNormal = type.m_PaintInfo.m_SurfaceAlign; Vector2 alignPercentage = type.m_PaintInfo.m_SurfaceAlignInfluence; // Get the terrain data int[,] data = terrain.terrainData.GetDetailLayer(0, 0, detailMapSizeW, detailMapSizeH, layer); // Iterate data for (int i = 0; i < detailMapSizeH; i++) { for (int j = 0; j < detailMapSizeW; j++) { // j,i not i,j int count = data[j, i]; if (count > 0) { // Minimum 1 never 0 count = Mathf.Clamp(Mathf.CeilToInt(count * density), 1, count + 1); // Map from local space cell space to local terrain space Vector2 cellMin = new Vector2(patchSizeW * i, patchSizeH * j); Vector2 cellMax = cellMin + new Vector2(patchSizeW, patchSizeH); for (int d = 0; d < count; d++) { Vector3 randomInCell; randomInCell.x = Random.Range(cellMin.x, cellMax.x); randomInCell.z = Random.Range(cellMin.y, cellMax.y); randomInCell.y = 0; randomInCell = FoliageTerrainUtilities.TerrainLocalToTerrainNormalizedPos(randomInCell, terrain); float y = FoliageTerrainUtilities.TerrainHeight(randomInCell, terrain); // Build the rotation Quaternion rotation = Quaternion.identity; if (followTerrainNormal) { Quaternion slopeOrientation = Quaternion.LookRotation(FoliageTerrainUtilities.TerrainNormal(randomInCell, terrain)) * Quaternion.Euler(90, 0, 0); // How much we orient towards the slope rotation = Quaternion.Slerp(rotation, slopeOrientation, Random.Range(alignPercentage.x, alignPercentage.y)); } // Rotate around the Y axis rotation *= Quaternion.Euler(0, Random.Range(0, 360), 0); // Random in cell in world position randomInCell = FoliageTerrainUtilities.TerrainNormalizedToWorldPos(randomInCell, terrain); randomInCell.y = y; Vector3 scale; float x = Random.Range(proto.minWidth, proto.maxWidth); scale.x = x; scale.z = x; scale.y = Random.Range(proto.minHeight, proto.maxHeight); // Construct a foliage instance based on the foliage type data FoliageInstance instance = new FoliageInstance(); // Build the foliage data based on the type instance.m_Position = randomInCell; instance.m_Rotation = rotation; instance.m_Scale = scale; // Instantiate at a random pos in that cell edit.AddFoliageInstance(mapping, instance, label); extracted++; } } // If we should delete if (delete) { data[j, i] = 0; } } } // End detail array iteration // If we deleted set the new detail layer data if (delete) { terrain.terrainData.SetDetailLayer(0, 0, layer, data); } } // End types iteration // If we should disable the trees and foliage draw if (disable) { terrain.drawTreesAndFoliage = false; } // If we deleted anything we need to save if (delete) { UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(terrain.gameObject.scene); } FoliageLog.i("Extracted details: " + extracted + " from: " + terrain.name); }
private static void ExtractFromTerrain(FoliagePainter painterRaw, FoliagePainterEditTime painter, Terrain terrain, bool autoExtract, bool disable, bool delete) { string label = FoliageGlobals.LABEL_TERRAIN_EXTRACTED + terrain.name; // Ensure that we have all the required foliage types List <TreeInstance> terrainTreeInstances = new List <TreeInstance>(terrain.terrainData.treeInstances); TreePrototype[] terrainTreePrototypes = terrain.terrainData.treePrototypes; // Attempt to build the prefab's that we don't have if (autoExtract) { AutoExtractTypes(painter, terrain); } int extracted = 0; for (int i = terrainTreeInstances.Count - 1; i >= 0; i--) { GameObject proto = terrainTreePrototypes[terrainTreeInstances[i].prototypeIndex].prefab; if (painter.HasFoliageType(proto) == false) { continue; } int hash = painter.GetFoliageTypeHash(proto); FoliageType type = painterRaw.GetFoliageTypeByHash(hash); FoliageInstance instance = new FoliageInstance(); // Populate the data // Get the world data float YOffset = Random.Range(type.m_PaintInfo.m_YOffset.x, type.m_PaintInfo.m_YOffset.y); Vector3 worldPosition = FoliageTerrainUtilities.TerrainNormalizedToWorldPos(terrainTreeInstances[i].position, terrain) + new Vector3(0, YOffset, 0); // YOffset too Vector3 worldScale = new Vector3(terrainTreeInstances[i].widthScale, terrainTreeInstances[i].heightScale, terrainTreeInstances[i].widthScale); Vector3 worldRotation = new Vector3(0, terrainTreeInstances[i].rotation * Mathf.Rad2Deg, 0); instance.m_Position = worldPosition; instance.m_Scale = worldScale; instance.m_Rotation = Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z); // Add the foliage instance painter.AddFoliageInstance(hash, instance, label); extracted++; // Delete the instance from the terrain if we have to if (delete) { terrainTreeInstances.RemoveAt(i); } } if (disable) { terrain.drawTreesAndFoliage = false; } // If we should delete then delete the instance if (delete) { terrain.terrainData.treeInstances = terrainTreeInstances.ToArray(); UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(terrain.gameObject.scene); } FoliageLog.i("Extracted objects: " + extracted + " from: " + terrain.name); }
/** * Add a new foliage instance to the system. For the foliage instance * we only need to populate it's world position, rotation and scale * there's no need to populate the bounds matrix and GUID. * * @param typeHash * Type hash of the foliage that we want to add * @param instance * Foliage instance data containing world position, scale and rotation * @param label * Optional label in case we want to add it to a different than 'hand painted' label. Usefull when * we want to quickly remove all the foliage with a specified label from the list of foliages */ public void AddFoliageInstance(int typeHash, FoliageInstance instance, string label = FoliageGlobals.LABEL_PAINTED) { m_Painter.AddFoliageInstance(typeHash, instance, label); }