├── VOXFileLoader ├── Demo │ ├── 8x8x8.vox │ └── chr_sword.vox ├── Scripts │ ├── ObjFileExport.cs │ ├── VOXModel.cs │ ├── VOXHashMap.cs │ ├── VOXCruncher.cs │ └── VOXFileImport.cs └── Editor │ └── VOXFileLoader.cs ├── LICENSE └── README.md /VOXFileLoader/Demo/8x8x8.vox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ray-cast/UnityVOXFileImport/HEAD/VOXFileLoader/Demo/8x8x8.vox -------------------------------------------------------------------------------- /VOXFileLoader/Demo/chr_sword.vox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ray-cast/UnityVOXFileImport/HEAD/VOXFileLoader/Demo/chr_sword.vox -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ~ 2018 Rui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MagicaVoxel for Unity 2017 2 | ======== 3 | ### VOXFileLoader ### 4 |   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. 5 | 6 | ![Alt](./Screenshots/screenshots.png) 7 | 8 | How to use: 9 | ======== 10 | * Download a zip archive from the github page 11 | * Un-zip the archive 12 | * Copy the VOXFileLoader folder to Assets folder 13 | * Open Window -> Tools -> Cubizer -> show VOXFileLoader inspector 14 | * Select .vox file in the project view 15 | * Click 'Create Prefab from .vox file' button, you'll get a prefab and obj file from project view 16 | 17 | Features: 18 | ------------ 19 | * Voxel file import for [MagicalVoxel](http://voxel.codeplex.com/) 20 | * Mesh Optimization 21 | * Level of detail 22 | 23 | Requirements: 24 | ======== 25 | * Unity 2017.2.0 or higher 26 | 27 | Contact: 28 | ------------ 29 | 30 | * Reach me via Twitter: [@Rui](https://twitter.com/Rui_cg). 31 | 32 | [License (MIT)](https://raw.githubusercontent.com/ray-cast/ray-mmd/developing/LICENSE.txt) 33 | ------------------------------------------------------------------------------- 34 | Copyright (C) 2016-2017 Ray-MMD Developers. All rights reserved. 35 | 36 | https://github.com/ray-cast/UnityVOXFileImport 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a 39 | copy of this software and associated documentation files (the "Software"), 40 | to deal in the Software without restriction, including without limitation 41 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 42 | and/or sell copies of the Software, and to permit persons to whom the 43 | Software is furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included 46 | in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 49 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 51 | BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 52 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 53 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 54 | 55 | References: 56 | -------- 57 | * Meshing in a Minecraft Game \[[link](https://0fps.net/2012/07/07/meshing-minecraft-part-2/)\] -------------------------------------------------------------------------------- /VOXFileLoader/Scripts/ObjFileExport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Diagnostics; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Runtime.Serialization.Formatters.Binary; 7 | using System.Text; 8 | using System.Linq.Expressions; 9 | 10 | using UnityEngine; 11 | 12 | namespace Cubizer 13 | { 14 | namespace Model 15 | { 16 | public class ObjFileExport 17 | { 18 | public static string MeshToString(MeshFilter mf, Vector3 scale) 19 | { 20 | Mesh mesh = mf.sharedMesh; 21 | 22 | Dictionary dictionary = new Dictionary(); 23 | 24 | if (mesh.subMeshCount > 1) 25 | { 26 | int[] triangles = mesh.GetTriangles(1); 27 | 28 | for (int j = 0; j < triangles.Length; j += 3) 29 | { 30 | if (!dictionary.ContainsKey(triangles[j])) 31 | dictionary.Add(triangles[j], 1); 32 | 33 | if (!dictionary.ContainsKey(triangles[j + 1])) 34 | dictionary.Add(triangles[j + 1], 1); 35 | 36 | if (!dictionary.ContainsKey(triangles[j + 2])) 37 | dictionary.Add(triangles[j + 2], 1); 38 | } 39 | } 40 | 41 | StringBuilder stringBuilder = new StringBuilder().Append("mtllib design.mtl").Append("\n").Append("g ").Append(mf.name).Append("\n"); 42 | 43 | Vector3[] vertices = mesh.vertices; 44 | foreach (Vector3 v in mesh.vertices) 45 | { 46 | stringBuilder.Append(string.Format("v {0} {1} {2}\n", v.x * scale.x, v.y * scale.y, v.z * scale.z)); 47 | } 48 | 49 | stringBuilder.Append("\n"); 50 | 51 | foreach (Vector3 n in mesh.normals) 52 | stringBuilder.Append(string.Format("vn {0} {1} {2}\n", -n.x, -n.y, n.z)); 53 | 54 | for (int num = 0; num != mesh.uv.Length; num++) 55 | { 56 | Vector2 uv = mesh.uv[num]; 57 | 58 | if (dictionary.ContainsKey(num)) 59 | stringBuilder.Append(string.Format("vt {0} {1}\n", mesh.uv[num].x, mesh.uv[num].y)); 60 | else 61 | stringBuilder.Append(string.Format("vt {0} {1}\n", uv.x, uv.y)); 62 | } 63 | 64 | for (int k = 0; k < mesh.subMeshCount; k++) 65 | { 66 | stringBuilder.Append("\n"); 67 | 68 | if (k == 0) 69 | stringBuilder.Append("usemtl ").Append("Material_design").Append("\n"); 70 | 71 | if (k == 1) 72 | stringBuilder.Append("usemtl ").Append("Material_logo").Append("\n"); 73 | 74 | int[] triangles2 = mesh.GetTriangles(k); 75 | 76 | for (int l = 0; l < triangles2.Length; l += 3) 77 | stringBuilder.Append(string.Format("f {0}/{0} {1}/{1} {2}/{2}\n", triangles2[l] + 1, triangles2[l + 2] + 1, triangles2[l + 1] + 1)); 78 | } 79 | 80 | return stringBuilder.ToString(); 81 | } 82 | 83 | public static void WriteToFile(string path, MeshFilter mf, Vector3 scale) 84 | { 85 | using (var sw = new StreamWriter(path)) 86 | { 87 | sw.Write(MeshToString(mf, new Vector3(-1f, 1f, 1f))); 88 | sw.Close(); 89 | } 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /VOXFileLoader/Scripts/VOXModel.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Cubizer 4 | { 5 | namespace Model 6 | { 7 | public class VOXModel 8 | { 9 | private static Vector3[,] _positions = new Vector3[6, 4] 10 | { 11 | { new Vector3(-1, -1, -1), new Vector3(-1, -1, +1), new Vector3(-1, +1, -1), new Vector3(-1, +1, +1) }, 12 | { new Vector3(+1, -1, -1), new Vector3(+1, -1, +1), new Vector3(+1, +1, -1), new Vector3(+1, +1, +1) }, 13 | { new Vector3(-1, +1, -1), new Vector3(-1, +1, +1), new Vector3(+1, +1, -1), new Vector3(+1, +1, +1) }, 14 | { new Vector3(-1, -1, -1), new Vector3(-1, -1, +1), new Vector3(+1, -1, -1), new Vector3(+1, -1, +1) }, 15 | { new Vector3(-1, -1, -1), new Vector3(-1, +1, -1), new Vector3(+1, -1, -1), new Vector3(+1, +1, -1) }, 16 | { new Vector3(-1, -1, +1), new Vector3(-1, +1, +1), new Vector3(+1, -1, +1), new Vector3(+1, +1, +1) } 17 | }; 18 | 19 | private static Vector3[] _normals = new Vector3[6] 20 | { 21 | new Vector3(-1, 0, 0), 22 | new Vector3(+1, 0, 0), 23 | new Vector3(0, +1, 0), 24 | new Vector3(0, -1, 0), 25 | new Vector3(0, 0, -1), 26 | new Vector3(0, 0, +1) 27 | }; 28 | 29 | private static Vector2[,] _uvs = new Vector2[6, 4] 30 | { 31 | { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1) }, 32 | { new Vector2(1, 0), new Vector2(0, 0), new Vector2(1, 1), new Vector2(0, 1) }, 33 | { new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 1), new Vector2(1, 0) }, 34 | { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 1) }, 35 | { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 1) }, 36 | { new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 0), new Vector2(0, 1) } 37 | }; 38 | 39 | private static int[,] _indices = new int[6, 6] 40 | { 41 | { 0, 3, 2, 0, 1, 3 }, 42 | { 0, 3, 1, 0, 2, 3 }, 43 | { 0, 3, 2, 0, 1, 3 }, 44 | { 0, 3, 1, 0, 2, 3 }, 45 | { 0, 3, 2, 0, 1, 3 }, 46 | { 0, 3, 1, 0, 2, 3 } 47 | }; 48 | 49 | public VOXCruncher[] voxels; 50 | 51 | public VOXModel(VOXCruncher[] array) 52 | { 53 | voxels = array; 54 | } 55 | 56 | 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) 57 | { 58 | bool[] visiable = new bool[] { faces.left, faces.right, faces.top, faces.bottom, faces.front, faces.back }; 59 | 60 | float s = 1.0f / 16.0f; 61 | float a = 0 + 1.0f / 32.0f; 62 | float b = s - 1.0f / 32.0f; 63 | 64 | for (int i = 0; i < 6; i++) 65 | { 66 | if (!visiable[i]) 67 | continue; 68 | 69 | for (int n = index * 4, k = 0; k < 4; k++, n++) 70 | { 71 | Vector3 v = _positions[i, k] * 0.5f; 72 | v.x *= scale.x; 73 | v.y *= scale.y; 74 | v.z *= scale.z; 75 | v += translate; 76 | 77 | float du = (palette % 16) * s; 78 | float dv = (palette / 16) * s; 79 | 80 | Vector2 coord; 81 | coord.x = du + (_uvs[i, k].x > 0 ? b : a); 82 | coord.y = dv + (_uvs[i, k].y > 0 ? b : a); 83 | 84 | vertices[n] = v; 85 | normals[n] = _normals[i]; 86 | uv[n] = coord; 87 | } 88 | 89 | for (int j = index * 6, k = 0; k < 6; k++, j++) 90 | triangles[j] = index * 4 + _indices[i, k]; 91 | 92 | index++; 93 | } 94 | } 95 | 96 | public static void CreateCubeMesh16x16(VOXCruncher it, ref Vector3[] vertices, ref Vector3[] normals, ref Vector2[] uv, ref int[] triangles, ref int index, float scaling) 97 | { 98 | Vector3 pos; 99 | pos.x = (it.begin.x + it.end.x + 1) * 0.5f * scaling; 100 | pos.y = (it.begin.y + it.end.y + 1) * 0.5f * scaling; 101 | pos.z = (it.begin.z + it.end.z + 1) * 0.5f * scaling; 102 | 103 | Vector3 scale; 104 | scale.x = (it.end.x + 1 - it.begin.x) * scaling; 105 | scale.y = (it.end.y + 1 - it.begin.y) * scaling; 106 | scale.z = (it.end.z + 1 - it.begin.z) * scaling; 107 | 108 | VOXModel.CreateCubeMesh16x16(ref vertices, ref normals, ref uv, ref triangles, ref index, it.faces, pos, scale, (uint)it.material); 109 | } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /VOXFileLoader/Editor/VOXFileLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | using UnityEngine; 5 | using UnityEditor; 6 | 7 | using Cubizer; 8 | using Cubizer.Model; 9 | 10 | public class VOXFileLoader : EditorWindow 11 | { 12 | public bool _isSelectCreatePrefab = true; 13 | public bool _isSelectCreateAssetbundle = true; 14 | 15 | [MenuItem("Tools/Cubizer/Show VOXFileLoader Inspector")] 16 | public static void ShowWindow() 17 | { 18 | VOXFileLoader.CreateInstance().Show(); 19 | } 20 | 21 | [MenuItem("Tools/Cubizer/Load .vox file as Prefab")] 22 | public static void LoadVoxelFileAsPrefab() 23 | { 24 | var filepath = EditorUtility.OpenFilePanel("Load .vox file", "", "vox"); 25 | if (!String.IsNullOrEmpty(filepath)) 26 | { 27 | if (!filepath.Contains(".vox")) 28 | { 29 | EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); 30 | return; 31 | } 32 | 33 | VoxFileImport.LoadVoxelFileAsPrefab(filepath); 34 | } 35 | } 36 | 37 | [MenuItem("Tools/Cubizer/Load .vox file as GameObject")] 38 | public static void LoadVoxelFileAsGameObject() 39 | { 40 | var filepath = EditorUtility.OpenFilePanel("Load .vox file", "", "vox"); 41 | if (!String.IsNullOrEmpty(filepath)) 42 | { 43 | if (!filepath.Contains(".vox")) 44 | { 45 | EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); 46 | return; 47 | } 48 | 49 | VoxFileImport.LoadVoxelFileAsGameObject(filepath); 50 | } 51 | } 52 | 53 | public void OnGUI() 54 | { 55 | GUILayout.Label("Selected Object:", EditorStyles.boldLabel); 56 | 57 | this._isSelectCreatePrefab = EditorGUILayout.Foldout(this._isSelectCreatePrefab, "Create Model from .vox file"); 58 | if (this._isSelectCreatePrefab) 59 | { 60 | if (GUILayout.Button("Create Prefab from .vox file")) 61 | CreateVoxelPrefabsFromSelection(); 62 | 63 | if (GUILayout.Button("Create Prefab LOD from .vox file")) 64 | CreateVoxelPrefabsFromSelection(3); 65 | 66 | if (GUILayout.Button("Create GameObject from .vox file")) 67 | CreateVoxelGameObjectFromSelection(); 68 | 69 | if (GUILayout.Button("Create GameObject LOD from .vox file")) 70 | CreateVoxelGameObjectFromSelection(3); 71 | } 72 | 73 | this._isSelectCreateAssetbundle = EditorGUILayout.Foldout(this._isSelectCreateAssetbundle, "Create AssetBundle"); 74 | if (this._isSelectCreateAssetbundle) 75 | { 76 | if (GUILayout.Button("Selection To StreamingAssets folder")) 77 | CreateAssetBundlesFromSelectionToStreamingAssets(); 78 | 79 | if (GUILayout.Button("Selection To Selected Folder")) 80 | CreateAssetBundlesWithFolderPanel(); 81 | } 82 | } 83 | 84 | private static bool CreateVoxelPrefabsFromSelection(int lodLevel = 0) 85 | { 86 | var SelectedAsset = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets); 87 | if (SelectedAsset.Length == 0) 88 | { 89 | EditorUtility.DisplayDialog("No Object Selected", "Please select any .vox file to create to prefab", "Ok"); 90 | return false; 91 | } 92 | 93 | foreach (var asset in SelectedAsset) 94 | { 95 | var path = AssetDatabase.GetAssetPath(asset); 96 | if (Path.GetExtension(path) != ".vox") 97 | { 98 | EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); 99 | return false; 100 | } 101 | 102 | if (path.Remove(0, path.LastIndexOf('.')) == ".vox") 103 | { 104 | if (lodLevel == 0) 105 | VoxFileImport.LoadVoxelFileAsPrefab(path); 106 | else 107 | VoxFileImport.LoadVoxelFileAsPrefab(path, "Assets/", lodLevel); 108 | } 109 | } 110 | 111 | return true; 112 | } 113 | 114 | private static bool CreateVoxelGameObjectFromSelection(int lodLevel = 0) 115 | { 116 | var SelectedAsset = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets); 117 | if (SelectedAsset.Length == 0) 118 | { 119 | EditorUtility.DisplayDialog("No Object Selected", "Please select any .vox file to create to prefab", "Ok"); 120 | return false; 121 | } 122 | 123 | foreach (var asset in SelectedAsset) 124 | { 125 | var path = AssetDatabase.GetAssetPath(asset); 126 | if (Path.GetExtension(path) != ".vox") 127 | { 128 | EditorUtility.DisplayDialog("Invalid File", "The end of the path wasn't \".vox\"", "Ok"); 129 | return false; 130 | } 131 | 132 | if (path.Remove(0, path.LastIndexOf('.')) == ".vox") 133 | { 134 | if (lodLevel == 0) 135 | VoxFileImport.LoadVoxelFileAsGameObject(path); 136 | else 137 | VoxFileImport.LoadVoxelFileAsGameObjectLOD(path, lodLevel); 138 | } 139 | } 140 | 141 | return true; 142 | } 143 | 144 | private static void CreateAssetBundlesFromSelection(string targetPath, string bundleName = "Resource", string ext = "") 145 | { 146 | var SelectedAsset = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets); 147 | 148 | if (SelectedAsset.Length > 0) 149 | { 150 | AssetBundleBuild[] buildMap = new AssetBundleBuild[2]; 151 | buildMap[0].assetBundleName = bundleName + ext; 152 | buildMap[0].assetNames = new string[SelectedAsset.Length]; 153 | 154 | for (int i = 0; i < SelectedAsset.Length; i++) 155 | buildMap[0].assetNames[i] = AssetDatabase.GetAssetPath(SelectedAsset[i]); 156 | 157 | if (!BuildPipeline.BuildAssetBundles(targetPath, buildMap, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows)) 158 | UnityEngine.Debug.Log(targetPath + ": failed to load"); 159 | 160 | AssetDatabase.Refresh(); 161 | } 162 | } 163 | 164 | private static void CreateAssetBundlesWithFolderPanel(string bundleName = "Resource", string ext = "") 165 | { 166 | var SelectedPath = EditorUtility.SaveFolderPanel("Save Resource", "", "New Resource"); 167 | if (SelectedPath.Length == 0) 168 | return; 169 | 170 | CreateAssetBundlesFromSelection(SelectedPath + "/", bundleName, ext); 171 | } 172 | 173 | private static void CreateAssetBundlesFromSelectionToStreamingAssets(string bundleName = "Resource", string ext = "") 174 | { 175 | CreateAssetBundlesFromSelection(Application.dataPath + "/StreamingAssets/", bundleName, ext); 176 | } 177 | } -------------------------------------------------------------------------------- /VOXFileLoader/Scripts/VOXHashMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Runtime.Serialization.Formatters.Binary; 7 | 8 | using UnityEngine; 9 | 10 | namespace Cubizer 11 | { 12 | using VOXMaterial = System.Int32; 13 | 14 | namespace Model 15 | { 16 | [Serializable] 17 | public class VOXHashMapNode<_Tx> 18 | where _Tx : struct 19 | { 20 | public _Tx x; 21 | public _Tx y; 22 | public _Tx z; 23 | public VOXMaterial element; 24 | 25 | public VOXHashMapNode() 26 | { 27 | element = int.MaxValue; 28 | } 29 | 30 | public VOXHashMapNode(_Tx xx, _Tx yy, _Tx zz, VOXMaterial value) 31 | { 32 | x = xx; 33 | y = yy; 34 | z = zz; 35 | element = value; 36 | } 37 | 38 | public bool is_empty() 39 | { 40 | return element == int.MaxValue; 41 | } 42 | } 43 | 44 | public class VOXHashMapNodeEnumerable<_Tx> : IEnumerable 45 | where _Tx : struct 46 | { 47 | private VOXHashMapNode<_Tx>[] _array; 48 | 49 | public VOXHashMapNodeEnumerable(VOXHashMapNode<_Tx>[] array) 50 | { 51 | _array = array; 52 | } 53 | 54 | IEnumerator IEnumerable.GetEnumerator() 55 | { 56 | return (IEnumerator)GetEnumerator(); 57 | } 58 | 59 | public VOXHashMapNodeEnum<_Tx> GetEnumerator() 60 | { 61 | return new VOXHashMapNodeEnum<_Tx>(_array); 62 | } 63 | } 64 | 65 | public class VOXHashMapNodeEnum<_Tx> : IEnumerator 66 | where _Tx : struct 67 | 68 | { 69 | private int position = -1; 70 | private VOXHashMapNode<_Tx>[] _array; 71 | 72 | public VOXHashMapNodeEnum(VOXHashMapNode<_Tx>[] list) 73 | { 74 | _array = list; 75 | } 76 | 77 | public bool MoveNext() 78 | { 79 | var length = _array.Length; 80 | for (position++; position < length; position++) 81 | { 82 | if (_array[position] == null) 83 | continue; 84 | if (_array[position].is_empty()) 85 | continue; 86 | break; 87 | } 88 | 89 | return position < _array.Length; 90 | } 91 | 92 | public void Reset() 93 | { 94 | position = -1; 95 | } 96 | 97 | object IEnumerator.Current 98 | { 99 | get 100 | { 101 | return Current; 102 | } 103 | } 104 | 105 | public VOXHashMapNode<_Tx> Current 106 | { 107 | get 108 | { 109 | return _array[position]; 110 | } 111 | } 112 | } 113 | 114 | [Serializable] 115 | public class VOXHashMap 116 | { 117 | protected int _count; 118 | protected int _allocSize; 119 | protected Vector3Int _bound; 120 | 121 | protected VOXHashMapNode[] _data; 122 | 123 | public int Count { get { return _count; } } 124 | public Vector3Int bound { get { return _bound; } } 125 | 126 | public VOXHashMap(Vector3Int bound) 127 | { 128 | _count = 0; 129 | _bound = bound; 130 | _allocSize = 0; 131 | } 132 | 133 | public VOXHashMap(Vector3Int bound, int count) 134 | { 135 | _count = 0; 136 | _bound = bound; 137 | _allocSize = 0; 138 | this.Create(count); 139 | } 140 | 141 | public VOXHashMap(int bound_x, int bound_y, int bound_z, int count) 142 | { 143 | _count = 0; 144 | _bound = new Vector3Int(bound_x, bound_y, bound_z); 145 | _allocSize = 0; 146 | this.Create(count); 147 | } 148 | 149 | public void Create(int count) 150 | { 151 | int usage = 1; 152 | while (usage < count) usage = usage << 1 | 1; 153 | 154 | _count = 0; 155 | _allocSize = usage; 156 | _data = new VOXHashMapNode[usage + 1]; 157 | } 158 | 159 | public bool Set(System.Byte x, System.Byte y, System.Byte z, VOXMaterial value, bool replace = true) 160 | { 161 | if (_allocSize == 0) 162 | this.Create(0xFF); 163 | 164 | var index = HashInt(x, y, z) & _allocSize; 165 | var entry = _data[index]; 166 | 167 | while (entry != null) 168 | { 169 | if (entry.x == x && entry.y == y && entry.z == z) 170 | { 171 | if (replace) 172 | { 173 | _data[index].element = value; 174 | return true; 175 | } 176 | 177 | return false; 178 | } 179 | 180 | index = (index + 1) & _allocSize; 181 | entry = _data[index]; 182 | } 183 | 184 | if (value != VOXMaterial.MaxValue) 185 | { 186 | _data[index] = new VOXHashMapNode(x, y, z, value); 187 | _count++; 188 | 189 | if (_count >= _allocSize) 190 | this.Grow(); 191 | 192 | return true; 193 | } 194 | 195 | return false; 196 | } 197 | 198 | public bool Get(System.Byte x, System.Byte y, System.Byte z, ref VOXMaterial instanceID) 199 | { 200 | if (_allocSize == 0) 201 | return false; 202 | 203 | var index = HashInt(x, y, z) & _allocSize; 204 | var entry = _data[index]; 205 | 206 | while (entry != null) 207 | { 208 | if (entry.x == x && entry.y == y && entry.z == z) 209 | { 210 | instanceID = entry.element; 211 | return instanceID != VOXMaterial.MaxValue; 212 | } 213 | 214 | index = (index + 1) & _allocSize; 215 | entry = _data[index]; 216 | } 217 | 218 | instanceID = VOXMaterial.MaxValue; 219 | return false; 220 | } 221 | 222 | public bool Exists(System.Byte x, System.Byte y, System.Byte z) 223 | { 224 | VOXMaterial instanceID = VOXMaterial.MaxValue; 225 | return this.Get(x, y, z, ref instanceID); 226 | } 227 | 228 | public bool Empty() 229 | { 230 | return _count == 0; 231 | } 232 | 233 | public VOXHashMapNodeEnumerable GetEnumerator() 234 | { 235 | if (_data == null) 236 | throw new System.ApplicationException("GetEnumerator: Empty data"); 237 | 238 | return new VOXHashMapNodeEnumerable(_data); 239 | } 240 | 241 | public static bool Save(string path, VOXHashMap map) 242 | { 243 | UnityEngine.Debug.Assert(map != null); 244 | 245 | var stream = new FileStream(path, FileMode.Create, FileAccess.Write); 246 | var serializer = new BinaryFormatter(); 247 | 248 | serializer.Serialize(stream, map); 249 | stream.Close(); 250 | 251 | return true; 252 | } 253 | 254 | public static VOXHashMap Load(string path) 255 | { 256 | var serializer = new BinaryFormatter(); 257 | var loadFile = new FileStream(path, FileMode.Open, FileAccess.Read); 258 | return serializer.Deserialize(loadFile) as VOXHashMap; 259 | } 260 | 261 | private bool Grow(VOXHashMapNode data) 262 | { 263 | var index = HashInt(data.x, data.y, data.z) & _allocSize; 264 | var entry = _data[index]; 265 | 266 | while (entry != null) 267 | { 268 | index = (index + 1) & _allocSize; 269 | entry = _data[index]; 270 | } 271 | 272 | if (data.element != VOXMaterial.MaxValue) 273 | { 274 | _data[index] = data; 275 | _count++; 276 | 277 | return true; 278 | } 279 | 280 | return false; 281 | } 282 | 283 | private void Grow() 284 | { 285 | var map = new VOXHashMap(_bound, _allocSize << 1 | 1); 286 | 287 | foreach (var it in GetEnumerator()) 288 | map.Grow(it); 289 | 290 | _count = map._count; 291 | _allocSize = map._allocSize; 292 | _data = map._data; 293 | } 294 | 295 | private static int _hash_int(int key) 296 | { 297 | key = ~key + (key << 15); 298 | key = key ^ (key >> 12); 299 | key = key + (key << 2); 300 | key = key ^ (key >> 4); 301 | key = key * 2057; 302 | key = key ^ (key >> 16); 303 | return key; 304 | } 305 | 306 | public static int HashInt(int x, int y, int z) 307 | { 308 | return _hash_int(x) ^ _hash_int(y) ^ _hash_int(z); 309 | } 310 | } 311 | } 312 | } -------------------------------------------------------------------------------- /VOXFileLoader/Scripts/VOXCruncher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | using UnityEngine; 6 | 7 | namespace Cubizer 8 | { 9 | namespace Model 10 | { 11 | using VOXMaterial = System.Int32; 12 | 13 | public enum VOXCruncherMode 14 | { 15 | Stupid, 16 | Culled, 17 | Greedy, 18 | } 19 | 20 | public struct VOXVisiableFaces 21 | { 22 | public bool left; 23 | public bool right; 24 | public bool bottom; 25 | public bool top; 26 | public bool back; 27 | public bool front; 28 | 29 | public VOXVisiableFaces(bool _left, bool _right, bool _bottom, bool _top, bool _back, bool _front) 30 | { 31 | left = _left; 32 | right = _right; 33 | bottom = _bottom; 34 | top = _top; 35 | back = _back; 36 | front = _front; 37 | } 38 | } 39 | 40 | public class VOXCruncher 41 | { 42 | public struct Vector3 43 | { 44 | public int x; 45 | public int y; 46 | public int z; 47 | } 48 | 49 | public Vector3 begin; 50 | public Vector3 end; 51 | 52 | public VOXMaterial material; 53 | public VOXVisiableFaces faces; 54 | 55 | public VOXCruncher(Vector3 begin, Vector3 end, VOXMaterial _material) 56 | { 57 | this.begin = begin; 58 | this.end = end; 59 | 60 | material = _material; 61 | 62 | faces.left = true; 63 | faces.right = true; 64 | faces.top = true; 65 | faces.bottom = true; 66 | faces.front = true; 67 | faces.back = true; 68 | } 69 | 70 | public VOXCruncher(int begin_x, int end_x, int begin_y, int end_y, int begin_z, int end_z, VOXMaterial _material) 71 | { 72 | begin.x = begin_x; 73 | begin.y = begin_y; 74 | begin.z = begin_z; 75 | 76 | end.x = end_x; 77 | end.y = end_y; 78 | end.z = end_z; 79 | 80 | material = _material; 81 | 82 | faces.left = true; 83 | faces.right = true; 84 | faces.top = true; 85 | faces.bottom = true; 86 | faces.front = true; 87 | faces.back = true; 88 | } 89 | 90 | public VOXCruncher(int begin_x, int end_x, int begin_y, int end_y, int begin_z, int end_z, VOXVisiableFaces _faces, VOXMaterial _material) 91 | { 92 | begin.x = begin_x; 93 | begin.y = begin_y; 94 | begin.z = begin_z; 95 | 96 | end.x = end_x; 97 | end.y = end_y; 98 | end.z = end_z; 99 | 100 | material = _material; 101 | faces = _faces; 102 | } 103 | } 104 | 105 | public interface IVOXCruncherStrategy 106 | { 107 | VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette); 108 | } 109 | 110 | public class VOXCruncherStupid : IVOXCruncherStrategy 111 | { 112 | public VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette) 113 | { 114 | var crunchers = new VOXCruncher[chunk.count]; 115 | var faces = new VOXVisiableFaces(true, true, true, true, true, true); 116 | 117 | int n = 0; 118 | 119 | for (int i = 0; i < chunk.x; ++i) 120 | { 121 | for (int j = 0; j < chunk.y; ++j) 122 | { 123 | for (int k = 0; k < chunk.z; ++k) 124 | { 125 | var m = chunk.voxels[i, j, k]; 126 | if (m != int.MaxValue) 127 | crunchers[n++] = new VOXCruncher(i, i, j, j, k, k, faces, m); 128 | } 129 | } 130 | } 131 | 132 | return new VOXModel(crunchers); 133 | } 134 | } 135 | 136 | public class VOXCruncherCulled : IVOXCruncherStrategy 137 | { 138 | public static bool GetVisiableFaces(VOXMaterial[,,] map, Vector3Int bound, int x, int y, int z, VOXMaterial material, Color32[] palette, out VOXVisiableFaces faces) 139 | { 140 | VOXMaterial[] instanceID = new VOXMaterial[6] { VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue, VOXMaterial.MaxValue }; 141 | 142 | if (x >= 1) instanceID[0] = map[(byte)(x - 1), y, z]; 143 | if (y >= 1) instanceID[2] = map[x, (byte)(y - 1), z]; 144 | if (z >= 1) instanceID[4] = map[x, y, (byte)(z - 1)]; 145 | if (x <= bound.x) instanceID[1] = map[(byte)(x + 1), y, z]; 146 | if (y <= bound.y) instanceID[3] = map[x, (byte)(y + 1), z]; 147 | if (z <= bound.z) instanceID[5] = map[x, y, (byte)(z + 1)]; 148 | 149 | var alpha = palette[material].a; 150 | if (alpha < 255) 151 | { 152 | bool f1 = (instanceID[0] == VOXMaterial.MaxValue) ? true : palette[instanceID[0]].a != alpha ? true : false; 153 | bool f2 = (instanceID[1] == VOXMaterial.MaxValue) ? true : palette[instanceID[1]].a != alpha ? true : false; 154 | bool f3 = (instanceID[2] == VOXMaterial.MaxValue) ? true : palette[instanceID[2]].a != alpha ? true : false; 155 | bool f4 = (instanceID[3] == VOXMaterial.MaxValue) ? true : palette[instanceID[3]].a != alpha ? true : false; 156 | bool f5 = (instanceID[4] == VOXMaterial.MaxValue) ? true : palette[instanceID[4]].a != alpha ? true : false; 157 | bool f6 = (instanceID[5] == VOXMaterial.MaxValue) ? true : palette[instanceID[5]].a != alpha ? true : false; 158 | 159 | faces.left = f1; 160 | faces.right = f2; 161 | faces.bottom = f3; 162 | faces.top = f4; 163 | faces.front = f5; 164 | faces.back = f6; 165 | } 166 | else 167 | { 168 | bool f1 = (instanceID[0] == VOXMaterial.MaxValue) ? true : palette[instanceID[0]].a < 255 ? true : false; 169 | bool f2 = (instanceID[1] == VOXMaterial.MaxValue) ? true : palette[instanceID[1]].a < 255 ? true : false; 170 | bool f3 = (instanceID[2] == VOXMaterial.MaxValue) ? true : palette[instanceID[2]].a < 255 ? true : false; 171 | bool f4 = (instanceID[3] == VOXMaterial.MaxValue) ? true : palette[instanceID[3]].a < 255 ? true : false; 172 | bool f5 = (instanceID[4] == VOXMaterial.MaxValue) ? true : palette[instanceID[4]].a < 255 ? true : false; 173 | bool f6 = (instanceID[5] == VOXMaterial.MaxValue) ? true : palette[instanceID[5]].a < 255 ? true : false; 174 | 175 | faces.left = f1; 176 | faces.right = f2; 177 | faces.bottom = f3; 178 | faces.top = f4; 179 | faces.front = f5; 180 | faces.back = f6; 181 | } 182 | 183 | return faces.left | faces.right | faces.bottom | faces.top | faces.front | faces.back; 184 | } 185 | 186 | public VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette) 187 | { 188 | var crunchers = new List(); 189 | var bound = new Vector3Int(chunk.x, chunk.y, chunk.z); 190 | 191 | for (int i = 0; i < chunk.x; ++i) 192 | { 193 | for (int j = 0; j < chunk.y; ++j) 194 | { 195 | for (int k = 0; k < chunk.z; ++k) 196 | { 197 | var c = chunk.voxels[i, j, k]; 198 | if (c != int.MaxValue) 199 | { 200 | VOXVisiableFaces faces; 201 | if (!GetVisiableFaces(chunk.voxels, bound, i, j, k, c, palette, out faces)) 202 | continue; 203 | 204 | crunchers.Add(new VOXCruncher((byte)i, (byte)i, (byte)j, (byte)j, (byte)k, (byte)k, faces, c)); 205 | } 206 | } 207 | } 208 | } 209 | 210 | var array = new VOXCruncher[crunchers.Count]; 211 | 212 | int numbers = 0; 213 | foreach (var it in crunchers) 214 | array[numbers++] = it; 215 | 216 | return new VOXModel(array); 217 | } 218 | } 219 | 220 | public class VOXCruncherGreedy : IVOXCruncherStrategy 221 | { 222 | public VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette) 223 | { 224 | var crunchers = new List(); 225 | var dims = new int[] { chunk.x, chunk.y, chunk.z }; 226 | 227 | var alloc = System.Math.Max(dims[0], System.Math.Max(dims[1], dims[2])); 228 | var mask = new int[alloc * alloc]; 229 | var map = chunk.voxels; 230 | 231 | for (var d = 0; d < 3; ++d) 232 | { 233 | var u = (d + 1) % 3; 234 | var v = (d + 2) % 3; 235 | 236 | var x = new int[3] { 0, 0, 0 }; 237 | var q = new int[3] { 0, 0, 0 }; 238 | 239 | q[d] = 1; 240 | 241 | var faces = new VOXVisiableFaces(false, false, false, false, false, false); 242 | 243 | for (x[d] = -1; x[d] < dims[d];) 244 | { 245 | var n = 0; 246 | 247 | for (x[v] = 0; x[v] < dims[v]; ++x[v]) 248 | { 249 | for (x[u] = 0; x[u] < dims[u]; ++x[u]) 250 | { 251 | var a = x[d] >= 0 ? map[x[0], x[1], x[2]] : VOXMaterial.MaxValue; 252 | var b = x[d] < dims[d] - 1 ? map[x[0] + q[0], x[1] + q[1], x[2] + q[2]] : VOXMaterial.MaxValue; 253 | if (a != b) 254 | { 255 | if (a == VOXMaterial.MaxValue) 256 | mask[n++] = b; 257 | else if (b == VOXMaterial.MaxValue) 258 | mask[n++] = -a; 259 | else 260 | mask[n++] = -b; 261 | } 262 | else 263 | { 264 | mask[n++] = VOXMaterial.MaxValue; 265 | } 266 | } 267 | } 268 | 269 | ++x[d]; 270 | 271 | n = 0; 272 | 273 | for (var j = 0; j < dims[v]; ++j) 274 | { 275 | for (var i = 0; i < dims[u];) 276 | { 277 | var c = mask[n]; 278 | if (c == VOXMaterial.MaxValue) 279 | { 280 | ++i; ++n; 281 | continue; 282 | } 283 | 284 | var w = 1; 285 | var h = 1; 286 | var k = 0; 287 | 288 | for (; (i + w) < dims[u] && c == mask[n + w]; ++w) { } 289 | 290 | var done = false; 291 | for (; (j + h) < dims[v]; ++h) 292 | { 293 | for (k = 0; k < w; ++k) 294 | { 295 | if (c != mask[n + k + h * dims[u]]) 296 | { 297 | done = true; 298 | break; 299 | } 300 | } 301 | 302 | if (done) 303 | break; 304 | } 305 | 306 | x[u] = i; x[v] = j; 307 | 308 | var du = new int[3] { 0, 0, 0 }; 309 | var dv = new int[3] { 0, 0, 0 }; 310 | 311 | du[u] = w; 312 | dv[v] = h; 313 | 314 | var v1 = new Vector3(x[0], x[1], x[2]); 315 | var v2 = new Vector3(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1], x[2] + du[2] + dv[2]); 316 | 317 | v2.x = System.Math.Max(v2.x - 1, 0); 318 | v2.y = System.Math.Max(v2.y - 1, 0); 319 | v2.z = System.Math.Max(v2.z - 1, 0); 320 | 321 | if (c > 0) 322 | { 323 | faces.front = d == 2; 324 | faces.back = false; 325 | faces.left = d == 0; 326 | faces.right = false; 327 | faces.top = false; 328 | faces.bottom = d == 1; 329 | } 330 | else 331 | { 332 | c = -c; 333 | faces.front = false; 334 | faces.back = d == 2; 335 | faces.left = false; 336 | faces.right = d == 0; 337 | faces.top = d == 1; 338 | faces.bottom = false; 339 | } 340 | 341 | crunchers.Add(new VOXCruncher((byte)v1.x, (byte)(v2.x), (byte)(v1.y), (byte)(v2.y), (byte)(v1.z), (byte)(v2.z), faces, c)); 342 | 343 | for (var l = 0; l < h; ++l) 344 | { 345 | for (k = 0; k < w; ++k) 346 | mask[n + k + l * dims[u]] = VOXMaterial.MaxValue; 347 | } 348 | 349 | i += w; n += w; 350 | } 351 | } 352 | } 353 | } 354 | 355 | var array = new VOXCruncher[crunchers.Count]; 356 | 357 | int numbers = 0; 358 | foreach (var it in crunchers) 359 | array[numbers++] = it; 360 | 361 | return new VOXModel(array); 362 | } 363 | } 364 | 365 | public class VOXPolygonCruncher 366 | { 367 | public static VOXModel CalcVoxelCruncher(VoxData chunk, Color32[] palette, VOXCruncherMode mode) 368 | { 369 | switch (mode) 370 | { 371 | case VOXCruncherMode.Stupid: 372 | return new VOXCruncherStupid().CalcVoxelCruncher(chunk, palette); 373 | 374 | case VOXCruncherMode.Culled: 375 | return new VOXCruncherCulled().CalcVoxelCruncher(chunk, palette); 376 | 377 | case VOXCruncherMode.Greedy: 378 | return new VOXCruncherGreedy().CalcVoxelCruncher(chunk, palette); 379 | 380 | default: 381 | return null; 382 | } 383 | } 384 | } 385 | } 386 | } -------------------------------------------------------------------------------- /VOXFileLoader/Scripts/VOXFileImport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | using UnityEngine; 6 | 7 | #if UNITY_EDITOR 8 | 9 | using UnityEditor; 10 | 11 | #endif 12 | 13 | namespace Cubizer 14 | { 15 | namespace Model 16 | { 17 | public struct VoxFileHeader 18 | { 19 | public byte[] header; 20 | public Int32 version; 21 | } 22 | 23 | public struct VoxFilePack 24 | { 25 | public byte[] name; 26 | public Int32 chunkContent; 27 | public Int32 chunkNums; 28 | public Int32 modelNums; 29 | } 30 | 31 | public struct VoxFileSize 32 | { 33 | public byte[] name; 34 | public Int32 chunkContent; 35 | public Int32 chunkNums; 36 | public Int32 x; 37 | public Int32 y; 38 | public Int32 z; 39 | } 40 | 41 | public struct VoxFileXYZI 42 | { 43 | public byte[] name; 44 | public Int32 chunkContent; 45 | public Int32 chunkNums; 46 | public VoxData voxels; 47 | } 48 | 49 | public struct VoxFileRGBA 50 | { 51 | public byte[] name; 52 | public Int32 chunkContent; 53 | public Int32 chunkNums; 54 | public uint[] values; 55 | } 56 | 57 | public struct VoxFileChunkChild 58 | { 59 | public VoxFileSize size; 60 | public VoxFileXYZI xyzi; 61 | } 62 | 63 | public struct VoxFileChunk 64 | { 65 | public byte[] name; 66 | public Int32 chunkContent; 67 | public Int32 chunkNums; 68 | } 69 | 70 | public struct VoxFileMaterial 71 | { 72 | public int id; 73 | public int type; 74 | public float weight; 75 | public int propertyBits; 76 | public float[] propertyValue; 77 | } 78 | 79 | public class VoxFileData 80 | { 81 | public VoxFileHeader hdr; 82 | public VoxFileChunk main; 83 | public VoxFilePack pack; 84 | public VoxFileChunkChild[] chunkChild; 85 | public VoxFileRGBA palette; 86 | } 87 | 88 | public class VoxData 89 | { 90 | public int x, y, z; 91 | public int[,,] voxels; 92 | 93 | public int count 94 | { 95 | get 96 | { 97 | int _count = 0; 98 | 99 | for (int i = 0; i < x; ++i) 100 | { 101 | for (int j = 0; j < y; ++j) 102 | for (int k = 0; k < z; ++k) 103 | if (voxels[i, j, k] != int.MaxValue) 104 | _count++; 105 | } 106 | 107 | return _count; 108 | } 109 | } 110 | 111 | public VoxData() 112 | { 113 | x = 0; y = 0; z = 0; 114 | } 115 | 116 | public VoxData(byte[] _voxels, int xx, int yy, int zz) 117 | { 118 | x = xx; 119 | y = zz; 120 | z = yy; 121 | voxels = new int[x, y, z]; 122 | 123 | for (int i = 0; i < x; ++i) 124 | { 125 | for (int j = 0; j < y; ++j) 126 | for (int k = 0; k < z; ++k) 127 | voxels[i, j, k] = int.MaxValue; 128 | } 129 | 130 | for (int j = 0; j < _voxels.Length; j += 4) 131 | { 132 | var x = _voxels[j]; 133 | var y = _voxels[j + 1]; 134 | var z = _voxels[j + 2]; 135 | var c = _voxels[j + 3]; 136 | 137 | voxels[x, z, y] = c; 138 | } 139 | } 140 | 141 | public int GetMajorityColorIndex(int xx, int yy, int zz, int lodLevel) 142 | { 143 | xx = Mathf.Min(xx, x - 2); 144 | yy = Mathf.Min(yy, y - 2); 145 | zz = Mathf.Min(zz, z - 2); 146 | 147 | int[] samples = new int[lodLevel * lodLevel * lodLevel]; 148 | 149 | for (int i = 0; i < lodLevel; i++) 150 | { 151 | for (int j = 0; j < lodLevel; j++) 152 | { 153 | for (int k = 0; k < lodLevel; k++) 154 | { 155 | if (xx + i > x - 1 || yy + j > y - 1 || zz + k > z - 1) 156 | samples[i * lodLevel * lodLevel + j * lodLevel + k] = int.MaxValue; 157 | else 158 | samples[i * lodLevel * lodLevel + j * lodLevel + k] = voxels[xx + i, yy + j, zz + k]; 159 | } 160 | } 161 | } 162 | 163 | int maxNum = 1; 164 | int maxNumIndex = 0; 165 | 166 | int[] numIndex = new int[samples.Length]; 167 | 168 | for (int i = 0; i < samples.Length; i++) 169 | numIndex[i] = samples[i] == int.MaxValue ? 0 : 1; 170 | 171 | for (int i = 0; i < samples.Length; i++) 172 | { 173 | for (int j = 0; j < samples.Length; j++) 174 | { 175 | if (i != j && samples[i] != int.MaxValue && samples[i] == samples[j]) 176 | { 177 | numIndex[i]++; 178 | if (numIndex[i] > maxNum) 179 | { 180 | maxNum = numIndex[i]; 181 | maxNumIndex = i; 182 | } 183 | } 184 | } 185 | } 186 | 187 | return samples[maxNumIndex]; 188 | } 189 | 190 | public VoxData GetVoxelDataLOD(int level) 191 | { 192 | if (x <= 1 || y <= 1 || z <= 1) 193 | return null; 194 | 195 | level = Mathf.Clamp(level, 0, 16); 196 | if (level <= 1) 197 | return this; 198 | 199 | if (x <= level && y <= level && z <= level) 200 | return this; 201 | 202 | VoxData data = new VoxData(); 203 | data.x = Mathf.CeilToInt((float)x / level); 204 | data.y = Mathf.CeilToInt((float)y / level); 205 | data.z = Mathf.CeilToInt((float)z / level); 206 | 207 | data.voxels = new int[data.x, data.y, data.z]; 208 | 209 | for (int x = 0; x < data.x; x++) 210 | { 211 | for (int y = 0; y < data.y; y++) 212 | { 213 | for (int z = 0; z < data.z; z++) 214 | { 215 | data.voxels[x, y, z] = this.GetMajorityColorIndex(x * level, y * level, z * level, level); 216 | } 217 | } 218 | } 219 | 220 | return data; 221 | } 222 | } 223 | 224 | public class VoxFileImport 225 | { 226 | private static uint[] _paletteDefault = new uint[256] 227 | { 228 | 0x00000000, 0xffffffff, 0xffccffff, 0xff99ffff, 0xff66ffff, 0xff33ffff, 0xff00ffff, 0xffffccff, 0xffccccff, 0xff99ccff, 0xff66ccff, 0xff33ccff, 0xff00ccff, 0xffff99ff, 0xffcc99ff, 0xff9999ff, 229 | 0xff6699ff, 0xff3399ff, 0xff0099ff, 0xffff66ff, 0xffcc66ff, 0xff9966ff, 0xff6666ff, 0xff3366ff, 0xff0066ff, 0xffff33ff, 0xffcc33ff, 0xff9933ff, 0xff6633ff, 0xff3333ff, 0xff0033ff, 0xffff00ff, 230 | 0xffcc00ff, 0xff9900ff, 0xff6600ff, 0xff3300ff, 0xff0000ff, 0xffffffcc, 0xffccffcc, 0xff99ffcc, 0xff66ffcc, 0xff33ffcc, 0xff00ffcc, 0xffffcccc, 0xffcccccc, 0xff99cccc, 0xff66cccc, 0xff33cccc, 231 | 0xff00cccc, 0xffff99cc, 0xffcc99cc, 0xff9999cc, 0xff6699cc, 0xff3399cc, 0xff0099cc, 0xffff66cc, 0xffcc66cc, 0xff9966cc, 0xff6666cc, 0xff3366cc, 0xff0066cc, 0xffff33cc, 0xffcc33cc, 0xff9933cc, 232 | 0xff6633cc, 0xff3333cc, 0xff0033cc, 0xffff00cc, 0xffcc00cc, 0xff9900cc, 0xff6600cc, 0xff3300cc, 0xff0000cc, 0xffffff99, 0xffccff99, 0xff99ff99, 0xff66ff99, 0xff33ff99, 0xff00ff99, 0xffffcc99, 233 | 0xffcccc99, 0xff99cc99, 0xff66cc99, 0xff33cc99, 0xff00cc99, 0xffff9999, 0xffcc9999, 0xff999999, 0xff669999, 0xff339999, 0xff009999, 0xffff6699, 0xffcc6699, 0xff996699, 0xff666699, 0xff336699, 234 | 0xff006699, 0xffff3399, 0xffcc3399, 0xff993399, 0xff663399, 0xff333399, 0xff003399, 0xffff0099, 0xffcc0099, 0xff990099, 0xff660099, 0xff330099, 0xff000099, 0xffffff66, 0xffccff66, 0xff99ff66, 235 | 0xff66ff66, 0xff33ff66, 0xff00ff66, 0xffffcc66, 0xffcccc66, 0xff99cc66, 0xff66cc66, 0xff33cc66, 0xff00cc66, 0xffff9966, 0xffcc9966, 0xff999966, 0xff669966, 0xff339966, 0xff009966, 0xffff6666, 236 | 0xffcc6666, 0xff996666, 0xff666666, 0xff336666, 0xff006666, 0xffff3366, 0xffcc3366, 0xff993366, 0xff663366, 0xff333366, 0xff003366, 0xffff0066, 0xffcc0066, 0xff990066, 0xff660066, 0xff330066, 237 | 0xff000066, 0xffffff33, 0xffccff33, 0xff99ff33, 0xff66ff33, 0xff33ff33, 0xff00ff33, 0xffffcc33, 0xffcccc33, 0xff99cc33, 0xff66cc33, 0xff33cc33, 0xff00cc33, 0xffff9933, 0xffcc9933, 0xff999933, 238 | 0xff669933, 0xff339933, 0xff009933, 0xffff6633, 0xffcc6633, 0xff996633, 0xff666633, 0xff336633, 0xff006633, 0xffff3333, 0xffcc3333, 0xff993333, 0xff663333, 0xff333333, 0xff003333, 0xffff0033, 239 | 0xffcc0033, 0xff990033, 0xff660033, 0xff330033, 0xff000033, 0xffffff00, 0xffccff00, 0xff99ff00, 0xff66ff00, 0xff33ff00, 0xff00ff00, 0xffffcc00, 0xffcccc00, 0xff99cc00, 0xff66cc00, 0xff33cc00, 240 | 0xff00cc00, 0xffff9900, 0xffcc9900, 0xff999900, 0xff669900, 0xff339900, 0xff009900, 0xffff6600, 0xffcc6600, 0xff996600, 0xff666600, 0xff336600, 0xff006600, 0xffff3300, 0xffcc3300, 0xff993300, 241 | 0xff663300, 0xff333300, 0xff003300, 0xffff0000, 0xffcc0000, 0xff990000, 0xff660000, 0xff330000, 0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088, 0xff000077, 0xff000055, 0xff000044, 242 | 0xff000022, 0xff000011, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700, 0xff005500, 0xff004400, 0xff002200, 0xff001100, 0xffee0000, 0xffdd0000, 0xffbb0000, 0xffaa0000, 243 | 0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000, 0xff110000, 0xffeeeeee, 0xffdddddd, 0xffbbbbbb, 0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111 244 | }; 245 | 246 | private static UnityEngine.Object _assetPrefab; 247 | 248 | public static VoxFileData Load(string path) 249 | { 250 | using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) 251 | { 252 | if (stream == null) 253 | throw new System.Exception("Failed to open file for FileStream."); 254 | 255 | using (var reader = new BinaryReader(stream)) 256 | { 257 | VoxFileData voxel = new VoxFileData(); 258 | voxel.hdr.header = reader.ReadBytes(4); 259 | voxel.hdr.version = reader.ReadInt32(); 260 | 261 | if (voxel.hdr.header[0] != 'V' || voxel.hdr.header[1] != 'O' || voxel.hdr.header[2] != 'X' || voxel.hdr.header[3] != ' ') 262 | throw new System.Exception("Bad Token: token is not VOX."); 263 | 264 | if (voxel.hdr.version != 150) 265 | throw new System.Exception("The version of file isn't 150 that version of vox, tihs version of file is " + voxel.hdr.version + "."); 266 | 267 | voxel.main.name = reader.ReadBytes(4); 268 | voxel.main.chunkContent = reader.ReadInt32(); 269 | voxel.main.chunkNums = reader.ReadInt32(); 270 | 271 | if (voxel.main.name[0] != 'M' || voxel.main.name[1] != 'A' || voxel.main.name[2] != 'I' || voxel.main.name[3] != 'N') 272 | throw new System.Exception("Bad Token: token is not MAIN."); 273 | 274 | if (voxel.main.chunkContent != 0) 275 | throw new System.Exception("Bad Token: chunk content is " + voxel.main.chunkContent + ", it should be 0."); 276 | 277 | if (reader.PeekChar() == 'P') 278 | { 279 | voxel.pack.name = reader.ReadBytes(4); 280 | if (voxel.pack.name[0] != 'P' || voxel.pack.name[1] != 'A' || voxel.pack.name[2] != 'C' || voxel.pack.name[3] != 'K') 281 | throw new System.Exception("Bad Token: token is not PACK"); 282 | 283 | voxel.pack.chunkContent = reader.ReadInt32(); 284 | voxel.pack.chunkNums = reader.ReadInt32(); 285 | voxel.pack.modelNums = reader.ReadInt32(); 286 | 287 | if (voxel.pack.modelNums == 0) 288 | throw new System.Exception("Bad Token: model nums must be greater than zero."); 289 | } 290 | else 291 | { 292 | voxel.pack.chunkContent = 0; 293 | voxel.pack.chunkNums = 0; 294 | voxel.pack.modelNums = 1; 295 | } 296 | 297 | voxel.chunkChild = new VoxFileChunkChild[voxel.pack.modelNums]; 298 | 299 | for (int i = 0; i < voxel.pack.modelNums; i++) 300 | { 301 | var chunk = new VoxFileChunkChild(); 302 | 303 | chunk.size.name = reader.ReadBytes(4); 304 | chunk.size.chunkContent = reader.ReadInt32(); 305 | chunk.size.chunkNums = reader.ReadInt32(); 306 | chunk.size.x = reader.ReadInt32(); 307 | chunk.size.y = reader.ReadInt32(); 308 | chunk.size.z = reader.ReadInt32(); 309 | 310 | if (chunk.size.name[0] != 'S' || chunk.size.name[1] != 'I' || chunk.size.name[2] != 'Z' || chunk.size.name[3] != 'E') 311 | throw new System.Exception("Bad Token: token is not SIZE"); 312 | 313 | if (chunk.size.chunkContent != 12) 314 | throw new System.Exception("Bad Token: chunk content is " + chunk.size.chunkContent + ", it should be 12."); 315 | 316 | chunk.xyzi.name = reader.ReadBytes(4); 317 | if (chunk.xyzi.name[0] != 'X' || chunk.xyzi.name[1] != 'Y' || chunk.xyzi.name[2] != 'Z' || chunk.xyzi.name[3] != 'I') 318 | throw new System.Exception("Bad Token: token is not XYZI"); 319 | 320 | chunk.xyzi.chunkContent = reader.ReadInt32(); 321 | chunk.xyzi.chunkNums = reader.ReadInt32(); 322 | if (chunk.xyzi.chunkNums != 0) 323 | throw new System.Exception("Bad Token: chunk nums is " + chunk.xyzi.chunkNums + ",i t should be 0."); 324 | 325 | var voxelNums = reader.ReadInt32(); 326 | var voxels = new byte[voxelNums * 4]; 327 | if (reader.Read(voxels, 0, voxels.Length) != voxels.Length) 328 | throw new System.Exception("Failed to read voxels"); 329 | 330 | chunk.xyzi.voxels = new VoxData(voxels, chunk.size.x, chunk.size.y, chunk.size.z); 331 | 332 | voxel.chunkChild[i] = chunk; 333 | } 334 | 335 | if (reader.BaseStream.Position < reader.BaseStream.Length) 336 | { 337 | byte[] palette = reader.ReadBytes(4); 338 | if (palette[0] != 'R' || palette[1] != 'G' || palette[2] != 'B' || palette[3] != 'A') 339 | throw new System.Exception("Bad Token: token is not RGBA"); 340 | 341 | voxel.palette.chunkContent = reader.ReadInt32(); 342 | voxel.palette.chunkNums = reader.ReadInt32(); 343 | 344 | var bytePalette = new byte[voxel.palette.chunkContent]; 345 | reader.Read(bytePalette, 0, voxel.palette.chunkContent); 346 | 347 | voxel.palette.values = new uint[voxel.palette.chunkContent / 4]; 348 | 349 | for (int i = 4; i < bytePalette.Length; i += 4) 350 | voxel.palette.values[i / 4] = BitConverter.ToUInt32(bytePalette, i - 4); 351 | } 352 | else 353 | { 354 | voxel.palette.values = new uint[256]; 355 | _paletteDefault.CopyTo(voxel.palette.values, 0); 356 | } 357 | 358 | return voxel; 359 | } 360 | } 361 | } 362 | 363 | public static Color32[] CreateColor32FromPelatte(uint[] palette) 364 | { 365 | Debug.Assert(palette.Length == 256); 366 | 367 | Color32[] colors = new Color32[256]; 368 | 369 | for (uint j = 0; j < 256; j++) 370 | { 371 | uint rgba = palette[j]; 372 | 373 | Color32 color = new Color32(); 374 | color.r = (byte)((rgba >> 0) & 0xFF); 375 | color.g = (byte)((rgba >> 8) & 0xFF); 376 | color.b = (byte)((rgba >> 16) & 0xFF); 377 | color.a = (byte)((rgba >> 24) & 0xFF); 378 | 379 | colors[j] = color; 380 | } 381 | 382 | return colors; 383 | } 384 | 385 | public static Texture2D CreateTextureFromColor16x16(Color32[] colors) 386 | { 387 | Debug.Assert(colors.Length == 256); 388 | 389 | Texture2D texture = new Texture2D(16, 16, TextureFormat.ARGB32, false, false); 390 | texture.name = "texture"; 391 | texture.SetPixels32(colors); 392 | texture.Apply(); 393 | 394 | return texture; 395 | } 396 | 397 | public static Texture2D CreateTextureFromColor256(Color32[] colors) 398 | { 399 | Debug.Assert(colors.Length == 256); 400 | 401 | Texture2D texture = new Texture2D(256, 1, TextureFormat.ARGB32, false, false); 402 | texture.name = "texture"; 403 | texture.SetPixels32(colors); 404 | texture.Apply(); 405 | 406 | return texture; 407 | } 408 | 409 | public static Texture2D CreateTextureFromPelatte16x16(uint[] palette) 410 | { 411 | Debug.Assert(palette.Length == 256); 412 | return CreateTextureFromColor16x16(CreateColor32FromPelatte(palette)); 413 | } 414 | 415 | public static int CalcFaceCountAsAllocate(VOXModel model, Color32[] palette, ref Dictionary entities) 416 | { 417 | entities.Add("opaque", 0); 418 | 419 | foreach (var it in model.voxels) 420 | { 421 | bool[] visiable = new bool[] { it.faces.left, it.faces.right, it.faces.top, it.faces.bottom, it.faces.front, it.faces.back }; 422 | 423 | int facesCount = 0; 424 | 425 | for (int j = 0; j < 6; j++) 426 | { 427 | if (visiable[j]) 428 | facesCount++; 429 | } 430 | 431 | entities["opaque"] += facesCount; 432 | } 433 | 434 | return entities.Count; 435 | } 436 | 437 | public static GameObject CreateGameObject(string name, VoxData data, Texture2D texture, Color32[] colors, float scale) 438 | { 439 | var cruncher = VOXPolygonCruncher.CalcVoxelCruncher(data, colors, VOXCruncherMode.Greedy); 440 | 441 | var entities = new Dictionary(); 442 | if (CalcFaceCountAsAllocate(cruncher, colors, ref entities) == 0) 443 | throw new System.Exception(name + ": There is no voxel for this file"); 444 | 445 | var model = new GameObject(name); 446 | 447 | foreach (var entity in entities) 448 | { 449 | if (entity.Value == 0) 450 | continue; 451 | 452 | var index = 0; 453 | var allocSize = entity.Value; 454 | 455 | var vertices = new Vector3[allocSize * 4]; 456 | var normals = new Vector3[allocSize * 4]; 457 | var uv = new Vector2[allocSize * 4]; 458 | var triangles = new int[allocSize * 6]; 459 | 460 | bool isTransparent = false; 461 | 462 | foreach (var it in cruncher.voxels) 463 | { 464 | VOXModel.CreateCubeMesh16x16(it, ref vertices, ref normals, ref uv, ref triangles, ref index, scale); 465 | isTransparent |= (colors[it.material].a < 255) ? true : false; 466 | } 467 | 468 | if (triangles.Length > 0) 469 | { 470 | Mesh mesh = new Mesh(); 471 | mesh.name = "mesh"; 472 | mesh.vertices = vertices; 473 | mesh.normals = normals; 474 | mesh.uv = uv; 475 | mesh.triangles = triangles; 476 | 477 | var meshFilter = model.AddComponent(); 478 | var meshRenderer = model.AddComponent(); 479 | 480 | #if UNITY_EDITOR 481 | MeshUtility.Optimize(mesh); 482 | 483 | meshFilter.sharedMesh = mesh; 484 | meshRenderer.sharedMaterial = new Material(Shader.Find("Mobile/Diffuse")); 485 | meshRenderer.sharedMaterial.name = "material"; 486 | meshRenderer.sharedMaterial.mainTexture = texture; 487 | #else 488 | meshFilter.mesh = mesh; 489 | meshRenderer.material = new Material(Shader.Find("Mobile/Diffuse")); 490 | meshRenderer.material.mainTexture = texture; 491 | #endif 492 | } 493 | } 494 | 495 | return model; 496 | } 497 | 498 | public static GameObject LoadVoxelFileAsGameObject(string name, VoxFileData voxel, int lodLevel) 499 | { 500 | Debug.Assert(!String.IsNullOrEmpty(name)); 501 | 502 | GameObject gameObject = new GameObject(); 503 | gameObject.name = name; 504 | gameObject.isStatic = true; 505 | 506 | try 507 | { 508 | var colors = CreateColor32FromPelatte(voxel.palette.values); 509 | var texture = CreateTextureFromColor16x16(colors); 510 | 511 | if (lodLevel <= 1) 512 | { 513 | foreach (var chunk in voxel.chunkChild) 514 | { 515 | var submesh = CreateGameObject("model", chunk.xyzi.voxels, texture, colors, 1); 516 | submesh.transform.parent = gameObject.transform; 517 | } 518 | } 519 | else 520 | { 521 | foreach (var chunk in voxel.chunkChild) 522 | { 523 | for (int lod = 1; lod < lodLevel + 1; lod++) 524 | { 525 | var submesh = CreateGameObject("lod" + (lod - 1), chunk.xyzi.voxels.GetVoxelDataLOD(lod), texture, colors, lod); 526 | submesh.transform.parent = gameObject.transform; 527 | } 528 | 529 | var lodgroup = gameObject.AddComponent(); 530 | var lods = lodgroup.GetLODs(); 531 | 532 | for (int i = 0; i < gameObject.transform.childCount; i++) 533 | lods[i].renderers = new Renderer[] { gameObject.transform.GetChild(i).GetComponent() }; 534 | 535 | lodgroup.SetLODs(lods); 536 | } 537 | } 538 | } 539 | catch (SystemException e) 540 | { 541 | GameObject.DestroyImmediate(gameObject); 542 | throw e; 543 | } 544 | 545 | return gameObject; 546 | } 547 | 548 | public static GameObject LoadVoxelFileAsGameObject(string path) 549 | { 550 | var voxel = VoxFileImport.Load(path); 551 | return LoadVoxelFileAsGameObject(Path.GetFileNameWithoutExtension(path), voxel, 0); 552 | } 553 | 554 | public static GameObject LoadVoxelFileAsGameObjectLOD(string path, int lodLevel) 555 | { 556 | var voxel = VoxFileImport.Load(path); 557 | return LoadVoxelFileAsGameObject(Path.GetFileNameWithoutExtension(path), voxel, lodLevel); 558 | } 559 | 560 | #if UNITY_EDITOR 561 | 562 | public static GameObject LoadVoxelFileAsPrefab(VoxFileData voxel, string name, string path = "Assets/", int lodLevel = 0) 563 | { 564 | Debug.Assert(!String.IsNullOrEmpty(name)); 565 | 566 | GameObject gameObject = null; 567 | 568 | try 569 | { 570 | gameObject = LoadVoxelFileAsGameObject(name, voxel, lodLevel); 571 | 572 | var prefabPath = path + name + ".prefab"; 573 | var prefab = PrefabUtility.CreateEmptyPrefab(prefabPath); 574 | var prefabTextures = new Dictionary(); 575 | 576 | for (int i = 0; i < gameObject.transform.childCount; i++) 577 | { 578 | var subObject = gameObject.transform.GetChild(i); 579 | 580 | var meshFilter = subObject.GetComponent(); 581 | if (meshFilter != null) 582 | { 583 | AssetDatabase.AddObjectToAsset(meshFilter.sharedMesh, prefabPath); 584 | } 585 | 586 | var renderer = subObject.GetComponent(); 587 | if (renderer != null) 588 | { 589 | if (renderer.sharedMaterial != null) 590 | { 591 | AssetDatabase.AddObjectToAsset(renderer.sharedMaterial, prefabPath); 592 | 593 | var textureName = renderer.sharedMaterial.mainTexture.name; 594 | if (!prefabTextures.ContainsKey(textureName)) 595 | { 596 | prefabTextures.Add(textureName, 1); 597 | 598 | AssetDatabase.AddObjectToAsset(renderer.sharedMaterial.mainTexture, prefabPath); 599 | } 600 | } 601 | } 602 | } 603 | 604 | return PrefabUtility.ReplacePrefab(gameObject, prefab, ReplacePrefabOptions.ReplaceNameBased); 605 | } 606 | finally 607 | { 608 | GameObject.DestroyImmediate(gameObject); 609 | } 610 | } 611 | 612 | public static GameObject LoadVoxelFileAsPrefab(string path, string outpath = "Assets/", int lodLevel = 0) 613 | { 614 | var voxel = VoxFileImport.Load(path); 615 | return LoadVoxelFileAsPrefab(voxel, Path.GetFileNameWithoutExtension(path), outpath, lodLevel); 616 | } 617 | 618 | #endif 619 | } 620 | } 621 | } 622 | --------------------------------------------------------------------------------