├── LICENSE ├── README.md ├── results ├── DiamondSquare1.png ├── DiamondSquare2.png ├── DiamondSquare3.png ├── DiamondSquare4.png └── DiamondSquare5.png └── src ├── DiamondSquare.cs ├── MouseMovement.cs ├── RoughnessSlider.cs └── WaterLevel.cs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Robert Stivanson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-Diamond-Square 2 | A lightweight Diamond-Square component for Unity. Last updated for Unity 5.4.2f2. 3 | 4 | # Diamond-Square 5 | The Diamond-square algorithm is a two-dimensional version of the Midpoint displacement algorithm. The algorithm is used to manipluate heightmaps to produce a natural-esk terrain. 6 | 7 | More details can be found at the following links: 8 | https://en.wikipedia.org/wiki/Diamond-square_algorithm 9 | http://www.paulboxley.com/blog/2011/03/terrain-generation-mark-one 10 | http://stevelosh.com/blog/2016/06/diamond-square/ 11 | 12 | # How To Use 13 | 1. Import the script into an existing Unity project. 14 | 2. Attach the script to a GameObject either in the inspector or during runtime. 15 | 3. From a seperate script, during runtime, call the Reset function to flatten the terrain. 16 | 4. Call the ExecuteDiamondSquare function to execute the Diamond-Square algorithm. 17 | 18 | # Results 19 | The following images show the result of this algorithm. The water depicted in the photos are NOT a result of the algorithm; this was added seperatly to show height. 20 | 21 | ![Diamond Square Result](results/DiamondSquare1.png) 22 | ![Diamond Square Result](results/DiamondSquare2.png) 23 | ![Diamond Square Result](results/DiamondSquare3.png) 24 | ![Diamond Square Result](results/DiamondSquare4.png) 25 | ![Diamond Square Result](results/DiamondSquare5.png) 26 | -------------------------------------------------------------------------------- /results/DiamondSquare1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RStivanson/unity-diamond-square/ac0a5ebcf6da90f5573bcf07f9a60168d553e1eb/results/DiamondSquare1.png -------------------------------------------------------------------------------- /results/DiamondSquare2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RStivanson/unity-diamond-square/ac0a5ebcf6da90f5573bcf07f9a60168d553e1eb/results/DiamondSquare2.png -------------------------------------------------------------------------------- /results/DiamondSquare3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RStivanson/unity-diamond-square/ac0a5ebcf6da90f5573bcf07f9a60168d553e1eb/results/DiamondSquare3.png -------------------------------------------------------------------------------- /results/DiamondSquare4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RStivanson/unity-diamond-square/ac0a5ebcf6da90f5573bcf07f9a60168d553e1eb/results/DiamondSquare4.png -------------------------------------------------------------------------------- /results/DiamondSquare5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RStivanson/unity-diamond-square/ac0a5ebcf6da90f5573bcf07f9a60168d553e1eb/results/DiamondSquare5.png -------------------------------------------------------------------------------- /src/DiamondSquare.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; // MonoBehaviour, TerrainCollider, TerrainData 2 | 3 | /// 4 | /// When attached to a Terrain GameObject, it can be used to randomized the heights 5 | /// using the Diamond-Square algorithm 6 | /// 7 | [RequireComponent(typeof(TerrainCollider))] 8 | public class DiamondSquare : MonoBehaviour { 9 | // Data container for heights of a terrain 10 | private TerrainData data; 11 | // Size of the sides of a terrain 12 | private int size; 13 | // Flag to set random corner heights when terrain is reset 14 | private bool randomizeCornerValues = false; 15 | // 2D array of heights 16 | private float[,] heights; 17 | // Control variable to determine smoothness of heights 18 | private float roughness = 0.8f; 19 | 20 | /// 21 | /// Getters / Setters for the roughness value. 22 | /// 23 | public float Roughness { 24 | get { return roughness; } 25 | set { roughness = Mathf.Clamp(value, 0.001f, 1.999f); } 26 | } 27 | 28 | /// 29 | /// Used for initialization 30 | /// 31 | private void Awake() { 32 | data = transform.GetComponent().terrainData; 33 | size = data.heightmapWidth; 34 | 35 | SetSeed((int)Random.value); 36 | Reset(); 37 | 38 | return; 39 | } 40 | 41 | /// 42 | /// Sets the seed of the random number generator 43 | /// 44 | /// A value that influences the random number generator 45 | public void SetSeed(int seed) { 46 | Random.InitState(seed); 47 | 48 | return; 49 | } 50 | 51 | /// 52 | /// Flips the value of the randomizeCornerValues flag 53 | /// 54 | public void ToggleRandomizeCornerValues() { 55 | randomizeCornerValues = !randomizeCornerValues; 56 | 57 | return; 58 | } 59 | 60 | /// 61 | /// Resets the values of the terrain. If randomizeCornerValues is true then the 62 | /// corner heights will be randomized, else it will be flat. 63 | /// 64 | public void Reset() { 65 | heights = new float[size, size]; 66 | 67 | // If the corners need to be randomized 68 | if (randomizeCornerValues) { 69 | heights[0, 0] = Random.value; 70 | heights[size - 1, 0] = Random.value; 71 | heights[0, size - 1] = Random.value; 72 | heights[size - 1, size - 1] = Random.value; 73 | } 74 | 75 | // Update the terrain data 76 | data.SetHeights(0, 0, heights); 77 | 78 | return; 79 | } 80 | 81 | /// 82 | /// Executes the DiamondSquare algorithm on the terrain. 83 | /// 84 | public void ExecuteDiamondSquare() { 85 | heights = new float[size, size]; 86 | float average, range = 0.5f; 87 | int sideLength, halfSide, x, y; 88 | 89 | // While the side length is greater than 1 90 | for (sideLength = size - 1; sideLength > 1; sideLength /= 2) { 91 | halfSide = sideLength / 2; 92 | 93 | // Run Diamond Step 94 | for (x = 0; x < size - 1; x += sideLength) { 95 | for (y = 0; y < size - 1; y += sideLength) { 96 | // Get the average of the corners 97 | average = heights[x, y]; 98 | average += heights[x + sideLength, y]; 99 | average += heights[x, y + sideLength]; 100 | average += heights[x + sideLength, y + sideLength]; 101 | average /= 4.0f; 102 | 103 | // Offset by a random value 104 | average += (Random.value * (range * 2.0f)) - range; 105 | heights[x + halfSide, y + halfSide] = average; 106 | } 107 | } 108 | 109 | // Run Square Step 110 | for (x = 0; x < size - 1; x += halfSide) { 111 | for (y = (x + halfSide) % sideLength; y < size - 1; y += sideLength) { 112 | // Get the average of the corners 113 | average = heights[(x - halfSide + size - 1) % (size - 1), y]; 114 | average += heights[(x + halfSide) % (size - 1), y]; 115 | average += heights[x, (y + halfSide) % (size - 1)]; 116 | average += heights[x, (y - halfSide + size - 1) % (size - 1)]; 117 | average /= 4.0f; 118 | 119 | // Offset by a random value 120 | average += (Random.value * (range * 2.0f)) - range; 121 | 122 | // Set the height value to be the calculated average 123 | heights[x, y] = average; 124 | 125 | // Set the height on the opposite edge if this is 126 | // an edge piece 127 | if (x == 0) { 128 | heights[size - 1, y] = average; 129 | } 130 | 131 | if (y == 0) { 132 | heights[x, size - 1] = average; 133 | } 134 | } 135 | } 136 | 137 | // Lower the random value range 138 | range -= range * 0.5f * roughness; 139 | } 140 | 141 | // Update the terrain heights 142 | data.SetHeights(0, 0, heights); 143 | 144 | return; 145 | } 146 | 147 | /// 148 | /// Returns the amount of vertices to skip using the given depth. 149 | /// 150 | /// The vertice detail depth on the height array 151 | /// Amount of vertices to skip 152 | public int GetStepSize(int depth) { 153 | // Return an invalid step size if the depth is invalid 154 | if (!ValidateDepth(depth)) { 155 | return -1; 156 | } 157 | 158 | // Return the amount of vertices to skip 159 | return (int)((size - 1) / Mathf.Pow(2, (depth - 1))); 160 | } 161 | 162 | /// 163 | /// Returns the maximum depth for this terrain's size. 164 | /// 165 | /// Maximum depth for this terrain 166 | public int MaxDepth() { 167 | // 0.69314718056f = Natural Log of 2 168 | return (int)((Mathf.Log(size - 1) / 0.69314718056f) + 1); 169 | } 170 | 171 | /// 172 | /// Returns false if the depth is above zero and below maximum depth, true otheriwse 173 | /// 174 | /// The vertice detail depth on the height array 175 | /// 176 | private bool ValidateDepth(int depth) { 177 | if (depth > 0 && depth <= MaxDepth()) { 178 | return true; 179 | } 180 | 181 | return false; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/MouseMovement.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; // MonoBehaviour 2 | 3 | /// 4 | /// Grabs the mouses movement and updates the camera as the mouse moves. This actually can be used on any object but 5 | /// for this project it is used on a camera object. 6 | /// 7 | [DisallowMultipleComponent] 8 | public class MouseMovement : MonoBehaviour { 9 | // Where we want our camera to focus 10 | Vector3 lookAt = Vector3.zero; 11 | 12 | // Current distance between lookAt and the camera 13 | float distance; 14 | // Camera movement speed in the x direction 15 | float xSpeed = 120.0f; 16 | // Camera movement speed in the y direction 17 | float ySpeed = 120.0f; 18 | // Camera zoom speed 19 | float scrollSpeed = 75.0f; 20 | 21 | float x = 0.0f, y = 0.0f; 22 | 23 | /// 24 | /// Used for initialization, this is called when the script is created before any Start functions. 25 | /// 26 | void Awake() { 27 | // Get the current camera angles in euler angles 28 | x = transform.eulerAngles.y; 29 | y = transform.eulerAngles.x; 30 | 31 | // Calculate the distance between lookAt and the camera 32 | distance = Vector3.Distance(transform.position, lookAt); 33 | return; 34 | } 35 | 36 | /// 37 | /// Called every frame, updates the camera position and rotation. 38 | /// 39 | void Update() { 40 | // Cameras current rotation 41 | Quaternion rotation = transform.rotation; 42 | // Cameras current positon 43 | Vector3 newPosition = transform.position; 44 | 45 | // Adjust the lookAt position 46 | if (Input.GetKeyDown(KeyCode.UpArrow)) { 47 | lookAt.y += 10; 48 | } else if (Input.GetKeyDown(KeyCode.DownArrow)) { 49 | lookAt.y -= 10; 50 | } 51 | 52 | // Adjust the distance to lookAt based on the mouse scroll wheel movement 53 | distance -= Input.GetAxis("Mouse ScrollWheel") * scrollSpeed; 54 | 55 | // If we are clicking the left mouse button 56 | if (Input.GetMouseButton(0)) { 57 | // Get the direction the mouse is moving and modify it by our speeds 58 | // Time.deltaTime is to keep speeds normal looking through frame lags 59 | x += Input.GetAxis("Mouse X") * xSpeed * Time.deltaTime; 60 | y -= Input.GetAxis("Mouse Y") * ySpeed * Time.deltaTime; 61 | 62 | // Convert the requested movements into a Quaternion rotation 63 | rotation = Quaternion.Euler(y, x, 0); 64 | } 65 | 66 | // Calculate the new position based on rotation and distance from lookAt 67 | newPosition = rotation * new Vector3(0.0f, 0.0f, -distance) + lookAt; 68 | 69 | // Update the cameras position and rotation 70 | transform.rotation = rotation; 71 | transform.position = newPosition; 72 | 73 | return; 74 | } 75 | } -------------------------------------------------------------------------------- /src/RoughnessSlider.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; // MonoBehaviour 2 | using UnityEngine.UI; // Slider 3 | 4 | /// 5 | /// This class is to be attached to a slider object and given an object with the 6 | /// DiamondSqaure script attached. 7 | /// 8 | [RequireComponent(typeof(Slider))] 9 | public class RoughnessSlider : MonoBehaviour { 10 | // The DiamondSquare script to be adjusted 11 | public DiamondSquare diamondSquare; 12 | // The slider to get the values from 13 | private Slider slider; 14 | 15 | /// 16 | /// Used for initialization. 17 | /// 18 | public void Start() { 19 | slider = GetComponent(); 20 | slider.value = diamondSquare.Roughness; 21 | 22 | return; 23 | } 24 | 25 | /// 26 | /// Sets the roughness in the DiamondSquare script to the value of the slider 27 | /// 28 | public void SetRoughness() { 29 | diamondSquare.Roughness = slider.value; 30 | 31 | return; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/WaterLevel.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; // MonoBehaviour, Transform, and Vector3 2 | using UnityEngine.UI; // Slider 3 | 4 | /// 5 | /// This class is to be attached to a slider object and given an object that 6 | /// represents the water. 7 | /// 8 | [RequireComponent(typeof(Slider))] 9 | public class WaterLevel : MonoBehaviour { 10 | // The object to be adjusted 11 | public Transform water; 12 | // The slider to get the values from 13 | private Slider slider; 14 | 15 | /// 16 | /// Used for initialization. 17 | /// 18 | public void Start() { 19 | // Get the slider component and sync it with the water object 20 | slider = GetComponent(); 21 | slider.value = water.position.y; 22 | 23 | return; 24 | } 25 | 26 | /// 27 | /// Adjusts the Y position of the water object. 28 | /// 29 | public void ChangeWaterLevel() { 30 | // Get the waters current position and change its y value 31 | Vector3 newPosition = water.position; 32 | newPosition.y = slider.value; 33 | 34 | // Update the waters position 35 | water.position = newPosition; 36 | 37 | return; 38 | } 39 | } --------------------------------------------------------------------------------