private static void AutoExtractTypes(FoliagePainterEditTime painter, Terrain terrain) { TreeInstance[] terrainTreeInstances = terrain.terrainData.treeInstances; TreePrototype[] terrainTreePrototypes = terrain.terrainData.treePrototypes; for (int i = 0; i < terrainTreePrototypes.Length; i++) { TreePrototype proto = terrainTreePrototypes[i]; GameObject prefab = proto.prefab; // See if we already have it if (painter.HasFoliageType(prefab) == false) { // Attempt to add it FoliageTypeBuilder builder; bool anyError; FoliageUtilitiesEditor.ConfigurePrefab(prefab, out builder, out anyError); if (anyError == false) { painter.AddFoliageType(builder); } else { FoliageLog.e("Could not add foliage type: " + prefab.name + " due to error. Check the log for more info."); } } } }
/** * 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(); } }
/** * Update the LOD min/max distances for an object. * * @param isSpeedTree * If this is set to true, then we will check if we have a billboard renderer to an LOD * and we will skip it in case that we have one */ public static void UpdateDistancesLOD(FoliageTypeLODTree[] treeLods, LOD[] groupLods, float maxDistance, bool isSpeedTree) { if (groupLods != null && groupLods.Length > 0) { FoliageLog.Assert(groupLods.Length >= treeLods.Length, "Must have same or more lods than the tree lods!"); for (int i = 0; i < treeLods.Length; i++) { // If we are a speedtree check if we have a billboard renderer if (isSpeedTree && groupLods[i].renderers[0].GetComponent <BillboardRenderer>() != null) { continue; } FoliageTypeLODTree lodTree = treeLods[i]; LOD lodGroupCurrent = groupLods[i]; lodTree.m_EndDistance = ((1.0f - lodGroupCurrent.screenRelativeTransitionHeight) * maxDistance); } } else { treeLods[0].m_EndDistance = maxDistance; } }
/** * 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); }
public static bool CanChangeType(FoliageType type, EFoliageType newType) { GameObject prefab = type.m_Prefab; // PrefabType prefabType = PrefabUtility.GetPrefabType(prefab); string path = AssetDatabase.GetAssetPath(prefab); if (IsSpeedTree(newType)) { // We want to change to a SpeedTree type if (path.EndsWith(".spm") == false) { FoliageLog.e("Can't change to SpeedTree foliage: " + type.m_Name + " since it's path does not end with '.spm'! It means that it's not a SpeedTree!"); return(false); } LODGroup group = prefab.GetComponent <LODGroup>(); if (group == null) { FoliageLog.e("Can't change to SpeedTree foliage: " + type.m_Name + " since it doesn't contain a 'LODGroup' component!"); return(false); } if (newType == EFoliageType.SPEEDTREE_TREE_BILLBOARD) { bool containsBillboardRenderer = false; LOD[] lods = group.GetLODs(); foreach (LOD lod in lods) { if (lod.renderers[0].GetComponent <BillboardRenderer>() != null) { containsBillboardRenderer = true; break; } } if (containsBillboardRenderer == false) { FoliageLog.e("Can't change to SpeedTree with billboard the foliage: " + type.m_Name + " since it doesn't contain a billboard renderer!"); return(false); } } } // We're cool no error return(true); }
public static float GetMaxDistance(EFoliageType type) { switch (type) { case EFoliageType.OTHER_GRASS: case EFoliageType.SPEEDTREE_GRASS: return(FOLIAGE_MAX_GRASS_DISTANCE); case EFoliageType.OTHER_TREE: case EFoliageType.SPEEDTREE_TREE: case EFoliageType.SPEEDTREE_TREE_BILLBOARD: return(FOLIAGE_MAX_TREE_DISTANCE); default: FoliageLog.Assert(false, "Wrong type!"); return(FOLIAGE_MAX_GRASS_DISTANCE); } }
public static float ClampDistance(EFoliageType type, float maxViewDistance) { switch (type) { case EFoliageType.OTHER_GRASS: case EFoliageType.SPEEDTREE_GRASS: return(Mathf.Clamp(maxViewDistance, 0, FOLIAGE_MAX_GRASS_DISTANCE)); case EFoliageType.OTHER_TREE: case EFoliageType.SPEEDTREE_TREE: case EFoliageType.SPEEDTREE_TREE_BILLBOARD: return(Mathf.Clamp(maxViewDistance, 0, FOLIAGE_MAX_TREE_DISTANCE)); default: FoliageLog.Assert(false, "Wrong type!"); return(Mathf.Clamp(maxViewDistance, 0, FOLIAGE_MAX_GRASS_DISTANCE)); } }
/** * 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 OnWizardCreate() { FoliageLog.d("On wizard create!"); List <int> hashes = new List <int>(); for (int i = 0; i < m_Types.Count; i++) { int hash = m_Types[i].m_Hash; if (m_Extracting[i] && m_Painter.HasFoliageType(hash) && hashes.Contains(hash) == false) { hashes.Add(hash); } } if (hashes.Count > 0) { m_Callback(hashes.ToArray()); } else { FoliageLog.w("Could not extract anything from the list!"); } }
void OnWizardCreate() { FoliageLog.d("On wizard create!"); // Build the unique stuff List <GameObject> uniq = new List <GameObject>(); for (int i = 0; i < m_Extract.Count; i++) { if (m_Extract[i] != null && uniq.Contains(m_Extract[i]) == false) { uniq.Add(m_Extract[i]); } } if (uniq.Count > 0) { m_Callback(uniq, m_AutoExtractPrototypes, m_DisableAfterExtraction, m_DeleteAfterExtraction); } else { FoliageLog.w("Could not extract anything from the list!"); } }
/** To be used at edit-time */ public static void BuildDataPartialEditTime(FoliagePainter painter, FoliageType type) { GameObject prefab = type.m_Prefab; // Update the type type.Type = type.Type; FoliageLog.Assert(prefab != null, "Null foliage prefab!"); if (type.m_RuntimeData == null) { type.m_RuntimeData = new FoliageTypeRuntimeData(); } FoliageTypeRuntimeData runtime = type.m_RuntimeData; List <Material> checkMaterials = new List <Material>(); // Init the SpeedTree data if (type.IsSpeedTreeType) { if (runtime.m_SpeedTreeData == null) { runtime.m_SpeedTreeData = new FoliageTypeSpeedTreeData(); } } if (type.IsGrassType) { // Build the data universally for all grass types if (runtime.m_LODDataGrass == null) { runtime.m_LODDataGrass = new FoliageTypeLODGrass(); } runtime.m_LODDataGrass.m_Mesh = prefab.GetComponentInChildren <MeshFilter>().sharedMesh; runtime.m_LODDataGrass.m_Material = prefab.GetComponentInChildren <MeshRenderer>().sharedMaterial; checkMaterials.Add(runtime.m_LODDataGrass.m_Material); FoliageLog.Assert(runtime.m_LODDataGrass.m_Mesh != null, "Could not find mesh for type: " + prefab.name + ". Make sure that is has at least one mesh and one material"); FoliageLog.Assert(runtime.m_LODDataGrass.m_Material != null, "Could not find material for type: " + prefab.name + ". Make sure that is has at least one mesh and one material"); } else { LODGroup group = prefab.GetComponent <LODGroup>(); if (group == null) { FoliageLog.w("Detected tree: " + prefab.name + " without a lod group. Are you sure that you require a tree for this?"); if (runtime.m_LODDataTree == null || runtime.m_LODDataTree.Length == 0) { runtime.m_LODDataTree = new FoliageTypeLODTree[1]; } runtime.m_LODDataTree[0] = new FoliageTypeLODTree(); runtime.m_LODDataTree[0].m_Mesh = prefab.GetComponentInChildren <MeshFilter>().sharedMesh; runtime.m_LODDataTree[0].m_Materials = prefab.GetComponentInChildren <MeshRenderer>().sharedMaterials; runtime.m_LODDataTree[0].m_EndDistance = type.m_RenderInfo.m_MaxDistance; checkMaterials.AddRange(runtime.m_LODDataTree[0].m_Materials); } else { List <FoliageTypeLODTree> treeLods = new List <FoliageTypeLODTree>(group.lodCount); LOD[] lods = group.GetLODs(); for (int i = 0; i < group.lodCount; i++) { if (lods[i].renderers[0].gameObject.GetComponent <BillboardRenderer>() != null) { // Extract the billboard data var speedData = runtime.m_SpeedTreeData; FoliageWindTreeUtilities.ExtractBillboardData(lods[i].renderers[0].gameObject.GetComponent <BillboardRenderer>(), speedData); continue; } FoliageTypeLODTree treeLod = new FoliageTypeLODTree(); MeshRenderer rend = lods[i].renderers[0].gameObject.GetComponent <MeshRenderer>(); MeshFilter filter = lods[i].renderers[0].gameObject.GetComponent <MeshFilter>(); treeLod.m_Mesh = filter.sharedMesh; treeLod.m_Materials = rend.sharedMaterials; checkMaterials.AddRange(rend.sharedMaterials); treeLods.Add(treeLod); } runtime.m_LODDataTree = treeLods.ToArray(); // Update the LOD distances UpdateDistancesLOD(runtime.m_LODDataTree, lods, type.m_RenderInfo.m_MaxDistance, type.IsSpeedTreeType); } } if (checkMaterials.Count > 0) { if (type.IsSpeedTreeType) { if (type.m_RenderInfo.m_Hue == new Color(0, 0, 0, 0)) { type.m_RenderInfo.m_Hue = checkMaterials[0].GetColor("_HueVariation"); } if (type.m_RenderInfo.m_Color == new Color(0, 0, 0, 0)) { type.m_RenderInfo.m_Color = checkMaterials[0].GetColor("_Color"); } } for (int i = 0; i < checkMaterials.Count; i++) { if (checkMaterials[i].enableInstancing == false) { checkMaterials[i].enableInstancing = true; FoliageLog.w("Material: [" + checkMaterials[i].name + "] did not had instancing enabled! We enabled it!"); } } } // Moved the build at partial edit-time if (type.Type == EFoliageType.SPEEDTREE_GRASS) { Shader shader = painter.GetShaderGrass(); FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Grass! Make sure that it is added to the project and that it compiled!"); FoliageTypeLODGrass lodGrass = type.m_RuntimeData.m_LODDataGrass; // Override the material at runtime lodGrass.m_Material = new Material(lodGrass.m_Material); lodGrass.m_Material.shader = shader; // Enable it for instancing lodGrass.m_Material.enableInstancing = true; } else if (type.Type == EFoliageType.SPEEDTREE_TREE || type.Type == EFoliageType.SPEEDTREE_TREE_BILLBOARD) { Shader shader = painter.GetShaderTreeMaster(); FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Master! Make sure that it is added to the project and that it compiled!"); FoliageTypeLODTree[] lodTree = type.m_RuntimeData.m_LODDataTree; for (int i = 0; i < lodTree.Length; i++) { FoliageTypeLODTree tree = lodTree[i]; Material[] mats = tree.m_Materials; for (int m = 0; m < mats.Length; m++) { // Set the new material mats[m] = new Material(mats[m]); mats[m].shader = shader; // Enable instancing mats[m].enableInstancing = true; } tree.m_Materials = mats; } } // Set the materials the values for enabling the bend stuff if we have it if (type.IsGrassType) { if (type.m_EnableBend) { type.m_RuntimeData.m_LODDataGrass.m_Material.EnableKeyword("CRITIAS_DISTANCE_BEND"); } else { type.m_RuntimeData.m_LODDataGrass.m_Material.DisableKeyword("CRITIAS_DISTANCE_BEND"); } } }
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 }
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); }
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); }
/** * 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; }
protected override bool DrawWizardGUI() { EditorGUILayout.LabelField("Add terrains or objects here: "); using (new ScopedLayout(() => { EditorGUILayout.BeginVertical("Box"); }, EBeginMode.BEGIN_HORIZONTAL)) { GameObject possibleTerrains = EditorGUILayout.ObjectField(null, typeof(GameObject), true) as GameObject; if (possibleTerrains != null) { List <Terrain> terrains = new List <Terrain>(); RecursivelyExtractTerrains(possibleTerrains, terrains); for (int i = 0; i < terrains.Count; i++) { Terrain extr = terrains[i]; if (m_TerrainsExtract.Contains(extr) == false) { if (extr.GetComponent <Terrain>() != null) { // If it's the first (main) terrain if (m_TerrainsExtract.Count == 0) { DetailPrototype[] protos = extr.GetComponent <Terrain>().terrainData.detailPrototypes; if (protos != null && protos.Length > 0) { m_TerrainsExtract.Add(extr); } else { EditorUtility.DisplayDialog("Warning!", "The first added (main) terrain does not have any terrain details!", "Ok"); } } else { // Check if the same details appear if (HasSameDetails(m_TerrainsExtract[0].GetComponent <Terrain>(), extr.GetComponent <Terrain>())) { m_TerrainsExtract.Add(extr); } else { EditorUtility.DisplayDialog("Warning!", "The added terrain does not have the same details as the first (main) terrain!", "Ok"); } } } else { EditorUtility.DisplayDialog("Warning!", "You can only extract details from terrains!", "Ok"); } } } } } // Display all the detail data if (m_TerrainsExtract.Count == 0) { return(false); } EditorGUILayout.Space(); // Extracted terrains EditorGUILayout.LabelField("Terrain to extract details from: "); // Show the data using (new ScopedLayout(() => { EditorGUILayout.BeginVertical("Box"); }, EBeginMode.BEGIN_HORIZONTAL)) { for (int i = 0; i < m_TerrainsExtract.Count; i++) { EditorGUILayout.LabelField("(T) " + m_TerrainsExtract[i].name + (i == 0 ? " [Main Details]" : "")); } } EditorGUILayout.Space(); EditorGUILayout.LabelField("Details mappings: "); if (m_Prototypes == null) { m_Prototypes = m_TerrainsExtract[0].GetComponent <Terrain>().terrainData.detailPrototypes; if (m_Prototypes == null || m_Prototypes.Length == 0) { FoliageLog.e("No terrain details found!"); return(false); } // Generate the UI data m_PrototypesData = new DetailPrototypeData[m_Prototypes.Length]; for (int i = 0; i < m_PrototypesData.Length; i++) { m_PrototypesData[i] = new DetailPrototypeData(); m_PrototypesData[i].m_DetailLayer = i; string protoName = m_Prototypes[i].prototype != null ? m_Prototypes[i].prototype.name : m_Prototypes[i].prototypeTexture.name; // Attempt to search through the data to check for a name or something int foundIdx = m_TypesRuntime.FindIndex((x) => { return(x.m_Name.ToLowerInvariant().Replace(" ", "").Contains(protoName.ToLowerInvariant().Replace(" ", ""))); }); if (foundIdx >= 0) { m_PrototypesData[i].m_NoneMapping = false; m_PrototypesData[i].m_FoliageHashMapping = m_TypesRuntime[foundIdx].m_Hash; m_PrototypesData[i].m_FoliageTypeNameMapping = m_TypesRuntime[foundIdx].m_Name; } } } // Set all the data related to that terrain and stuff DetailPrototype[] prototypes = m_Prototypes; using (new ScopedLayout(() => { EditorGUILayout.BeginVertical("Box"); }, EBeginMode.BEGIN_VERTICAL)) { for (int i = 0; i < prototypes.Length; i++) { DetailPrototype proto = prototypes[i]; DetailPrototypeData protoData = m_PrototypesData[i]; using (new ScopedLayout(() => { EditorGUILayout.BeginVertical(); }, EBeginMode.BEGIN_HORIZONTAL)) { string name; if (proto.prototype != null) { name = proto.prototype.name; } else { name = proto.prototypeTexture.name; } // Each prototype data using (new ScopedLayout(() => { EditorGUILayout.BeginHorizontal(); }, EBeginMode.BEGIN_HORIZONTAL)) { protoData.m_ShouldExtract = EditorGUILayout.Toggle(new GUIContent("Extract: [" + name + "]", "If we should extract that type"), protoData.m_ShouldExtract); if (protoData.m_ShouldExtract) { EditorGUILayout.LabelField("as: ", GUILayout.Width(30)); bool dropdown = EditorGUILayout.DropdownButton(new GUIContent(protoData.m_NoneMapping ? "None" : protoData.m_FoliageTypeNameMapping, "To what foliage type this detail will be changed transformed when extracting from the terrain"), FocusType.Passive); if (dropdown) { GenericMenu menu = new GenericMenu(); menu.AddItem(new GUIContent("None"), protoData.m_NoneMapping, (object obj) => { protoData.m_NoneMapping = true; }, null); menu.AddSeparator(""); for (int r = 0; r < m_TypesRuntime.Count; r++) { FoliageTypeRuntime rt = m_TypesRuntime[r]; bool on = protoData.m_NoneMapping == false && protoData.m_FoliageHashMapping == rt.m_Hash; menu.AddItem(new GUIContent(rt.m_Name), on, (object obj) => { protoData.m_NoneMapping = false; protoData.m_FoliageHashMapping = ((FoliageTypeRuntime)obj).m_Hash; protoData.m_FoliageTypeNameMapping = ((FoliageTypeRuntime)obj).m_Name; }, rt); } menu.ShowAsContext(); } } } if (protoData.m_ShouldExtract) { protoData.m_ExtractedDensity = EditorGUILayout.Slider(new GUIContent("[" + name + "] Density [0..1]", "A multiplier for the count of extracted details from the terrain. 1 means extract all instances, 0.5 means extract half of them, 0 extract none. " + "Use mostly for extracted grass, but not details like rocks or anything else like that. Since on the terrain we only have a lot of billboards as " + "grass and we might map them to some 3D mesh clumps it's higly likely that we will only need a half (0.5) or a third (0.3) of that existing terrain data. "), protoData.m_ExtractedDensity, 0, 1); } } } } EditorGUILayout.Space(); // Settings EditorGUILayout.LabelField("Settings: "); using (new ScopedLayout(() => { EditorGUILayout.BeginVertical("Box"); }, EBeginMode.BEGIN_VERTICAL)) { m_DisableAfterExtraction = EditorGUILayout.Toggle(new GUIContent( "Disable After Extraction", "If we should disable the details after we extracted them from the terrain. This will 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 details that were extracted. Will delete the extracted details. 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 details from the terrain! " + "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); }
/** To be used at runtime. Will create all we need for SpeedTree wind and other data */ public static void BuildDataRuntime(FoliagePainter painter, FoliageType type, Transform attachmentPoint) { FoliageLog.Assert(type.IsRuntimeInitialized == false, "Runtime data already initialized!"); // Everyone has MPB's type.m_RuntimeData.m_TypeMPB = new MaterialPropertyBlock(); if (type.IsSpeedTreeType) { // Create the glued mesh var speedData = type.m_RuntimeData.m_SpeedTreeData; FoliageLog.Assert(speedData != null, "Speed data must already be partly generated if we have a SpeedTree!"); // Get the lod and instnatiade the one with the least instructions LOD[] lods = type.m_Prefab.GetComponent <LODGroup>().GetLODs(); for (int i = lods.Length - 1; i >= 0; i--) { if (lods[i].renderers[0].GetComponent <BillboardRenderer>() != null) { continue; } else { // Init the object with the lowest possible LOD speedData.m_SpeedTreeWindObject = GameObject.Instantiate(lods[i].renderers[0].gameObject, attachmentPoint); break; } } // Set the data speedData.m_SpeedTreeWindObjectMesh = speedData.m_SpeedTreeWindObject.GetComponentInChildren <MeshRenderer>(); // Set the NULL invisible shader Shader nullShader = painter.GetShaderNull(); FoliageLog.Assert(nullShader, "Null shader not found! Make sure it exists and that it compiled!"); // Set the invisible null shader, we only need the wind Material[] mats = speedData.m_SpeedTreeWindObjectMesh.materials; for (int i = 0; i < mats.Length; i++) { mats[i].shader = nullShader; } // Attach the wind object. Ensures that we are enabled. speedData.m_SpeedTreeWindObject.AddComponent <FoliageWindTreeWind>(); speedData.m_SpeedTreeWindObjectMesh.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; speedData.m_SpeedTreeWindObject.transform.SetParent(attachmentPoint, false); speedData.m_SpeedTreeWindObject.transform.localPosition = new Vector3(0, 0, 0); MeshFilter m = speedData.m_SpeedTreeWindObject.GetComponentInChildren <MeshFilter>(); Bounds b = m.mesh.bounds; b.Expand(4.5f); m.mesh.bounds = b; speedData.m_SpeedTreeWindObject.GetComponentInChildren <MeshFilter>().mesh = m.mesh; } // Not used here, since at edit time we already created the materials /* * if (type.Type == EFoliageType.SPEEDTREE_GRASS) * { * Shader shader = painter.GetShaderGrass(); * FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Grass! Make sure that it is added to the project and that it compiled!"); * * FoliageTypeLODGrass lodGrass = type.m_RuntimeData.m_LODDataGrass; * * // Override the material at runtime * // lodGrass.m_Material = new Material(lodGrass.m_Material); * // lodGrass.m_Material.shader = shader; * * // Enable it for instancing * // lodGrass.m_Material.enableInstancing = true; * } * else if(type.Type == EFoliageType.SPEEDTREE_TREE || type.Type == EFoliageType.SPEEDTREE_TREE_BILLBOARD) * { * Shader shader = painter.GetShaderTreeMaster(); * FoliageLog.Assert(shader, "Could not find shader: Critias/SpeedTree_Master! Make sure that it is added to the project and that it compiled!"); * * FoliageTypeLODTree[] lodTree = type.m_RuntimeData.m_LODDataTree; * * for(int i = 0; i < lodTree.Length; i++) * { * FoliageTypeLODTree tree = lodTree[i]; * * Material[] mats = tree.m_Materials; * * for(int m = 0; m < mats.Length; m++) * { * // Set the new material * //mats[m] = new Material(mats[m]); * //mats[m].shader = shader; * * // Enable instancing * //mats[m].enableInstancing = true; * } * * tree.m_Materials = mats; * } * } */ // We did initialize type.IsRuntimeInitialized = true; }
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 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 }
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!"); } }