summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/ExampleScenes/Scenes/InfiniteWorld/ProceduralWorld.cs
blob: 4e369a4b31a8abcfc25f301dfdfe31b74fbd901a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace Pathfinding.Examples {
	/// <summary>Example script for generating an infinite procedural world</summary>
	[HelpURL("https://arongranberg.com/astar/documentation/stable/proceduralworld.html")]
	public class ProceduralWorld : MonoBehaviour {
		public Transform target;

		public ProceduralPrefab[] prefabs;

		/// <summary>How far away to generate tiles</summary>
		public int range = 1;

		public int disableAsyncLoadWithinRange = 1;

		/// <summary>World size of tiles</summary>
		public float tileSize = 100;
		public int subTiles = 20;

		/// <summary>
		/// Enable static batching on generated tiles.
		/// Will improve overall FPS, but might cause FPS drops on
		/// some frames when static batching is done
		/// </summary>
		public bool staticBatching = false;

		Queue<IEnumerator> tileGenerationQueue = new Queue<IEnumerator>();

		public enum RotationRandomness {
			AllAxes,
			Y
		}

		[System.Serializable]
		public class ProceduralPrefab {
			/// <summary>Prefab to use</summary>
			public GameObject prefab;

			/// <summary>Number of objects per square world unit</summary>
			public float density = 0;

			/// <summary>
			/// Multiply by [perlin noise].
			/// Value from 0 to 1 indicating weight.
			/// </summary>
			public float perlin = 0;

			/// <summary>
			/// Perlin will be raised to this power.
			/// A higher value gives more distinct edges
			/// </summary>
			public float perlinPower = 1;

			/// <summary>Some offset to avoid identical density maps</summary>
			public Vector2 perlinOffset = Vector2.zero;

			/// <summary>
			/// Perlin noise scale.
			/// A higher value spreads out the maximums and minimums of the density.
			/// </summary>
			public float perlinScale = 1;

			/// <summary>
			/// Multiply by [random].
			/// Value from 0 to 1 indicating weight.
			/// </summary>
			public float random = 1;

			/// <summary>Minimum scale of prefab</summary>
			public float minScale = 1;

			/// <summary>Maximum scale of prefab</summary>
			public float maxScale = 1;

			public RotationRandomness randomRotation = RotationRandomness.AllAxes;

			/// <summary>If checked, a single object will be created in the center of each tile</summary>
			public bool singleFixed = false;
		}

		/// <summary>All tiles</summary>
		Dictionary<Int2, ProceduralTile> tiles = new Dictionary<Int2, ProceduralTile>();

		// Use this for initialization
		void Start () {
			// Calculate the closest tiles
			// and then recalculate the graph
			Update();
			AstarPath.active.Scan();

			StartCoroutine(GenerateTiles());
		}

		// Update is called once per frame
		void Update () {
			// Calculate the tile the target is standing on
			Int2 p = new Int2(Mathf.RoundToInt((target.position.x - tileSize*0.5f) / tileSize), Mathf.RoundToInt((target.position.z - tileSize*0.5f) / tileSize));

			// Clamp range
			range = range < 1 ? 1 : range;

			// Remove tiles which are out of range
			bool changed = true;
			while (changed) {
				changed = false;
				foreach (KeyValuePair<Int2, ProceduralTile> pair in tiles) {
					if (Mathf.Abs(pair.Key.x-p.x) > range || Mathf.Abs(pair.Key.y-p.y) > range) {
						pair.Value.Destroy();
						tiles.Remove(pair.Key);
						changed = true;
						break;
					}
				}
			}

			// Add tiles which have come in range
			// and start calculating them
			for (int x = p.x-range; x <= p.x+range; x++) {
				for (int z = p.y-range; z <= p.y+range; z++) {
					if (!tiles.ContainsKey(new Int2(x, z))) {
						ProceduralTile tile = new ProceduralTile(this, x, z);
						var generator = tile.Generate();
						// Tick it one step forward
						generator.MoveNext();
						// Calculate the rest later
						tileGenerationQueue.Enqueue(generator);
						tiles.Add(new Int2(x, z), tile);
					}
				}
			}

			// The ones directly adjacent to the current one
			// should always be completely calculated
			// make sure they are
			for (int x = p.x-disableAsyncLoadWithinRange; x <= p.x+disableAsyncLoadWithinRange; x++) {
				for (int z = p.y-disableAsyncLoadWithinRange; z <= p.y+disableAsyncLoadWithinRange; z++) {
					tiles[new Int2(x, z)].ForceFinish();
				}
			}
		}

		IEnumerator GenerateTiles () {
			while (true) {
				if (tileGenerationQueue.Count > 0) {
					var generator = tileGenerationQueue.Dequeue();
					yield return StartCoroutine(generator);
				}
				yield return null;
			}
		}

		class ProceduralTile {
			int x, z;
			System.Random rnd;

			ProceduralWorld world;

			public bool destroyed { get; private set; }

			public ProceduralTile (ProceduralWorld world, int x, int z) {
				this.x = x;
				this.z = z;
				this.world = world;
				rnd = new System.Random((x * 10007) ^ (z*36007));
			}

			Transform root;
			IEnumerator ie;

			public IEnumerator Generate () {
				ie = InternalGenerate();
				GameObject rt = new GameObject("Tile " + x + " " + z);
				root = rt.transform;
				while (ie != null && root != null && ie.MoveNext()) yield return ie.Current;
				ie = null;
			}

			public void ForceFinish () {
				while (ie != null && root != null && ie.MoveNext()) {}
				ie = null;
			}

			Vector3 RandomInside () {
				Vector3 v = new Vector3();

				v.x = (x + (float)rnd.NextDouble())*world.tileSize;
				v.z = (z + (float)rnd.NextDouble())*world.tileSize;
				return v;
			}

			Vector3 RandomInside (float px, float pz) {
				Vector3 v = new Vector3();

				v.x = (px + (float)rnd.NextDouble()/world.subTiles)*world.tileSize;
				v.z = (pz + (float)rnd.NextDouble()/world.subTiles)*world.tileSize;
				return v;
			}

			Quaternion RandomYRot (ProceduralPrefab prefab) {
				return prefab.randomRotation == RotationRandomness.AllAxes ? Quaternion.Euler(360*(float)rnd.NextDouble(), 360*(float)rnd.NextDouble(), 360*(float)rnd.NextDouble()) : Quaternion.Euler(0, 360 * (float)rnd.NextDouble(), 0);
			}

			IEnumerator InternalGenerate () {
				Debug.Log("Generating tile " + x + ", " + z);
				int counter = 0;

				float[, ] ditherMap = new float[world.subTiles+2, world.subTiles+2];

				//List<GameObject> objs = new List<GameObject>();

				for (int i = 0; i < world.prefabs.Length; i++) {
					ProceduralPrefab pref = world.prefabs[i];

					if (pref.singleFixed) {
						Vector3 p = new Vector3((x+0.5f) * world.tileSize, 0, (z+0.5f) * world.tileSize);
						GameObject ob = GameObject.Instantiate(pref.prefab, p, Quaternion.identity) as GameObject;
						ob.transform.parent = root;
					} else {
						float subSize = world.tileSize/world.subTiles;

						for (int sx = 0; sx < world.subTiles; sx++) {
							for (int sz = 0; sz < world.subTiles; sz++) {
								ditherMap[sx+1, sz+1] = 0;
							}
						}

						for (int sx = 0; sx < world.subTiles; sx++) {
							for (int sz = 0; sz < world.subTiles; sz++) {
								float px = x + sx/(float)world.subTiles;//sx / world.tileSize;
								float pz = z + sz/(float)world.subTiles;//sz / world.tileSize;

								float perl = Mathf.Pow(Mathf.PerlinNoise((px + pref.perlinOffset.x)*pref.perlinScale, (pz + pref.perlinOffset.y)*pref.perlinScale), pref.perlinPower);

								float density = pref.density * Mathf.Lerp(1, perl, pref.perlin) * Mathf.Lerp(1, (float)rnd.NextDouble(), pref.random);
								float fcount = subSize*subSize*density + ditherMap[sx+1, sz+1];
								int count = Mathf.RoundToInt(fcount);

								// Apply dithering
								// See http://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
								ditherMap[sx+1+1, sz+1+0] += (7f/16f) * (fcount - count);
								ditherMap[sx+1-1, sz+1+1] += (3f/16f) * (fcount - count);
								ditherMap[sx+1+0, sz+1+1] += (5f/16f) * (fcount - count);
								ditherMap[sx+1+1, sz+1+1] += (1f/16f) * (fcount - count);

								// Create a number of objects
								for (int j = 0; j < count; j++) {
									// Find a random position inside the current sub-tile
									Vector3 p = RandomInside(px, pz);
									GameObject ob = GameObject.Instantiate(pref.prefab, p, RandomYRot(pref)) as GameObject;
									ob.transform.parent = root;

									var scale = Mathf.Lerp(pref.minScale, pref.maxScale, Mathf.PerlinNoise(p.x * pref.perlinScale, p.z * pref.perlinScale));
									ob.transform.localScale = Vector3.one * scale;
									//ob.SetActive ( false );
									//objs.Add ( ob );
									counter++;
									if (counter % 2 == 0)
										yield return null;
								}
							}
						}
					}
				}

				ditherMap = null;

				yield return null;
				yield return null;

				//Batch everything for improved performance
				if (Application.HasProLicense() && world.staticBatching) {
					StaticBatchingUtility.Combine(root.gameObject);
				}
			}

			public void Destroy () {
				if (root != null) {
					Debug.Log("Destroying tile " + x + ", " + z);
					GameObject.Destroy(root.gameObject);
					root = null;
				}

				// Make sure the tile generator coroutine is destroyed
				ie = null;
			}
		}
	}
}