/** * Removes all the foliage instances of the type from the data. */ public void RemoveType(int typeHash) { bool anyRemoved = false; int cellsPurged = 0; foreach (FoliageCellData data in m_FoliageData.Values) { if (data.m_TypeHashLocationsEditor.Remove(typeHash)) { anyRemoved = true; cellsPurged++; } foreach (FoliageCellSubdividedData subdivData in data.m_FoliageDataSubdivided.Values) { if (subdivData.m_TypeHashLocationsEditor.Remove(typeHash)) { anyRemoved = true; cellsPurged++; } } } if (anyRemoved) { FoliageLog.i("Removed type instance from: " + cellsPurged + " cells."); RemoveEmptyData(); // Recalc the bounds RecalculateBoundsAfterRemove(); } }
/** * Removes any empty data from the list. Called before disk writing so that we're sure that * we are not going to test for any empty cells at runtime. */ public void RemoveEmptyData() { HashSet <int> emptyCells = null; HashSet <int> emptyCellsSubdiv = null; // Iterate cells foreach (var cell in m_FoliageData) { RemoveEmptyTypeDataCell(cell.Value); if (IsCellEmpty(cell.Value)) { if (emptyCells == null) { emptyCells = new HashSet <int>(); } emptyCells.Add(cell.Key); } if (emptyCellsSubdiv != null) { emptyCellsSubdiv.Clear(); } // Iterate sub-cells too foreach (var cellSubdiv in cell.Value.m_FoliageDataSubdivided) { RemoveEmptyTypeDataCellSubdivided(cellSubdiv.Value); if (IsSubCellEmpty(cellSubdiv.Value)) { if (emptyCellsSubdiv == null) { emptyCellsSubdiv = new HashSet <int>(); } emptyCellsSubdiv.Add(cellSubdiv.Key); } } if (emptyCellsSubdiv != null && emptyCellsSubdiv.Count > 0) { foreach (int key in emptyCellsSubdiv) { cell.Value.m_FoliageDataSubdivided.Remove(key); } } } // Remove the empty cells if (emptyCells != null) { foreach (int key in emptyCells) { m_FoliageData.Remove(key); } } FoliageLog.i("Removed: " + (emptyCells != null ? emptyCells.Count : 0) + " empty cells and: " + (emptyCellsSubdiv != null ? emptyCellsSubdiv.Count : 0) + " empty subdivided cells."); }
private void Awake() { // Extract the planes methods MethodInfo info = typeof(GeometryUtility).GetMethod("Internal_ExtractPlanes", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Plane[]), typeof(Matrix4x4) }, null); ExtractPlanes = Delegate.CreateDelegate(typeof(Action <Plane[], Matrix4x4>), info) as Action <Plane[], Matrix4x4>; // Matrices init { m_MtxLODTemp = new Matrix4x4[FoliageGlobals.RENDER_MAX_LOD_COUNT][]; m_MtxLODTempShadow = new Matrix4x4[FoliageGlobals.RENDER_BATCH_SIZE][]; // Fill the matrices with the identity for (int i = 0; i < FoliageGlobals.RENDER_MAX_LOD_COUNT; i++) { m_MtxLODTemp[i] = new Matrix4x4[FoliageGlobals.RENDER_BATCH_SIZE]; m_MtxLODTempShadow[i] = new Matrix4x4[FoliageGlobals.RENDER_BATCH_SIZE]; for (int mtx = 0; mtx < FoliageGlobals.RENDER_BATCH_SIZE; mtx++) { m_MtxLODTemp[i][mtx] = Matrix4x4.identity; m_MtxLODTempShadow[i][mtx] = Matrix4x4.identity; } } } // Set the system data if (!m_Settings.m_WindTransform) { FoliageLog.i("Wind transform not found, defaulting to 'Camera.main.transform'!"); m_Settings.m_WindTransform = Camera.main.transform; } if (!m_Settings.m_UsedCameraCulling) { FoliageLog.i("Culling camera not found, defaulting to 'Camera.main'!"); m_Settings.m_UsedCameraCulling = Camera.main; } // Get shader data // Used for maximum distance m_ShaderIDCritiasFoliageDistance = Shader.PropertyToID("CRITIAS_MaxFoliageTypeDistance"); m_ShaderIDCritiasFoliageDistanceSqr = Shader.PropertyToID("CRITIAS_MaxFoliageTypeDistanceSqr"); // Used for LOD distance m_ShaderIDCritiasFoliageLOD = Shader.PropertyToID("CRITIAS_FoliageMaxDistanceLOD"); m_ShaderIDCritiasFoliageLODSqr = Shader.PropertyToID("CRITIAS_FoliageMaxDistanceLODSqr"); // Used for the indirect buffer m_ShaderIDCritiasInstanceBuffer = Shader.PropertyToID("CRITIAS_InstancePositionBuffer"); // Used for the positions m_ShaderIDCritiasBendPosition = Shader.PropertyToID("CRITIAS_Bend_Position"); m_ShaderIDCritiasBendDistance = Shader.PropertyToID("CRITIAS_Bend_Distance"); m_ShaderIDCritiasBendScale = Shader.PropertyToID("CRITIAS_Bend_Scale"); }
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); }
/** * Update the foliage types. */ public void UpdateFoliageTypes(List <FoliageType> foliageTypes) { m_MaxDistanceGrass = 0; m_MaxDistanceTree = 0; m_MaxDistanceAll = 0; m_FoliageTypes.Clear(); foreach (FoliageType type in foliageTypes) { if (type.IsGrassType == true && type.m_RenderInfo.m_MaxDistance > m_MaxDistanceGrass) { m_MaxDistanceGrass = type.m_RenderInfo.m_MaxDistance; } else if (type.IsGrassType == false && type.m_RenderInfo.m_MaxDistance > m_MaxDistanceTree) { m_MaxDistanceTree = type.m_RenderInfo.m_MaxDistance; } m_FoliageTypes.Add(type.m_Hash, type); } m_FoliageTypesArray = foliageTypes.ToArray(); // Update the maximum grass distance m_MaxDistanceGrass = Mathf.Clamp(m_MaxDistanceGrass, 0, FoliageGlobals.FOLIAGE_MAX_GRASS_DISTANCE); m_MaxDistanceGrassSqr = m_MaxDistanceGrass * m_MaxDistanceGrass; m_MaxDistanceTree = Mathf.Clamp(m_MaxDistanceTree, 0, FoliageGlobals.FOLIAGE_MAX_TREE_DISTANCE); m_MaxDistanceTreeSqr = m_MaxDistanceTree * m_MaxDistanceTree; m_MaxDistanceAll = Mathf.Max(m_MaxDistanceGrass, m_MaxDistanceTree); m_MaxDistanceAllSqr = m_MaxDistanceAll * m_MaxDistanceAll; // Cell recursion count based on the maximum value of all values m_CellNeighborCount = Mathf.CeilToInt(m_MaxDistanceAll / FoliageGlobals.CELL_SIZE); FoliageLog.i("Neighbor cell count: " + m_CellNeighborCount); }
void Update() { #if UNITY_EDITOR FoliageLog.Assert(m_FoliageData != null); #endif // Camera used for culling m_CurrentFrameCameraCull = m_Settings.m_UsedCameraCulling; // Camera used for drawing m_CurrentFrameCameraDraw = m_Settings.m_UsedCameraDrawing; m_CurrentFrameLayer = LayerMask.NameToLayer(m_Settings.m_UsedLayer); // Extract the planes ExtractPlanes(m_CameraPlanes, m_CurrentFrameCameraCull.projectionMatrix * m_CurrentFrameCameraCull.worldToCameraMatrix); // Set the position m_CurrentFrameCameraPosition = m_CurrentFrameCameraCull.transform.position; // Set the bend position m_CurrentFrameBendPosition = m_Settings.m_BendTransform != null ? m_Settings.m_BendTransform.position : m_CurrentFrameCameraPosition; // Current cell position currentCell.Set(m_CurrentFrameCameraPosition); m_CurrentFrameAllowIndirect = m_Settings.m_AllowDrawInstancedIndirect; m_DrawStats.Reset(); // Copy the wind for SpeedTree types for (int i = 0; i < m_FoliageTypesArray.Length; i++) { if (m_FoliageTypesArray[i].IsSpeedTreeType) { m_FoliageTypesArray[i].CopyBlock(); } } bool applyShadowCorrection = m_Settings.m_ApplyShadowPoppingCorrection; float shadowCorrectionDistanceSqr = m_Settings.m_ShadowPoppingCorrection * m_Settings.m_ShadowPoppingCorrection; // We iterate only as many cells as we need FoliageCell.IterateNeighboring(currentCell, m_CellNeighborCount, (int hash) => { FoliageCellDataRuntime data; if (m_FoliageData.m_FoliageData.TryGetValue(hash, out data)) { // If it is within distance and in the frustum float distanceSqr = data.m_Bounds.SqrDistance(m_CurrentFrameCameraPosition); // Check for the maximum distance if (distanceSqr <= m_MaxDistanceAllSqr && GeometryUtility.TestPlanesAABB(m_CameraPlanes, data.m_Bounds)) { // Process the big cells if we are withing the tree distance if (distanceSqr <= m_MaxDistanceTreeSqr) { ProcessCellTree(data, distanceSqr, applyShadowCorrection, shadowCorrectionDistanceSqr, false); } // Process subdivided cells only if we have instancing enabled and only if it is within the distance proximity for grass if (distanceSqr <= m_MaxDistanceGrassSqr && m_Settings.m_DrawInstanced) { ProcessCellGrass(data); } m_DrawStats.m_ProcessedCells++; } else if (distanceSqr <= shadowCorrectionDistanceSqr) { ProcessCellTree(data, distanceSqr, applyShadowCorrection, shadowCorrectionDistanceSqr, true); } } }); #if UNITY_EDITOR if (Time.frameCount % 300 == 0) { FoliageLog.i(string.Format("Proc cells:{0} Proc subdiv cells: {1} Proc tree instances: {2} Proc draw calls: {3}", m_DrawStats.m_ProcessedCells, m_DrawStats.m_ProcessedCellsSubdiv, m_DrawStats.m_ProcessedInstances, m_DrawStats.m_ProcessedDrawCalls)); } #endif }
/** * Rebuild the hierarchy that contains the type. Must be used when we make a type that was * a tree into a grass (therefore having to use the subdivided cells) or vice-versa. * * Will preserve labeling data. */ public void RebuildType(int typeHash, bool subdivided) { Dictionary <string, List <FoliageInstance> > instances = new Dictionary <string, List <FoliageInstance> >(); foreach (FoliageCellData data in m_FoliageData.Values) { if (data.m_TypeHashLocationsEditor.ContainsKey(typeHash)) { var labeledData = data.m_TypeHashLocationsEditor[typeHash]; // Get all the data from the cells foreach (var labeled in labeledData) { string label = labeled.Key; if (instances.ContainsKey(label) == false) { instances.Add(label, new List <FoliageInstance>()); } instances[label].AddRange(labeled.Value); } } foreach (FoliageCellSubdividedData subdivData in data.m_FoliageDataSubdivided.Values) { if (subdivData.m_TypeHashLocationsEditor.ContainsKey(typeHash)) { var labeledData = subdivData.m_TypeHashLocationsEditor[typeHash]; // Get all the data from the subdivided cells foreach (var labeled in labeledData) { string label = labeled.Key; if (instances.ContainsKey(label) == false) { instances.Add(label, new List <FoliageInstance>()); } instances[label].AddRange(labeled.Value); } } } } if (instances.Count > 0) { // Clear the old added data RemoveType(typeHash); int count = 0; foreach (var labeledInstances in instances) { string label = labeledInstances.Key; List <FoliageInstance> inst = labeledInstances.Value; count += inst.Count; // Add back the instances AddInstances(typeHash, inst, subdivided, label); } FoliageLog.i("Relocated: " + count + " instanced."); } }
public static void ConfigurePrefab(GameObject foliage, out FoliageTypeBuilder outBuilder, out bool outAnyError) { PrefabType type = PrefabUtility.GetPrefabType(foliage); if (type != PrefabType.ModelPrefab && type != PrefabType.Prefab) { FoliageLog.e("The prefab type of: " + foliage.name + " is: '" + type + "'! Must be a 'ModelPrefab' or a 'Prefab'!"); outAnyError = true; } string path = AssetDatabase.GetAssetPath(foliage); FoliageLog.i("Path of added foliage type: " + path); FoliageTypeBuilder builder = new FoliageTypeBuilder(); // Build all it's required data builder.m_PaintInfo = new FoliageTypePaintInfo(); builder.m_RenderInfo = new FoliageTypeRenderInfo(); bool anyWeirdGrassError = false; // Attempt to auto-configure the foliage for the easiest possible user interaction if (path.EndsWith(".spm")) { // We have a SpeedTree LODGroup lodGroup = foliage.GetComponent <LODGroup>(); if (lodGroup != null) { if (lodGroup.lodCount == 1) { FoliageLog.i("Detected a SpeedTree grass, since it has 1 LOD!"); builder.m_Type = EFoliageType.SPEEDTREE_GRASS; builder.m_EnableCollision = false; if (foliage.GetComponentInChildren <MeshRenderer>() == null) { anyWeirdGrassError = true; FoliageLog.e("SpeedTree grass without any MeshRenderer detected!"); } } else { FoliageLog.i("Detected a SpeedTree tree, since it has 1+ LODS!"); builder.m_Type = EFoliageType.SPEEDTREE_TREE; builder.m_EnableCollision = foliage.GetComponentInChildren <Collider>() != null ? true : false; if (foliage.GetComponentInChildren <BillboardRenderer>() != null) { builder.m_Type = EFoliageType.SPEEDTREE_TREE_BILLBOARD; } if (foliage.GetComponentInChildren <MeshRenderer>() == null) { anyWeirdGrassError = true; FoliageLog.e("SpeedTree tree without any MeshRenderer detected!"); } } // Set the bounds builder.m_Bounds = lodGroup.GetLODs()[0].renderers[0].GetComponent <MeshFilter>().sharedMesh.bounds; } else { FoliageLog.e("Weird, we have a SpeedTree without a lod group. Anything wrong here? Please fix."); anyWeirdGrassError = true; builder.m_Bounds = foliage.GetComponentInChildren <MeshFilter>().sharedMesh.bounds; } } else { LODGroup lodGroup = foliage.GetComponent <LODGroup>(); if (lodGroup != null && lodGroup.lodCount > 1) { FoliageLog.i("Detected an object tree!"); builder.m_Type = EFoliageType.OTHER_TREE; builder.m_EnableCollision = foliage.GetComponentInChildren <Collider>() != null ? true : false; if (foliage.GetComponentInChildren <MeshRenderer>() == null) { anyWeirdGrassError = true; FoliageLog.e("Object tree without any MeshRenderer detected!"); } // Set the bounds builder.m_Bounds = lodGroup.GetLODs()[0].renderers[0].GetComponent <MeshFilter>().sharedMesh.bounds; } else { FoliageLog.i("Detected an object grass!"); builder.m_Type = EFoliageType.OTHER_GRASS; builder.m_EnableCollision = false; if (foliage.GetComponentInChildren <MeshRenderer>() == null) { anyWeirdGrassError = true; FoliageLog.e("Object grass without any LOD group or MeshRenderer detected!"); } // Set the bounds builder.m_Bounds = foliage.GetComponentInChildren <MeshFilter>().sharedMesh.bounds; } } LODGroup group = foliage.GetComponent <LODGroup>(); if (group != null && group.lodCount > 0) { LOD[] lods = group.GetLODs(); for (int i = 0; i < lods.Length; i++) { Renderer[] rends = lods[i].renderers; if (rends == null || rends.Length != 1) { anyWeirdGrassError = true; FoliageLog.e("Detected object with a lod without any renderers on it or with more than one renderer attached to it!"); } } } if (anyWeirdGrassError) { EditorUtility.DisplayDialog("Error", "Found error for foliage: " + foliage.name + "! Could not add to system! Check the log for more info!", "Ok"); FoliageLog.e("Found error for foliage: " + foliage.name + "! Could not add to system!"); } else { // Set the prefab builder.m_Prefab = foliage; builder.m_PaintEnabled = true; switch (builder.m_Type) { case EFoliageType.OTHER_GRASS: case EFoliageType.SPEEDTREE_GRASS: builder.m_RenderInfo.m_CastShadow = false; builder.m_RenderInfo.m_MaxDistance = 30; break; case EFoliageType.OTHER_TREE: case EFoliageType.SPEEDTREE_TREE: case EFoliageType.SPEEDTREE_TREE_BILLBOARD: builder.m_RenderInfo.m_CastShadow = true; builder.m_RenderInfo.m_MaxDistance = 100; break; } } switch (builder.m_Type) { case EFoliageType.OTHER_GRASS: case EFoliageType.SPEEDTREE_GRASS: builder.m_PaintInfo.m_SurfaceAlign = true; break; case EFoliageType.OTHER_TREE: case EFoliageType.SPEEDTREE_TREE_BILLBOARD: case EFoliageType.SPEEDTREE_TREE: builder.m_PaintInfo.m_SurfaceAlign = false; break; } if (anyWeirdGrassError == false) { if (builder.m_Type == EFoliageType.SPEEDTREE_GRASS || builder.m_Type == EFoliageType.SPEEDTREE_TREE || builder.m_Type == EFoliageType.SPEEDTREE_TREE_BILLBOARD) { // Get a hue builder.m_RenderInfo.m_Hue = foliage.GetComponentInChildren <MeshRenderer>().sharedMaterial.GetColor("_HueVariation"); builder.m_RenderInfo.m_Color = foliage.GetComponentInChildren <MeshRenderer>().sharedMaterial.GetColor("_Color"); if (builder.m_RenderInfo.m_Hue == new Color(0, 0, 0, 0)) { builder.m_RenderInfo.m_Hue = Color.white; } if (builder.m_RenderInfo.m_Color == new Color(0, 0, 0, 0)) { builder.m_RenderInfo.m_Color = Color.white; } } else { builder.m_RenderInfo.m_Hue = Color.white; builder.m_RenderInfo.m_Color = Color.white; } } outAnyError = anyWeirdGrassError; outBuilder = builder; }
private void Update() { if (!m_Settings.m_WatchedTransform) { return; } m_CameraPosTemp = m_Settings.m_WatchedTransform.position; float x = m_CameraPosTemp.x - m_LastPosition.x; float y = m_CameraPosTemp.y - m_LastPosition.y; float z = m_CameraPosTemp.z - m_LastPosition.z; float distWalked = x * x + y * y + z * z; // If we didn't walked enough, return if (distWalked > m_Settings.m_CollisionRefreshDistance * m_Settings.m_CollisionRefreshDistance) { // Update last position m_LastPosition = m_CameraPosTemp; m_Layer = LayerMask.NameToLayer(m_Settings.m_UsedLayer); // Reset counter m_DataIssuedActiveColliders = 0; // Reset all the cache's data foreach (CollisionCache cache in m_Cache.Values) { if (cache != null) { cache.Reset(); } } // Set the current cell currentCell.Set(m_LastPosition); // Refresh eveything float collDistSqr = m_Settings.m_CollisionDistance * m_Settings.m_CollisionDistance; // Iterate cells FoliageCell.IterateNeighboring(currentCell, 1, (int hash) => { FoliageCellDataRuntime data; if (m_FoliageData.m_FoliageData.TryGetValue(hash, out data)) { // If it is within distance and in the frustum float distanceSqr = data.m_Bounds.SqrDistance(m_LastPosition); if (distanceSqr <= collDistSqr) { ProcessCell(data, collDistSqr); } } }); } #if UNITY_EDITOR if (Time.frameCount % 300 == 0) { FoliageLog.i("Issued colliders: " + m_DataIssuedActiveColliders); } #endif }
protected override bool DrawWizardGUI() { // Have a size of 1 always if (m_Extract.Count == 0) { m_Extract.Add(null); } // Objects to extract EditorGUILayout.LabelField("Objects to extract trees from: "); using (new ScopedLayout(() => { EditorGUILayout.BeginVertical("Box"); }, EBeginMode.BEGIN_HORIZONTAL)) { bool lastNonNull = false; for (int i = 0; i < m_Extract.Count; i++) { GameObject extr = EditorGUILayout.ObjectField(m_Extract[i], typeof(GameObject), true) as GameObject; if (extr != null) { if (extr.GetComponent <Terrain>() != null) { FoliageLog.i("Terrain detected!"); m_Extract[i] = extr; } else { if (extr.transform.parent == null) { FoliageLog.i("Tree holder detected!"); m_Extract[i] = extr; } else { EditorUtility.DisplayDialog("Warning!", "You can only extract foliage from terrains and objects that are at the root of the hierarchy!", "Ok"); } } } if (i == m_Extract.Count - 1 && m_Extract[i] != null) { lastNonNull = true; } } // If the last item is not null, then make it larger if (lastNonNull) { m_Extract.Add(null); } } // Settings EditorGUILayout.LabelField("Settings: "); using (new ScopedLayout(() => { EditorGUILayout.BeginVertical("Box"); }, EBeginMode.BEGIN_VERTICAL)) { using (new ScopedLayout(() => { EditorGUILayout.BeginHorizontal(); }, EBeginMode.BEGIN_HORIZONTAL)) { EditorGUILayout.LabelField(new GUIContent( "Auto Extract Types (Terrain Only)", "If this is checked and we are extracting foliage from a terrain then the system will atempt to create automatically " + "the foliage types that it requires. It will not work for for objects that are not terrains!"), GUILayout.ExpandWidth(true)); m_AutoExtractPrototypes = EditorGUILayout.Toggle(m_AutoExtractPrototypes, GUILayout.ExpandWidth(false)); } m_DisableAfterExtraction = EditorGUILayout.Toggle(new GUIContent( "Disable After Extraction", "If we should disable the trees after we extracted them from the terrain or object. This will disable the extracted objects " + "with 'SetActive(false)' and will set 'Terrain.drawTreesAndFoliage' to false."), m_DisableAfterExtraction, GUILayout.ExpandWidth(true)); bool deleteAfterExtraction = EditorGUILayout.Toggle(new GUIContent( "Delete After Extraction", "If this is checked it will delete all the objects that were extracter. Will delete the extracted trees and all the extracted " + "tree instances from the terrains. not advisable!"), m_DeleteAfterExtraction, GUILayout.ExpandWidth(true)); if (m_DeleteAfterExtraction != deleteAfterExtraction) { if (deleteAfterExtraction) { bool sure = EditorUtility.DisplayDialog("Warning", "Setting this to true will delete all the extracted trees and foliage! " + "Not recomended if you want to try multiple iterations! Are you sure?", "Yes", "No"); if (sure) { m_DeleteAfterExtraction = true; } } else { m_DeleteAfterExtraction = deleteAfterExtraction; } } } return(false); }
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); }
void OnWizardCreate() { if (m_TerrainsExtract.Count > 0) { if (m_TerrainsExtract.Count > 1) { for (int i = 1; i < m_TerrainsExtract.Count; i++) { if (HasSameDetails(m_TerrainsExtract[0].GetComponent <Terrain>(), m_TerrainsExtract[i].GetComponent <Terrain>()) == false) { FoliageLog.e("Missing type when verified! Don't modify the types/details while extracting details!"); return; } } } Terrain terrain = m_TerrainsExtract[0].GetComponent <Terrain>(); DetailPrototype[] proto = terrain.terrainData.detailPrototypes; List <FoliageDetailExtracterMapping> mappings = new List <FoliageDetailExtracterMapping>(); // Build all the extracting data for (int i = 0; i < m_PrototypesData.Length; i++) { var data = m_PrototypesData[i]; if (data.m_ShouldExtract == true && data.m_NoneMapping == false) { FoliageDetailExtracterMapping mapping = new FoliageDetailExtracterMapping(); mapping.m_DetailLayer = data.m_DetailLayer; mapping.m_FoliageTypeHash = data.m_FoliageHashMapping; mapping.m_ExtractedDensity = data.m_ExtractedDensity; mappings.Add(mapping); } } if (mappings.Count > 0) { // Check that we have all the types and nothing has been tampered between the create for (int i = 0; i < mappings.Count; i++) { if (m_Painter.HasFoliageType(mappings[i].m_FoliageTypeHash) == false) { FoliageLog.e("Missing type when created! Don't modify the types while extracting details!"); return; } if (mappings[i].m_DetailLayer < 0 || mappings[i].m_DetailLayer >= proto.Length) { FoliageLog.e("Missing type when created! Don't modify the types while extracting details!"); return; } } // Proceed with the extraction m_Callback(mappings, m_TerrainsExtract, m_DisableAfterExtraction, m_DeleteAfterExtraction); } else { FoliageLog.i("Nothing to extract!"); } } else { FoliageLog.i("Nothing to extract!"); } }