void ApplyAt(DeformationData data) { currentHeights = data.terrainData.GetHeights(data.X - data.W/2, data.Y - data.H/2, W, H); for (int i = 0; i < data.W; i++) { for (int j = 0; j < data.H; j++) { if (data.Type == DeformationType.Additive) currentHeights[i, j] += data.heightmap[i, j]; else currentHeights[i, j] *= data.heightmap[i, j]; } } data.terrainData.SetHeights(data.X - data.W/2, data.Y - data.H/2, currentHeights); }
// - H, W . // , H - , W - . float GetDepth(float distanceToCenter, int holeDepth, int wallsHeight, int holeRadius, int craterRadius) { if (distanceToCenter <= holeRadius) { return (Mathf.Pow(distanceToCenter, 2) * (holeDepth + wallsHeight) / Mathf.Pow(holeRadius, 2)) - holeDepth; } else if (distanceToCenter <= craterRadius) { return Mathf.Pow(craterRadius - distanceToCenter, 2) * wallsHeight / Mathf.Pow(craterRadius - holeRadius, 2); } else return 0f; } float[,] Generate(int holeDepth, int wallsHeight, int holeRadius, int craterRadius) { var heightmap = new float[W, H]; for (var x = 0; x < W; x++) { for (var y = 0; y < H; y++) { var offsetX = x - W / 2; var offsetY = y - H / 2; var depth = GetDepth(Mathf.Sqrt(offsetX * offsetX + offsetY * offsetY), holeDepth, wallsHeight, holeRadius, craterRadius); heightmap[x, y] = depth; } } }
: Super Terrain. .
/// <summary> /// Compound terrain object. /// </summary> public class SuperTerrain { /// <summary> /// Contains the array of subterrain objects /// </summary> private Terrain[,] subterrains; /// <summary> /// Superterrain detail. The resulting superterrain is 2^detail terrains. /// </summary> /// <value>The detail.</value> public int Detail { get; private set; } /// <summary> /// Parent gameobject to nest created terrains into. /// </summary> /// <value>The parent.</value> public Transform Parent { get; private set; } /// <summary> /// Builds the new terrain object. /// </summary> /// <returns>The new terrain.</returns> private Terrain BuildNewTerrain() { // Using this divisor because of internal workings of the engine. // The resulting terrain is still going to be subterrain size. var divisor = GameplayConstants.SuperTerrainHeightmapResolution / GameplayConstants.SubterrainSize * 2; var terrainData = new TerrainData { size = new Vector3 (GameplayConstants.SubterrainSize / divisor, GameplayConstants.WorldHeight, GameplayConstants.SubterrainSize / divisor), heightmapResolution = GameplayConstants.SuperTerrainHeightmapResolution }; var newTerrain = Terrain.CreateTerrainGameObject(terrainData).GetComponent<Terrain>(); newTerrain.transform.parent = Parent; newTerrain.transform.gameObject.layer = GameplayConstants.TerrainLayer; newTerrain.heightmapPixelError = GameplayConstants.SuperTerrainPixelError; return newTerrain; } /// <summary> /// Initializes the terrain array and moves the terrain transforms to match their position in the array. /// </summary> private void InitializeTerrainArray() { subterrains = new Terrain[Detail, Detail]; for (int x = 0; x < Detail; x++) { for (int y = 0; y < Detail; y++) { subterrains[y, x] = BuildNewTerrain(); subterrains[y, x].transform.Translate(new Vector3(x * GameplayConstants.SubterrainSize, 0f, y * GameplayConstants.SubterrainSize)); } } } /// <summary> /// Initializes a new instance of the <see cref="SuperTerrain"/> class. /// </summary> /// <param name="detail">Superterrain detail. The resultsing superterrain is 2^detail terrains.</param> /// <param name="parent">Parent gameobject to nest created terrains into.</param> public SuperTerrain(int detail, Transform parent) { Detail = detail; Parent = parent; InitializeTerrainArray(); SetNeighbors(); } /// <summary> /// Iterates through the terrain object and sets the neightbours to match LOD settings. /// </summary> private void SetNeighbors() { ForEachSubterrain ((x, y, subterrain) => { subterrain.SetNeighbors(SafeGetTerrain(x - 1, y), SafeGetTerrain(x, y + 1), SafeGetTerrain(x + 1, y), SafeGetTerrain(x, y - 1)); }); } #region [ Array Helpers ] /// <summary> /// Safely retrieves the terrain object from the array. /// </summary> /// <param name="x">The x coordinate.</param> /// <param name="y">The y coordinate.</param> private Terrain SafeGetTerrain(int x, int y) { if (x < 0 || y < 0 || x >= Detail || y >= Detail) return null; return subterrains[y, x]; } /// <summary> /// Iterates over terrain object and executes the given action /// </summary> /// <param name="lambda">Lambda.</param> private void ForEachSubterrain(Action<int, int, Terrain> lambda) { for (int x = 0; x < Detail; x++) { for (int y = 0; y < Detail; y++) { lambda (x, y, SafeGetTerrain(x, y)); } } } #endregion }
/// <summary> /// Sets the global heightmap to match the given one. Given heightmap must match the (SubterrainHeightmapResolution * Detail). /// </summary> /// <param name="heightmap">Heightmap to set the heights from.</param> public void SetGlobalHeightmap(float[,] heightmap) { ForEachSubterrain((x, y, subterrain) => { var chunkStartX = x * GameplayConstants.SuperTerrainHeightmapResolution; var chunkStartY = y * GameplayConstants.SuperTerrainHeightmapResolution; var nextChunkStartX = chunkStartX + GameplayConstants.SuperTerrainHeightmapResolution + 1; var nextChunkStartY = chunkStartY + GameplayConstants.SuperTerrainHeightmapResolution + 1; var sumHm = GetSubHeightMap(heightmap, nextChunkStartX, nextChunkStartY, chunkStartX, chunkStartY)); subterrain.terrainData.SetHeights(0, 0, subHm); }); } /// <summary> /// Retrieves the minor heightmap from the entire heightmap array. /// </summary> /// <returns>The minor height map.</returns> /// <param name="heightMap">Major heightmap.</param> /// <param name="Xborder">Xborder.</param> /// <param name="Yborder">Yborder.</param> /// <param name="x">The x coordinate.</param> /// <param name="y">The y coordinate.</param> private float[,] GetSubHeightMap (float[,] heightMap, int Xborder, int Yborder, int x, int y) { if (Xborder == x || Yborder == y || x < 0 || y < 0) return null; var temp = new float[Yborder - y, Xborder - x]; for (int i = x; i < Xborder; i++) { for(int j = y; j < Yborder; j++) { temp[j - y, i - x] = heightMap[j, i]; } } return temp; }
namespace Habitat.Game { /// <summary> /// Contains the gameplay constants. /// </summary> public static class GameplayConstants { /// <summary> /// The height of the world. Used in terrain raycasting and Superterrain generation. /// </summary> public const float WorldHeight = 512f; /// <summary> /// Number of the "Terrain" layer /// </summary> public const int TerrainLayer = 8; /// <summary> /// Calculated mask for raycasting against the terrain. /// </summary> public const int TerrainLayerMask = 1 << TerrainLayer; /// <summary> /// Superterrain part side size. /// </summary> public const int SubterrainSize = 256; /// <summary> /// Heightmap resolution for the SuperTerrain. /// </summary> public const int SuperTerrainHeightmapResolution = 512; /// <summary> /// Pixel error for the SuperTerrain. /// </summary> public const int SuperTerrainPixelError = 1; } }
namespace Habitat.DynamicTerrain.Deformation { public abstract class TerrainDeformation { /// <summary> /// Height of the deformation in hightmap pixels. /// </summary> public int H { get; private set; } /// <summary> /// Width of the deformation in hightmap pixels. /// </summary> public int W { get; private set; } /// <summary> /// Heightmap matrix object /// </summary> public float[,] Heightmap { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="Habitat.DynamicTerrain.Deformation.TerrainDeformation"/> class. /// </summary> /// <param name="height">Height in heightmap pixels</param> /// <param name="width">Width in heightmap pixels</param> protected TerrainDeformation(int height, int width) { H = height; W = width; Heightmap = new float[height,width]; } /// <summary> /// Initializes a new instance of the <see cref="Habitat.DynamicTerrain.Deformation.TerrainDeformation"/> class. /// </summary> /// <param name="bitmap">Normalized heightmap matrix.</param> protected TerrainDeformation(float[,] bitmap) { Heightmap = bitmap; H = bitmap.GetUpperBound(0); W = bitmap.GetUpperBound(1); } /// <summary> /// Applies deformation to the point. Additive by default. /// </summary> /// <returns>The to point.</returns> /// <param name="currentValue">Current value.</param> /// <param name="newValue">New value.</param> public virtual float ApplyToPoint(float currentValue, float newValue) { return currentValue + newValue; } /// <summary> /// Generates the heightmap matrix based on constructor parameters. /// </summary> public abstract TerrainDeformation Generate(); } }
/// <summary> /// Compound terrain object. /// </summary> public class SuperTerrain { //... ///<summary> ///Resolution of each terrain in the SuperTerrain; ///</summary> private readonly int hmResolution = GameplayConstants.SuperTerrainHeightmapResolution; /// Applies the partial heightmap to a single terrain object. /// </summary> /// <param name="heightmap">Heightmap.</param> /// <param name="chunkX">Terrain x.</param> /// <param name="chunkY">Terrain y.</param> /// <param name="startX">Start x.</param> /// <param name="startY">Start y.</param> /// <param name="type">Deformation type.</param> private void ApplyPartialHeightmap(float[,] heightmap, int chunkX, int chunkY, int startX, int startY, TerrainDeformation td) { if (heightmap == null) return; var current = subterrains [chunkY, chunkX].terrainData.GetHeights( startX, startY, heightmap.GetUpperBound (1) + 1, heightmap.GetUpperBound (0) + 1); for (int x = 0; x <= heightmap.GetUpperBound(1); x++) { for (int y = 0; y <= heightmap.GetUpperBound(0); y++) { current[y, x] = td.ApplyToPoint(current[y, x], heightmap[y, x]); } } subterrains[chunkY, chunkX].terrainData.SetHeights (startX, startY, current); } private int TransformCoordinate (float coordinate) { return Mathf.RoundToInt(coordinate * hmResolution / GameplayConstants.SubterrainSize); } /// <summary> /// Applies the local deformation. /// </summary> /// <param name="deformation">Deformation.</param> /// <param name="x">The x coordinate.</param> /// <param name="y">The y coordinate.</param> public void ApplyDeformation(TerrainDeformation td, float xCoord, float yCoord) { int x = TransformCoordinate (xCoord); int y = TransformCoordinate (yCoord); var chunkX = x / hmResolution; var chunkY = y / hmResolution; ApplyPartialHeightmap(GetBottomLeftSubmap(td, x, y), chunkX - 1, chunkY - 1, hmResolution, hmResolution, td); ApplyPartialHeightmap(GetLeftSubmap(td, x, y), chunkX - 1, chunkY, hmResolution, y % hmResolution, td); ApplyPartialHeightmap(GetTopLeftSubmap(td, x, y), chunkX - 1, chunkY + 1, hmResolution, 0, td); ApplyPartialHeightmap(GetBottomSubmap(td, x, y), chunkX, chunkY - 1, x % hmResolution, hmResolution, td); ApplyPartialHeightmap(GetBottomRightSubmap(td, x, y), chunkX + 1, chunkY - 1, 0, hmResolution, td); ApplyPartialHeightmap(GetMiddleSubmap(td, x, y), chunkX, chunkY, x % hmResolution, y % hmResolution, td); ApplyPartialHeightmap(GetTopSubmap(td, x, y), chunkX, chunkY + 1, x % hmResolution, 0, td); ApplyPartialHeightmap(GetRightSubmap(td, x, y), chunkX + 1, chunkY, 0, y % hmResolution, td); ApplyPartialHeightmap(GetTopRightSubmap(td, x, y), chunkX + 1, chunkY + 1, 0, 0, td); } ///Retrieves the bottom-left part of the deformation (Subheightmap, applied to the bottom ///left chunk of the targetChunk) or null if no such submap has to be applied. ///Covers corner cases private float[,] GetBottomLeftSubmap(TerrainDeformation td, int x, int y) { if (x % hmResolution == 0 && y % hmResolution == 0 && x / hmResolution > 0 && y / hmResolution > 0) { return new float[,] {{ td.Heightmap[0, 0] }}; } return null; } ///Retrieves the left part of the deformation (Subheightmap, applied to the ///left chunk of the targetChunk) or null if no such submap has to be applied. ///Covers edge cases private float[,] GetLeftSubmap(TerrainDeformation td, int x, int y) { if (x % hmResolution == 0 && x / hmResolution > 0) { int endY = Math.Min((y / hmResolution + 1) * hmResolution, y + td.H); return GetSubHeightMap(td.Heightmap, 1, endY - y, 0, 0); } return null; } ///Retrieves the bottom part of the deformation (Subheightmap, applied to the bottom ///chunk of the targetChunk) or null if no such submap has to be applied. ///Covers edge cases private float[,] GetBottomSubmap(TerrainDeformation td, int x, int y) { if (y % hmResolution == 0 && y / hmResolution > 0) { int endX = Math.Min((x / hmResolution + 1) * hmResolution, x + td.W); return GetSubHeightMap(td.Heightmap, endX - x, 1, 0, 0); } return null; } ///Retrieves the top-left part of the deformation (Subheightmap, applied to the top ///left chunk of the targetChunk) or null if no such submap has to be applied. ///Covers split edge cases private float[,] GetTopLeftSubmap(TerrainDeformation td, int x, int y) { if (x % hmResolution == 0 && x / hmResolution > 0) { int startY = (y / hmResolution + 1) * hmResolution; int endY = y + td.H; if (startY > endY) return null; return GetSubHeightMap(td.Heightmap, 1, td.H, 0, startY - y); } return null; } ///Retrieves the bottom-right part of the deformation (Subheightmap, applied to the bottom ///right chunk of the targetChunk) or null if no such submap has to be applied. ///Covers split edge cases private float[,] GetBottomRightSubmap(TerrainDeformation td, int x, int y) { if (y % hmResolution == 0 && y / hmResolution > 0) { int startX = (x / hmResolution + 1) * hmResolution; int endX = x + td.W; if (startX > endX) return null; return GetSubHeightMap(td.Heightmap, td.W, 1, startX - x, 0); } return null; } ///Retrieves the main deformation part. private float[,] GetMiddleSubmap(TerrainDeformation td, int x, int y) { int endX = Math.Min((x / hmResolution + 1) * hmResolution, x + td.W); int endY = Math.Min((y / hmResolution + 1) * hmResolution, y + td.H); return GetSubHeightMap(td.Heightmap, Math.Min(endX - x + 1, td.Heightmap.GetUpperBound(0) + 1), Math.Min(endY - y + 1, td.Heightmap.GetUpperBound(1) + 1), 0, 0); } ///Retrieves the top deformation part or null if none required private float[,] GetTopSubmap(TerrainDeformation td, int x, int y) { int startY = (y / hmResolution + 1) * hmResolution; if (y + td.H < startY) return null; int endX = Math.Min((x / hmResolution + 1) * hmResolution, x + td.W); return GetSubHeightMap(td.Heightmap, Math.Min (endX - x + 1, td.Heightmap.GetUpperBound(0) + 1), td.H, 0, startY - y); } ///Retrieves the left deformation part or null if none required private float[,] GetRightSubmap(TerrainDeformation td, int x, int y) { int startX = (x / hmResolution + 1) * hmResolution; if (x + td.W < startX) return null; int endY = Math.Min((y / hmResolution + 1) * hmResolution, y + td.H); return GetSubHeightMap(td.Heightmap, td.W, Math.Min(endY - y + 1, td.Heightmap.GetUpperBound(1) + 1), startX - x, 0); } ///Retrieves the top-right part of the main deformation. private float[,] GetTopRightSubmap(TerrainDeformation td, int x, int y) { int startX = (x / hmResolution + 1) * hmResolution; int startY = (y / hmResolution + 1) * hmResolution; if (x + td.W < startX || y + td.H < startY) return null; return GetSubHeightMap(td.Heightmap, td.W, td.H, startX - x, startY - y); } }
Source: https://habr.com/ru/post/269645/
All Articles