├── ITerrainGenerator.cs
├── LICENSE.txt
├── MeshFactory.cs
├── NodeEditor.cs
├── Nodes.cs
├── Pooling.cs
├── QuixelEngine.cs
├── README.txt
├── Threading.cs
└── Utilities.cs
/ITerrainGenerator.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | public interface IGenerator
11 | {
12 | ///
13 | /// Calculate the density at a given point.
14 | ///
15 | /// The (real world) position
16 | ///
17 | VoxelData calculateDensity(Vector3 pos);
18 | }
19 |
20 | public struct VoxelData
21 | {
22 | public float density;
23 | public byte material;
24 | }
25 |
26 | internal class BasicTerrain : IGenerator
27 | {
28 | VoxelData IGenerator.calculateDensity(Vector3 pos)
29 | {
30 | float xx, yy, zz;
31 | xx = pos.x;
32 | yy = pos.y;
33 | zz = pos.z;
34 |
35 | float d = yy - (-50f);
36 |
37 | return new VoxelData()
38 | {
39 | density = d,
40 | material = 0
41 | };
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Gerrit Jamerson
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.
--------------------------------------------------------------------------------
/MeshFactory.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | ///
11 | /// Controls mesh generation (threaded)
12 | ///
13 | internal static class MeshFactory
14 | {
15 | #region Fields
16 | public static IGenerator terrainGenerator = new BasicTerrain();
17 | public static float isolevel = 5f;
18 |
19 | public static Queue setMeshQueue = new Queue();
20 | public static GeneratorThread[] generatorThreads = new GeneratorThread[4];
21 | public static FileThread fileThread;
22 |
23 | //[DEBUG] used to keep track of debug info
24 | public static int requestNum = 0;
25 | public static int threadNum = 0;
26 | public static int chunksFinished = 0;
27 | public static int nodesSaved = 0;
28 | public static int nodesLoaded = 0;
29 |
30 | public static GameObject chunkObj;
31 | public static GameObject terrainObj;
32 | #endregion
33 |
34 | public static void start()
35 | {
36 | chunkObj = new GameObject();
37 | chunkObj.AddComponent();
38 | chunkObj.AddComponent();
39 | chunkObj.AddComponent();
40 |
41 | for (int i = 0; i < requestArray.Length; i++)
42 | requestArray[i] = new Queue();
43 | for (int i = 0; i < generatorThreads.Length; i++)
44 | generatorThreads[i] = new GeneratorThread();
45 | fileThread = new FileThread();
46 | }
47 |
48 | public static void update()
49 | {
50 | if (setMeshQueue.Count > 0)
51 | {
52 | MeshRequest req = setMeshQueue.Dequeue();
53 | req.node.setMesh(req.meshData);
54 | }
55 |
56 | for (int i = 0; i < generatorThreads.Length; i++)
57 | {
58 | MeshRequest req;
59 | while ((req = generatorThreads[i].getFinishedMesh()) != null)
60 | {
61 | chunksFinished++;
62 | if (req.node.disposed == false)
63 | {
64 | req.node.densityData = req.densities;
65 | if (req.meshData.triangleArray.Length > 0)
66 | setMeshQueue.Enqueue(req);
67 | else
68 | req.node.setMesh(req.meshData);
69 | }
70 | }
71 | }
72 |
73 | #region Save/Load threads
74 | Node node;
75 | while ((node = fileThread.getFinishedLoadRequest()) != null)
76 | { node.regenerateChunk(); nodesLoaded++; }
77 | #endregion
78 | }
79 |
80 | #region Node Saving/Loading
81 | ///
82 | /// Adds a node to the save queue.
83 | ///
84 | ///
85 | public static void requestSave(Node node)
86 | {
87 | fileThread.enqueueSave(node);
88 | }
89 |
90 | ///
91 | /// Adds a node to the save queue.
92 | ///
93 | ///
94 | public static void requestLoad(Node node)
95 | {
96 | fileThread.enqueueLoad(node);
97 | }
98 | #endregion
99 |
100 | #region Mesh Requests
101 | public static Queue[] requestArray = new Queue[NodeManager.maxLOD + 2];
102 |
103 | ///
104 | /// Adds a request to generate a mesh
105 | ///
106 | /// The node the mesh is for
107 | public static void requestMesh(Node _node)
108 | {
109 | MeshRequest req = new MeshRequest
110 | {
111 | node = _node,
112 | pos = _node.position,
113 | LOD = _node.LOD,
114 | isDone = false,
115 | hasDensities = (_node.densityData != null)
116 | };
117 |
118 | if (!req.hasDensities)
119 | lock (requestArray[req.LOD + 1])
120 | requestArray[req.LOD + 1].Enqueue(req);
121 | else
122 | lock (requestArray[0])
123 | requestArray[0].Enqueue(req);
124 | }
125 |
126 | ///
127 | /// Returns the next logical mesh to generate.
128 | ///
129 | ///
130 | public static MeshRequest[] getNextRequests()
131 | {
132 | int size = Math.Min(10, getRequestCount());
133 | MeshRequest[] ret = new MeshRequest[size];
134 |
135 | lock (requestArray)
136 | {
137 | for (int i = 0; i < ret.Length; i++)
138 | {
139 | ret[i] = getNextRequest();
140 | }
141 | }
142 |
143 | return ret;
144 | }
145 |
146 | ///
147 | /// Returns the next request.
148 | ///
149 | ///
150 | public static MeshRequest getNextRequest()
151 | {
152 | for (int o = 0; o < requestArray.Length; o++)
153 | {
154 | if (requestArray[o].Count > 0)
155 | lock (requestArray[o])
156 | return requestArray[o].Dequeue();
157 | }
158 |
159 | return null;
160 | }
161 |
162 | ///
163 | /// Returns a count of all remaining mesh requests.
164 | ///
165 | ///
166 | public static int getRequestCount()
167 | {
168 | int ret = 0;
169 | for (int i = 0; i < requestArray.Length; i++)
170 | {
171 | ret += requestArray[i].Count;
172 | }
173 |
174 | return ret;
175 | }
176 |
177 | public class MeshRequest
178 | {
179 | public int LOD;
180 | public Vector3 pos;
181 | public Node node;
182 | public bool isDone;
183 | public bool hasDensities;
184 | public MeshData meshData;
185 | public DensityData densities;
186 | }
187 | #endregion
188 |
189 | #region Threaded Generation
190 | ///
191 | /// Generates the mesh data
192 | ///
193 | public static void GenerateMeshData(MeshRequest request)
194 | {
195 | MeshData meshData = new MeshData();
196 | request.meshData = meshData;
197 |
198 | //Check if the node requesting a generation has been disposed
199 | if (request == null || request.node.disposed)
200 | {
201 | meshData.triangleArray = new Vector3[0];
202 | meshData.indexArray = new int[0][];
203 | meshData.uvArray = new Vector2[0];
204 | meshData.normalArray = new Vector3[0];
205 | request.isDone = true;
206 | return;
207 | }
208 |
209 | DensityData densityArray = request.densities;
210 |
211 | Vector3[, ,] densityNormals = new Vector3[17, 17, 17];
212 | List triangleList = new List();
213 | List subMeshIDList = new List();
214 | int[] subMeshTriCount = new int[QuixelEngine.materials.Length];
215 | Node node = request.node;
216 | request.meshData = meshData;
217 |
218 | //Unoptimized generation
219 | densityNormals = new Vector3[18, 18, 18];
220 | if (!request.hasDensities)
221 | {
222 | for (int x = -1; x < 18; x++)
223 | {
224 | for (int y = -1; y < 18; y++)
225 | {
226 | for (int z = -1; z < 18; z++)
227 | {
228 | VoxelData data = calculateDensity(node, new Vector3I(x, y, z));
229 | densityArray.set(x, y, z, data.density);
230 | densityArray.setMaterial(x, y, z, data.material);
231 | }
232 | }
233 | }
234 | }
235 |
236 | for (int x = 0; x < 17; x++)
237 | {
238 | for (int y = 0; y < 17; y++)
239 | {
240 | for (int z = 0; z < 17; z++)
241 | {
242 | densityNormals[x, y, z] = calculateDensityNormal(new Vector3I(x, y, z), densityArray, node.LOD);
243 | }
244 | }
245 | }
246 | for (int x = 0; x < 16; x++)
247 | {
248 | for (int y = 0; y < 16; y++)
249 | {
250 | for (int z = 0; z < 16; z++)
251 | {
252 | generateTriangles(node, new Vector3I(x, y, z), triangleList, subMeshIDList, subMeshTriCount, densityArray, densityNormals);
253 | }
254 | }
255 | }
256 | int ppos = 0;
257 | int li = 0;
258 | try
259 | {
260 | meshData.triangleArray = new Vector3[triangleList.Count * 3];
261 | meshData.indexArray = new int[QuixelEngine.materials.Length][];
262 | for (int i = 0; i < QuixelEngine.materials.Length; i++)
263 | meshData.indexArray[i] = new int[(subMeshTriCount[i] * 3) * 3];
264 | meshData.uvArray = new Vector2[meshData.triangleArray.Length];
265 | meshData.normalArray = new Vector3[meshData.triangleArray.Length];
266 |
267 | int count = 0;
268 | int[] indCount = new int[QuixelEngine.materials.Length];
269 | for (int i = 0; i < triangleList.Count; i++)
270 | {
271 | ppos = i;
272 | Triangle triangle = triangleList[i];
273 | meshData.triangleArray[count + 2] = triangle.pointOne;
274 | meshData.triangleArray[count + 1] = triangle.pointTwo;
275 | meshData.triangleArray[count + 0] = triangle.pointThree;
276 |
277 | meshData.normalArray[count + 2] = triangle.nOne;
278 | meshData.normalArray[count + 1] = triangle.nTwo;
279 | meshData.normalArray[count + 0] = triangle.nThree;
280 |
281 | int ind = subMeshIDList[i];
282 | li = subMeshIDList[i];
283 | meshData.indexArray[ind][indCount[ind] + 0] = count + 0;
284 | meshData.indexArray[ind][indCount[ind] + 1] = count + 1;
285 | meshData.indexArray[ind][indCount[ind] + 2] = count + 2;
286 |
287 | meshData.uvArray[count + 0] = new Vector2(meshData.triangleArray[count + 0].x, meshData.triangleArray[count + 0].z);
288 | meshData.uvArray[count + 1] = new Vector2(meshData.triangleArray[count + 1].x, meshData.triangleArray[count + 1].z);
289 | meshData.uvArray[count + 2] = new Vector2(meshData.triangleArray[count + 2].x, meshData.triangleArray[count + 2].z);
290 | count += 3;
291 | indCount[subMeshIDList[i]] += 3;
292 | }
293 | }
294 | catch (Exception e)
295 | {
296 | StreamWriter sw = new StreamWriter("Error Log.txt");
297 | sw.WriteLine(e.Message + "\r\n" + e.StackTrace);
298 | for (int i = 0; i < QuixelEngine.materials.Length; i++)
299 | sw.WriteLine(i + ": " + subMeshTriCount[i]);
300 | sw.WriteLine(ppos);
301 | sw.WriteLine(li);
302 | sw.Close();
303 | }
304 | request.isDone = true;
305 | }
306 |
307 | ///
308 | /// Calculates a density value given a location
309 | ///
310 | ///
311 | ///
312 | ///
313 | private static VoxelData calculateDensity(Node node, Vector3I pos)
314 | {
315 | int nodeWidth = NodeManager.LODSize[node.LOD];
316 | Vector3 ws = new Vector3(node.position.x + (nodeWidth * pos.x),
317 | node.position.y + (nodeWidth * pos.y),
318 | node.position.z + (nodeWidth * pos.z));
319 |
320 | return terrainGenerator.calculateDensity(ws);
321 | }
322 |
323 | #region Marching Cubes
324 | ///
325 | /// Generates the triangles for a specific voxel
326 | ///
327 | /// The node that contains the voxel
328 | /// The voxel position (Not real position) [16,16,16]
329 | /// The list used to contain triangles made so far
330 | /// The array that contains density information
331 | /// The array that contains density normals
332 | private static void generateTriangles(Node node, Vector3I pos, List triangleList, List submeshIDList, int[] subMeshTriCount, DensityData densities, Vector3[, ,] densityNormals)
333 | {
334 | float size = NodeManager.LODSize[node.LOD];
335 |
336 | float[] denses = new float[8];
337 | denses[0] = densities.get(pos.x, pos.y, pos.z + 1);
338 | denses[1] = densities.get(pos.x + 1, pos.y, pos.z + 1);
339 | denses[2] = densities.get(pos.x + 1, pos.y, pos.z);
340 | denses[3] = densities.get(pos.x, pos.y, pos.z);
341 | denses[4] = densities.get(pos.x, pos.y + 1, pos.z + 1);
342 | denses[5] = densities.get(pos.x + 1, pos.y + 1, pos.z + 1);
343 | denses[6] = densities.get(pos.x + 1, pos.y + 1, pos.z);
344 | denses[7] = densities.get(pos.x, pos.y + 1, pos.z);
345 |
346 | byte cubeIndex = 0;
347 |
348 | if (denses[0] < isolevel)
349 | cubeIndex |= 1;
350 | if (denses[1] < isolevel)
351 | cubeIndex |= 2;
352 | if (denses[2] < isolevel)
353 | cubeIndex |= 4;
354 | if (denses[3] < isolevel)
355 | cubeIndex |= 8;
356 | if (denses[4] < isolevel)
357 | cubeIndex |= 16;
358 | if (denses[5] < isolevel)
359 | cubeIndex |= 32;
360 | if (denses[6] < isolevel)
361 | cubeIndex |= 64;
362 | if (denses[7] < isolevel)
363 | cubeIndex |= 128;
364 |
365 | if (cubeIndex == 0 || cubeIndex == 255)
366 | return;
367 |
368 | Vector3 origin = new Vector3((size * (pos.x))
369 | , (size * (pos.y))
370 | , (size * (pos.z)));
371 |
372 | Vector3[] positions = new Vector3[8];
373 | positions[0] = new Vector3(origin.x, origin.y, origin.z + size);
374 | positions[1] = new Vector3(origin.x + size, origin.y, origin.z + size);
375 | positions[2] = new Vector3(origin.x + size, origin.y, origin.z);
376 | positions[3] = new Vector3(origin.x, origin.y, origin.z);
377 | positions[4] = new Vector3(origin.x, origin.y + size, origin.z + size);
378 | positions[5] = new Vector3(origin.x + size, origin.y + size, origin.z + size);
379 | positions[6] = new Vector3(origin.x + size, origin.y + size, origin.z);
380 | positions[7] = new Vector3(origin.x, origin.y + size, origin.z);
381 |
382 | Vector3[][] vertlist = new Vector3[12][];
383 | if (IsBitSet(edgeTable[cubeIndex], 1))
384 | vertlist[0] = VertexInterp(isolevel, positions[0], positions[1], denses[0], denses[1], densityNormals[pos.x, pos.y, pos.z + 1], densityNormals[pos.x + 1, pos.y, pos.z + 1]);
385 | if (IsBitSet(edgeTable[cubeIndex], 2))
386 | vertlist[1] = VertexInterp(isolevel, positions[1], positions[2], denses[1], denses[2], densityNormals[pos.x + 1, pos.y, pos.z + 1], densityNormals[pos.x + 1, pos.y, pos.z]);
387 | if (IsBitSet(edgeTable[cubeIndex], 4))
388 | vertlist[2] = VertexInterp(isolevel, positions[2], positions[3], denses[2], denses[3], densityNormals[pos.x + 1, pos.y, pos.z], densityNormals[pos.x, pos.y, pos.z]);
389 | if (IsBitSet(edgeTable[cubeIndex], 8))
390 | vertlist[3] = VertexInterp(isolevel, positions[3], positions[0], denses[3], denses[0], densityNormals[pos.x, pos.y, pos.z], densityNormals[pos.x, pos.y, pos.z + 1]);
391 | if (IsBitSet(edgeTable[cubeIndex], 16))
392 | vertlist[4] = VertexInterp(isolevel, positions[4], positions[5], denses[4], denses[5], densityNormals[pos.x, pos.y + 1, pos.z + 1], densityNormals[pos.x + 1, pos.y + 1, pos.z + 1]);
393 | if (IsBitSet(edgeTable[cubeIndex], 32))
394 | vertlist[5] = VertexInterp(isolevel, positions[5], positions[6], denses[5], denses[6], densityNormals[pos.x + 1, pos.y + 1, pos.z + 1], densityNormals[pos.x + 1, pos.y + 1, pos.z]);
395 | if (IsBitSet(edgeTable[cubeIndex], 64))
396 | vertlist[6] = VertexInterp(isolevel, positions[6], positions[7], denses[6], denses[7], densityNormals[pos.x + 1, pos.y + 1, pos.z], densityNormals[pos.x, pos.y + 1, pos.z]);
397 | if (IsBitSet(edgeTable[cubeIndex], 128))
398 | vertlist[7] = VertexInterp(isolevel, positions[7], positions[4], denses[7], denses[4], densityNormals[pos.x, pos.y + 1, pos.z], densityNormals[pos.x, pos.y + 1, pos.z + 1]);
399 | if (IsBitSet(edgeTable[cubeIndex], 256))
400 | vertlist[8] = VertexInterp(isolevel, positions[0], positions[4], denses[0], denses[4], densityNormals[pos.x, pos.y, pos.z + 1], densityNormals[pos.x, pos.y + 1, pos.z + 1]);
401 | if (IsBitSet(edgeTable[cubeIndex], 512))
402 | vertlist[9] = VertexInterp(isolevel, positions[1], positions[5], denses[1], denses[5], densityNormals[pos.x + 1, pos.y, pos.z + 1], densityNormals[pos.x + 1, pos.y + 1, pos.z + 1]);
403 | if (IsBitSet(edgeTable[cubeIndex], 1024))
404 | vertlist[10] = VertexInterp(isolevel, positions[2], positions[6], denses[2], denses[6], densityNormals[pos.x + 1, pos.y, pos.z], densityNormals[pos.x + 1, pos.y + 1, pos.z]);
405 | if (IsBitSet(edgeTable[cubeIndex], 2048))
406 | vertlist[11] = VertexInterp(isolevel, positions[3], positions[7], denses[3], denses[7], densityNormals[pos.x, pos.y, pos.z], densityNormals[pos.x, pos.y + 1, pos.z]);
407 |
408 | int submesh = densities.getMaterial(pos.x, pos.y, pos.z);
409 | for (int i = 0; triTable[cubeIndex][i] != -1; i += 3)
410 | {
411 | submeshIDList.Add(submesh);
412 | subMeshTriCount[submesh] = subMeshTriCount[submesh] + 1;
413 | triangleList.Add(new Triangle(vertlist[triTable[cubeIndex][i]][0], vertlist[triTable[cubeIndex][i + 1]][0], vertlist[triTable[cubeIndex][i + 2]][0],
414 | vertlist[triTable[cubeIndex][i]][1], vertlist[triTable[cubeIndex][i + 1]][1], vertlist[triTable[cubeIndex][i + 2]][1]));
415 | }
416 | }
417 |
418 | ///
419 | /// Interpolates the Vertex
420 | ///
421 | /// The isolevel
422 | /// Position One
423 | /// Position Two
424 | /// Value Position One
425 | /// Value Position Two
426 | ///
427 | private static Vector3[] VertexInterp(float isolevel, Vector3 p1, Vector3 p2, float valp1, float valp2, Vector3 n1, Vector3 n2)
428 | {
429 | float mu;
430 | Vector3[] p = new Vector3[2];
431 |
432 | if (Mathf.Abs(isolevel - valp1) < 0.00001)
433 | {
434 | p[0] = p1;
435 | return p;
436 | }
437 | if (Mathf.Abs(isolevel - valp2) < 0.00001)
438 | {
439 | p[0] = p2;
440 | return p;
441 | }
442 | if (Mathf.Abs(valp1 - valp2) < 0.00001)
443 | {
444 | p[0] = p1;
445 | return p;
446 | }
447 |
448 | mu = (isolevel - valp1) / (valp2 - valp1);
449 | p[0].x = p1.x + mu * (p2.x - p1.x);
450 | p[0].y = p1.y + mu * (p2.y - p1.y);
451 | p[0].z = p1.z + mu * (p2.z - p1.z);
452 |
453 | float dist1 = Vector3.Distance(p1, p2);
454 | float dist2 = Vector3.Distance(p2, p[0]);
455 | p[1] = Vector3.Lerp(n2, n1, dist2 / dist1);
456 | p[1].Normalize();
457 |
458 | return (p);
459 | }
460 |
461 | ///
462 | /// Find out if a certain bit is set
463 | ///
464 | /// The bit to be checked
465 | /// The position of the bit to check
466 | ///
467 | static bool IsBitSet(int b, int pos)
468 | {
469 | return ((b & pos) == pos);
470 | }
471 |
472 | ///
473 | /// Calculates the normal.
474 | ///
475 | ///
476 | private static Vector3 calculateDensityNormal(Vector3I p, DensityData densities, int lod)
477 | {
478 | Vector3 normal = new Vector3();
479 | normal.x = (densities.get(p.x + 1, p.y, p.z) - densities.get(p.x - 1, p.y, p.z)) / (NodeManager.LODSize[lod]);
480 | normal.y = (densities.get(p.x, p.y + 1, p.z) - densities.get(p.x, p.y - 1, p.z)) / (NodeManager.LODSize[lod]);
481 | normal.z = (densities.get(p.x, p.y, p.z + 1) - densities.get(p.x, p.y, p.z - 1)) / (NodeManager.LODSize[lod]);
482 | normal.Normalize();
483 | return normal;
484 | }
485 | #endregion
486 |
487 | #region Lookup Table A
488 | static int[] edgeTable = new int[256] {
489 | 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
490 | 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
491 | 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
492 | 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
493 | 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
494 | 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
495 | 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
496 | 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
497 | 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
498 | 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
499 | 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
500 | 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
501 | 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
502 | 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
503 | 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
504 | 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
505 | 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
506 | 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
507 | 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
508 | 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
509 | 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
510 | 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
511 | 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
512 | 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
513 | 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
514 | 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
515 | 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
516 | 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
517 | 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
518 | 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
519 | 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
520 | 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 };
521 | #endregion
522 |
523 | #region Lookup Table B
524 |
525 | static int[][] triTable = new int[256][] {
526 | new int[] {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
527 | new int[] {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
528 | new int[] {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
529 | new int[] {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
530 | new int[] {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
531 | new int[] {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
532 | new int[] {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
533 | new int[] {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
534 | new int[] {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
535 | new int[] {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
536 | new int[] {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
537 | new int[] {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
538 | new int[] {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
539 | new int[] {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
540 | new int[] {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},
541 | new int[] {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
542 | new int[] {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
543 | new int[] {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
544 | new int[] {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
545 | new int[] {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
546 | new int[] {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
547 | new int[] {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
548 | new int[] {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
549 | new int[] {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
550 | new int[] {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
551 | new int[] {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
552 | new int[] {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
553 | new int[] {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
554 | new int[] {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},
555 | new int[] {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
556 | new int[] {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},
557 | new int[] {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
558 | new int[] {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
559 | new int[] {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
560 | new int[] {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
561 | new int[] {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
562 | new int[] {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
563 | new int[] {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
564 | new int[] {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},
565 | new int[] {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
566 | new int[] {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
567 | new int[] {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
568 | new int[] {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
569 | new int[] {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
570 | new int[] {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},
571 | new int[] {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
572 | new int[] {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},
573 | new int[] {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
574 | new int[] {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
575 | new int[] {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
576 | new int[] {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},
577 | new int[] {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
578 | new int[] {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},
579 | new int[] {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
580 | new int[] {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},
581 | new int[] {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
582 | new int[] {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},
583 | new int[] {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
584 | new int[] {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},
585 | new int[] {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
586 | new int[] {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},
587 | new int[] {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
588 | new int[] {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},
589 | new int[] {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
590 | new int[] {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
591 | new int[] {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
592 | new int[] {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
593 | new int[] {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
594 | new int[] {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
595 | new int[] {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
596 | new int[] {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},
597 | new int[] {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
598 | new int[] {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
599 | new int[] {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
600 | new int[] {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
601 | new int[] {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
602 | new int[] {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},
603 | new int[] {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
604 | new int[] {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},
605 | new int[] {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
606 | new int[] {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
607 | new int[] {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
608 | new int[] {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
609 | new int[] {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
610 | new int[] {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},
611 | new int[] {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
612 | new int[] {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},
613 | new int[] {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
614 | new int[] {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
615 | new int[] {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
616 | new int[] {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},
617 | new int[] {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
618 | new int[] {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},
619 | new int[] {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
620 | new int[] {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},
621 | new int[] {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
622 | new int[] {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
623 | new int[] {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
624 | new int[] {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},
625 | new int[] {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
626 | new int[] {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},
627 | new int[] {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
628 | new int[] {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
629 | new int[] {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
630 | new int[] {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},
631 | new int[] {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
632 | new int[] {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},
633 | new int[] {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
634 | new int[] {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},
635 | new int[] {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
636 | new int[] {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},
637 | new int[] {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
638 | new int[] {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},
639 | new int[] {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
640 | new int[] {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},
641 | new int[] {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
642 | new int[] {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},
643 | new int[] {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
644 | new int[] {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},
645 | new int[] {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
646 | new int[] {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},
647 | new int[] {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
648 | new int[] {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},
649 | new int[] {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
650 | new int[] {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},
651 | new int[] {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
652 | new int[] {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},
653 | new int[] {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
654 | new int[] {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
655 | new int[] {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
656 | new int[] {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
657 | new int[] {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
658 | new int[] {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
659 | new int[] {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
660 | new int[] {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
661 | new int[] {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
662 | new int[] {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
663 | new int[] {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
664 | new int[] {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},
665 | new int[] {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
666 | new int[] {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},
667 | new int[] {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
668 | new int[] {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},
669 | new int[] {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
670 | new int[] {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
671 | new int[] {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
672 | new int[] {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},
673 | new int[] {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
674 | new int[] {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},
675 | new int[] {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
676 | new int[] {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},
677 | new int[] {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
678 | new int[] {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},
679 | new int[] {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
680 | new int[] {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},
681 | new int[] {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
682 | new int[] {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},
683 | new int[] {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
684 | new int[] {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},
685 | new int[] {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
686 | new int[] {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
687 | new int[] {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
688 | new int[] {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
689 | new int[] {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
690 | new int[] {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
691 | new int[] {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
692 | new int[] {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},
693 | new int[] {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
694 | new int[] {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},
695 | new int[] {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
696 | new int[] {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},
697 | new int[] {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
698 | new int[] {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},
699 | new int[] {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
700 | new int[] {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},
701 | new int[] {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
702 | new int[] {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},
703 | new int[] {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
704 | new int[] {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},
705 | new int[] {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
706 | new int[] {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},
707 | new int[] {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
708 | new int[] {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},
709 | new int[] {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
710 | new int[] {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},
711 | new int[] {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
712 | new int[] {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},
713 | new int[] {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
714 | new int[] {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},
715 | new int[] {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
716 | new int[] {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
717 | new int[] {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
718 | new int[] {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
719 | new int[] {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
720 | new int[] {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},
721 | new int[] {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
722 | new int[] {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},
723 | new int[] {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
724 | new int[] {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},
725 | new int[] {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
726 | new int[] {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},
727 | new int[] {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
728 | new int[] {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},
729 | new int[] {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
730 | new int[] {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
731 | new int[] {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
732 | new int[] {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},
733 | new int[] {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
734 | new int[] {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},
735 | new int[] {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
736 | new int[] {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},
737 | new int[] {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
738 | new int[] {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},
739 | new int[] {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
740 | new int[] {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},
741 | new int[] {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
742 | new int[] {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},
743 | new int[] {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
744 | new int[] {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},
745 | new int[] {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
746 | new int[] {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},
747 | new int[] {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
748 | new int[] {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},
749 | new int[] {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
750 | new int[] {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},
751 | new int[] {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
752 | new int[] {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},
753 | new int[] {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
754 | new int[] {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},
755 | new int[] {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
756 | new int[] {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},
757 | new int[] {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
758 | new int[] {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},
759 | new int[] {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
760 | new int[] {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},
761 | new int[] {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
762 | new int[] {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},
763 | new int[] {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
764 | new int[] {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
765 | new int[] {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
766 | new int[] {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
767 | new int[] {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
768 | new int[] {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},
769 | new int[] {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
770 | new int[] {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},
771 | new int[] {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
772 | new int[] {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
773 | new int[] {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
774 | new int[] {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},
775 | new int[] {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
776 | new int[] {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},
777 | new int[] {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
778 | new int[] {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
779 | new int[] {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
780 | new int[] {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
781 | new int[] {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}};
782 | #endregion
783 | #endregion
784 |
785 | ///
786 | /// Used to draw information about mesh generation.
787 | ///
788 | public static void debugDraw()
789 | {
790 | GUILayout.Label("Requests: " + getRequestCount());
791 | //GUILayout.Label("Threads: " + threadList.Count);
792 | //GUILayout.Label("Active: " + activeRequests.Count);
793 | GUILayout.Label("Finished: " + chunksFinished);
794 | GUILayout.Label("Free Game Objects: " + ChunkPool.chunkList.Count);
795 | GUILayout.Label("Total Game Objects: " + ChunkPool.totalCreated);
796 | //GUILayout.Label("Saves Queued: " + saveQueue.Count);
797 | GUILayout.Label("Nodes Saved: " + nodesSaved);
798 | //GUILayout.Label("Loads Queued: " + loadQueue.Count);
799 | GUILayout.Label("Nodes Loaded: " + nodesLoaded);
800 | }
801 | }
802 | }
--------------------------------------------------------------------------------
/NodeEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | public static class NodeEditor
11 | {
12 | #region Fields
13 | /// The type of brush to edit with.
14 | public enum BrushType
15 | {
16 | BOX, SPHERE
17 | }
18 |
19 | public static float DENSITY_SOLIDHARD = MeshFactory.isolevel - 0.1f;
20 | public static float DENSITY_SOLIDSOFT = MeshFactory.isolevel - 5f;
21 | public static float DENSITY_EMPTY = MeshFactory.isolevel + 1f;
22 |
23 | private static byte materialID = 0;
24 | #endregion
25 |
26 | ///
27 | /// Sets the material id to paint/brush with.
28 | /// The material ID is the material's location in the material array.
29 | ///
30 | ///
31 | public static void setMaterial(byte matID)
32 | {
33 | materialID = matID;
34 | }
35 |
36 | ///
37 | /// Applies a brush to the terrain.
38 | ///
39 | /// The type/shape of the brush.
40 | /// The size (radius) of the brush.
41 | /// The (real world) position to apply the brush.
42 | /// Density to apply.
43 | public static void applyBrush(BrushType type, int size, Vector3 pos, float val)
44 | {
45 | List changedNodes = new List();
46 | float nodeWidth = NodeManager.LODSize[0];
47 | Vector3 realPos = new Vector3();
48 | Vector3I point = new Vector3I((int)Math.Round(pos.x / nodeWidth),
49 | (int)Math.Round(pos.y / nodeWidth),
50 | (int)Math.Round(pos.z / nodeWidth));
51 |
52 | List points = getPoints(type, size, pos);
53 | for (int o = 0; o < points.Count; o++)
54 | {
55 | realPos = points[o];
56 | Node[] editNodes = NodeManager.searchNodeContainingDensity(realPos, 0);
57 |
58 | for (int i = 0; i < editNodes.Length; i++)
59 | {
60 | Node editNode = editNodes[i];
61 | if (editNode != null && editNode.LOD == 0)
62 | {
63 | if (!changedNodes.Contains(editNode))
64 | changedNodes.Add(editNode);
65 |
66 |
67 | if (type == BrushType.SPHERE)
68 | {
69 | float v = val;
70 | float dist = Vector3.Distance(pos, realPos);
71 |
72 | if (val > MeshFactory.isolevel)
73 | v = 10f - ((dist / (float)size) * 5f);
74 | if (val < MeshFactory.isolevel)
75 | v = 10f + ((dist / (float)size) * 5f);
76 |
77 | }
78 |
79 | editNode.setDensityFromWorldPos(realPos, val);
80 | editNode.setMaterialFromWorldPos(realPos, materialID);
81 |
82 | }
83 | }
84 | }
85 |
86 | applyPaint(type, size + 1, pos, false);
87 | for (int i = 0; i < changedNodes.Count; i++)
88 | changedNodes[i].regenerateChunk();
89 | }
90 |
91 | ///
92 | /// 'Heals' the terrain around a given point, essentially returning the terrain to it's original state.
93 | ///
94 | /// The type/shape of the brush.
95 | /// The size (radius) of the brush.
96 | /// The (real world) position to apply the brush.
97 | public static void applyHeal(BrushType type, int size, Vector3 pos)
98 | {
99 | byte mID = materialID;
100 | materialID = 0;
101 | applyBrush(type, size, pos, -100000f);
102 | materialID = mID;
103 | }
104 |
105 | ///
106 | /// Applies 'paint' to the area around the brush.
107 | ///
108 | /// The type/shape of the brush.
109 | /// The size (radius) of the brush.
110 | /// The (real world) position to apply the brush.
111 | /// Set to true to have the engine regen the chunk after painting.
112 | public static void applyPaint(BrushType type, int size, Vector3 pos, bool regen)
113 | {
114 | List changedNodes = new List();
115 | float nodeWidth = NodeManager.LODSize[0];
116 | Vector3 realPos = new Vector3();
117 | Vector3I point = new Vector3I((int)Math.Round(pos.x / nodeWidth),
118 | (int)Math.Round(pos.y / nodeWidth),
119 | (int)Math.Round(pos.z / nodeWidth));
120 |
121 | List points = getPoints(type, size, pos);
122 | for (int o = 0; o < points.Count; o++)
123 | {
124 | realPos = points[o];
125 | Node[] editNodes = NodeManager.searchNodeContainingDensity(realPos, 0);
126 |
127 | for (int i = 0; i < editNodes.Length; i++)
128 | {
129 | Node editNode = editNodes[i];
130 | if (editNode != null && editNode.LOD == 0)
131 | {
132 | if (!changedNodes.Contains(editNode))
133 | changedNodes.Add(editNode);
134 |
135 | editNode.setMaterialFromWorldPos(realPos, materialID);
136 |
137 | }
138 | }
139 | }
140 |
141 | if (regen)
142 | for (int i = 0; i < changedNodes.Count; i++)
143 | changedNodes[i].regenerateChunk();
144 | }
145 |
146 | ///
147 | /// Returns a list of points inside of a given brush type and size.
148 | ///
149 | ///
150 | ///
151 | ///
152 | ///
153 | private static List getPoints(BrushType type, int size, Vector3 pos)
154 | {
155 | float nodeWidth = NodeManager.LODSize[0];
156 | Vector3I point = new Vector3I((int)Math.Round(pos.x / nodeWidth),
157 | (int)Math.Round(pos.y / nodeWidth),
158 | (int)Math.Round(pos.z / nodeWidth));
159 |
160 | List ret = new List();
161 | switch (type)
162 | {
163 | case BrushType.BOX:
164 | for (int x = 0; x <= size; x++)
165 | {
166 | for (int y = 0; y <= size; y++)
167 | {
168 | for (int z = 0; z <= size; z++)
169 | {
170 | Vector3 realPos = new Vector3();
171 | realPos.x = ((point.x + x) * nodeWidth);
172 | realPos.y = ((point.y + y) * nodeWidth);
173 | realPos.z = ((point.z + z) * nodeWidth);
174 | ret.Add(realPos);
175 | }
176 | }
177 | }
178 | break;
179 |
180 | case BrushType.SPHERE:
181 | for (int x = -size; x < size; x++)
182 | {
183 | for (int y = -size * 2; y < size; y++)
184 | {
185 | for (int z = -size; z < size; z++)
186 | {
187 | Vector3 realPos = new Vector3();
188 | realPos.x = ((point.x + x) * nodeWidth);
189 | realPos.y = ((point.y + y) * nodeWidth);
190 | realPos.z = ((point.z + z) * nodeWidth);
191 |
192 | if (Vector3.Distance(realPos, pos) < size * nodeWidth)
193 | {
194 | ret.Add(realPos);
195 | }
196 | }
197 | }
198 | }
199 | break;
200 | }
201 |
202 | return ret;
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/Nodes.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | ///
11 | /// Controls the 27 top-level nodes. Handles node searching.
12 | ///
13 | internal static class NodeManager
14 | {
15 | #region Fields
16 | public static string worldName;
17 | public static Vector3I curBottomNode = new Vector3I(-111, 0, 0);
18 | public static Vector3I curTopNode = new Vector3I(0, 0, 0);
19 |
20 | public static Vector3I[] viewChunkPos;
21 |
22 | //public static Node[,,] topNodes = new Node[3,3,3];
23 | public static Node[, ,] topNodes = new Node[3, 3, 3];
24 |
25 | /// Mask used for positioning subnodes
26 | public static Vector3[] mask = new Vector3[8] {
27 | new Vector3(0, 0, 0),
28 | new Vector3(1, 0, 0),
29 | new Vector3(0, 1, 0),
30 | new Vector3(0, 0, 1),
31 | new Vector3(1, 1, 0),
32 | new Vector3(1, 0, 1),
33 | new Vector3(0, 1, 1),
34 | new Vector3(1, 1, 1)
35 | };
36 |
37 | /// Refers to the tri size of each vertex
38 | public static int[] LODSize = new int[11] {
39 | 1,
40 | 2,
41 | 4,
42 | 8, 16, 32, 64, 128, 256, 512, 1024
43 | };
44 |
45 | //DEPRECATED
46 | /// Radius for each LOD value
47 | public static int[] LODRange = new int[11] {
48 | 0,
49 | 7,
50 | 12,
51 | 26, 50, 100, 210, 400, 700, 1200, 2400
52 | };
53 |
54 | public static int[] nodeCount = new int[11];
55 |
56 | /// Density array size
57 | public static int nodeSize = 16;
58 |
59 | /// The maximum allowed LOD. The top level nodes will be this LOD.
60 | public static int maxLOD = 10;
61 | #endregion
62 |
63 | ///
64 | /// Initializes the node manager.
65 | ///
66 | public static void init(string worldName)
67 | {
68 | NodeManager.worldName = worldName;
69 | float nSize = LODSize[maxLOD] * nodeSize;
70 | for (int x = -1; x < 2; x++)
71 | {
72 | for (int y = -1; y < 2; y++)
73 | {
74 | for (int z = -1; z < 2; z++)
75 | {
76 | topNodes[x + 1, y + 1, z + 1] = new Node(null,
77 | new Vector3(x * nSize,
78 | y * nSize,
79 | z * nSize),
80 | 0, maxLOD, Node.RenderType.FRONT);
81 | }
82 | }
83 | }
84 |
85 | viewChunkPos = new Vector3I[maxLOD + 1];
86 | for (int i = 0; i <= maxLOD; i++)
87 | viewChunkPos[i] = new Vector3I();
88 | }
89 |
90 | ///
91 | /// Sets the view position, and checks if chunks need to be updated
92 | ///
93 | ///
94 | public static void setViewPosition(Vector3 pos)
95 | {
96 | for (int i = 0; i <= maxLOD; i++)
97 | {
98 | float nWidth = LODSize[i] * nodeSize;
99 | viewChunkPos[i].x = (int)(pos.x / nWidth);
100 | viewChunkPos[i].y = (int)(pos.y / nWidth);
101 | viewChunkPos[i].z = (int)(pos.z / nWidth);
102 | }
103 |
104 | float sWidth = LODSize[0] * nodeSize * 0.5f;
105 | Vector3I newPos = new Vector3I((int)(pos.x / sWidth), (int)(pos.y / sWidth), (int)(pos.z / sWidth));
106 |
107 | if (!curTopNode.Equals(getTopNode(pos)))
108 | {
109 | float nodeWidth = LODSize[maxLOD] * nodeSize;
110 | Vector3I diff = getTopNode(pos).Subtract(curTopNode);
111 | curTopNode = getTopNode(pos);
112 | while (diff.x > 0)
113 | {
114 | for (int y = 0; y < 3; y++)
115 | {
116 | for (int z = 0; z < 3; z++)
117 | {
118 | topNodes[0, y, z].dispose();
119 | topNodes[0, y, z] = topNodes[1, y, z];
120 | topNodes[1, y, z] = topNodes[2, y, z];
121 | topNodes[2, y, z] = new Node(null,
122 | new Vector3((curTopNode.x * nodeWidth) + nodeWidth,
123 | (curTopNode.y * nodeWidth) + ((y - 1) * nodeWidth),
124 | (curTopNode.z * nodeWidth) + ((z - 1) * nodeWidth)),
125 | 0, maxLOD, Node.RenderType.FRONT);
126 | }
127 | }
128 | diff.x--;
129 | }
130 |
131 | while (diff.x < 0)
132 | {
133 | for (int y = 0; y < 3; y++)
134 | {
135 | for (int z = 0; z < 3; z++)
136 | {
137 | topNodes[2, y, z].dispose();
138 | topNodes[2, y, z] = topNodes[1, y, z];
139 | topNodes[1, y, z] = topNodes[0, y, z];
140 | topNodes[0, y, z] = new Node(null,
141 | new Vector3((curTopNode.x * nodeWidth) - nodeWidth,
142 | (curTopNode.y * nodeWidth) + ((y - 1) * nodeWidth),
143 | (curTopNode.z * nodeWidth) + ((z - 1) * nodeWidth)),
144 | 0, maxLOD, Node.RenderType.FRONT);
145 | }
146 | }
147 | diff.x++;
148 | }
149 |
150 | while (diff.y > 0)
151 | {
152 | for (int x = 0; x < 3; x++)
153 | {
154 | for (int z = 0; z < 3; z++)
155 | {
156 | topNodes[x, 0, z].dispose();
157 | topNodes[x, 0, z] = topNodes[x, 1, z];
158 | topNodes[x, 1, z] = topNodes[x, 2, z];
159 | topNodes[x, 2, z] = new Node(null,
160 | new Vector3((curTopNode.x * nodeWidth) + ((x - 1) * nodeWidth),
161 | (curTopNode.y * nodeWidth) + nodeWidth,
162 | (curTopNode.z * nodeWidth) + ((z - 1) * nodeWidth)),
163 | 0, maxLOD, Node.RenderType.FRONT);
164 | }
165 | }
166 | diff.y--;
167 | }
168 |
169 | while (diff.y < 0)
170 | {
171 | for (int x = 0; x < 3; x++)
172 | {
173 | for (int z = 0; z < 3; z++)
174 | {
175 | topNodes[x, 2, z].dispose();
176 | topNodes[x, 2, z] = topNodes[x, 1, z];
177 | topNodes[x, 1, z] = topNodes[x, 0, z];
178 | topNodes[x, 0, z] = new Node(null,
179 | new Vector3((curTopNode.x * nodeWidth) + ((x - 1) * nodeWidth),
180 | (curTopNode.y * nodeWidth) - nodeWidth,
181 | (curTopNode.z * nodeWidth) + ((z - 1) * nodeWidth)),
182 | 0, maxLOD, Node.RenderType.FRONT);
183 | }
184 | }
185 |
186 | diff.y++;
187 | }
188 |
189 | while (diff.z > 0)
190 | {
191 | for (int x = 0; x < 3; x++)
192 | {
193 | for (int y = 0; y < 3; y++)
194 | {
195 | topNodes[x, y, 0].dispose();
196 | topNodes[x, y, 0] = topNodes[x, y, 1];
197 | topNodes[x, y, 1] = topNodes[x, y, 2];
198 | topNodes[x, y, 2] = new Node(null,
199 | new Vector3((curTopNode.x * nodeWidth) + ((x - 1) * nodeWidth),
200 | (curTopNode.y * nodeWidth) + ((y - 1) * nodeWidth),
201 | (curTopNode.z * nodeWidth) + nodeWidth),
202 | 0, maxLOD, Node.RenderType.FRONT);
203 | }
204 | }
205 |
206 | diff.z--;
207 | }
208 |
209 | while (diff.z < 0)
210 | {
211 | for (int x = 0; x < 3; x++)
212 | {
213 | for (int y = 0; y < 3; y++)
214 | {
215 | topNodes[x, y, 2].dispose();
216 | topNodes[x, y, 2] = topNodes[x, y, 1];
217 | topNodes[x, y, 1] = topNodes[x, y, 0];
218 | topNodes[x, y, 0] = new Node(null,
219 | new Vector3((curTopNode.x * nodeWidth) + ((x - 1) * nodeWidth),
220 | (curTopNode.y * nodeWidth) + ((y - 1) * nodeWidth),
221 | (curTopNode.z * nodeWidth) - nodeWidth),
222 | 0, maxLOD, Node.RenderType.FRONT);
223 | }
224 | }
225 | diff.z++;
226 | }
227 | }
228 |
229 | if (curBottomNode.x != newPos.x || curBottomNode.y != newPos.y || curBottomNode.z != newPos.z)
230 | {
231 | Vector3 setPos = new Vector3(newPos.x * sWidth + (sWidth / 1f), newPos.y * sWidth + (sWidth / 1f), newPos.z * sWidth + (sWidth / 1f));
232 | for (int x = 0; x < 3; x++)
233 | for (int y = 0; y < 3; y++)
234 | for (int z = 0; z < 3; z++)
235 | {
236 | topNodes[x, y, z].viewPosChanged(setPos);
237 | }
238 |
239 | curBottomNode = newPos;
240 | }
241 | }
242 |
243 | ///
244 | /// Returns a node containing the point as close as possible to the requested LOD.
245 | ///
246 | ///
247 | ///
248 | /// Null if no such node exists.
249 | public static Node[] searchNodeContainingDensity(Vector3 pos, int searchLOD)
250 | {
251 | Node[] ret = new Node[8];
252 | for (int x = 0; x < 3; x++)
253 | {
254 | for (int y = 0; y < 3; y++)
255 | {
256 | for (int z = 0; z < 3; z++)
257 | {
258 | if (topNodes[x, y, z] != null)
259 | topNodes[x, y, z].searchNodeCreate(pos, searchLOD, ref ret);
260 | }
261 | }
262 | }
263 |
264 | return ret;
265 | }
266 |
267 | ///
268 | /// Returns a node containing the point as close as possible to the requested LOD.
269 | ///
270 | ///
271 | ///
272 | /// Null if no such node exists.
273 | public static Node searchNode(Vector3 pos, int searchLOD)
274 | {
275 | for (int x = 0; x < 3; x++)
276 | {
277 | for (int y = 0; y < 3; y++)
278 | {
279 | for (int z = 0; z < 3; z++)
280 | {
281 | if (topNodes[x, y, z] != null)
282 | if (topNodes[x, y, z].containsPoint(pos))
283 | return topNodes[x, y, z].searchNode(pos, searchLOD);
284 | }
285 | }
286 | }
287 |
288 | return null;
289 | }
290 |
291 | ///
292 | /// Calculates the offset position given a parent's node and the subnode ID.
293 | ///
294 | /// Parent that contains t
295 | /// Index of the node in the subnode array
296 | ///
297 | public static Vector3 getOffsetPosition(Node parentNode, int subNodeID)
298 | {
299 | //Vector3 ret = new Vector3(parentNode.position.x, parentNode.position.y, parentNode.position.z);
300 | int parentWidth = nodeSize * LODSize[parentNode.LOD];
301 | return new Vector3
302 | {
303 | x = parentNode.position.x + ((parentWidth / 2) * mask[subNodeID].x),
304 | y = parentNode.position.y + ((parentWidth / 2) * mask[subNodeID].y),
305 | z = parentNode.position.z + ((parentWidth / 2) * mask[subNodeID].z)
306 | };
307 | }
308 |
309 | ///
310 | /// Returns a 3d integer vector position of the "top" (highest LOD) node that contains the given position.
311 | ///
312 | ///
313 | ///
314 | public static Vector3I getTopNode(Vector3 pos)
315 | {
316 | Vector3I ret = new Vector3I();
317 | ret.x = (int)Mathf.Floor(pos.x / (NodeManager.LODSize[maxLOD] * nodeSize));
318 | ret.y = (int)Mathf.Floor(pos.y / (NodeManager.LODSize[maxLOD] * nodeSize));
319 | ret.z = (int)Mathf.Floor(pos.z / (NodeManager.LODSize[maxLOD] * nodeSize));
320 |
321 | return ret;
322 | }
323 |
324 | ///
325 | /// Used to draw the chunk wirecubes in scene view
326 | ///
327 | ///
328 | public static int debugDraw()
329 | {
330 | int ct = 0;
331 | for (int x = 0; x < 3; x++)
332 | {
333 | for (int y = 0; y < 3; y++)
334 | {
335 | for (int z = 0; z < 3; z++)
336 | {
337 | if (topNodes[x, y, z] != null)
338 | ct += topNodes[x, y, z].debugDraw();
339 | }
340 | }
341 | }
342 | return ct;
343 | }
344 | }
345 |
346 | ///
347 | /// Octree node (chunk)
348 | ///
349 | internal class Node
350 | {
351 | #region Fields
352 | /// When we are already generating a new mesh and need a new regeneration.
353 | public bool regenFlag = false;
354 | private bool regenReq = false;
355 | public bool permanent = false;
356 | public bool hasDensityChangeData = false;
357 |
358 | /// Density array that contains the mesh data.
359 | public DensityData densityData;
360 | public DensityData densityChangeData;
361 |
362 | /// Index of this node in the parent node's subnode array
363 | public int subNodeID;
364 |
365 | /// Level of Detail value
366 | public int LOD;
367 |
368 | /// The integer position of the chunk (Not real)
369 | public Vector3I chunkPos;
370 |
371 | /// Real position
372 | public Vector3 position;
373 |
374 | /// Subnodes under this parent node
375 | public Node[] subNodes = new Node[8];
376 |
377 | ///
378 | /// Neighbor nodes of the same LOD
379 | /// Null means the neighbor node either doesn't exist or hasn't been allocated yet.
380 | ///
381 | public Node[] neighborNodes = new Node[6];
382 |
383 | /// The parent that owns this node. Null if top-level
384 | public Node parent;
385 |
386 | /// Gameobject that contains the mesh
387 | private GameObject chunk;
388 |
389 | /// Center of the chunk in real pos
390 | public Vector3 center;
391 |
392 | public bool disposed = false;
393 | public bool hasMesh = false;
394 | public bool collides = false;
395 | public bool empty = true;
396 |
397 | public RenderType renderType;
398 | public enum RenderType
399 | {
400 | FRONT, BACK
401 | }
402 |
403 | /// Mask used for positioning subnodes
404 | public static Vector3[] neighborMask = new Vector3[6] {
405 | new Vector3(-1, 0, 0),
406 | new Vector3(0, -1, 0),
407 | new Vector3(0, 0, -1),
408 | new Vector3(1, 0, 0),
409 | new Vector3(0, 1, 0),
410 | new Vector3(0, 0, 1),
411 | };
412 |
413 | public static int[] oppositeNeighbor = new int[6] {
414 | 3, 4, 5, 0, 1, 2
415 | };
416 | #endregion
417 |
418 | public Node(Node parent, Vector3 position, int subNodeID, int LOD, RenderType renderType)
419 | {
420 | densityChangeData = new DensityData();
421 | this.parent = parent;
422 | this.position = position;
423 | this.subNodeID = subNodeID;
424 | this.LOD = LOD;
425 |
426 | float chunkWidth = (NodeManager.LODSize[LOD] * NodeManager.nodeSize) / 2f;
427 | center = new Vector3(position.x + chunkWidth,
428 | position.y + chunkWidth,
429 | position.z + chunkWidth);
430 |
431 | setRenderType(renderType);
432 |
433 | if (parent != null && parent.permanent)
434 | permanent = true;
435 |
436 | NodeManager.nodeCount[LOD]++;
437 |
438 | float nWidth = NodeManager.LODSize[LOD] * NodeManager.nodeSize;
439 | chunkPos.x = (int)(center.x / nWidth);
440 | chunkPos.y = (int)(center.y / nWidth);
441 | chunkPos.z = (int)(center.z / nWidth);
442 |
443 | if (LOD == 0)
444 | {
445 | string dir = getDirectory();
446 | if (Directory.Exists(dir) && File.Exists(dir + "\\densities.txt"))
447 | MeshFactory.requestLoad(this);
448 | }
449 |
450 | regenReq = true;
451 | MeshFactory.requestMesh(this);
452 | }
453 |
454 | ///
455 | /// Called when the viewpoint has changed.
456 | ///
457 | ///
458 | public void viewPosChanged(Vector3 pos)
459 | {
460 | //float sep = 10f;
461 |
462 | //float distance = ((center.x - pos.x) * (center.x - pos.x) +
463 | //(center.y - pos.y) * (center.y - pos.y) +
464 | //(center.z - pos.z) * (center.z - pos.z));
465 |
466 | //if (distance < (((float)NodeManager.LODRange[LOD]) * sep) * (((float)NodeManager.LODRange[LOD]) * sep))
467 | Vector3I viewPos = NodeManager.viewChunkPos[LOD];
468 | int size = 1;
469 | if ((viewPos.x >= chunkPos.x - size && viewPos.x <= chunkPos.x + size)
470 | && (viewPos.y >= chunkPos.y - size && viewPos.y <= chunkPos.y + size)
471 | && (viewPos.z >= chunkPos.z - size && viewPos.z <= chunkPos.z + size))
472 | {
473 | if (isBottomLevel())
474 | createSubNodes(RenderType.FRONT);
475 |
476 | for (int i = 0; i < 8; i++)
477 | {
478 | if (subNodes[i] != null)
479 | subNodes[i].viewPosChanged(pos);
480 | }
481 | }
482 | //else if (!permanent)
483 | else
484 | {
485 | size += 2;
486 | if (LOD < 3 && (viewPos.x < chunkPos.x - size || viewPos.x > chunkPos.x + size)
487 | || (viewPos.y < chunkPos.y - size || viewPos.y > chunkPos.y + size)
488 | || (viewPos.z < chunkPos.z - size || viewPos.z > chunkPos.z + size))
489 | {
490 | for (int i = 0; i < 8; i++)
491 | {
492 | if (subNodes[i] != null)
493 | {
494 | subNodes[i].dispose();
495 | subNodes[i] = null;
496 | }
497 | }
498 | }
499 | else if (LOD >= 3)
500 | {
501 | for (int i = 0; i < 8; i++)
502 | {
503 | if (subNodes[i] != null)
504 | {
505 | subNodes[i].dispose();
506 | subNodes[i] = null;
507 | }
508 | }
509 | }
510 | }
511 |
512 | if (LOD == 0)
513 | {
514 | float nodeSize = (float)NodeManager.LODSize[0] * (float)NodeManager.nodeSize;
515 | Vector3I viewChunk = new Vector3I((int)(pos.x / nodeSize),
516 | (int)(pos.y / nodeSize),
517 | (int)(pos.z / nodeSize));
518 |
519 | Vector3I curChunk = new Vector3I((int)(position.x / nodeSize),
520 | (int)(position.y / nodeSize),
521 | (int)(position.z / nodeSize));
522 |
523 | if (curChunk.x >= viewChunk.x - 3 && curChunk.x <= viewChunk.x + 3 &&
524 | curChunk.y >= viewChunk.y - 3 && curChunk.y <= viewChunk.y + 3 &&
525 | curChunk.z >= viewChunk.z - 3 && curChunk.z <= viewChunk.z + 3)
526 | {
527 | collides = true;
528 | if (chunk != null)
529 | {
530 | chunk.GetComponent().sharedMesh = chunk.GetComponent().sharedMesh;
531 | }
532 | }
533 | }
534 |
535 | renderCheck();
536 | }
537 |
538 | ///
539 | /// Searches for a node containing the given point and LOD, creating it if none is found.
540 | ///
541 | ///
542 | ///
543 | public void searchNodeCreate(Vector3 pos, int searchLOD, ref Node[] list)
544 | {
545 | if (containsDensityPoint(pos))
546 | {
547 | if (LOD == searchLOD)
548 | {
549 | for (int i = 0; i < list.Length; i++)
550 | if (list[i] == null)
551 | {
552 | list[i] = this;
553 | return;
554 | }
555 | Debug.Log("A");
556 | }
557 | else
558 | {
559 | if (isBottomLevel())
560 | createSubNodes(RenderType.FRONT);
561 |
562 | for (int i = 0; i < 8; i++)
563 | {
564 | subNodes[i].searchNodeCreate(pos, searchLOD, ref list);
565 | }
566 | }
567 | }
568 | }
569 |
570 | ///
571 | /// Searches for a node containing the given point and LOD.
572 | ///
573 | ///
574 | ///
575 | public Node searchNode(Vector3 pos, int searchLOD)
576 | {
577 | if (containsDensityPoint(pos))
578 | {
579 | if (searchLOD == LOD)
580 | return this;
581 |
582 | if (!isBottomLevel())
583 | for (int i = 0; i < 8; i++)
584 | if (subNodes[i] != null)
585 | {
586 | if (subNodes[i].containsPoint(pos))
587 | return subNodes[i].searchNode(pos, searchLOD);
588 | }
589 |
590 | return this;
591 | }
592 |
593 | if (parent != null)
594 | return parent.searchNode(pos, searchLOD);
595 |
596 | return NodeManager.searchNode(pos, searchLOD);
597 | }
598 |
599 | ///
600 | /// Returns whether or not the point is within the chunk.
601 | ///
602 | ///
603 | ///
604 | public bool containsPoint(Vector3 pos)
605 | {
606 | float chunkWidth = NodeManager.LODSize[LOD] * NodeManager.nodeSize;
607 | if ((pos.x >= position.x && pos.y >= position.y && pos.z >= position.z)
608 | && (pos.x <= position.x + chunkWidth && pos.y <= position.y + chunkWidth && pos.z <= position.z + chunkWidth))
609 | {
610 | return true;
611 | }
612 |
613 | return false;
614 | }
615 |
616 | ///
617 | /// Returns whether or not the point is within the chunk.
618 | ///
619 | ///
620 | ///
621 | public bool containsDensityPoint(Vector3 pos)
622 | {
623 | float chunkWidth = NodeManager.LODSize[LOD] * NodeManager.nodeSize;
624 | Vector3 corner1 = new Vector3(position.x - NodeManager.LODSize[LOD],
625 | position.y - NodeManager.LODSize[LOD],
626 | position.z - NodeManager.LODSize[LOD]);
627 |
628 | Vector3 corner2 = new Vector3(position.x + chunkWidth + NodeManager.LODSize[LOD],
629 | position.y + chunkWidth + NodeManager.LODSize[LOD],
630 | position.z + chunkWidth + NodeManager.LODSize[LOD]);
631 |
632 | if((pos.x >= corner1.x && pos.y >= corner1.y && pos.z >= corner1.z) &&
633 | (pos.x <= corner2.x && pos.y <= corner2.y && pos.z <= corner2.z)) {
634 | return true;
635 | }
636 |
637 | return false;
638 | }
639 |
640 | ///
641 | /// Checks whether this chunk should render and where.
642 | ///
643 | public void renderCheck()
644 | {
645 | if (disposed)
646 | {
647 | if (chunk != null)
648 | chunk.GetComponent().renderer.enabled = false;
649 | return;
650 | }
651 |
652 | if (chunk != null)
653 | if (isBottomLevel())
654 | {
655 | chunk.GetComponent().renderer.enabled = true;
656 | setRenderType(RenderType.FRONT);
657 | }
658 | else
659 | {
660 | bool render = false;
661 | for (int i = 0; i < 8; i++)
662 | if (subNodes[i] != null && !subNodes[i].hasMesh)
663 | {
664 | render = true;
665 | setRenderType(RenderType.FRONT);
666 | }
667 | chunk.GetComponent().renderer.enabled = render;
668 | }
669 | }
670 |
671 | ///
672 | /// Called when a mesh has been generated
673 | ///
674 | /// Mesh data
675 | public void setMesh(MeshData meshData)
676 | {
677 | densityData.setChangeData(densityChangeData);
678 |
679 | regenReq = false;
680 | if (regenFlag)
681 | {
682 | regenFlag = false;
683 | regenReq = true;
684 | MeshFactory.requestMesh(this);
685 | }
686 |
687 | hasMesh = true;
688 | if (meshData.indexArray.Length == 0)
689 | return;
690 |
691 | if (chunk == null)
692 | {
693 | chunk = ChunkPool.getChunk();
694 | if (LOD > 2)
695 | chunk.transform.position = position - new Vector3(0f, (NodeManager.LODSize[LOD] / 2f), 0f);
696 | else
697 | chunk.transform.position = position;
698 |
699 | //chunk.GetComponent().mesh.subMeshCount = QuixelEngine.materials.Length;
700 | chunk.GetComponent().materials = QuixelEngine.materials;
701 | }
702 |
703 | empty = false;
704 | Mesh mesh = new Mesh();
705 | mesh.subMeshCount = QuixelEngine.materials.Length;
706 | mesh.vertices = meshData.triangleArray;
707 |
708 | for (int i = 0; i < QuixelEngine.materials.Length; i++)
709 | {
710 | if (meshData.indexArray[i].Length > 0)
711 | mesh.SetTriangles(meshData.indexArray[i], i);
712 | else if (mesh.triangles.Length != 0)
713 | mesh.SetTriangles(new int[3] { 0, 0, 0 }, i);
714 | }
715 | //mesh.triangles = meshData.indexArray;
716 | mesh.uv = meshData.uvArray;
717 |
718 | mesh.normals = meshData.normalArray;
719 | //mesh.RecalculateBounds();
720 | mesh.Optimize();
721 |
722 | chunk.GetComponent().mesh = mesh;
723 |
724 | if (LOD == 0 && collides)
725 | chunk.GetComponent().sharedMesh = mesh;
726 | meshData.dispose();
727 |
728 | renderCheck();
729 | switch (renderType)
730 | {
731 | case RenderType.BACK:
732 | if (chunk != null)
733 | chunk.layer = 9;
734 | break;
735 |
736 | case RenderType.FRONT:
737 | if (chunk != null)
738 | chunk.layer = 8;
739 | break;
740 | }
741 | if (parent != null)
742 | parent.renderCheck();
743 | }
744 |
745 | ///
746 | /// Sets the render type of the chunk.
747 | /// Front will be rendered last, on top of Back.
748 | ///
749 | ///
750 | public void setRenderType(RenderType r)
751 | {
752 | switch (r)
753 | {
754 | case RenderType.BACK:
755 | if (chunk != null)
756 | chunk.layer = 9;
757 | break;
758 |
759 | case RenderType.FRONT:
760 | if (chunk != null)
761 | chunk.layer = 8;
762 | break;
763 | }
764 |
765 | renderType = r;
766 | }
767 |
768 | ///
769 | /// Returns whether or not this is the bottom level of the tree.
770 | ///
771 | ///
772 | public bool isBottomLevel()
773 | {
774 | for (int i = 0; i < 8; i++)
775 | {
776 | if (subNodes[i] == null)
777 | return true;
778 | else if (subNodes[i].disposed)
779 | return true;
780 | }
781 |
782 | return false;
783 | }
784 |
785 | ///
786 | /// Checks if this is a transitional node.
787 | /// True if an adjacent node of the same LOD has subnodes.
788 | ///
789 | ///
790 | public bool isTransitional()
791 | {
792 | if (LOD == 1 || LOD == 0)
793 | return false;
794 |
795 | for (int i = 0; i < 6; i++)
796 | {
797 | if (neighborNodes[i] != null)
798 | if (neighborNodes[i].LOD == this.LOD && neighborNodes[i].isBottomLevel() && !isBottomLevel())
799 | return true;
800 | }
801 |
802 | return false;
803 | }
804 |
805 | ///
806 | /// Populates the subnode array
807 | ///
808 | public void createSubNodes(RenderType type)
809 | {
810 | if (LOD == 0)
811 | return;
812 |
813 | if (subNodes[0] != null)
814 | {
815 | for (int i = 0; i < 8; i++)
816 | {
817 | if (subNodes[i].renderType != type)
818 | subNodes[i].setRenderType(type);
819 |
820 | subNodes[i].disposed = false;
821 | }
822 | return;
823 | }
824 |
825 | for (int i = 0; i < 8; i++)
826 | {
827 | if (subNodes[i] == null)
828 | subNodes[i] = new Node(this, NodeManager.getOffsetPosition(this, i), i, LOD - 1, type);
829 | }
830 | }
831 | ///
832 | /// Sets the density of a point, given a world pos.
833 | ///
834 | ///
835 | public void setDensityFromWorldPos(Vector3 worldPos, float val)
836 | {
837 | worldPos = worldPos - position;
838 | Vector3I arrayPos = new Vector3I((int)Math.Round(worldPos.x) / NodeManager.LODSize[LOD],
839 | (int)Math.Round(worldPos.y) / NodeManager.LODSize[LOD],
840 | (int)Math.Round(worldPos.z) / NodeManager.LODSize[LOD]);
841 |
842 | if (arrayPos.x < -1 || arrayPos.x > 17 ||
843 | arrayPos.y < -1 || arrayPos.y > 17 ||
844 | arrayPos.z < -1 || arrayPos.z > 17)
845 | {
846 | Debug.Log("Wrong node. " + arrayPos + ":" + worldPos + ":" + containsDensityPoint(worldPos).ToString());
847 | return;
848 | }
849 |
850 | densityChangeData.set(arrayPos.x, arrayPos.y, arrayPos.z, val);
851 | setPermanence(true);
852 |
853 | hasDensityChangeData = true;
854 | MeshFactory.requestSave(this);
855 | }
856 |
857 | ///
858 | /// Sets the material of the voxel at the given world position.
859 | ///
860 | ///
861 | ///
862 | public void setMaterialFromWorldPos(Vector3 worldPos, byte val)
863 | {
864 | worldPos = worldPos - position;
865 | Vector3I arrayPos = new Vector3I((int)Math.Round(worldPos.x) / NodeManager.LODSize[LOD],
866 | (int)Math.Round(worldPos.y) / NodeManager.LODSize[LOD],
867 | (int)Math.Round(worldPos.z) / NodeManager.LODSize[LOD]);
868 |
869 | if (arrayPos.x < -1 || arrayPos.x > 17 ||
870 | arrayPos.y < -1 || arrayPos.y > 17 ||
871 | arrayPos.z < -1 || arrayPos.z > 17)
872 | {
873 | Debug.Log("Wrong node. " + arrayPos);
874 | return;
875 | }
876 |
877 | bool change = (densityChangeData.getMaterial(arrayPos.x, arrayPos.y, arrayPos.z) != val);
878 | densityChangeData.setMaterial(arrayPos.x, arrayPos.y, arrayPos.z, val);
879 |
880 | if (change)
881 | {
882 | setPermanence(true);
883 | hasDensityChangeData = true;
884 | MeshFactory.requestSave(this);
885 | }
886 | }
887 |
888 | ///
889 | /// Regenerates the chunk without threading.
890 | ///
891 | public void regenerateChunk()
892 | {
893 | if (regenReq)
894 | {
895 | regenFlag = true;
896 | }
897 | else
898 | {
899 | MeshFactory.requestMesh(this);
900 | regenReq = true;
901 | }
902 | }
903 |
904 | ///
905 | /// Saves changes, if any.
906 | ///
907 | public void saveChanges()
908 | {
909 | if (!permanent)
910 | return;
911 |
912 | string dir = getDirectory();
913 | if (!Directory.Exists(dir))
914 | Directory.CreateDirectory(dir);
915 |
916 | int[] matData = densityChangeData.compressMaterialData();
917 | float[] data = densityChangeData.compressDensityData();
918 |
919 | StreamWriter writer = new StreamWriter(dir + "\\materials.txt");
920 | for (int i = 0; i < matData.Length; i += 2)
921 | writer.WriteLine(matData[i] + "," + matData[i + 1]);
922 | writer.Close();
923 |
924 | writer = new StreamWriter(dir + "\\densities.txt");
925 | for (int i = 0; i < data.Length; i += 2)
926 | writer.WriteLine(data[i] + "," + data[i + 1]);
927 | writer.Close();
928 | }
929 |
930 | ///
931 | /// Attempts to load density changes.
932 | ///
933 | public bool loadChanges()
934 | {
935 | string dir = getDirectory();
936 | if (!Directory.Exists(dir))
937 | return false;
938 |
939 | if (!File.Exists(dir + "\\densities.txt"))
940 | return false;
941 |
942 | List fileData = new List();
943 | StreamReader reader = new StreamReader(dir + "\\densities.txt");
944 | while (!reader.EndOfStream)
945 | fileData.Add(reader.ReadLine());
946 |
947 | float[] data = new float[fileData.Count * 2];
948 | string[] split = new string[2];
949 | for (int i = 0; i < fileData.Count; i++)
950 | {
951 | split = fileData[i].Split(',');
952 | data[(i * 2)] = float.Parse(split[0]);
953 | data[(i * 2) + 1] = float.Parse(split[1]);
954 | }
955 |
956 | reader.Close();
957 | densityChangeData.decompressDensityData(data);
958 |
959 | fileData = new List();
960 | reader = new StreamReader(dir + "\\materials.txt");
961 | while (!reader.EndOfStream)
962 | fileData.Add(reader.ReadLine());
963 |
964 | int[] mdata = new int[fileData.Count * 2];
965 | split = new string[2];
966 | for (int i = 0; i < fileData.Count; i++)
967 | {
968 | split = fileData[i].Split(',');
969 | mdata[(i * 2)] = int.Parse(split[0]);
970 | mdata[(i * 2) + 1] = int.Parse(split[1]);
971 | }
972 |
973 | reader.Close();
974 | densityChangeData.decompressMaterialData(mdata);
975 |
976 | hasDensityChangeData = true;
977 | return true;
978 | }
979 |
980 | ///
981 | /// Disposes of subnodes
982 | ///
983 | public void dispose()
984 | {
985 | //If already disposed, exit.
986 | if (disposed)
987 | return;
988 |
989 | disposed = true;
990 |
991 | for (int i = 0; i < 8; i++)
992 | {
993 | if (subNodes[i] != null)
994 | {
995 | subNodes[i].dispose();
996 | subNodes[i] = null;
997 | }
998 | }
999 |
1000 | if (permanent)
1001 | {
1002 | if (chunk != null)
1003 | chunk.GetComponent().renderer.enabled = false;
1004 | return;
1005 | }
1006 |
1007 | NodeManager.nodeCount[LOD]--;
1008 |
1009 | hasMesh = false;
1010 | if (parent != null)
1011 | parent.renderCheck();
1012 |
1013 | //Remove this node from the neighbor of existing nodes.
1014 | for (int i = 0; i < 6; i++)
1015 | {
1016 | if (neighborNodes[i] != null)
1017 | {
1018 | neighborNodes[i].neighborNodes[oppositeNeighbor[i]] = null;
1019 | }
1020 | }
1021 |
1022 | if (chunk != null)
1023 | {
1024 | UnityEngine.Object.Destroy(chunk.GetComponent().sharedMesh);
1025 | Mesh mesh = chunk.GetComponent().sharedMesh;
1026 | if (mesh != null)
1027 | UnityEngine.Object.Destroy(mesh);
1028 |
1029 | ChunkPool.recycleChunk(chunk);
1030 | }
1031 |
1032 | if (densityData != null)
1033 | DensityPool.recycleDensityData(densityData);
1034 | if (densityChangeData != null)
1035 | DensityPool.recycleDensityData(densityChangeData);
1036 | }
1037 |
1038 | ///
1039 | /// Attempts to find any neighbors that we don't have a reference to.
1040 | ///
1041 | private void findNeighbors()
1042 | {
1043 | float nodeWidth = NodeManager.LODSize[LOD] * NodeManager.nodeSize;
1044 | Vector3 searchPos = new Vector3();
1045 | for (int i = 0; i < 6; i++)
1046 | {
1047 | if (neighborNodes[i] != null)
1048 | {
1049 | if (neighborNodes[i].LOD == LOD)
1050 | continue;
1051 | }
1052 | searchPos.x = center.x + (neighborMask[i].x * nodeWidth);
1053 | searchPos.y = center.y + (neighborMask[i].y * nodeWidth);
1054 | searchPos.z = center.z + (neighborMask[i].z * nodeWidth);
1055 | neighborNodes[i] = NodeManager.searchNode(searchPos, LOD);
1056 |
1057 | if (neighborNodes[i] != null && neighborNodes[i].LOD == LOD)
1058 | neighborNodes[i].neighborNodes[oppositeNeighbor[i]] = this;
1059 | }
1060 | }
1061 |
1062 | ///
1063 | /// Sets the permanence of this node. If true, it will not be disposed of when out of range.
1064 | ///
1065 | private void setPermanence(bool perm)
1066 | {
1067 | if (perm == true)
1068 | if (parent != null)
1069 | parent.setPermanence(true);
1070 |
1071 | if (perm == false)
1072 | for (int i = 0; i < 8; i++)
1073 | if (subNodes[i] != null)
1074 | subNodes[i].setPermanence(false);
1075 |
1076 | permanent = perm;
1077 | }
1078 |
1079 | ///
1080 | /// Checks if the node intersects with a boundary box of the given size around the player.
1081 | ///
1082 | ///
1083 | ///
1084 | ///
1085 | private bool checkRangeIntersection(Vector3 pos, float width)
1086 | {
1087 | float nodeWidth = NodeManager.nodeSize * NodeManager.LODSize[LOD];
1088 | Vector3[] corners = new Vector3[8] {
1089 | new Vector3(position.x, position.y, position.z),
1090 | new Vector3(position.x + nodeWidth, position.y, position.z),
1091 | new Vector3(position.x, position.y + nodeWidth, position.z),
1092 | new Vector3(position.x, position.y, position.z + nodeWidth),
1093 | new Vector3(position.x + nodeWidth, position.y + nodeWidth, position.z),
1094 | new Vector3(position.x + nodeWidth, position.y, position.z + nodeWidth),
1095 | new Vector3(position.x, position.y + nodeWidth, position.z + nodeWidth),
1096 | new Vector3(position.x + nodeWidth, position.y + nodeWidth, position.z + nodeWidth),
1097 | };
1098 |
1099 | for (int i = 0; i < 8; i++)
1100 | {
1101 | //float distance = (float)Math.Sqrt((corners[i].x - pos.x) * (corners[i].x - pos.x) +
1102 | //(corners[i].y - pos.y) * (corners[i].y - pos.y) +
1103 | //(corners[i].z - pos.z) * (corners[i].z - pos.z));
1104 | float distance = Vector3.Distance(pos, corners[i]);
1105 | if (distance < width)
1106 | return true;
1107 | }
1108 |
1109 | return false;
1110 | }
1111 |
1112 | ///
1113 | /// Gets the directory of where this node should be saved.
1114 | ///
1115 | ///
1116 | private string getDirectory()
1117 | {
1118 | if (parent == null) return NodeManager.worldName + "\\" + subNodeID.ToString();
1119 | return parent.getDirectory() + "\\" + subNodeID;
1120 | }
1121 |
1122 | public override string ToString()
1123 | {
1124 | return "LOD: " + LOD + ", Pos: " + position.ToString();
1125 | }
1126 |
1127 | ///
1128 | /// Draws chunk outlines (Very buggy and laggy)
1129 | ///
1130 | ///
1131 | public int debugDraw()
1132 | {
1133 | return 0;
1134 | //Debug drawing, draws the outlines of chunks, very laggy use at own risk.
1135 | /*
1136 | if (true)
1137 | {
1138 | int nodeWidth = NodeManager.nodeSize * NodeManager.LODSize[LOD];
1139 | if (LOD == 0) Gizmos.color = Color.red;
1140 | if (LOD == 1) Gizmos.color = Color.yellow;
1141 | if (LOD == 2) Gizmos.color = Color.white;
1142 | if (LOD == 3) Gizmos.color = Color.green;
1143 | if (LOD == 4) Gizmos.color = Color.blue;
1144 |
1145 | //if(isTransitional())
1146 | //Gizmos.color = Color.magenta;
1147 |
1148 | Gizmos.color = new Color(Gizmos.color.r, Gizmos.color.g, Gizmos.color.b, 0.5f);
1149 | Vector3 drawPos = new Vector3(position.x + (nodeWidth / 2), position.y + (nodeWidth / 2), position.z + (nodeWidth / 2));
1150 |
1151 | //if (!isTransitional())
1152 | //{
1153 | if (LOD == 0)// && !empty)
1154 | {
1155 | Gizmos.DrawWireCube(drawPos, new Vector3(nodeWidth, nodeWidth, nodeWidth));
1156 | //}
1157 | //else if(LOD == 3)
1158 | //{
1159 | // Gizmos.DrawCube(drawPos, new Vector3(nodeWidth, nodeWidth, nodeWidth));
1160 | //}
1161 | Gizmos.DrawCube(center, new Vector3(10, 10, 10));
1162 | }
1163 | }
1164 |
1165 | int ct = 1;
1166 | for (int i = 0; i < 8; i++)
1167 | if (subNodes[i] != null)
1168 | ct += subNodes[i].debugDraw();
1169 | return ct;
1170 | */
1171 | }
1172 | }
1173 | }
1174 |
--------------------------------------------------------------------------------
/Pooling.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | internal static class ChunkPool
11 | {
12 | /// A list of unused chunk objects
13 | public static List chunkList = new List();
14 | public static int totalCreated = 0;
15 |
16 | ///
17 | /// Gets a chunk from the chunk pool, creates one if the chunk pool is empty
18 | ///
19 | ///
20 | public static GameObject getChunk()
21 | {
22 | if (chunkList.Count == 0)
23 | {
24 | totalCreated++;
25 | GameObject obj = (GameObject)GameObject.Instantiate(MeshFactory.chunkObj);
26 | obj.transform.parent = MeshFactory.terrainObj.transform;
27 | return obj;
28 | }
29 |
30 | GameObject chunk = chunkList[0];
31 | chunkList.RemoveAt(0);
32 | chunk.SetActive(true);
33 | return chunk;
34 | }
35 |
36 | ///
37 | /// Recycles a chunk gameobject to the chunk pool
38 | ///
39 | ///
40 | public static void recycleChunk(GameObject chunk)
41 | {
42 | chunk.GetComponent().sharedMesh = null;
43 | chunk.GetComponent().sharedMesh = null;
44 | chunk.SetActive(false);
45 | chunkList.Add(chunk);
46 | }
47 | }
48 |
49 | internal static class DensityPool
50 | {
51 | ///
52 | /// A pool of reusable density arrays.
53 | ///
54 | //private static Queue densityPool = new Queue();
55 | //private static Queue densityRecycleList = new Queue();
56 | private static DensityThread densityThread;
57 |
58 | ///
59 | /// Initializes the density pool, starts the recycle thread.
60 | ///
61 | public static void init()
62 | {
63 | //densityThread = new DensityThread();
64 | }
65 |
66 | ///
67 | /// Updates the density pool.
68 | ///
69 | public static void update()
70 | {
71 | return;
72 |
73 | /*
74 | DensityData d;
75 | while ((d = densityThread.getFinishedDensity()) != null)
76 | {
77 | lock(densityPool)
78 | densityPool.Enqueue(d);
79 | }
80 | */
81 | }
82 |
83 | ///
84 | /// Attempts to pull a recycled array of densities from the pool. Creates one if none found.
85 | ///
86 | ///
87 | public static DensityData getDensityData()
88 | {
89 | return new DensityData();
90 |
91 | //See: recycleDensityData
92 | /*
93 | if (densityPool.Count == 0)
94 | return new DensityData();
95 |
96 | lock (densityPool)
97 | return densityPool.Dequeue();
98 | */
99 | }
100 |
101 | ///
102 | /// Recycles (threaded) a density data array.
103 | ///
104 | ///
105 | public static void recycleDensityData(DensityData arr)
106 | {
107 | //Visual bug caused by recycling densities
108 | //densityThread.queueRecycleDensity(arr);
109 | return;
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/QuixelEngine.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | public static class QuixelEngine
11 | {
12 | public static Material[] materials;
13 | private static GameObject cameraObj;
14 | private static bool active = true;
15 |
16 | ///
17 | /// Initializes the Quixel Engine
18 | ///
19 | /// Array of materials.
20 | /// Parent terrain object. (empty)
21 | /// Name of the world. Used for paging. (empty)
22 | public static void init(Material[] mats, GameObject terrainObj, string worldName)
23 | {
24 | MeshFactory.terrainObj = terrainObj;
25 |
26 | materials = mats;
27 | Debug.Log("Materials: " + mats.Length);
28 | DensityPool.init();
29 | MeshFactory.start();
30 | NodeManager.init(worldName);
31 | }
32 |
33 | ///
34 | /// Sets the width/length/height of the smallest LOD voxel.
35 | /// The width of a single voxel will be 2^(size + LOD)
36 | ///
37 | /// The size (units).
38 | public static void setVoxelSize(int size, int maxLOD)
39 | {
40 | NodeManager.maxLOD = maxLOD;
41 | NodeManager.nodeCount = new int[maxLOD+1];
42 | NodeManager.LODSize = new int[maxLOD+1];
43 | for (int i = 0; i <= maxLOD; i++)
44 | {
45 | NodeManager.LODSize[i] = (int)Mathf.Pow(2, i + size);
46 | }
47 | }
48 |
49 | ///
50 | /// Sets the terrain generator to use when generating terrain.
51 | ///
52 | public static void setTerrainGenerator(IGenerator gen)
53 | {
54 | MeshFactory.terrainGenerator = gen;
55 | }
56 |
57 | ///
58 | /// Updates the Quixel system. Should be called every step.
59 | ///
60 | public static void update()
61 | {
62 | DensityPool.update();
63 | MeshFactory.update();
64 |
65 | if (cameraObj != null)
66 | NodeManager.setViewPosition(cameraObj.transform.position);
67 |
68 | if (!Application.isPlaying)
69 | active = false;
70 | }
71 |
72 | ///
73 | /// Sets the object to follow for the LOD system.
74 | ///
75 | ///
76 | public static void setCameraObj(GameObject obj)
77 | {
78 | cameraObj = obj;
79 | }
80 |
81 | ///
82 | /// Returns true if the player is still active.
83 | ///
84 | ///
85 | public static bool isActive()
86 | {
87 | return active;
88 | }
89 |
90 | ///
91 | /// Terminates the engine.
92 | ///
93 | ///
94 | public static void terminate()
95 | {
96 | active = false;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | Documentation can be found here:
2 | https://docs.google.com/document/d/1ha5eLEsvmYe76cXGLJBBtMrsIqIsPUjYbMszIoxdXnI/edit?usp=sharing
3 |
4 | Hello and welcome to the now open source Quixel terrain engine!
5 |
6 | This is a project that initially started as a hobby project, to see if
7 | I could create an efficient voxel engine for Unity. Initially, I planned
8 | on selling this on the Unity asset store, however life happened suddenly
9 | and I'm unable to work on this as much as before, if at all. So here it
10 | is, open sourced under the MIT license. You're free to use it for
11 | commercial use, however I'd really love to hear about any project using
12 | this. Thanks!
13 |
14 | Suppose I'll keep the important updates here.
15 |
16 | 4/14/2014:
17 | Uploaded project to GitHub.
18 |
19 | Known Bugs:
20 | Editing terrain with NodeEditor does not work if the initial voxel width is not 1.
--------------------------------------------------------------------------------
/Threading.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | ///
11 | /// Thread used for generating chunks.
12 | ///
13 | internal class GeneratorThread
14 | {
15 | private Queue genQueue;
16 | private Queue finishedQueue;
17 | private Thread thread;
18 |
19 | public GeneratorThread()
20 | {
21 | Debug.Log("Generation Thread Started");
22 | genQueue = new Queue();
23 | finishedQueue = new Queue();
24 |
25 | try
26 | {
27 | thread = new Thread((System.Object obj) =>
28 | {
29 | generateLoop();
30 | });
31 |
32 | //new ThreadStart(generateLoop));
33 | thread.Priority = System.Threading.ThreadPriority.BelowNormal;
34 | thread.IsBackground = true;
35 | thread.Start();
36 | }
37 | catch (Exception e)
38 | {
39 | Debug.Log(e.Message + "\r\n" + e.StackTrace);
40 | }
41 | }
42 |
43 | ///
44 | /// Queue a mesh for generation.
45 | ///
46 | ///
47 | public void queueGenerateMesh(MeshFactory.MeshRequest req)
48 | {
49 | lock (genQueue)
50 | genQueue.Enqueue(req);
51 | }
52 |
53 | ///
54 | /// Returns a finished mesh request if there is one.
55 | ///
56 | ///
57 | public MeshFactory.MeshRequest getFinishedMesh()
58 | {
59 | if (finishedQueue.Count > 0)
60 | lock (finishedQueue)
61 | return finishedQueue.Dequeue();
62 |
63 | return null;
64 | }
65 |
66 | ///
67 | /// Gets the count of queued mesh requests.
68 | ///
69 | ///
70 | public int getCount()
71 | {
72 | return genQueue.Count;
73 | }
74 |
75 | ///
76 | /// Continuously check if we have chunks to generate.
77 | ///
78 | private void generateLoop()
79 | {
80 | bool sleep = true;
81 | while (QuixelEngine.isActive())
82 | {
83 | sleep = false;
84 |
85 | MeshFactory.MeshRequest req = MeshFactory.getNextRequest();
86 | if (req == null)
87 | sleep = true;
88 | else
89 | {
90 | if (req.densities == null)
91 | if (!req.hasDensities)
92 | req.densities = DensityPool.getDensityData();
93 | else
94 | req.densities = req.node.densityData;
95 |
96 | MeshFactory.GenerateMeshData(req);
97 |
98 | lock (finishedQueue)
99 | finishedQueue.Enqueue(req);
100 | }
101 |
102 | if (sleep)
103 | Thread.Sleep(30);
104 | else
105 | Thread.Sleep(4);
106 | }
107 | }
108 | }
109 |
110 | ///
111 | /// Thread used for generating densities.
112 | /// Currently unused due to bugs
113 | ///
114 | internal class DensityThread
115 | {
116 | private Queue genQueue;
117 | private Queue finishedQueue;
118 | private Thread thread;
119 |
120 | public DensityThread()
121 | {
122 | genQueue = new Queue();
123 | finishedQueue = new Queue();
124 |
125 | thread = new Thread(new ThreadStart(recycleLoop));
126 | thread.Priority = System.Threading.ThreadPriority.BelowNormal;
127 | thread.IsBackground = true;
128 | thread.Start();
129 | }
130 |
131 | ///
132 | /// Queue a mesh for generation.
133 | ///
134 | ///
135 | public void queueRecycleDensity(DensityData req)
136 | {
137 | lock (genQueue)
138 | genQueue.Enqueue(req);
139 | }
140 |
141 | ///
142 | /// Returns a finished mesh request if there is one.
143 | ///
144 | ///
145 | public DensityData getFinishedDensity()
146 | {
147 | if (finishedQueue.Count > 0)
148 | lock (finishedQueue)
149 | return finishedQueue.Dequeue();
150 |
151 | return null;
152 | }
153 |
154 | ///
155 | /// Continuously check if we have chunks to generate.
156 | ///
157 | private void recycleLoop()
158 | {
159 | while (QuixelEngine.isActive())
160 | {
161 | Thread.Sleep(1);
162 | if (genQueue.Count > 0)
163 | {
164 | DensityData d = null;
165 | lock (genQueue)
166 | d = genQueue.Dequeue();
167 |
168 | d.dispose();
169 |
170 | lock (finishedQueue)
171 | finishedQueue.Enqueue(d);
172 | }
173 | else
174 | {
175 | Thread.Sleep(30);
176 | }
177 | }
178 | }
179 | }
180 |
181 | ///
182 | /// Thread used for saving/loading chunks.
183 | ///
184 | internal class FileThread
185 | {
186 | private Queue finishedLoadQueue;
187 | private Queue loadQueue;
188 | private Queue saveQueue;
189 | private Thread thread;
190 |
191 | public FileThread()
192 | {
193 | finishedLoadQueue = new Queue();
194 | loadQueue = new Queue();
195 | saveQueue = new Queue();
196 |
197 | try
198 | {
199 | thread = new Thread(new ThreadStart(commitLoop));
200 | thread.Priority = System.Threading.ThreadPriority.BelowNormal;
201 | thread.IsBackground = true;
202 | thread.Start();
203 | }
204 | catch (Exception e)
205 | {
206 | Debug.Log(e.Message + "\r\n" + e.StackTrace);
207 | }
208 | }
209 |
210 | ///
211 | /// Queues the node for saving.
212 | ///
213 | ///
214 | public void enqueueSave(Node node)
215 | {
216 | lock (saveQueue)
217 | if (!saveQueue.Contains(node))
218 | saveQueue.Enqueue(node);
219 | }
220 |
221 | ///
222 | /// Queues the node for loading.
223 | ///
224 | ///
225 | public void enqueueLoad(Node node)
226 | {
227 | lock (loadQueue)
228 | loadQueue.Enqueue(node);
229 | }
230 |
231 | ///
232 | /// Returns a finished node request if there is one.
233 | ///
234 | ///
235 | public Node getFinishedLoadRequest()
236 | {
237 | if (finishedLoadQueue.Count > 0)
238 | lock (finishedLoadQueue)
239 | return finishedLoadQueue.Dequeue();
240 |
241 | return null;
242 | }
243 |
244 | ///
245 | /// Loop for committing saves and loads.
246 | ///
247 | private void commitLoop()
248 | {
249 | while (QuixelEngine.isActive())
250 | {
251 | try
252 | {
253 | Thread.Sleep(3);
254 | if (saveQueue.Count > 0)
255 | {
256 | MeshFactory.nodesSaved++;
257 | lock (saveQueue)
258 | saveQueue.Dequeue().saveChanges();
259 | }
260 |
261 | if (loadQueue.Count > 0)
262 | {
263 | Node node = null;
264 | lock (loadQueue)
265 | node = loadQueue.Dequeue();
266 | if (node.loadChanges())
267 | {
268 | MeshFactory.nodesLoaded++;
269 | lock (finishedLoadQueue)
270 | finishedLoadQueue.Enqueue(node);
271 | }
272 | }
273 | }
274 | catch (Exception e)
275 | {
276 | StreamWriter writer = new StreamWriter("Error Log.txt");
277 | writer.WriteLine(e.Message + "\r\n" + e.StackTrace);
278 | writer.Close();
279 | }
280 | }
281 | }
282 | }
283 |
284 |
285 | }
--------------------------------------------------------------------------------
/Utilities.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System;
6 | using System.IO;
7 |
8 | namespace Quixel
9 | {
10 | internal class DensityData
11 | {
12 | #region Fields
13 | /// Reference to another density data that contains the change in densities used for paging.
14 | private DensityData changeData;
15 |
16 | /// Density values
17 | public float[, ,] Values;
18 |
19 | /// Array of bytes that dictates which material to render
20 | public byte[, ,] Materials;
21 | #endregion
22 |
23 | public DensityData()
24 | {
25 | Values = new float[19, 19, 19];
26 | Materials = new byte[19, 19, 19];
27 | for (int x = 0; x < 19; x++)
28 | for (int y = 0; y < 19; y++)
29 | for (int z = 0; z < 19; z++)
30 | {
31 | Values[x, y, z] = -100000f;
32 | Materials[x, y, z] = 0;
33 | }
34 | }
35 |
36 | ///
37 | /// Returns the density value at the given coordinates.
38 | ///
39 | ///
40 | ///
41 | ///
42 | ///
43 | public float get(int x, int y, int z)
44 | {
45 | if (changeData != null)
46 | if (changeData.get(x, y, z) > -99999f)
47 | return changeData.get(x, y, z);
48 |
49 | return Values[x + 1, y + 1, z + 1];
50 | }
51 |
52 | ///
53 | /// Returns the density value at the given coordinates.
54 | ///
55 | ///
56 | ///
57 | ///
58 | ///
59 | public float get(Vector3I pos)
60 | {
61 | if (changeData != null)
62 | if (changeData.get(pos) > -99999f)
63 | return changeData.get(pos);
64 |
65 | return get(pos.x, pos.y, pos.z);
66 | }
67 |
68 | ///
69 | /// Returns the material index of a particular voxel
70 | ///
71 | ///
72 | ///
73 | ///
74 | ///
75 | public byte getMaterial(int x, int y, int z)
76 | {
77 | if (changeData != null)
78 | if (changeData.getMaterial(x, y, z) != 0)
79 | return changeData.getMaterial(x, y, z);
80 | return Materials[x + 1, y + 1, z + 1];
81 | }
82 |
83 | ///
84 | /// Sets the density value at the given coordinates.
85 | ///
86 | ///
87 | ///
88 | ///
89 | ///
90 | public void set(int x, int y, int z, float val)
91 | {
92 | Values[x + 1, y + 1, z + 1] = val;
93 | }
94 |
95 | ///
96 | /// Sets the density value at the given coordinates.
97 | ///
98 | ///
99 | ///
100 | ///
101 | ///
102 | public void set(Vector3I pos, float val)
103 | {
104 | set(pos.x, pos.y, pos.z, val);
105 | }
106 |
107 | ///
108 | /// Sets the material of a particular voxel.
109 | ///
110 | ///
111 | ///
112 | ///
113 | ///
114 | public void setMaterial(int x, int y, int z, byte val)
115 | {
116 | Materials[x + 1, y + 1, z + 1] = val;
117 | }
118 |
119 | ///
120 | /// Applies changes (additive) using another Density Data
121 | ///
122 | ///
123 | public void applyChanges(DensityData other)
124 | {
125 | for (int x = 0; x < 19; x++)
126 | for (int y = 0; y < 19; y++)
127 | for (int z = 0; z < 19; z++)
128 | if (other.Values[x, y, z] > -99999f)
129 | Values[x, y, z] = other.Values[x, y, z];
130 | }
131 |
132 | ///
133 | /// Compresses (RLE) the density data.
134 | ///
135 | ///
136 | public float[] compressDensityData()
137 | {
138 | List data = new List();
139 | float last = Values[0, 0, 0];
140 | data.Add(0f);
141 | data.Add(last);
142 |
143 | int count = 0;
144 | for (int x = 0; x < 19; x++)
145 | for (int y = 0; y < 19; y++)
146 | for (int z = 0; z < 19; z++)
147 | {
148 | count++;
149 | if (Values[x, y, z] == last)
150 | {
151 | data[data.Count - 2] = count;
152 | }
153 | else
154 | {
155 | data.Add(1f);
156 | data.Add(Values[x, y, z]);
157 | count = 1;
158 | }
159 |
160 | last = Values[x, y, z];
161 | }
162 |
163 | float[] ret = new float[data.Count];
164 | for (int i = 0; i < data.Count; i++)
165 | ret[i] = data[i];
166 |
167 | return ret;
168 | }
169 |
170 | ///
171 | /// Compress the materials using run-length encoding.
172 | ///
173 | ///
174 | public int[] compressMaterialData()
175 | {
176 | List data = new List();
177 | int last = Materials[0, 0, 0];
178 | data.Add(0);
179 | data.Add(last);
180 |
181 | int count = 0;
182 | for (int x = 0; x < 19; x++)
183 | for (int y = 0; y < 19; y++)
184 | for (int z = 0; z < 19; z++)
185 | {
186 | count++;
187 | if (Materials[x, y, z] == last)
188 | {
189 | data[data.Count - 2] = count;
190 | }
191 | else
192 | {
193 | data.Add(1);
194 | data.Add(Materials[x, y, z]);
195 | count = 1;
196 | }
197 |
198 | last = Materials[x, y, z];
199 | }
200 |
201 | int[] ret = new int[data.Count];
202 | for (int i = 0; i < data.Count; i++)
203 | ret[i] = data[i];
204 |
205 | return ret;
206 | }
207 |
208 | ///
209 | /// Decompresses a sparse array of densities.
210 | ///
211 | ///
212 | public void decompressDensityData(float[] data)
213 | {
214 | int total = 0;
215 | int index = 0;
216 | int count = 0;
217 | for (int x = 0; x < 19; x++)
218 | for (int y = 0; y < 19; y++)
219 | for (int z = 0; z < 19; z++)
220 | {
221 | total++;
222 |
223 | if (count >= data[index])
224 | {
225 | count = 0;
226 | index += 2;
227 | }
228 |
229 | count++;
230 |
231 | Values[x, y, z] = data[index + 1];
232 | }
233 | }
234 |
235 | ///
236 | /// Decompresses a compressed array of materials.
237 | ///
238 | ///
239 | public void decompressMaterialData(int[] data)
240 | {
241 | int total = 0;
242 | int index = 0;
243 | int count = 0;
244 | for (int x = 0; x < 19; x++)
245 | for (int y = 0; y < 19; y++)
246 | for (int z = 0; z < 19; z++)
247 | {
248 | total++;
249 |
250 | if (count >= data[index])
251 | {
252 | count = 0;
253 | index += 2;
254 | }
255 |
256 | count++;
257 |
258 | Materials[x, y, z] = (byte)data[index + 1];
259 | }
260 | }
261 |
262 | ///
263 | /// Sets the change data for this density array.
264 | /// Values are pulled from here if available.
265 | ///
266 | public void setChangeData(DensityData data)
267 | {
268 | changeData = data;
269 | }
270 |
271 | ///
272 | /// Disposes of the density array.
273 | ///
274 | public void dispose()
275 | {
276 | for (int x = 0; x < 19; x++)
277 | for (int y = 0; y < 19; y++)
278 | for (int z = 0; z < 19; z++)
279 | Values[x, y, z] = -100000f;
280 | }
281 | }
282 |
283 | internal struct Triangle
284 | {
285 | public Vector3 pointOne, pointTwo, pointThree;
286 | public Vector3 nOne, nTwo, nThree;
287 |
288 | ///
289 | /// Creates a triangle consisting of 6 vectors. 3 for points, 3 for normals.
290 | ///
291 | ///
292 | ///
293 | ///
294 | ///
295 | ///
296 | ///
297 | public Triangle(Vector3 PointOne, Vector3 PointTwo, Vector3 PointThree, Vector3 nOne, Vector3 nTwo, Vector3 nThree)
298 | {
299 | this.pointOne = PointOne;
300 | this.pointTwo = PointTwo;
301 | this.pointThree = PointThree;
302 |
303 | this.nOne = nOne;
304 | this.nTwo = nTwo;
305 | this.nThree = nThree;
306 | }
307 | }
308 |
309 | internal struct Vector3I
310 | {
311 | public int x;
312 | public int y;
313 | public int z;
314 |
315 | public Vector3I(int x, int y, int z)
316 | {
317 | this.x = x;
318 | this.y = y;
319 | this.z = z;
320 | }
321 |
322 | ///
323 | /// Checks equality with another Vector3I object
324 | ///
325 | ///
326 | ///
327 | public override bool Equals(object obj)
328 | {
329 | if (obj == null || GetType() != obj.GetType())
330 | {
331 | return false;
332 | }
333 |
334 | Vector3I o = (Vector3I)obj;
335 | return (x == o.x && y == o.y && z == o.z);
336 | }
337 |
338 | ///
339 | /// Only because Unity complained if I didn't
340 | ///
341 | ///
342 | public override int GetHashCode()
343 | {
344 | return base.GetHashCode();
345 | }
346 |
347 | ///
348 | /// Adds another vector's values to this
349 | ///
350 | ///
351 | ///
352 | public Vector3I Add(Vector3I other)
353 | {
354 | return new Vector3I(x + other.x, y + other.y, z + other.z);
355 | }
356 |
357 | ///
358 | /// Subtracts another vectory's values from this
359 | ///
360 | ///
361 | ///
362 | public Vector3I Subtract(Vector3I other)
363 | {
364 | return new Vector3I(x - other.x, y - other.y, z - other.z);
365 | }
366 |
367 | ///
368 | /// Convert to string
369 | ///
370 | ///
371 | public override string ToString()
372 | {
373 | return string.Format("({0},{1},{2})", x, y, z);
374 | }
375 | }
376 |
377 | internal class MeshData
378 | {
379 | public Vector3[] triangleArray;
380 | public Vector2[] uvArray;
381 | public int[][] indexArray;
382 | public Vector3[] normalArray;
383 |
384 | ///
385 | /// Disposes the meshdata.
386 | ///
387 | public void dispose()
388 | {
389 | triangleArray = null;
390 | uvArray = null;
391 | indexArray = null;
392 | normalArray = null;
393 | }
394 | }
395 | }
--------------------------------------------------------------------------------