using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; #if HDPipeline using UnityEngine.Rendering.HighDefinition; #endif namespace Gaia { [System.Serializable] public class BakedMaskCacheEntry { public string fileName; public string assetPath; public string fullPath; public RenderTexture texture; } [System.Serializable] public class BakedMaskCache : UnityEngine.Object { public bool m_autoSpawnRunning = false; //bool m_cacheClearedForAutoSpawn = false; public GaiaSession m_gaiaSession; public GaiaSessionManager m_gaiaSessionManager; private Terrain m_worldMapTerrain; public BakedMaskCacheEntry[] m_cacheEntries { get { if (m_gaiaSession == null) { m_gaiaSession = GaiaSessionManager.GetSessionManager(false).m_session; } return m_gaiaSession.m_bakedMaskCacheEntries; } set { if (m_gaiaSession == null) { m_gaiaSession = GaiaSessionManager.GetSessionManager(false).m_session; } m_gaiaSession.m_bakedMaskCacheEntries = value; } } private GaiaSettings m_gaiaSettings; private GaiaSettings GaiaSettings { get { if (m_gaiaSettings == null) { m_gaiaSettings = GaiaUtils.GetGaiaSettings(); } return m_gaiaSettings; } } public RenderTexture BakeTerrainTagCollisions(Terrain terrain, string tag, float radius) { GameObject[] allGOsWithTag = new GameObject[0]; bool tagFound = false; try { allGOsWithTag = GameObject.FindGameObjectsWithTag(tag); tagFound = true; } catch { tagFound = false; } if (!tagFound) { Debug.LogWarning("Could not find Game Objects for tag: " + tag + " when trying to bake a collision mask. Does this tag exist?"); return null; } //Array to store all positions and radi of the objects Vector4[] posAndRad = new Vector4[allGOsWithTag.Length]; for (int i = 0; i < allGOsWithTag.Length; i++) { //need scalar position on the terrain from 0..1 //start with the actual center of the object - the transform.position is at the pivot which is not necessarily the center of the object. Vector3 centerPosition = GetCenterAverage(allGOsWithTag[i].transform); //get relative positon on the terrain first float scaleFactor = (allGOsWithTag[i].transform.localScale.x + allGOsWithTag[i].transform.localScale.z) / 2f; Vector4 v4 = new Vector4(centerPosition.x, centerPosition.y, centerPosition.z, WorldDistance2UVDistance(terrain, radius * scaleFactor)); v4.x -= terrain.transform.position.x; v4.y -= terrain.transform.position.y; v4.z -= terrain.transform.position.z; //now make that relative position scalar (0..1) v4.x /= terrain.terrainData.size.x; v4.y /= terrain.terrainData.size.y; v4.z /= terrain.terrainData.size.z; //flip on z-axis v4.z = 1 - v4.z; posAndRad[i] = v4; } string fileName = GetTagCollisionMaskFileName(terrain, tag, radius); return BakeVectorArrayForTerrain(posAndRad, terrain, fileName); } private Vector3 GetCenterAverage(Transform transform) { int childCount = transform.childCount; if (childCount == 0) { return transform.position; } else { Vector3 returnVector = Vector3.zero; for (int i = 0; i < childCount; i++) { returnVector += GetCenterAverage(transform.GetChild(i)); } returnVector /= childCount; return returnVector; } } public void WriteCacheToDisk() { #if UNITY_EDITOR int length = m_cacheEntries.Length; for (int i = 0; i < length; i++) { ImageProcessing.WriteRenderTexture(m_cacheEntries[i].fullPath, m_cacheEntries[i].texture); //Refresh mask immediately by reimporting it in the same step AssetDatabase.ImportAsset(m_cacheEntries[i].assetPath, ImportAssetOptions.ForceUpdate); m_cacheEntries[i].texture = null; } //clear the cache for a clean start, this will free the cache from outdated entries, etc. //the cache will be rebuilt quickly only with the relevant files which are saved to disk now. m_cacheEntries = new BakedMaskCacheEntry[0]; #endif } public RenderTexture BakeTerrainWorldBiomeMask(Terrain terrain, string worldBiomeMaskGUID) { if (m_gaiaSessionManager == null) { m_gaiaSessionManager = GaiaSessionManager.GetSessionManager(false); } if (m_worldMapTerrain == null) { m_worldMapTerrain = TerrainHelper.GetWorldMapTerrain(); } if (m_worldMapTerrain == null) { Debug.LogWarning("Found no world map terrain for baking a world biome mask."); return null; } if (String.IsNullOrEmpty(worldBiomeMaskGUID)) { return null; } if (m_gaiaSessionManager.m_session == null) { Debug.LogWarning("Found no session for baking a world biome mask."); return null; } if (m_gaiaSessionManager.m_session.m_worldBiomeMaskSettings == null) { Debug.LogWarning("Found no world designer settings in the session for baking a world biome mask."); return null; } //we need to apply the mask stack for this biome mask on the world map to get the result, then copy the appropiate rectangle for the queried terrain into the cache & return it. bool worldMapActiveState = m_worldMapTerrain.gameObject.activeInHierarchy; m_worldMapTerrain.gameObject.SetActive(true); GameObject emptyGO = new GameObject(); emptyGO.transform.position = new Vector3(m_worldMapTerrain.transform.position.x + m_worldMapTerrain.terrainData.size.x / 2f, m_worldMapTerrain.transform.position.y, m_worldMapTerrain.transform.position.z + +m_worldMapTerrain.terrainData.size.z / 2f); GaiaMultiTerrainOperation operation = new GaiaMultiTerrainOperation(m_worldMapTerrain, emptyGO.transform, m_worldMapTerrain.terrainData.size.x); operation.m_isWorldMapOperation = true; operation.GetHeightmap(); operation.GetNormalmap(); operation.CollectTerrainBakedMasks(); RenderTextureDescriptor rtDescriptor = operation.RTbakedMask.descriptor; RenderTexture inputTexture = RenderTexture.GetTemporary(rtDescriptor); RenderTexture currentRT = RenderTexture.active; RenderTexture.active = inputTexture; GL.Clear(true, true, Color.white); RenderTexture.active = currentRT; RenderTexture ruleOutputTexture = RenderTexture.GetTemporary(rtDescriptor); ImageMask[] maskStack = m_gaiaSessionManager.m_session.m_worldBiomeMaskSettings.m_spawnerRules.Find(x => x.GUID == worldBiomeMaskGUID).m_imageMasks; //Calculate Target position &/ resolution BoundsDouble bounds = new BoundsDouble(); TerrainHelper.GetTerrainBounds(ref bounds); bounds.center -= TerrainLoaderManager.Instance.GetOrigin(); RenderTexture chunkContent = new RenderTexture(terrain.terrainData.heightmapTexture.descriptor); int maxTilesX = Mathf.RoundToInt((float)bounds.size.x / terrain.terrainData.size.x); int maxTilesZ = Mathf.RoundToInt((float)bounds.size.z / terrain.terrainData.size.z); int currentTileX = Mathf.RoundToInt((terrain.transform.position.x - (float)bounds.min.x) / terrain.terrainData.size.x); int currentTileZ = Mathf.RoundToInt((terrain.transform.position.z - (float)bounds.min.z) / terrain.terrainData.size.z); float res = (terrain.terrainData.heightmapResolution) / ((float)bounds.size.x / terrain.terrainData.bounds.size.x * (terrain.terrainData.heightmapResolution - 1)); Bounds worldSpaceBounds = terrain.terrainData.bounds; worldSpaceBounds.center = new Vector3(worldSpaceBounds.center.x + terrain.transform.position.x, worldSpaceBounds.center.y + terrain.transform.position.y, worldSpaceBounds.center.z + terrain.transform.position.z); float xPos = ((float)currentTileX * terrain.terrainData.heightmapResolution) / (maxTilesX * terrain.terrainData.heightmapResolution); float zPos = ((float)currentTileZ * terrain.terrainData.heightmapResolution) / (maxTilesZ * terrain.terrainData.heightmapResolution); Vector2 pos = new Vector2(xPos, zPos); //If we have a mask stack, we need to process it if (maskStack != null && maskStack.Length > 0) { //We start from a white texture, so we need the first mask action in the stack to always be "Multiply", otherwise there will be no result. maskStack[0].m_blendMode = ImageMaskBlendMode.Multiply; float maxWorldHeight = 0f; float minWorldHeight = 0f; m_gaiaSessionManager.GetWorldMinMax(ref minWorldHeight, ref maxWorldHeight, true); //Iterate through all image masks and set up the current paint context in case the shader uses heightmap data foreach (ImageMask mask in maskStack) { mask.m_multiTerrainOperation = operation; mask.m_seaLevel = m_gaiaSessionManager.GetSeaLevel(); mask.m_maxWorldHeight = maxWorldHeight; mask.m_minWorldHeight = minWorldHeight; } Graphics.Blit(ImageProcessing.ApplyMaskStack(inputTexture, ruleOutputTexture, maskStack, ImageMaskInfluence.Local), ruleOutputTexture); } else { //no mask stack -> just blit the white input texture over as the output Graphics.Blit(inputTexture, ruleOutputTexture); } operation.CloseOperation(); DestroyImmediate(emptyGO); RenderTexture.ReleaseTemporary(inputTexture); m_worldMapTerrain.gameObject.SetActive(worldMapActiveState); //copy the rule output into the right size for the requested terrain chunk Graphics.Blit(ruleOutputTexture, chunkContent, new Vector2(res, res), pos); string filename = GetWorldBiomeMaskFilename(terrain, worldBiomeMaskGUID); SaveBakedMaskInCache(chunkContent, terrain, filename); RenderTexture.ReleaseTemporary(ruleOutputTexture); return chunkContent; } public RenderTexture BakeLayerGameObjectCollisions(Terrain terrain, LayerMask layerMask, float distance) { //Set up the scene for the layer mask bake. Do all this in a try catch so we can clean up in case anything goes wrong. RenderTexture collisionMask = OrthoLayerBake(terrain, layerMask, distance); string fileName = GetLayerGameObjectCollisionMaskFileName(terrain, layerMask, distance); SaveBakedMaskInCache(collisionMask, terrain, fileName); return collisionMask; } private RenderTexture OrthoLayerBake(Terrain terrain, LayerMask layerMask, float distance) { RenderTextureDescriptor rtDesc = GetDefaultCollisionMaskRTDescriptor(terrain); RenderTexture collisionMask = new RenderTexture(rtDesc); RenderTexture currentRT = RenderTexture.active; RenderTexture.active = collisionMask; //we start with a full white texture, the areas that would create a collision are painted in black GL.Clear(true, true, Color.white); RenderTexture.active = currentRT; var originalLODBias = QualitySettings.lodBias; try { #if HDPipeline //In HD Pipeline we can use a frame override to set the LOD Bias for the capture. OrthographicBake.m_HDLODBiasOverride = 100; #else QualitySettings.lodBias = 100; #endif OrthographicBake.m_gaiaSettings = GaiaSettings; OrthographicBake.LightsOff(); OrthographicBake.CreateBakeDirectionalLight(9999f, Color.white); OrthographicBake.BakeTerrain(terrain, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution, layerMask, null); Material growShrinkMaterial = new Material(Shader.Find("Hidden/Gaia/GrowShrink")); growShrinkMaterial.SetTexture("_InputTex", OrthographicBake.m_tmpRenderTexture); //translate the grow shrink distance into a scalar terrain value, as the shader works with U/V coordinates float scalarDistance = distance / terrain.terrainData.size.x; growShrinkMaterial.SetFloat("_Distance", scalarDistance); growShrinkMaterial.SetFloat("_TexelSize", OrthographicBake.m_tmpRenderTexture.texelSize.x); Graphics.Blit(OrthographicBake.m_tmpRenderTexture, collisionMask, growShrinkMaterial, 1); OrthographicBake.CleanUpRenderTexture(); } catch (Exception ex) { Debug.LogError("Error while trying to bake a layer-based game object collision mask. Error Message: " + ex.Message + " Stack Trace: " + ex.StackTrace); } finally { #if !HDPipeline QualitySettings.lodBias = originalLODBias; #endif OrthographicBake.LightsOn(); OrthographicBake.RemoveBakeDirectionalLight(); OrthographicBake.RemoveOrthoCam(); } return collisionMask; } private RenderTexture BakeLayerTreeCollisions(Terrain terrain, LayerMask inputlayerMask, float growShrinkDistance) { bool originalHeightmapEnabled = terrain.drawHeightmap; bool[] originalChildEnabledStates = new bool[terrain.transform.childCount]; for (int i = 0; i < terrain.transform.childCount; i++) { originalChildEnabledStates[i] = terrain.transform.GetChild(i).gameObject.activeInHierarchy; } RenderTexture collisionMask = null; try { //Add in the layer of the terrain to the layer selection - needs to be visible for the ortho bake so the trees show up in the baked image int terrainlayerbits = 1 << terrain.gameObject.layer; LayerMask bakeLayerMask = inputlayerMask | terrainlayerbits; terrain.drawHeightmap = false; for (int i = 0; i < terrain.transform.childCount; i++) { terrain.transform.GetChild(i).gameObject.SetActive(false); } collisionMask = OrthoLayerBake(terrain, bakeLayerMask, growShrinkDistance); string fileName = GetLayerTreeCollisionMaskFileName(terrain, inputlayerMask, growShrinkDistance); SaveBakedMaskInCache(collisionMask, terrain, fileName); } catch (Exception ex) { Debug.LogError("Error while trying to bake a layer-based tree collision mask. Error Message: " + ex.Message + " Stack Trace: " + ex.StackTrace); } finally { terrain.drawHeightmap = originalHeightmapEnabled; for (int i = 0; i < terrain.transform.childCount; i++) { terrain.transform.GetChild(i).gameObject.SetActive(originalChildEnabledStates[i]); } } return collisionMask; } public RenderTexture BakeTerrainTreeCollisions(Terrain terrain, CollisionMask collisionMask, float boundsRadius) { //Iterate through the trees and build a v4 array with the positions List treePrototypeIds = new List(); List selectedTreeRules = CollisionMask.m_allTreeSpawnRules.Where(x => collisionMask.m_selectedTreeSpawnRuleGUIDs.Contains(x.GUID)).ToList(); foreach (SpawnRule sr in selectedTreeRules) { if (sr == null) { continue; } int treePrototypeID = TerrainHelper.GetTreePrototypeIDFromSpawnRule(sr, terrain); treePrototypeIds.Add(treePrototypeID); } TreeInstance[] allRelevantTrees = terrain.terrainData.treeInstances.Where(x => treePrototypeIds.Contains(x.prototypeIndex)).ToArray(); //Array to store all positions and radi of the tree Vector4[] posAndRad = new Vector4[allRelevantTrees.Length]; for (int i = 0; i < allRelevantTrees.Length; i++) { //note the flip on the z-axis posAndRad[i] = new Vector4(allRelevantTrees[i].position.x, allRelevantTrees[i].position.y, 1 - allRelevantTrees[i].position.z, WorldDistance2UVDistance(terrain, boundsRadius * allRelevantTrees[i].widthScale)); } string fileName = GetTreeCollisionMaskFileName(terrain, collisionMask.RadiusTreeMaskValues.ToString(), boundsRadius); return BakeVectorArrayForTerrain(posAndRad, terrain, fileName); } /// /// Converts a regular world space unity unit distance to a distance in scalar (0-1) UV-space on a terrain. /// /// The terrain on which the conversion takes place /// The distance to convert. /// private float WorldDistance2UVDistance(Terrain terrain, float distance) { float longerSideLength = Mathf.Max(terrain.terrainData.size.x, terrain.terrainData.size.z); return Mathf.InverseLerp(0, longerSideLength, distance); } private RenderTexture BakeVectorArrayForTerrain(Vector4[] posAndRad, Terrain terrain, string filename) { //setting up with default settings from the paint context source render texture RenderTextureDescriptor rtDesc = GetDefaultCollisionMaskRTDescriptor(terrain); RenderTexture collisionMask = new RenderTexture(rtDesc); RenderTexture currentRT = RenderTexture.active; RenderTexture.active = collisionMask; //we start with a full white texture, the areas that would create a collision are painted in black GL.Clear(true, true, Color.white); RenderTexture.active = currentRT; //We need a buffer texture to continously feed the output of the tree collision baking back into the shader. //while still iterating through the trees. RenderTexture collisionMaskBuffer = RenderTexture.GetTemporary(collisionMask.descriptor); //Clear the buffer for a clean start Graphics.Blit(collisionMask, collisionMaskBuffer); //Prepare a material that will bake the trees into a mask as we spawn them Material treeBakeMat = new Material(Shader.Find("Hidden/Gaia/TreeCollisionMaskBaking")); //treeBakeMat.SetFloat("_Strength", 1f); //AnimationCurve tempCurve = new AnimationCurve(new Keyframe[] { new Keyframe { time = 0, value = 1 }, new Keyframe { time = 1, value = 0 } }); //Texture2D tempTexture = new Texture2D(256, 1); //ImageProcessing.CreateMaskCurveTexture(ref tempTexture); //ImageProcessing.BakeCurveTexture(tempCurve, tempTexture); //treeBakeMat.SetTexture("_DistanceMaskTex", tempTexture); //treeBakeMat.SetInt("_Invert", 0); // int currentPosNumber = 0; while (currentPosNumber < posAndRad.Length) { int segmentLength = Math.Min(1000, posAndRad.Length - currentPosNumber); Vector4[] segment = new Vector4[segmentLength]; Array.Copy(posAndRad, Math.Max(0, currentPosNumber - 1), segment, 0, segmentLength); treeBakeMat.SetTexture("_InputTex", collisionMaskBuffer); treeBakeMat.SetVectorArray("_TargetPosition", segment); treeBakeMat.SetInt("_PositionsCount", segmentLength); Graphics.Blit(collisionMaskBuffer, collisionMask, treeBakeMat, 2); Graphics.Blit(collisionMask, collisionMaskBuffer); currentPosNumber += segmentLength; } ////Iterate through the trees and bake //foreach (Vector4 v4 in posAndRad) //{ // //treeBakeMat.SetVector("_TargetPosition", new Vector4(v4.x, v4.z, 0, 0)); // treeBakeMat.SetTexture("_InputTex", collisionMaskBuffer); // Graphics.Blit(collisionMaskBuffer, collisionMask, treeBakeMat, 2); // Graphics.Blit(collisionMask, collisionMaskBuffer); //} //Store the final result in the appropiate file SaveBakedMaskInCache(collisionMask, terrain, filename); //Clean Up RenderTexture.ReleaseTemporary(collisionMaskBuffer); return collisionMask; } private RenderTextureDescriptor GetDefaultCollisionMaskRTDescriptor(Terrain terrain) { RenderTextureDescriptor rtDesc = new RenderTextureDescriptor(); rtDesc.autoGenerateMips = true; rtDesc.bindMS = false; rtDesc.colorFormat = RenderTextureFormat.R16; rtDesc.depthBufferBits = 0; rtDesc.dimension = UnityEngine.Rendering.TextureDimension.Tex2D; rtDesc.enableRandomWrite = true; rtDesc.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R16_UNorm; rtDesc.height = Mathf.RoundToInt(terrain.terrainData.size.z); rtDesc.memoryless = RenderTextureMemoryless.None; rtDesc.msaaSamples = 1; rtDesc.sRGB = false; rtDesc.shadowSamplingMode = UnityEngine.Rendering.ShadowSamplingMode.None; rtDesc.useDynamicScale = false; rtDesc.useMipMap = false; rtDesc.volumeDepth = 1; rtDesc.vrUsage = VRTextureUsage.None; rtDesc.width = Mathf.RoundToInt(terrain.terrainData.size.x); return rtDesc; } private string GetTreeCollisionMaskFileName(Terrain terrain, string spawnRuleGUID, float radius) { return terrain.name + "_" + BakedMaskTypeInternal.RadiusTree.ToString() + "_" + spawnRuleGUID + "_" + radius.ToString(); } private string GetTreeCollisionMaskSearchString(string spawnRuleGUID) { return BakedMaskTypeInternal.RadiusTree.ToString() + "_" + spawnRuleGUID.ToString(); } private string GetLayerGameObjectCollisionMaskFileName(Terrain terrain, LayerMask layerMask, float distance) { return terrain.name + "_" + BakedMaskTypeInternal.LayerGameObject.ToString() + "_" + layerMask.value.ToString() + "_" + distance.ToString(); } private string GetLayerTreeCollisionMaskFileName(Terrain terrain, LayerMask layerMask, float distance) { return terrain.name + "_" + BakedMaskTypeInternal.LayerTree.ToString() + "_" + layerMask.value.ToString() + "_" + distance.ToString(); } private string GetTagCollisionMaskFileName(Terrain terrain, string m_tag, float radius) { return terrain.name + "_" + BakedMaskTypeInternal.RadiusTag.ToString() + "_" + m_tag + "_" + radius.ToString(); } private LayerMask GetLayerMaskFromFileName(string fileName) { string masktypeName = ""; if (fileName.Contains(BakedMaskTypeInternal.LayerGameObject.ToString())) { masktypeName = BakedMaskTypeInternal.LayerGameObject.ToString(); } if (fileName.Contains(BakedMaskTypeInternal.LayerTree.ToString())) { masktypeName = BakedMaskTypeInternal.LayerTree.ToString(); } if (masktypeName == "") { return -1; } string layerMaskString = fileName.Substring(fileName.IndexOf(masktypeName) + masktypeName.Length + 1); layerMaskString = layerMaskString.Substring(0, layerMaskString.LastIndexOf("_")); try { int parsedInt = -1; int.TryParse(layerMaskString, out parsedInt); LayerMask layerMask = parsedInt; return layerMask; } catch (Exception ex) { Debug.LogError("could not reset a cached collision layer mask, tried to use the following layer mask string: " + layerMaskString); //workaround to avoid warning in the compiler if (ex.Message == "") { } return -1; } } private string GetTagCollisionMaskSearchString(string tag) { return BakedMaskTypeInternal.RadiusTag.ToString() + "_" + tag; } private string GetWorldBiomeMaskFilename(Terrain terrain, string GUID) { return terrain.name + "_" + BakedMaskTypeInternal.WorldBiomeMask.ToString() + "_" + GUID; } private string GetWorldBiomeMaskSearchString(string GUID) { return BakedMaskTypeInternal.WorldBiomeMask.ToString() + "_" + GUID; } private void SaveBakedMaskInCache(RenderTexture renderTexture, Terrain terrain, string fileName) { if (m_cacheEntries == null) { m_cacheEntries = new BakedMaskCacheEntry[0]; } //Convert the RT //Texture2D texture = GaiaUtils.ConvertRenderTextureToTexture2D(renderTexture); //texture.name = fileName; //Check if there is an entry already int length = m_cacheEntries.Length; bool found = false; for (int i = 0; i < length; i++) { if (m_cacheEntries[i].fileName == fileName) { //found an entry, update with new render texture found = true; if (m_cacheEntries[i].texture != null) { m_cacheEntries[i].texture.Release(); DestroyImmediate(m_cacheEntries[i].texture); } WriteCacheEntry(m_cacheEntries[i], renderTexture, terrain, fileName); break; } } //Not found? Need to append cache array and write the new entry at the end if (!found) { AddNewCollisionMaskCacheEntry(renderTexture, terrain, fileName); } } private void AddNewCollisionMaskCacheEntry(RenderTexture texture, Terrain terrain, string fileName) { BakedMaskCacheEntry[] newArray = new BakedMaskCacheEntry[m_cacheEntries.Length + 1]; int length2 = m_cacheEntries.Length; for (int i = 0; i < length2; i++) { newArray[i] = m_cacheEntries[i]; } newArray[newArray.Length - 1] = new BakedMaskCacheEntry(); WriteCacheEntry(newArray[newArray.Length - 1], texture, terrain, fileName); m_cacheEntries = newArray; //EditorUtility.SetDirty(this); #if UNITY_EDITOR EditorUtility.SetDirty(GaiaSessionManager.GetSessionManager(false)); #endif } private void WriteCacheEntry(BakedMaskCacheEntry entry, RenderTexture texture, Terrain terrain, string fileName) { //build the paths string assetPath = GaiaDirectories.GetTerrainCollisionDirectory(terrain) + "/" + fileName; string fullPath = assetPath.Replace("Assets", Application.dataPath); //clear old texture if (entry.texture != null && entry.texture != Texture2D.whiteTexture) { UnityEngine.Object.DestroyImmediate(entry.texture); entry.texture = null; } //overwrite texture contents entry.texture = texture; //assign Paths & name entry.fileName = fileName; entry.assetPath = assetPath; entry.fullPath = fullPath; } public RenderTexture LoadBakedMask(Terrain terrain, BakedMaskTypeInternal type, CollisionMask collisionMask = null, string worldMaskGUID = "") //float radius, int id = 0, string tag = "", string GUID = "") { if (m_cacheEntries == null) { m_cacheEntries = new BakedMaskCacheEntry[0]; } string fileName = ""; switch (type) { case BakedMaskTypeInternal.RadiusTree: fileName = GetTreeCollisionMaskFileName(terrain, collisionMask.RadiusTreeMaskValues.ToString(), collisionMask.m_Radius); break; case BakedMaskTypeInternal.RadiusTag: fileName = GetTagCollisionMaskFileName(terrain, collisionMask.m_tag, collisionMask.m_Radius); break; case BakedMaskTypeInternal.WorldBiomeMask: fileName = GetWorldBiomeMaskFilename(terrain, worldMaskGUID); break; case BakedMaskTypeInternal.LayerGameObject: fileName = GetLayerGameObjectCollisionMaskFileName(terrain, collisionMask.m_layerMask, collisionMask.m_growShrinkDistance); break; case BakedMaskTypeInternal.LayerTree: fileName = GetLayerTreeCollisionMaskFileName(terrain, collisionMask.m_layerMask, collisionMask.m_growShrinkDistance); break; default: return null; } if (fileName != "") { //file in cache? If yes, return from there int length = m_cacheEntries.Length; for (int i = 0; i < length; i++) { if (m_cacheEntries[i].fileName == fileName) //does it have a texture as well? if (m_cacheEntries[i].texture != null) { //found it, we can return it & are done return m_cacheEntries[i].texture; } } //not in cache? We need to bake it then! switch (type) { case BakedMaskTypeInternal.RadiusTree: return BakeTerrainTreeCollisions(terrain, collisionMask, collisionMask.m_Radius); case BakedMaskTypeInternal.RadiusTag: return BakeTerrainTagCollisions(terrain, collisionMask.m_tag, collisionMask.m_Radius); case BakedMaskTypeInternal.WorldBiomeMask: return BakeTerrainWorldBiomeMask(terrain, worldMaskGUID); case BakedMaskTypeInternal.LayerGameObject: return BakeLayerGameObjectCollisions(terrain, collisionMask.m_layerMask, collisionMask.m_growShrinkDistance); case BakedMaskTypeInternal.LayerTree: return BakeLayerTreeCollisions(terrain, collisionMask.m_layerMask, collisionMask.m_growShrinkDistance); default: return null; } //not in cache? try reading from disk... #if UNITY_EDITOR //Texture2D texture = (Texture2D)AssetDatabase.LoadAssetAtPath(GaiaDirectories.GetTerrainCollisionDirectory(terrain) + "/" + GetTreeCollisionMaskFileName(terrain, id, radius), typeof(Texture2D)); //if (texture != null) //{ // //found it on disk, write a copy of it in cache for next access attempt, then return the copy. // //We need to work with a copy here because otherwise we can run into issue when trying to overwrite the image file // Texture2D copy = new Texture2D(texture.width, texture.height, texture.format, true); // Graphics.CopyTexture(texture, copy); // AddNewCollisionMaskCacheEntry(copy, terrain, fileName); // return copy; //} #endif } else return null; } public void BakeAllLayerGameObjectCollisions(LayerMask layerMask, float growShrinkDistance) { foreach (Terrain t in Terrain.activeTerrains) { BakeLayerGameObjectCollisions(t, layerMask, growShrinkDistance); } } public void BakeAllLayerTreeCollisions(LayerMask layerMask, float growShrinkDistance) { foreach (Terrain t in Terrain.activeTerrains) { BakeLayerTreeCollisions(t, layerMask, growShrinkDistance); } } public void BakeAllTreeCollisions(CollisionMask collisionMask, float radius) { foreach (Terrain t in Terrain.activeTerrains) { BakeTerrainTreeCollisions(t, collisionMask, radius); } } public void BakeAllTagCollisions(string m_tag, float m_tagRadius) { foreach (Terrain t in Terrain.activeTerrains) { BakeTerrainTagCollisions(t, m_tag, m_tagRadius); } } public void ClearCache() { #if UNITY_EDITOR int length = m_cacheEntries.Length; for (int i = 0; i < length; i++) { if (m_cacheEntries[i].texture != null) { m_cacheEntries[i].texture.Release(); //DestroyImmediate(m_cacheEntries[i].texture); m_cacheEntries[i].texture = null; } } m_cacheEntries = new BakedMaskCacheEntry[0]; #endif } public void ClearCacheForSpawn() { ClearCache(); } public void SetWorldBiomeMaskDirty(string worldBiomeMaskGUID) { string searchString = GetWorldBiomeMaskSearchString(worldBiomeMaskGUID); SetDirty(searchString); } public void SetTreeDirty(string spawnRuleGUID, int layerID) { string searchString = GetTreeCollisionMaskSearchString(spawnRuleGUID); SetDirty(searchString); SetLayerIDDirty(layerID); } public void SetGameObjectDirty(GameObject gameObject) { if (gameObject.tag != "Untagged") { string searchString = GetTagCollisionMaskSearchString(gameObject.tag); SetDirty(searchString); } SetLayerIDDirty(gameObject.layer); } public void SetLayerIDDirty(int layerID) { //Iterate through the cached textures, and fish out the ones that fit the layer ID according to the layer mask, then relase them for (int i = 0; i < m_cacheEntries.Length; i++) { LayerMask layerMask = GetLayerMaskFromFileName(m_cacheEntries[i].fileName); if (layerMask != -1) { if ((layerMask & 1 << layerID) != 0) { //found the layer in the layer mask, need to release the texture if (m_cacheEntries[i].texture != null) { m_cacheEntries[i].texture.Release(); //DestroyImmediate(m_cacheEntries[i].texture); m_cacheEntries[i].texture = null; } } } } } private void SetDirty(string searchString) { //Release the affected render textures for (int i = 0; i < m_cacheEntries.Length; i++) { if (m_cacheEntries[i].fileName.Contains(searchString)) { if (m_cacheEntries[i].texture != null) { m_cacheEntries[i].texture.Release(); //DestroyImmediate(m_cacheEntries[i].texture); m_cacheEntries[i].texture = null; } } } //Remove the affected array entries m_cacheEntries = m_cacheEntries.Where(x => x.fileName.Contains(searchString) != true).ToArray(); } //public void BeginAutoSpawn() //{ // m_autoSpawnRunning = true; // m_cacheClearedForAutoSpawn = false; //} //public void EndAutoSpawn() //{ // m_autoSpawnRunning = false; // m_cacheClearedForAutoSpawn = false; //} } }