/// <summary> /// Places objects on the Tile if positions have /// already been computed. /// </summary> public void PlaceObjects() { //Get TerraSettings instance TerraConfig config = UnityEngine.Object.FindObjectOfType <TerraConfig>(); int length = config.Generator.Length; if (Positions != null && config != null) { foreach (PositionsContainer p in Positions) { ObjectContainer container = GetContainerForType(p.ObjectPlacementData); Transform parent = GetParent(); if (container != null) { foreach (Vector3 pos in p.Positions) { GameObject go = container.GetObject(parent); p.ObjectPlacementData.TransformGameObject(go, pos); //Translate object back into place (terrain origin is centered) go.transform.position = new Vector3(go.transform.position.x - length / 2f, go.transform.position.y, go.transform.position.z - length / 2f); PlacedObjects.Add(go); } } } } }
public TerraGUI(TerraConfig config) { this._config = config; _biomeList = new ReorderableBiomeList(config); _biomeDetails = new List <KeyValuePair <BiomeData, DetailSubList> >(); }
/// <summary> /// Works the same as <see cref="GenerateCoroutine"/> without the /// yield instructions. Runs synchronously. /// </summary> /// <param name="remapMin"></param> /// <param name="remapMax"></param> private void GenerateEditor(float remapMin = 0f, float remapMax = 1f) { TerraConfig.Log($"Started tile {GridPosition.ToString()}"); //Make & set heightmap MeshManager.CalculateHeightmap(GridPosition, remapMin, remapMax); MeshManager.SetTerrainHeightmap(); MeshManager.SetVisible(); //Create TilePaint object TilePaint painter = new TilePaint(this); //Make biomemap int[,] map = painter.GetBiomeMap(); //Paint terrain painter.Paint(map); //Apply details to terrain // ReSharper disable once IteratorMethodResultIsIgnored ApplyDetails(painter, map); MeshManager.SetVisible(true); TerraConfig.Log("Completed tile " + GridPosition); }
/// <summary> /// Uses raycast hit information to check whether a grass /// vertex can be placed at this location in the world. /// Checks against grass height and angle information found /// in TerraSettings. /// </summary> /// <param name="hit">Raycast hit</param> /// <returns>true if this vertex should be placed here</returns> private bool CanPlaceAt(RaycastHit hit) { float height = hit.point.y; float angle = Vector3.Angle(Vector3.up, hit.normal); TerraConfig set = TerraConfig.Instance; bool passesHeight = height >= set.Grass.GrassMinHeight && height <= set.Grass.GrassMaxHeight; bool passesAngle = angle >= set.Grass.GrassAngleMin && angle <= set.Grass.GrassAngleMax; if (set.Grass.GrassConstrainHeight && set.Grass.GrassConstrainAngle) { return(passesHeight && passesAngle); } else if (set.Grass.GrassConstrainHeight) { return(passesHeight); } else if (set.Grass.GrassConstrainAngle) { return(passesAngle); } else { return(true); } }
/// <summary> /// Adds a <see cref="UnityEngine.Terrain"/> component to this <see cref="Tile"/>'s /// gameobject and sets it up according to <see cref="TerraConfig"/>. /// Overwrites <see cref="ActiveTerrain"/> if it already exists. /// </summary> public void AddTerrainComponent() { //Destory current Terrain instance if it exists if (ActiveTerrain != null) { #if UNITY_EDITOR UnityEngine.Object.DestroyImmediate(ActiveTerrain); #else UnityEngine.Object.Destroy(ActiveTerrain); #endif } TerraConfig conf = TerraConfig.Instance; int length = conf.Generator.Length; UnityEngine.Terrain t = _tile.gameObject.AddComponent <UnityEngine.Terrain>(); t.terrainData = new TerrainData(); t.terrainData.size = new Vector3(length, conf.Generator.Amplitude, length); t.allowAutoConnect = true; t.drawInstanced = true; TerrainCollider tc = _tile.gameObject.AddComponent <TerrainCollider>(); tc.terrainData = t.terrainData; t.materialTemplate = conf.Generator.TerrainMaterial != null ? conf.Generator.TerrainMaterial : GetDefaultTerrainMaterial(); }
/// <summary> /// Places objects on the Tile if positions have /// already been computed. /// </summary> public void PlaceObjects() { //Get TerraSettings instance TerraConfig config = UnityEngine.Object.FindObjectOfType <TerraConfig>(); if (Positions != null && config != null) { foreach (PositionsContainer p in Positions) { ObjectContainer container = GetContainerForType(p.ObjectPlacementData); Transform parent = GetParent(); if (container != null) { foreach (Vector3 pos in p.Positions) { GameObject go = container.GetObject(parent); p.ObjectPlacementData.TransformGameObject(go, pos, config.Generator.Length, Tile.transform.position); PlacedObjects.Add(go); } } } } }
/// <summary> /// Creates an ObjectPlacerPreview instance with the passed /// TerraSettings (for sampling different placement settings) and /// a Mesh to place the objects on. /// </summary> /// <param name="config">TerraSettings</param> /// <param name="m">A preview mesh to draw on top of</param> public ObjectPlacerPreview(TerraConfig config, Mesh m) { _config = config; PreviewMesh = m; Placer = new ObjectRenderer(); CreateParentGO(); }
private Dictionary <int, Rect> _positions; //Cached positions at last repaint public ReorderableBiomeList(TerraConfig config) : base(config.BiomesData, null, MAX_HEIGHT) { _config = config; _positions = new Dictionary <int, Rect>(); _materialList = new List <KeyValuePair <BiomeData, ReorderableMaterialList> >(); _objectList = new List <KeyValuePair <BiomeData, ReorderableObjectList> >(); }
/// <summary> /// Generates a map of constructed from connected biome nodes. Instead of /// returning a biome map of size [resolution, resolution], it returns one /// of size [resolution + kernel size, resolution + kernel size] where /// kernel size is defined in <code>BlurUtils.GetGaussianKernelSize()</code> /// </summary> /// <param name="config"></param> /// <param name="position"></param> /// <param name="resolution"></param> /// <param name="deviation"></param> /// <returns></returns> public int[,] GetGaussianBlurrableBiomeMap(TerraConfig config, GridPosition position, int resolution, int deviation) { int[,] biomeMap = GetBiomeMap(config, position, resolution); int kernelSize = BlurUtils.GetGaussianKernelSize(deviation); int newRes = resolution + kernelSize; int[,] blurrableBiomeMap = new int[newRes, newRes]; // Fill in area surrounding the center biome map }
private IEnumerator GenerateCoroutine(Action onComplete, float remapMin = 0f, float remapMax = 1f) { TerraConfig conf = TerraConfig.Instance; TerraConfig.Log("Started tile " + GridPosition); //Make & set heightmap bool madeHm = false; MeshManager.CalculateHeightmapAsync(remapMin, remapMax, () => madeHm = true); while (!madeHm) { yield return(null); } MeshManager.SetTerrainHeightmap(); MeshManager.SetVisible(); //Create TilePaint object TilePaint painter = new TilePaint(this); //Make biomemap bool madeBm = false; int[,] map = null; conf.Worker.Enqueue(() => map = painter.GetBiomeMap(), () => madeBm = true); while (!madeBm) { yield return(null); //Skip frame until biomemap made } //Paint terrain bool madePaint = false; yield return(StartCoroutine(painter.PaintAsync(map, () => madePaint = true))); while (!madePaint) { yield return(null); } //Apply details to terrain bool madeDetails = false; yield return(StartCoroutine(ApplyDetails(painter, map, () => madeDetails = true))); while (!madeDetails) { yield return(null); } MeshManager.SetVisible(true); TerraConfig.Log("Completed tile " + GridPosition); onComplete(); }
/// <summary> /// Creates a new ObjectPlacer that uses mesh information provided /// by TerraSettings to calculate where to place objects on meshes. /// Optionally disable observing TerrainTiles if you wish to /// manage the placement of tiles manually rather than displaying /// and hiding when a Tile activates or deactivates. /// </summary> /// <param name="observeTiles">Observe Tile events?</param> public ObjectRenderer(bool observeTiles = true) { _config = TerraConfig.Instance; ObserveTiles = observeTiles; ObjectsToPlace = _config.ObjectData; Pool = new ObjectPool(this); if (ObserveTiles) { TerraEvent.OnTileActivated += OnTerrainTileActivate; TerraEvent.OnTileDeactivated += OnTerrainTileDeactivate; } }
/// <summary> /// Calculates the min and maximum values to use when applying a heightmap /// remap. This sets <see cref="RemapMax"/> and <see cref="RemapMin"/>. /// </summary> public void CalculateHeightmapRemap() { bool shouldProfile = TerraConfig.Instance.EditorState.ShowDebugMessages; Stopwatch sw = null; if (shouldProfile) { sw = new Stopwatch(); sw.Start(); } float min = float.PositiveInfinity; float max = float.NegativeInfinity; int res = Config.Generator.RemapResolution; var generator = Config.Graph.GetEndGenerator(); for (int x = 0; x < res; x++) { for (int z = 0; z < res; z++) { float value = generator.GetValue(x / (float)res, z / (float)res, 0f); if (value > max) { max = value; } if (value < min) { min = value; } } } //Set remap values for instance _remapMin = min; _remapMax = max; if (shouldProfile) { sw.Stop(); TerraConfig.Log("CalculateHeightmapRemap took " + sw.ElapsedMilliseconds + "ms to complete. " + "New min=" + min + " New max=" + max); } }
/// <summary> /// Sets the heightmap to the current LOD asynchronously /// </summary> public IEnumerator UpdateHeightmapAsync(Action onComplete, float remapMin, float remapMax) { MeshManager.Lod = GetLodLevel(); bool updatedHm = false; TerraConfig.Log("Updating heightmap start"); MeshManager.CalculateHeightmapAsync(remapMin, remapMax, () => { MeshManager.SetTerrainHeightmap(); updatedHm = true; }); while (!updatedHm) { yield return(null); } if (onComplete != null) { onComplete(); } }
/// <summary> /// Sets the <see cref="UnityEngine.Terrain"/> component's heightmap to this instances' /// <see cref="Heightmap"/>. If <see cref="UnityEngine.Terrain"/> hasn't been created, /// it is added as a component. /// </summary> /// <remarks>Since this method creates and adds a <see cref="UnityEngine.Terrain"/> /// component, it is not thread safe.</remarks> /// <param name="heightmap">Optionally use the passed heightmap instead of /// <see cref="TileMesh"/>'s <see cref="Heightmap"/></param> public void SetTerrainHeightmap(float[,] heightmap = null) { float[,] hm = heightmap ?? Heightmap; if (hm == null) { return; } if (ActiveTerrain == null) { AddTerrainComponent(); } //ReSharper disable once PossibleNullReferenceException TerrainData td = ActiveTerrain.terrainData; TerraConfig conf = TerraConfig.Instance; int length = conf.Generator.Length; td.heightmapResolution = HeightmapResolution; td.SetHeights(0, 0, hm); td.size = new Vector3(length, conf.Generator.Amplitude, length); ActiveTerrain.Flush(); }
/// <summary> /// Creates a heightmap of resolution <see cref="HeightmapResolution"/> asynchronously. /// If a <see cref="Heightmap"/> of the same resolution or higher has already been /// created, this method does nothing. /// A heightmap is 2D array of floats that represents the Y values (or heights) /// of to-be created vertices in 3D space. /// </summary> /// <param name="remapMin">Optionally linear transform the heightmap from [min, max] to [0, 1]</param> /// <param name="remapMax">Optionally linear transform the heightmap from [min, max] to [0, 1]</param> /// <param name="onComplete">Called when the heightmap has been created</param> public void CalculateHeightmapAsync(float remapMin = 0f, float remapMax = 1f, Action onComplete = null) { _lastGeneratedLodLevel = _tile.GetLodLevel(); Lod = _lastGeneratedLodLevel; bool shouldProfile = TerraConfig.Instance.EditorState.ShowDebugMessages; Stopwatch wsw = null; if (shouldProfile) { wsw = new Stopwatch(); wsw.Start(); } TerraConfig.Instance.Worker.Enqueue(() => { Stopwatch sw = null; if (shouldProfile) { sw = new Stopwatch(); Profiler.BeginThreadProfiling("Heightmap Workers", "Tile " + _tile.GridPosition); sw.Start(); } CalculateHeightmap(null, remapMin, remapMax); if (shouldProfile) { sw.Stop(); TerraConfig.Log("Tile " + _tile.GridPosition + " heightmap time: " + sw.ElapsedMilliseconds); Profiler.EndThreadProfiling(); } }, () => { if (shouldProfile) { wsw.Stop(); MTDispatch.Instance().Enqueue(() => { TerraConfig.Log("CalculateHM worker time elapsed " + wsw.ElapsedMilliseconds); onComplete(); }); } }); }
/// <summary> /// Generates a map of biomes constructed from connected biome nodes. /// </summary> /// <param name="config">TerraConfig instance for pulling length & spread</param> /// <param name="position">Position of in Terra grid units of this map</param> /// <param name="resolution">Resolution of this biome map</param> public int[,] GetBiomeMap(TerraConfig config, GridPosition position, int resolution) { return(GetBiomeMap(position, config.Generator.Length, config.Generator.Spread, resolution)); }
// Use this for initialization void Start() { TerraConfig config = FindObjectOfType <TerraConfig>(); //m = settings.CustomMaterial; }
public TerraGUI(TerraConfig config) { this._config = config; }
/// <summary> /// Updates tiles that are surrounding the tracked GameObject /// asynchronously. When calling this method using /// <see cref="MonoBehaviour.StartCoroutine(IEnumerator)"/>, /// tiles are generated once per frame /// </summary> public IEnumerator UpdateTiles() { List <GridPosition> nearbyPositions = GetTilePositionsFromRadius(); List <GridPosition> newPositions = Cache.GetNewTilePositions(nearbyPositions); List <Tile> toRegenerate = new List <Tile>(); RemoveOldPositions(ref nearbyPositions, ref toRegenerate); //Add new positions _queuedTiles = newPositions.Count + toRegenerate.Count; foreach (GridPosition pos in newPositions) { if (_isGenerating && Application.isPlaying) //Wait and generate one tile per frame { yield return(null); } Tile cached = Cache.GetCachedTileAtPosition(pos); //Attempt to pull from cache, generate if not available if (cached != null) { if (cached.IsHeightmapLodValid()) //Cached tile is valid, use it { AddTile(cached); _queuedTiles--; continue; } TerraConfig.Log("Cached tile " + cached + " has heightmap res=" + cached.MeshManager.HeightmapResolution + ". requested res=" + cached.GetLodLevel().Resolution + ". Regenerating."); toRegenerate.Add(cached); Cache.AddActiveTile(cached); continue; } _isGenerating = true; CreateTileAt(pos, tile => { _queuedTiles--; if (_queuedTiles == 0) { UpdateNeighbors(newPositions.ToArray(), false); } _isGenerating = false; }); } //Regenerate tiles with outdated heightmaps for (int i = 0; i < toRegenerate.Count; i++) { Tile t = toRegenerate[i]; TerraConfig.Log("Active tile " + t + " has heightmap res=" + t.MeshManager.HeightmapResolution + ". requested res=" + t.GetLodLevel().Resolution + ". Regenerating."); //Generate one tile per frame if (Application.isPlaying) { yield return(null); } if (Application.isPlaying) { Config.StartCoroutine(t.UpdateHeightmapAsync(() => { UpdateNeighbors(new[] { t.GridPosition }, false); _queuedTiles--; }, RemapMin, RemapMax)); } else { t.UpdateHeightmap(RemapMin, RemapMax); } } //If tiles were updated synchronously, notify queue completion if (newPositions.Count > 0 && _queuedTiles == 0) { UpdateNeighbors(newPositions.ToArray(), false); } }
public ReorderableMaterialList(TerraConfig config, DetailData detail) : base(detail.SplatsData, null, MAX_HEIGHT) { _detail = detail; }
public ObjectPlacer(TerraConfig config) { Config = config; //ObjectsToPlace = Settings.ObjectData; }
public DetailSubList(TerraConfig config, DetailData details) { ObjectsList = new ReorderableObjectList(config, details); MaterialsList = new ReorderableMaterialList(config, details); }
void Start() { TerraConfig t = FindObjectOfType <TerraConfig>(); //m = t.CustomMaterial; }
private Dictionary <int, Rect> _positions; //Cached positions at last repaint public ReorderableObjectList(TerraConfig config, DetailData detail) : base(detail.ObjectData, null, MAX_HEIGHT) { _positions = new Dictionary <int, Rect>(); }