Repository: ray-cast/UnityVOXFileImport Branch: master Commit: 649b302719dd Files: 12 Total size: 49.6 KB Directory structure: gitextract_tyd_gif7/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md └── VOXFileLoader/ ├── Demo/ │ ├── 8x8x8.vox │ └── chr_sword.vox ├── Editor/ │ └── VOXFileLoader.cs └── Scripts/ ├── ObjFileExport.cs ├── VOXCruncher.cs ├── VOXFileImport.cs ├── VOXHashMap.cs └── VOXModel.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # git files .gitattributes export-ignore .gitignore export-ignore # Folders/Files Screenshots/ export-ignore ================================================ FILE: .gitignore ================================================ *.meta Standard Assets Materials ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 ~ 2018 Rui Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ MagicaVoxel for Unity 2017 ======== ### VOXFileLoader ###   The VOXFileLoader is a .vox reader to convert the .vox file from [MagicaVoxel](https://ephtracy.github.io/) to static GameObject and Prefab with optimized obj file. ![Alt](./Screenshots/screenshots.png) How to use: ======== * Download a zip archive from the github page * Un-zip the archive * Copy the VOXFileLoader folder to Assets folder * Open Window -> Tools -> Cubizer -> show VOXFileLoader inspector * Select .vox file in the project view * Click 'Create Prefab from .vox file' button, you'll get a prefab and obj file from project view Features: ------------ * Voxel file import for [MagicalVoxel](http://voxel.codeplex.com/) * Mesh Optimization * Level of detail Requirements: ======== * Unity 2017.2.0 or higher Contact: ------------ * Reach me via Twitter: [@Rui](https://twitter.com/Rui_cg). [License (MIT)](https://raw.githubusercontent.com/ray-cast/ray-mmd/developing/LICENSE.txt) ------------------------------------------------------------------------------- Copyright (C) 2016-2017 Ray-MMD Developers. All rights reserved. https://github.com/ray-cast/UnityVOXFileImport Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. References: -------- * Meshing in a Minecraft Game \[[link](https://0fps.net/2012/07/07/meshing-minecraft-part-2/)\] ================================================ FILE: VOXFileLoader/Editor/VOXFileLoader.cs ================================================ using System; using System.IO; using UnityEngine; using UnityEditor; using Cubizer; using Cubizer.Model; public class VOXFileLoader : EditorWindow { public bool _isSelectCreatePrefab = true; public bool _isSelectCreateAssetbundle = true; [MenuItem("Tools/Cubizer/Show VOXFileLoader Inspector")] public static void ShowWindow() { VOXFileLoader.CreateInstance().Show(); } [MenuItem("Tools/Cubizer/Load .vox file as Prefab")] public static void LoadVoxelFileAsPrefab() { var filepath = EditorUtility.OpenFilePanel("Load .vox file", "", "vox"); if (!String.IsNullOrEmpty(filepath)) { if (!filepath.Contains(".vox")) { EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); return; } VoxFileImport.LoadVoxelFileAsPrefab(filepath); } } [MenuItem("Tools/Cubizer/Load .vox file as GameObject")] public static void LoadVoxelFileAsGameObject() { var filepath = EditorUtility.OpenFilePanel("Load .vox file", "", "vox"); if (!String.IsNullOrEmpty(filepath)) { if (!filepath.Contains(".vox")) { EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); return; } VoxFileImport.LoadVoxelFileAsGameObject(filepath); } } public void OnGUI() { GUILayout.Label("Selected Object:", EditorStyles.boldLabel); this._isSelectCreatePrefab = EditorGUILayout.Foldout(this._isSelectCreatePrefab, "Create Model from .vox file"); if (this._isSelectCreatePrefab) { if (GUILayout.Button("Create Prefab from .vox file")) CreateVoxelPrefabsFromSelection(); if (GUILayout.Button("Create Prefab LOD from .vox file")) CreateVoxelPrefabsFromSelection(3); if (GUILayout.Button("Create GameObject from .vox file")) CreateVoxelGameObjectFromSelection(); if (GUILayout.Button("Create GameObject LOD from .vox file")) CreateVoxelGameObjectFromSelection(3); } this._isSelectCreateAssetbundle = EditorGUILayout.Foldout(this._isSelectCreateAssetbundle, "Create AssetBundle"); if (this._isSelectCreateAssetbundle) { if (GUILayout.Button("Selection To StreamingAssets folder")) CreateAssetBundlesFromSelectionToStreamingAssets(); if (GUILayout.Button("Selection To Selected Folder")) CreateAssetBundlesWithFolderPanel(); } } private static bool CreateVoxelPrefabsFromSelection(int lodLevel = 0) { var SelectedAsset = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets); if (SelectedAsset.Length == 0) { EditorUtility.DisplayDialog("No Object Selected", "Please select any .vox file to create to prefab", "Ok"); return false; } foreach (var asset in SelectedAsset) { var path = AssetDatabase.GetAssetPath(asset); if (Path.GetExtension(path) != ".vox") { EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); return false; } if (path.Remove(0, path.LastIndexOf('.')) == ".vox") { if (lodLevel == 0) VoxFileImport.LoadVoxelFileAsPrefab(path); else VoxFileImport.LoadVoxelFileAsPrefab(path, "Assets/", lodLevel); } } return true; } private static bool CreateVoxelGameObjectFromSelection(int lodLevel = 0) { var SelectedAsset = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets); if (SelectedAsset.Length == 0) { EditorUtility.DisplayDialog("No Object Selected", "Please select any .vox file to create to prefab", "Ok"); return false; } foreach (var asset in SelectedAsset) { var path = AssetDatabase.GetAssetPath(asset); if (Path.GetExtension(path) != ".vox") { EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); return false; } if (path.Remove(0, path.LastIndexOf('.')) == ".vox") { if (lodLevel == 0) VoxFileImport.LoadVoxelFileAsGameObject(path); else VoxFileImport.LoadVoxelFileAsGameObjectLOD(path, lodLevel); } } return true; } private static void CreateAssetBundlesFromSelection(string targetPath, string bundleName = "Resource", string ext = "") { var SelectedAsset = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets); if (SelectedAsset.Length > 0) { AssetBundleBuild[] buildMap = new AssetBundleBuild[2]; buildMap[0].assetBundleName = bundleName + ext; buildMap[0].assetNames = new string[SelectedAsset.Length]; for (int i = 0; i < SelectedAsset.Length; i++) buildMap[0].assetNames[i] = AssetDatabase.GetAssetPath(SelectedAsset[i]); if (!BuildPipeline.BuildAssetBundles(targetPath, buildMap, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows)) UnityEngine.Debug.Log(targetPath + ": failed to load"); AssetDatabase.Refresh(); } } private static void CreateAssetBundlesWithFolderPanel(string bundleName = "Resource", string ext = "") { var SelectedPath = EditorUtility.SaveFolderPanel("Save Resource", "", "New Resource"); if (SelectedPath.Length == 0) return; CreateAssetBundlesFromSelection(SelectedPath + "/", bundleName, ext); } private static void CreateAssetBundlesFromSelectionToStreamingAssets(string bundleName = "Resource", string ext = "") { CreateAssetBundlesFromSelection(Application.dataPath + "/StreamingAssets/", bundleName, ext); } } ================================================ FILE: VOXFileLoader/Scripts/ObjFileExport.cs ================================================ using System; using System.IO; using System.Diagnostics; using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Linq.Expressions; using UnityEngine; namespace Cubizer { namespace Model { public class ObjFileExport { public static string MeshToString(MeshFilter mf, Vector3 scale) { Mesh mesh = mf.sharedMesh; Dictionary dictionary = new Dictionary(); if (mesh.subMeshCount > 1) { int[] triangles = mesh.GetTriangles(1); for (int j = 0; j < triangles.Length; j += 3) { if (!dictionary.ContainsKey(triangles[j])) dictionary.Add(triangles[j], 1); if (!dictionary.ContainsKey(triangles[j + 1])) dictionary.Add(triangles[j + 1], 1); if (!dictionary.ContainsKey(triangles[j + 2])) dictionary.Add(triangles[j + 2], 1); } } StringBuilder stringBuilder = new StringBuilder().Append("mtllib design.mtl").Append("\n").Append("g ").Append(mf.name).Append("\n"); Vector3[] vertices = mesh.vertices; foreach (Vector3 v in mesh.vertices) { stringBuilder.Append(string.Format("v {0} {1} {2}\n", v.x * scale.x, v.y * scale.y, v.z * scale.z)); } stringBuilder.Append("\n"); foreach (Vector3 n in mesh.normals) stringBuilder.Append(string.Format("vn {0} {1} {2}\n", -n.x, -n.y, n.z)); for (int num = 0; num != mesh.uv.Length; num++) { Vector2 uv = mesh.uv[num]; if (dictionary.ContainsKey(num)) stringBuilder.Append(string.Format("vt {0} {1}\n", mesh.uv[num].x, mesh.uv[num].y)); else stringBuilder.Append(string.Format("vt {0} {1}\n", uv.x, uv.y)); } for (int k = 0; k < mesh.subMeshCount; k++) { stringBuilder.Append("\n"); if (k == 0) stringBuilder.Append("usemtl ").Append("Material_design").Append("\n"); if (k == 1) stringBuilder.Append("usemtl ").Append("Material_logo").Append("\n"); int[] triangles2 = mesh.GetTriangles(k); for (int l = 0; l < triangles2.Length; l += 3) stringBuilder.Append(string.Format("f {0}/{0} {1}/{1} {2}/{2}\n", triangles2[l] + 1, triangles2[l + 2] + 1, triangles2[l + 1] + 1)); } return stringBuilder.ToString(); } public static void WriteToFile(string path, MeshFilter mf, Vector3 scale) { using (var sw = new StreamWriter(path)) { sw.Write(MeshToString(mf, new Vector3(-1f, 1f, 1f))); sw.Close(); } } } } } ================================================ FILE: VOXFileLoader/Scripts/VOXCruncher.cs ================================================ using System; using System.Collections.Generic; using System.IO; using UnityEngine; namespace Cubizer { namespace Model { using VOXMaterial = System.Int32; public enum VOXCruncherMode { Stupid, Culled, Greedy, } public struct VOXVisiableFaces { public bool left; public bool right; public bool bottom; public bool top; public bool back; public bool front; public VOXVisiableFaces(bool _left, bool _right, bool _bottom, bool _top, bool _back, bool _front) { left = _left; right = _right; bottom = _bottom; top = _top; back = _back; front = _front; } } public class VOXCruncher { public struct Vector3 { public int x; public int y; public int z; } public Vector3 begin; public Vector3 end; public VOXMaterial material; public VOXVisiableFaces faces; public VOXCruncher(Vector3 begin, Vector3 end, VOXMaterial _material) { this.begin = begin; this.end = end; material = _material; faces.left = true; faces.right = true; faces.top = true; faces.bottom = true; faces.front = true; faces.back = true; } public VOXCruncher(int begin_x, int end_x, int begin_y, int end_y, int begin_z, int end_z, VOXMaterial _material) { begin.x = begin_x; begin.y = begin_y; begin.z = begin_z; end.x = end_x; end.y = end_y; end.z = end_z; material = _material; faces.left = true; faces.right = true; faces.top = true; faces.bottom = true; faces.front = true; faces.back = true; } public VOXCruncher(int begin_x, int end_x, int begin_y, int end_y, int begin_z, int end_z, VOXVisiableFaces _faces, VOXMaterial _material) { begin.x = begin_x; begin.y = begin_y; begin.z = begin_z; end.x = end_x; end.y = end_y; end.z = end_z; material = _material; faces = _faces; } } public interface IVOXCruncherStrategy { VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette); } public class VOXCruncherStupid : IVOXCruncherStrategy { public VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette) { var crunchers = new VOXCruncher[chunk.count]; var faces = new VOXVisiableFaces(true, true, true, true, true, true); int n = 0; for (int i = 0; i < chunk.x; ++i) { for (int j = 0; j < chunk.y; ++j) { for (int k = 0; k < chunk.z; ++k) { var m = chunk.voxels[i, j, k]; if (m != int.MaxValue) crunchers[n++] = new VOXCruncher(i, i, j, j, k, k, faces, m); } } } return new VOXModel(crunchers); } } public class VOXCruncherCulled : IVOXCruncherStrategy { public static bool GetVisiableFaces(VOXMaterial[,,] map, Vector3Int bound, int x, int y, int z, VOXMaterial material, Color32[] palette, out VOXVisiableFaces faces) { VOXMaterial[] instanceID = new VOXMaterial[6] { VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue }; if (x >= 1) instanceID[0] = map[(byte)(x - 1), y, z]; if (y >= 1) instanceID[2] = map[x, (byte)(y - 1), z]; if (z >= 1) instanceID[4] = map[x, y, (byte)(z - 1)]; if (x <= bound.x) instanceID[1] = map[(byte)(x + 1), y, z]; if (y <= bound.y) instanceID[3] = map[x, (byte)(y + 1), z]; if (z <= bound.z) instanceID[5] = map[x, y, (byte)(z + 1)]; var alpha = palette[material].a; if (alpha < 255) { bool f1 = (instanceID[0] == VOXMaterial.MaxValue) ? true : palette[instanceID[0]].a != alpha ? true : false; bool f2 = (instanceID[1] == VOXMaterial.MaxValue) ? true : palette[instanceID[1]].a != alpha ? true : false; bool f3 = (instanceID[2] == VOXMaterial.MaxValue) ? true : palette[instanceID[2]].a != alpha ? true : false; bool f4 = (instanceID[3] == VOXMaterial.MaxValue) ? true : palette[instanceID[3]].a != alpha ? true : false; bool f5 = (instanceID[4] == VOXMaterial.MaxValue) ? true : palette[instanceID[4]].a != alpha ? true : false; bool f6 = (instanceID[5] == VOXMaterial.MaxValue) ? true : palette[instanceID[5]].a != alpha ? true : false; faces.left = f1; faces.right = f2; faces.bottom = f3; faces.top = f4; faces.front = f5; faces.back = f6; } else { bool f1 = (instanceID[0] == VOXMaterial.MaxValue) ? true : palette[instanceID[0]].a < 255 ? true : false; bool f2 = (instanceID[1] == VOXMaterial.MaxValue) ? true : palette[instanceID[1]].a < 255 ? true : false; bool f3 = (instanceID[2] == VOXMaterial.MaxValue) ? true : palette[instanceID[2]].a < 255 ? true : false; bool f4 = (instanceID[3] == VOXMaterial.MaxValue) ? true : palette[instanceID[3]].a < 255 ? true : false; bool f5 = (instanceID[4] == VOXMaterial.MaxValue) ? true : palette[instanceID[4]].a < 255 ? true : false; bool f6 = (instanceID[5] == VOXMaterial.MaxValue) ? true : palette[instanceID[5]].a < 255 ? true : false; faces.left = f1; faces.right = f2; faces.bottom = f3; faces.top = f4; faces.front = f5; faces.back = f6; } return faces.left | faces.right | faces.bottom | faces.top | faces.front | faces.back; } public VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette) { var crunchers = new List(); var bound = new Vector3Int(chunk.x, chunk.y, chunk.z); for (int i = 0; i < chunk.x; ++i) { for (int j = 0; j < chunk.y; ++j) { for (int k = 0; k < chunk.z; ++k) { var c = chunk.voxels[i, j, k]; if (c != int.MaxValue) { VOXVisiableFaces faces; if (!GetVisiableFaces(chunk.voxels, bound, i, j, k, c, palette, out faces)) continue; crunchers.Add(new VOXCruncher((byte)i, (byte)i, (byte)j, (byte)j, (byte)k, (byte)k, faces, c)); } } } } var array = new VOXCruncher[crunchers.Count]; int numbers = 0; foreach (var it in crunchers) array[numbers++] = it; return new VOXModel(array); } } public class VOXCruncherGreedy : IVOXCruncherStrategy { public VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette) { var crunchers = new List(); var dims = new int[] { chunk.x, chunk.y, chunk.z }; var alloc = System.Math.Max(dims[0], System.Math.Max(dims[1], dims[2])); var mask = new int[alloc * alloc]; var map = chunk.voxels; for (var d = 0; d < 3; ++d) { var u = (d + 1) % 3; var v = (d + 2) % 3; var x = new int[3] { 0, 0, 0 }; var q = new int[3] { 0, 0, 0 }; q[d] = 1; var faces = new VOXVisiableFaces(false, false, false, false, false, false); for (x[d] = -1; x[d] < dims[d];) { var n = 0; for (x[v] = 0; x[v] < dims[v]; ++x[v]) { for (x[u] = 0; x[u] < dims[u]; ++x[u]) { var a = x[d] >= 0 ? map[x[0], x[1], x[2]] : VOXMaterial.MaxValue; var b = x[d] < dims[d] - 1 ? map[x[0] + q[0], x[1] + q[1], x[2] + q[2]] : VOXMaterial.MaxValue; if (a != b) { if (a == VOXMaterial.MaxValue) mask[n++] = b; else if (b == VOXMaterial.MaxValue) mask[n++] = -a; else mask[n++] = -b; } else { mask[n++] = VOXMaterial.MaxValue; } } } ++x[d]; n = 0; for (var j = 0; j < dims[v]; ++j) { for (var i = 0; i < dims[u];) { var c = mask[n]; if (c == VOXMaterial.MaxValue) { ++i; ++n; continue; } var w = 1; var h = 1; var k = 0; for (; (i + w) < dims[u] && c == mask[n + w]; ++w) { } var done = false; for (; (j + h) < dims[v]; ++h) { for (k = 0; k < w; ++k) { if (c != mask[n + k + h * dims[u]]) { done = true; break; } } if (done) break; } x[u] = i; x[v] = j; var du = new int[3] { 0, 0, 0 }; var dv = new int[3] { 0, 0, 0 }; du[u] = w; dv[v] = h; var v1 = new Vector3(x[0], x[1], x[2]); var v2 = new Vector3(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1], x[2] + du[2] + dv[2]); v2.x = System.Math.Max(v2.x - 1, 0); v2.y = System.Math.Max(v2.y - 1, 0); v2.z = System.Math.Max(v2.z - 1, 0); if (c > 0) { faces.front = d == 2; faces.back = false; faces.left = d == 0; faces.right = false; faces.top = false; faces.bottom = d == 1; } else { c = -c; faces.front = false; faces.back = d == 2; faces.left = false; faces.right = d == 0; faces.top = d == 1; faces.bottom = false; } crunchers.Add(new VOXCruncher((byte)v1.x, (byte)(v2.x), (byte)(v1.y), (byte)(v2.y), (byte)(v1.z), (byte)(v2.z), faces, c)); for (var l = 0; l < h; ++l) { for (k = 0; k < w; ++k) mask[n + k + l * dims[u]] = VOXMaterial.MaxValue; } i += w; n += w; } } } } var array = new VOXCruncher[crunchers.Count]; int numbers = 0; foreach (var it in crunchers) array[numbers++] = it; return new VOXModel(array); } } public class VOXPolygonCruncher { public static VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette, VOXCruncherMode mode) { switch (mode) { case VOXCruncherMode.Stupid: return new VOXCruncherStupid().CalcVoxelCruncher(chunk, palette); case VOXCruncherMode.Culled: return new VOXCruncherCulled().CalcVoxelCruncher(chunk, palette); case VOXCruncherMode.Greedy: return new VOXCruncherGreedy().CalcVoxelCruncher(chunk, palette); default: return null; } } } } } ================================================ FILE: VOXFileLoader/Scripts/VOXFileImport.cs ================================================ using System; using System.Collections.Generic; using System.IO; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace Cubizer { namespace Model { public struct VoxFileHeader { public byte[] header; public Int32 version; } public struct VoxFilePack { public byte[] name; public Int32 chunkContent; public Int32 chunkNums; public Int32 modelNums; } public struct VoxFileSize { public byte[] name; public Int32 chunkContent; public Int32 chunkNums; public Int32 x; public Int32 y; public Int32 z; } public struct VoxFileXYZI { public byte[] name; public Int32 chunkContent; public Int32 chunkNums; public VoxData voxels; } public struct VoxFileRGBA { public byte[] name; public Int32 chunkContent; public Int32 chunkNums; public uint[] values; } public struct VoxFileChunkChild { public VoxFileSize size; public VoxFileXYZI xyzi; } public struct VoxFileChunk { public byte[] name; public Int32 chunkContent; public Int32 chunkNums; } public struct VoxFileMaterial { public int id; public int type; public float weight; public int propertyBits; public float[] propertyValue; } public class VoxFileData { public VoxFileHeader hdr; public VoxFileChunk main; public VoxFilePack pack; public VoxFileChunkChild[] chunkChild; public VoxFileRGBA palette; } public class VoxData { public int x, y, z; public int[,,] voxels; public int count { get { int _count = 0; for (int i = 0; i < x; ++i) { for (int j = 0; j < y; ++j) for (int k = 0; k < z; ++k) if (voxels[i, j, k] != int.MaxValue) _count++; } return _count; } } public VoxData() { x = 0; y = 0; z = 0; } public VoxData(byte[] _voxels, int xx, int yy, int zz) { x = xx; y = zz; z = yy; voxels = new int[x, y, z]; for (int i = 0; i < x; ++i) { for (int j = 0; j < y; ++j) for (int k = 0; k < z; ++k) voxels[i, j, k] = int.MaxValue; } for (int j = 0; j < _voxels.Length; j += 4) { var x = _voxels[j]; var y = _voxels[j + 1]; var z = _voxels[j + 2]; var c = _voxels[j + 3]; voxels[x, z, y] = c; } } public int GetMajorityColorIndex(int xx, int yy, int zz, int lodLevel) { xx = Mathf.Min(xx, x - 2); yy = Mathf.Min(yy, y - 2); zz = Mathf.Min(zz, z - 2); int[] samples = new int[lodLevel * lodLevel * lodLevel]; for (int i = 0; i < lodLevel; i++) { for (int j = 0; j < lodLevel; j++) { for (int k = 0; k < lodLevel; k++) { if (xx + i > x - 1 || yy + j > y - 1 || zz + k > z - 1) samples[i * lodLevel * lodLevel + j * lodLevel + k] = int.MaxValue; else samples[i * lodLevel * lodLevel + j * lodLevel + k] = voxels[xx + i, yy + j, zz + k]; } } } int maxNum = 1; int maxNumIndex = 0; int[] numIndex = new int[samples.Length]; for (int i = 0; i < samples.Length; i++) numIndex[i] = samples[i] == int.MaxValue ? 0 : 1; for (int i = 0; i < samples.Length; i++) { for (int j = 0; j < samples.Length; j++) { if (i != j && samples[i] != int.MaxValue && samples[i] == samples[j]) { numIndex[i]++; if (numIndex[i] > maxNum) { maxNum = numIndex[i]; maxNumIndex = i; } } } } return samples[maxNumIndex]; } public VoxData GetVoxelDataLOD(int level) { if (x <= 1 || y <= 1 || z <= 1) return null; level = Mathf.Clamp(level, 0, 16); if (level <= 1) return this; if (x <= level && y <= level && z <= level) return this; VoxData data = new VoxData(); data.x = Mathf.CeilToInt((float)x / level); data.y = Mathf.CeilToInt((float)y / level); data.z = Mathf.CeilToInt((float)z / level); data.voxels = new int[data.x, data.y, data.z]; for (int x = 0; x < data.x; x++) { for (int y = 0; y < data.y; y++) { for (int z = 0; z < data.z; z++) { data.voxels[x, y, z] = this.GetMajorityColorIndex(x * level, y * level, z * level, level); } } } return data; } } public class VoxFileImport { private static uint[] _paletteDefault = new uint[256] { 0x00000000, 0xffffffff, 0xffccffff, 0xff99ffff, 0xff66ffff, 0xff33ffff, 0xff00ffff, 0xffffccff, 0xffccccff, 0xff99ccff, 0xff66ccff, 0xff33ccff, 0xff00ccff, 0xffff99ff, 0xffcc99ff, 0xff9999ff, 0xff6699ff, 0xff3399ff, 0xff0099ff, 0xffff66ff, 0xffcc66ff, 0xff9966ff, 0xff6666ff, 0xff3366ff, 0xff0066ff, 0xffff33ff, 0xffcc33ff, 0xff9933ff, 0xff6633ff, 0xff3333ff, 0xff0033ff, 0xffff00ff, 0xffcc00ff, 0xff9900ff, 0xff6600ff, 0xff3300ff, 0xff0000ff, 0xffffffcc, 0xffccffcc, 0xff99ffcc, 0xff66ffcc, 0xff33ffcc, 0xff00ffcc, 0xffffcccc, 0xffcccccc, 0xff99cccc, 0xff66cccc, 0xff33cccc, 0xff00cccc, 0xffff99cc, 0xffcc99cc, 0xff9999cc, 0xff6699cc, 0xff3399cc, 0xff0099cc, 0xffff66cc, 0xffcc66cc, 0xff9966cc, 0xff6666cc, 0xff3366cc, 0xff0066cc, 0xffff33cc, 0xffcc33cc, 0xff9933cc, 0xff6633cc, 0xff3333cc, 0xff0033cc, 0xffff00cc, 0xffcc00cc, 0xff9900cc, 0xff6600cc, 0xff3300cc, 0xff0000cc, 0xffffff99, 0xffccff99, 0xff99ff99, 0xff66ff99, 0xff33ff99, 0xff00ff99, 0xffffcc99, 0xffcccc99, 0xff99cc99, 0xff66cc99, 0xff33cc99, 0xff00cc99, 0xffff9999, 0xffcc9999, 0xff999999, 0xff669999, 0xff339999, 0xff009999, 0xffff6699, 0xffcc6699, 0xff996699, 0xff666699, 0xff336699, 0xff006699, 0xffff3399, 0xffcc3399, 0xff993399, 0xff663399, 0xff333399, 0xff003399, 0xffff0099, 0xffcc0099, 0xff990099, 0xff660099, 0xff330099, 0xff000099, 0xffffff66, 0xffccff66, 0xff99ff66, 0xff66ff66, 0xff33ff66, 0xff00ff66, 0xffffcc66, 0xffcccc66, 0xff99cc66, 0xff66cc66, 0xff33cc66, 0xff00cc66, 0xffff9966, 0xffcc9966, 0xff999966, 0xff669966, 0xff339966, 0xff009966, 0xffff6666, 0xffcc6666, 0xff996666, 0xff666666, 0xff336666, 0xff006666, 0xffff3366, 0xffcc3366, 0xff993366, 0xff663366, 0xff333366, 0xff003366, 0xffff0066, 0xffcc0066, 0xff990066, 0xff660066, 0xff330066, 0xff000066, 0xffffff33, 0xffccff33, 0xff99ff33, 0xff66ff33, 0xff33ff33, 0xff00ff33, 0xffffcc33, 0xffcccc33, 0xff99cc33, 0xff66cc33, 0xff33cc33, 0xff00cc33, 0xffff9933, 0xffcc9933, 0xff999933, 0xff669933, 0xff339933, 0xff009933, 0xffff6633, 0xffcc6633, 0xff996633, 0xff666633, 0xff336633, 0xff006633, 0xffff3333, 0xffcc3333, 0xff993333, 0xff663333, 0xff333333, 0xff003333, 0xffff0033, 0xffcc0033, 0xff990033, 0xff660033, 0xff330033, 0xff000033, 0xffffff00, 0xffccff00, 0xff99ff00, 0xff66ff00, 0xff33ff00, 0xff00ff00, 0xffffcc00, 0xffcccc00, 0xff99cc00, 0xff66cc00, 0xff33cc00, 0xff00cc00, 0xffff9900, 0xffcc9900, 0xff999900, 0xff669900, 0xff339900, 0xff009900, 0xffff6600, 0xffcc6600, 0xff996600, 0xff666600, 0xff336600, 0xff006600, 0xffff3300, 0xffcc3300, 0xff993300, 0xff663300, 0xff333300, 0xff003300, 0xffff0000, 0xffcc0000, 0xff990000, 0xff660000, 0xff330000, 0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088, 0xff000077, 0xff000055, 0xff000044, 0xff000022, 0xff000011, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700, 0xff005500, 0xff004400, 0xff002200, 0xff001100, 0xffee0000, 0xffdd0000, 0xffbb0000, 0xffaa0000, 0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000, 0xff110000, 0xffeeeeee, 0xffdddddd, 0xffbbbbbb, 0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111 }; private static UnityEngine.Object _assetPrefab; public static VoxFileData Load(string path) { using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { if (stream == null) throw new System.Exception("Failed to open file for FileStream."); using (var reader = new BinaryReader(stream)) { VoxFileData voxel = new VoxFileData(); voxel.hdr.header = reader.ReadBytes(4); voxel.hdr.version = reader.ReadInt32(); if (voxel.hdr.header[0] != 'V' || voxel.hdr.header[1] != 'O' || voxel.hdr.header[2] != 'X' || voxel.hdr.header[3] != ' ') throw new System.Exception("Bad Token: token is not VOX."); if (voxel.hdr.version != 150) throw new System.Exception("The version of file isn't 150 that version of vox, tihs version of file is " + voxel.hdr.version + "."); voxel.main.name = reader.ReadBytes(4); voxel.main.chunkContent = reader.ReadInt32(); voxel.main.chunkNums = reader.ReadInt32(); if (voxel.main.name[0] != 'M' || voxel.main.name[1] != 'A' || voxel.main.name[2] != 'I' || voxel.main.name[3] != 'N') throw new System.Exception("Bad Token: token is not MAIN."); if (voxel.main.chunkContent != 0) throw new System.Exception("Bad Token: chunk content is " + voxel.main.chunkContent + ", it should be 0."); if (reader.PeekChar() == 'P') { voxel.pack.name = reader.ReadBytes(4); if (voxel.pack.name[0] != 'P' || voxel.pack.name[1] != 'A' || voxel.pack.name[2] != 'C' || voxel.pack.name[3] != 'K') throw new System.Exception("Bad Token: token is not PACK"); voxel.pack.chunkContent = reader.ReadInt32(); voxel.pack.chunkNums = reader.ReadInt32(); voxel.pack.modelNums = reader.ReadInt32(); if (voxel.pack.modelNums == 0) throw new System.Exception("Bad Token: model nums must be greater than zero."); } else { voxel.pack.chunkContent = 0; voxel.pack.chunkNums = 0; voxel.pack.modelNums = 1; } voxel.chunkChild = new VoxFileChunkChild[voxel.pack.modelNums]; for (int i = 0; i < voxel.pack.modelNums; i++) { var chunk = new VoxFileChunkChild(); chunk.size.name = reader.ReadBytes(4); chunk.size.chunkContent = reader.ReadInt32(); chunk.size.chunkNums = reader.ReadInt32(); chunk.size.x = reader.ReadInt32(); chunk.size.y = reader.ReadInt32(); chunk.size.z = reader.ReadInt32(); if (chunk.size.name[0] != 'S' || chunk.size.name[1] != 'I' || chunk.size.name[2] != 'Z' || chunk.size.name[3] != 'E') throw new System.Exception("Bad Token: token is not SIZE"); if (chunk.size.chunkContent != 12) throw new System.Exception("Bad Token: chunk content is " + chunk.size.chunkContent + ", it should be 12."); chunk.xyzi.name = reader.ReadBytes(4); if (chunk.xyzi.name[0] != 'X' || chunk.xyzi.name[1] != 'Y' || chunk.xyzi.name[2] != 'Z' || chunk.xyzi.name[3] != 'I') throw new System.Exception("Bad Token: token is not XYZI"); chunk.xyzi.chunkContent = reader.ReadInt32(); chunk.xyzi.chunkNums = reader.ReadInt32(); if (chunk.xyzi.chunkNums != 0) throw new System.Exception("Bad Token: chunk nums is " + chunk.xyzi.chunkNums + ",i t should be 0."); var voxelNums = reader.ReadInt32(); var voxels = new byte[voxelNums * 4]; if (reader.Read(voxels, 0, voxels.Length) != voxels.Length) throw new System.Exception("Failed to read voxels"); chunk.xyzi.voxels = new VoxData(voxels, chunk.size.x, chunk.size.y, chunk.size.z); voxel.chunkChild[i] = chunk; } if (reader.BaseStream.Position < reader.BaseStream.Length) { byte[] palette = reader.ReadBytes(4); if (palette[0] != 'R' || palette[1] != 'G' || palette[2] != 'B' || palette[3] != 'A') throw new System.Exception("Bad Token: token is not RGBA"); voxel.palette.chunkContent = reader.ReadInt32(); voxel.palette.chunkNums = reader.ReadInt32(); var bytePalette = new byte[voxel.palette.chunkContent]; reader.Read(bytePalette, 0, voxel.palette.chunkContent); voxel.palette.values = new uint[voxel.palette.chunkContent / 4]; for (int i = 4; i < bytePalette.Length; i += 4) voxel.palette.values[i / 4] = BitConverter.ToUInt32(bytePalette, i - 4); } else { voxel.palette.values = new uint[256]; _paletteDefault.CopyTo(voxel.palette.values, 0); } return voxel; } } } public static Color32[] CreateColor32FromPelatte(uint[] palette) { Debug.Assert(palette.Length == 256); Color32[] colors = new Color32[256]; for (uint j = 0; j < 256; j++) { uint rgba = palette[j]; Color32 color = new Color32(); color.r = (byte)((rgba >> 0) & 0xFF); color.g = (byte)((rgba >> 8) & 0xFF); color.b = (byte)((rgba >> 16) & 0xFF); color.a = (byte)((rgba >> 24) & 0xFF); colors[j] = color; } return colors; } public static Texture2D CreateTextureFromColor16x16(Color32[] colors) { Debug.Assert(colors.Length == 256); Texture2D texture = new Texture2D(16, 16, TextureFormat.ARGB32, false, false); texture.name = "texture"; texture.SetPixels32(colors); texture.Apply(); return texture; } public static Texture2D CreateTextureFromColor256(Color32[] colors) { Debug.Assert(colors.Length == 256); Texture2D texture = new Texture2D(256, 1, TextureFormat.ARGB32, false, false); texture.name = "texture"; texture.SetPixels32(colors); texture.Apply(); return texture; } public static Texture2D CreateTextureFromPelatte16x16(uint[] palette) { Debug.Assert(palette.Length == 256); return CreateTextureFromColor16x16(CreateColor32FromPelatte(palette)); } public static int CalcFaceCountAsAllocate(VOXModel model, Color32[] palette, ref Dictionary entities) { entities.Add("opaque", 0); foreach (var it in model.voxels) { bool[] visiable = new bool[] { it.faces.left, it.faces.right, it.faces.top, it.faces.bottom, it.faces.front, it.faces.back }; int facesCount = 0; for (int j = 0; j < 6; j++) { if (visiable[j]) facesCount++; } entities["opaque"] += facesCount; } return entities.Count; } public static GameObject CreateGameObject(string name, VoxData data, Texture2D texture, Color32[] colors, float scale) { var cruncher = VOXPolygonCruncher.CalcVoxelCruncher(data, colors, VOXCruncherMode.Greedy); var entities = new Dictionary(); if (CalcFaceCountAsAllocate(cruncher, colors, ref entities) == 0) throw new System.Exception(name + ": There is no voxel for this file"); var model = new GameObject(name); foreach (var entity in entities) { if (entity.Value == 0) continue; var index = 0; var allocSize = entity.Value; var vertices = new Vector3[allocSize * 4]; var normals = new Vector3[allocSize * 4]; var uv = new Vector2[allocSize * 4]; var triangles = new int[allocSize * 6]; bool isTransparent = false; foreach (var it in cruncher.voxels) { VOXModel.CreateCubeMesh16x16(it, ref vertices, ref normals, ref uv, ref triangles, ref index, scale); isTransparent |= (colors[it.material].a < 255) ? true : false; } if (triangles.Length > 0) { Mesh mesh = new Mesh(); mesh.name = "mesh"; mesh.vertices = vertices; mesh.normals = normals; mesh.uv = uv; mesh.triangles = triangles; var meshFilter = model.AddComponent(); var meshRenderer = model.AddComponent(); #if UNITY_EDITOR MeshUtility.Optimize(mesh); meshFilter.sharedMesh = mesh; meshRenderer.sharedMaterial = new Material(Shader.Find("Mobile/Diffuse")); meshRenderer.sharedMaterial.name = "material"; meshRenderer.sharedMaterial.mainTexture = texture; #else meshFilter.mesh = mesh; meshRenderer.material = new Material(Shader.Find("Mobile/Diffuse")); meshRenderer.material.mainTexture = texture; #endif } } return model; } public static GameObject LoadVoxelFileAsGameObject(string name, VoxFileData voxel, int lodLevel) { Debug.Assert(!String.IsNullOrEmpty(name)); GameObject gameObject = new GameObject(); gameObject.name = name; gameObject.isStatic = true; try { var colors = CreateColor32FromPelatte(voxel.palette.values); var texture = CreateTextureFromColor16x16(colors); if (lodLevel <= 1) { foreach (var chunk in voxel.chunkChild) { var submesh = CreateGameObject("model", chunk.xyzi.voxels, texture, colors, 1); submesh.transform.parent = gameObject.transform; } } else { foreach (var chunk in voxel.chunkChild) { for (int lod = 1; lod < lodLevel + 1; lod++) { var submesh = CreateGameObject("lod" + (lod - 1), chunk.xyzi.voxels.GetVoxelDataLOD(lod), texture, colors, lod); submesh.transform.parent = gameObject.transform; } var lodgroup = gameObject.AddComponent(); var lods = lodgroup.GetLODs(); for (int i = 0; i < gameObject.transform.childCount; i++) lods[i].renderers = new Renderer[] { gameObject.transform.GetChild(i).GetComponent() }; lodgroup.SetLODs(lods); } } } catch (SystemException e) { GameObject.DestroyImmediate(gameObject); throw e; } return gameObject; } public static GameObject LoadVoxelFileAsGameObject(string path) { var voxel = VoxFileImport.Load(path); return LoadVoxelFileAsGameObject(Path.GetFileNameWithoutExtension(path), voxel, 0); } public static GameObject LoadVoxelFileAsGameObjectLOD(string path, int lodLevel) { var voxel = VoxFileImport.Load(path); return LoadVoxelFileAsGameObject(Path.GetFileNameWithoutExtension(path), voxel, lodLevel); } #if UNITY_EDITOR public static GameObject LoadVoxelFileAsPrefab(VoxFileData voxel, string name, string path = "Assets/", int lodLevel = 0) { Debug.Assert(!String.IsNullOrEmpty(name)); GameObject gameObject = null; try { gameObject = LoadVoxelFileAsGameObject(name, voxel, lodLevel); var prefabPath = path + name + ".prefab"; var prefab = PrefabUtility.CreateEmptyPrefab(prefabPath); var prefabTextures = new Dictionary(); for (int i = 0; i < gameObject.transform.childCount; i++) { var subObject = gameObject.transform.GetChild(i); var meshFilter = subObject.GetComponent(); if (meshFilter != null) { AssetDatabase.AddObjectToAsset(meshFilter.sharedMesh, prefabPath); } var renderer = subObject.GetComponent(); if (renderer != null) { if (renderer.sharedMaterial != null) { AssetDatabase.AddObjectToAsset(renderer.sharedMaterial, prefabPath); var textureName = renderer.sharedMaterial.mainTexture.name; if (!prefabTextures.ContainsKey(textureName)) { prefabTextures.Add(textureName, 1); AssetDatabase.AddObjectToAsset(renderer.sharedMaterial.mainTexture, prefabPath); } } } } return PrefabUtility.ReplacePrefab(gameObject, prefab, ReplacePrefabOptions.ReplaceNameBased); } finally { GameObject.DestroyImmediate(gameObject); } } public static GameObject LoadVoxelFileAsPrefab(string path, string outpath = "Assets/", int lodLevel = 0) { var voxel = VoxFileImport.Load(path); return LoadVoxelFileAsPrefab(voxel, Path.GetFileNameWithoutExtension(path), outpath, lodLevel); } #endif } } } ================================================ FILE: VOXFileLoader/Scripts/VOXHashMap.cs ================================================ using System; using System.Diagnostics; using System.Collections; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using UnityEngine; namespace Cubizer { using VOXMaterial = System.Int32; namespace Model { [Serializable] public class VOXHashMapNode<_Tx> where _Tx : struct { public _Tx x; public _Tx y; public _Tx z; public VOXMaterial element; public VOXHashMapNode() { element = int.MaxValue; } public VOXHashMapNode(_Tx xx, _Tx yy, _Tx zz, VOXMaterial value) { x = xx; y = yy; z = zz; element = value; } public bool is_empty() { return element == int.MaxValue; } } public class VOXHashMapNodeEnumerable<_Tx> : IEnumerable where _Tx : struct { private VOXHashMapNode<_Tx>[] _array; public VOXHashMapNodeEnumerable(VOXHashMapNode<_Tx>[] array) { _array = array; } IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator)GetEnumerator(); } public VOXHashMapNodeEnum<_Tx> GetEnumerator() { return new VOXHashMapNodeEnum<_Tx>(_array); } } public class VOXHashMapNodeEnum<_Tx> : IEnumerator where _Tx : struct { private int position = -1; private VOXHashMapNode<_Tx>[] _array; public VOXHashMapNodeEnum(VOXHashMapNode<_Tx>[] list) { _array = list; } public bool MoveNext() { var length = _array.Length; for (position++; position < length; position++) { if (_array[position] == null) continue; if (_array[position].is_empty()) continue; break; } return position < _array.Length; } public void Reset() { position = -1; } object IEnumerator.Current { get { return Current; } } public VOXHashMapNode<_Tx> Current { get { return _array[position]; } } } [Serializable] public class VOXHashMap { protected int _count; protected int _allocSize; protected Vector3Int _bound; protected VOXHashMapNode[] _data; public int Count { get { return _count; } } public Vector3Int bound { get { return _bound; } } public VOXHashMap(Vector3Int bound) { _count = 0; _bound = bound; _allocSize = 0; } public VOXHashMap(Vector3Int bound, int count) { _count = 0; _bound = bound; _allocSize = 0; this.Create(count); } public VOXHashMap(int bound_x, int bound_y, int bound_z, int count) { _count = 0; _bound = new Vector3Int(bound_x, bound_y, bound_z); _allocSize = 0; this.Create(count); } public void Create(int count) { int usage = 1; while (usage < count) usage = usage << 1 | 1; _count = 0; _allocSize = usage; _data = new VOXHashMapNode[usage + 1]; } public bool Set(System.Byte x, System.Byte y, System.Byte z, VOXMaterial value, bool replace = true) { if (_allocSize == 0) this.Create(0xFF); var index = HashInt(x, y, z) & _allocSize; var entry = _data[index]; while (entry != null) { if (entry.x == x && entry.y == y && entry.z == z) { if (replace) { _data[index].element = value; return true; } return false; } index = (index + 1) & _allocSize; entry = _data[index]; } if (value != VOXMaterial.MaxValue) { _data[index] = new VOXHashMapNode(x, y, z, value); _count++; if (_count >= _allocSize) this.Grow(); return true; } return false; } public bool Get(System.Byte x, System.Byte y, System.Byte z, ref VOXMaterial instanceID) { if (_allocSize == 0) return false; var index = HashInt(x, y, z) & _allocSize; var entry = _data[index]; while (entry != null) { if (entry.x == x && entry.y == y && entry.z == z) { instanceID = entry.element; return instanceID != VOXMaterial.MaxValue; } index = (index + 1) & _allocSize; entry = _data[index]; } instanceID = VOXMaterial.MaxValue; return false; } public bool Exists(System.Byte x, System.Byte y, System.Byte z) { VOXMaterial instanceID = VOXMaterial.MaxValue; return this.Get(x, y, z, ref instanceID); } public bool Empty() { return _count == 0; } public VOXHashMapNodeEnumerable GetEnumerator() { if (_data == null) throw new System.ApplicationException("GetEnumerator: Empty data"); return new VOXHashMapNodeEnumerable(_data); } public static bool Save(string path, VOXHashMap map) { UnityEngine.Debug.Assert(map != null); var stream = new FileStream(path, FileMode.Create, FileAccess.Write); var serializer = new BinaryFormatter(); serializer.Serialize(stream, map); stream.Close(); return true; } public static VOXHashMap Load(string path) { var serializer = new BinaryFormatter(); var loadFile = new FileStream(path, FileMode.Open, FileAccess.Read); return serializer.Deserialize(loadFile) as VOXHashMap; } private bool Grow(VOXHashMapNode data) { var index = HashInt(data.x, data.y, data.z) & _allocSize; var entry = _data[index]; while (entry != null) { index = (index + 1) & _allocSize; entry = _data[index]; } if (data.element != VOXMaterial.MaxValue) { _data[index] = data; _count++; return true; } return false; } private void Grow() { var map = new VOXHashMap(_bound, _allocSize << 1 | 1); foreach (var it in GetEnumerator()) map.Grow(it); _count = map._count; _allocSize = map._allocSize; _data = map._data; } private static int _hash_int(int key) { key = ~key + (key << 15); key = key ^ (key >> 12); key = key + (key << 2); key = key ^ (key >> 4); key = key * 2057; key = key ^ (key >> 16); return key; } public static int HashInt(int x, int y, int z) { return _hash_int(x) ^ _hash_int(y) ^ _hash_int(z); } } } } ================================================ FILE: VOXFileLoader/Scripts/VOXModel.cs ================================================ using UnityEngine; namespace Cubizer { namespace Model { public class VOXModel { private static Vector3[,] _positions = new Vector3[6, 4] { { new Vector3(-1, -1, -1), new Vector3(-1, -1, +1), new Vector3(-1, +1, -1), new Vector3(-1, +1, +1) }, { new Vector3(+1, -1, -1), new Vector3(+1, -1, +1), new Vector3(+1, +1, -1), new Vector3(+1, +1, +1) }, { new Vector3(-1, +1, -1), new Vector3(-1, +1, +1), new Vector3(+1, +1, -1), new Vector3(+1, +1, +1) }, { new Vector3(-1, -1, -1), new Vector3(-1, -1, +1), new Vector3(+1, -1, -1), new Vector3(+1, -1, +1) }, { new Vector3(-1, -1, -1), new Vector3(-1, +1, -1), new Vector3(+1, -1, -1), new Vector3(+1, +1, -1) }, { new Vector3(-1, -1, +1), new Vector3(-1, +1, +1), new Vector3(+1, -1, +1), new Vector3(+1, +1, +1) } }; private static Vector3[] _normals = new Vector3[6] { new Vector3(-1, 0, 0), new Vector3(+1, 0, 0), new Vector3(0, +1, 0), new Vector3(0, -1, 0), new Vector3(0, 0, -1), new Vector3(0, 0, +1) }; private static Vector2[,] _uvs = new Vector2[6, 4] { { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1) }, { new Vector2(1, 0), new Vector2(0, 0), new Vector2(1, 1), new Vector2(0, 1) }, { new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 1), new Vector2(1, 0) }, { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 1) }, { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 1) }, { new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 0), new Vector2(0, 1) } }; private static int[,] _indices = new int[6, 6] { { 0, 3, 2, 0, 1, 3 }, { 0, 3, 1, 0, 2, 3 }, { 0, 3, 2, 0, 1, 3 }, { 0, 3, 1, 0, 2, 3 }, { 0, 3, 2, 0, 1, 3 }, { 0, 3, 1, 0, 2, 3 } }; public VOXCruncher[] voxels; public VOXModel(VOXCruncher[] array) { voxels = array; } public static void CreateCubeMesh16x16(ref Vector3[] vertices, ref Vector3[] normals, ref Vector2[] uv, ref int[] triangles, ref int index, VOXVisiableFaces faces, Vector3 translate, Vector3 scale, uint palette) { bool[] visiable = new bool[] { faces.left, faces.right, faces.top, faces.bottom, faces.front, faces.back }; float s = 1.0f / 16.0f; float a = 0 + 1.0f / 32.0f; float b = s - 1.0f / 32.0f; for (int i = 0; i < 6; i++) { if (!visiable[i]) continue; for (int n = index * 4, k = 0; k < 4; k++, n++) { Vector3 v = _positions[i, k] * 0.5f; v.x *= scale.x; v.y *= scale.y; v.z *= scale.z; v += translate; float du = (palette % 16) * s; float dv = (palette / 16) * s; Vector2 coord; coord.x = du + (_uvs[i, k].x > 0 ? b : a); coord.y = dv + (_uvs[i, k].y > 0 ? b : a); vertices[n] = v; normals[n] = _normals[i]; uv[n] = coord; } for (int j = index * 6, k = 0; k < 6; k++, j++) triangles[j] = index * 4 + _indices[i, k]; index++; } } public static void CreateCubeMesh16x16(VOXCruncher it, ref Vector3[] vertices, ref Vector3[] normals, ref Vector2[] uv, ref int[] triangles, ref int index, float scaling) { Vector3 pos; pos.x = (it.begin.x + it.end.x + 1) * 0.5f * scaling; pos.y = (it.begin.y + it.end.y + 1) * 0.5f * scaling; pos.z = (it.begin.z + it.end.z + 1) * 0.5f * scaling; Vector3 scale; scale.x = (it.end.x + 1 - it.begin.x) * scaling; scale.y = (it.end.y + 1 - it.begin.y) * scaling; scale.z = (it.end.z + 1 - it.begin.z) * scaling; VOXModel.CreateCubeMesh16x16(ref vertices, ref normals, ref uv, ref triangles, ref index, it.faces, pos, scale, (uint)it.material); } } } }