├── 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 | } --------------------------------------------------------------------------------