{"id":19923754,"url":"https://github.com/simplestargame/simplemeshchunksample","last_synced_at":"2025-09-19T04:32:31.568Z","repository":{"id":193802854,"uuid":"689521764","full_name":"simplestargame/SimpleMeshChunkSample","owner":"simplestargame","description":"Simple Mesh Chunk Sample","archived":false,"fork":false,"pushed_at":"2023-09-10T05:06:37.000Z","size":1905,"stargazers_count":36,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-12T22:15:13.084Z","etag":null,"topics":["mesh","unity"],"latest_commit_sha":null,"homepage":"https://twitter.com/lpcwstr","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simplestargame.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-09-10T04:28:44.000Z","updated_at":"2024-03-30T21:24:59.000Z","dependencies_parsed_at":"2023-09-10T05:39:12.430Z","dependency_job_id":null,"html_url":"https://github.com/simplestargame/SimpleMeshChunkSample","commit_stats":null,"previous_names":["simplestargame/simplemeshchunksample"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshChunkSample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshChunkSample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshChunkSample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshChunkSample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simplestargame","download_url":"https://codeload.github.com/simplestargame/SimpleMeshChunkSample/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233550431,"owners_count":18692843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mesh","unity"],"created_at":"2024-11-12T22:15:21.720Z","updated_at":"2025-09-19T04:32:26.089Z","avatar_url":"https://github.com/simplestargame.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Runtime Mesh Merging Sample\n\nWelcome to the Runtime Mesh Merging Sample repository!\nThis project is a sample demonstrating how to merge cube meshes into a single massive mesh without causing significant disruption to the main thread.  \n\n![サンプルシーン](main.png)\n\n## Features\n- **Create Mesh Data Files from Any Mesh:** Included is a tool that allows you to save mesh data in a dedicated format by providing a MeshFilter.  \n- **Face Culling:** The dedicated mesh data files contain vertex data divided into six faces of the cube and an other regions to prevent unnecessary faces from being built when merged.  \n- **Optimization:** The Job System is employed to minimize background multithreading, ensuring minimal impact on the main thread.  \n\n## Getting Started\nFollow these steps to try out the Runtime Mesh Merging Sample:\n\n1. Clone this repository.\n1. Open the project from the cloned folder using Unity Hub.\n1. Play the sample scene to understand how mesh merging is performed.\n\n# ランタイム メッシュ結合サンプル\n\n**ランタイム メッシュ結合サンプル** リポジトリへようこそ！  \nこのプロジェクトは、メインスレッドに極力迷惑をかけずにキューブメッシュを結合して  \n巨大な一つのメッシュを結合して作成するサンプルです。\n\n## 特徴\n\n- **任意のメッシュからメッシュデータファイルを作成:** MeshFilterを渡せば、専用のメッシュデータファイル形式で保存するツールが付属しています。\n- **不要面除去:** 専用メッシュデータファイルにはキューブの6面とそれ以外の領域に分かれた頂点データが格納されています、結合したときに  \n不要な面が構築されないようにカリングする仕組みが導入されています。\n- **最適化:** Job System を用いて極力バックグラウンドマルチスレッド化がほどこされ、メインスレッドに負荷を与えません。\n\n## 始め方\n\n以下の手順に従って、ランタイム メッシュ結合サンプルをお試しできます。\n\n1. このリポジトリをクローンします。\n1. Unity Hub 経由でクローンしたフォルダからプロジェクトを開きます。\n1. サンプルシーンを開いて再生することでメッシュの結合サンプル処理を理解します。\n\n## How to use\n\n```csharp\nusing System;\nusing System.Collections;\nusing System.IO;\nusing System.Threading.Tasks;\nusing Unity.Collections;\nusing Unity.Collections.LowLevel.Unsafe;\nusing Unity.Jobs;\nusing Unity.Mathematics;\nusing UnityEngine;\nusing UnityEngine.Rendering;\n\nnamespace SimplestarGame\n{\n    public struct XYZ\n    {\n        public int x;\n        public int y;\n        public int z;\n    }\n\n    /// \u003csummary\u003e\n    /// チャンクメッシュを作成するサンプル\n    /// Corutine の yield return null にて、メインスレッドを休めながら\n    /// 各種データ作成や書き込み処理を Job 化して、完了を待つ形で実装している\n    /// ゲームを実行するとメッシュオブジェクトが作成される形\n    /// \u003c/summary\u003e\n    internal class MeshChunkSample : MonoBehaviour\n    {\n        public string fileName = \"InsetCube.caw\";\n        public Material material;\n        public int width = 32;\n        public int height = 32;\n        public int depth = 32;\n        [Range(0, 1)] public float densityThreshold = 0.25f;\n\n        void Awake()\n        {\n            Application.targetFrameRate = 60;\n        }\n\n        void Start()\n        {\n            StartCoroutine(this.CoCreateMeshChunk());\n        }\n\n        IEnumerator CoCreateMeshChunk()\n        {\n            var voxelData = new NativeArray\u003cbyte\u003e(this.width * this.height * this.depth, Allocator.Persistent);\n            var voxcelDataJobHandle = this.GenerateVoxelData(voxelData, this.width, this.height, this.depth);\n            while (!voxcelDataJobHandle.IsCompleted)\n            {\n                yield return null;\n            }\n            voxcelDataJobHandle.Complete();\n            var meshDataArray = Mesh.AllocateWritableMeshData(1);\n            var task = Task.Run(() =\u003e ReadCAWFileTask());\n            while (!task.IsCompleted)\n            {\n                yield return null;\n            }\n            task.Wait();\n            if (task.Result == null)\n            {\n                yield break;\n            }\n            var result = task.Result;\n\n            Mesh.MeshData meshData = meshDataArray[0];\n            var xyz = new NativeArray\u003cXYZ\u003e(this.width * this.height * this.depth, Allocator.Persistent);\n            var countOffsets = new NativeArray\u003cint\u003e(xyz.Length, Allocator.Persistent);\n            for (int x = 0; x \u003c width; x++)\n            {\n                for (int y = 0; y \u003c height; y++)\n                {\n                    for (int z = 0; z \u003c depth; z++)\n                    {\n                        var index = x * this.height * this.depth + y * this.depth + z;\n                        xyz[index] = new XYZ { x = x, y = y, z = z };\n                        countOffsets[index] = 0;\n                    }\n                }\n            }\n            var countJobHandle = new CountChunkVertexJob()\n            {\n                vertexCounts = result.vertexCounts,\n                voxelData = voxelData,\n                xyz = xyz,\n                results = countOffsets,\n                heightDepth = this.height * this.depth,\n                height = this.height,\n                depth = this.depth\n            }.Schedule(xyz.Length, 8);\n            while (!countJobHandle.IsCompleted)\n            {\n                yield return null;\n            }\n            countJobHandle.Complete();\n            int vertexIndexCount = 0;\n            for (int index = 0; index \u003c countOffsets.Length; index++)\n            {\n                var counts = countOffsets[index];\n                countOffsets[index] = vertexIndexCount;\n                vertexIndexCount += counts;\n            }\n\n            meshData.subMeshCount = 1;\n            meshData.SetVertexBufferParams(vertexIndexCount, CustomLayoutMesh.VERTEX_ATTRIBUTE_DESCRIPTORS);\n            meshData.SetIndexBufferParams(vertexIndexCount, IndexFormat.UInt32);\n            NativeArray\u003cint\u003e indexData = meshData.GetIndexData\u003cint\u003e();\n            NativeArray\u003cCustomVertexLayout\u003e vertexData = meshData.GetVertexData\u003cCustomVertexLayout\u003e(stream: 0);\n            var writeJobHandle = new WriteChunkDataJob()\n            {\n                vertexCounts = result.vertexCounts,\n                voxelData = voxelData,\n                xyz = xyz,\n                countOffsets = countOffsets,\n                heightDepth = this.height * this.depth,\n                height = this.height,\n                depth = this.depth,\n                fileVertexData = result.fileVertexData,\n                indexData = indexData,\n                vertexData = vertexData\n            }.Schedule(xyz.Length, 8);\n            while (!writeJobHandle.IsCompleted)\n            {\n                yield return null;\n            }\n            writeJobHandle.Complete();\n            countOffsets.Dispose();\n            xyz.Dispose();\n\n            float3x2 bounds = new float3x2();\n            bounds.c0 = math.min(bounds.c0, new float3(-0.5f, -0.5f, -0.5f));\n            bounds.c1 = math.max(bounds.c1, new float3(this.width + 0.5f, this.height + 0.5f, this.depth + 0.5f));\n\n            result.fileVertexData.Dispose();\n            result.vertexCounts.Dispose();\n            voxelData.Dispose();\n            Mesh newMesh = CreateMesh(ref meshDataArray, vertexIndexCount, bounds);\n            vertexData.Dispose();\n            indexData.Dispose();\n            GameObject newGameObject = new GameObject(\"TestCubeMeshObject\");\n            newGameObject.AddComponent\u003cMeshFilter\u003e().sharedMesh = newMesh;\n            newGameObject.AddComponent\u003cMeshRenderer\u003e().sharedMaterial = this.material;\n            NativeArray\u003cint\u003e meshIds = new NativeArray\u003cint\u003e(1, Allocator.Persistent);\n            meshIds[0] = newMesh.GetInstanceID();\n            var bakeMeshJob = new BakeMeshJob(meshIds);\n            var bakeMeshJobHandle = bakeMeshJob.Schedule(meshIds.Length, 1);\n            while (!bakeMeshJobHandle.IsCompleted)\n            {\n                yield return null;\n            }\n            bakeMeshJobHandle.Complete();\n            meshIds.Dispose();\n            newGameObject.AddComponent\u003cMeshCollider\u003e().sharedMesh = newMesh;\n        }\n\n        class TaskResult\n        {\n            public NativeArray\u003cint\u003e vertexCounts;\n            public NativeArray\u003cCustomVertexLayout\u003e fileVertexData;\n        }\n\n        TaskResult ReadCAWFileTask()\n        {\n            NativeArray\u003cint\u003e vertexCounts;\n            NativeArray\u003cCustomVertexLayout\u003e fileVertexData;\n            if (!UnsafeReadCAWFile(Path.Combine(Application.streamingAssetsPath, this.fileName), out vertexCounts, out fileVertexData))\n            {\n                return null;\n            }\n            return new TaskResult\n            {\n                vertexCounts = vertexCounts,\n                fileVertexData = fileVertexData\n            };\n        }\n\n        /// \u003csummary\u003e\n        /// メッシュの作成\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"meshDataArray\"\u003eデータ設定済みメッシュデータ\u003c/param\u003e\n        /// \u003cparam name=\"vertexIndexCount\"\u003eインデックス数=頂点数\u003c/param\u003e\n        /// \u003cparam name=\"bounds\"\u003eバウンディングボックス情報\u003c/param\u003e\n        /// \u003creturns\u003eメッシュ\u003c/returns\u003e\n        static Mesh CreateMesh(ref Mesh.MeshDataArray meshDataArray, int vertexIndexCount, float3x2 bounds)\n        {\n            Mesh.MeshData meshData = meshDataArray[0];\n            var newMesh = new Mesh();\n            newMesh.name = \"CustomLayoutMesh\";\n            newMesh.bounds = new Bounds((bounds.c0 + bounds.c1) * 0.5f, bounds.c1 - bounds.c0);\n            meshData.SetSubMesh(0, new SubMeshDescriptor\n            {\n                topology = MeshTopology.Triangles,\n                vertexCount = vertexIndexCount,\n                indexCount = vertexIndexCount,\n                baseVertex = 0,\n                firstVertex = 0,\n                indexStart = 0,\n                bounds = newMesh.bounds\n            }, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers);\n            Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, new[] { newMesh },\n                MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers);\n            return newMesh;\n        }\n\n        /// \u003csummary\u003e\n        /// ファイルから頂点データの読み込み\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"filePath\"\u003eファイルパス\u003c/param\u003e\n        /// \u003cparam name=\"vertexCounts\"\u003e成功時、Disposeする必要あり\u003c/param\u003e\n        /// \u003cparam name=\"vertexData\"\u003e成功時、Disposeする必要あり\u003c/param\u003e\n        /// \u003creturns\u003e成功時true\u003c/returns\u003e\n        static unsafe bool UnsafeReadCAWFile(string filePath, out NativeArray\u003cint\u003e vertexCounts, out NativeArray\u003cCustomVertexLayout\u003e vertexData)\n        {\n            vertexCounts = new NativeArray\u003cint\u003e(7, Allocator.Persistent);\n            int fileSize = 0;\n            try\n            {\n                fileSize = (int)new FileInfo(filePath).Length;\n            }\n            catch (FileNotFoundException e)\n            {\n                Debug.LogError($\"指定したファイルがありません: {e.Message}\");\n                vertexData = new NativeArray\u003cCustomVertexLayout\u003e(1, Allocator.Persistent);\n                vertexData.Dispose();\n                vertexCounts.Dispose();\n                return false;\n            }\n            var headerByteCount = 4 + 7 * sizeof(int);\n            var vertexCount = (fileSize - headerByteCount) / sizeof(CustomVertexLayout);\n            var magicCode = new NativeArray\u003cbyte\u003e(4, Allocator.Persistent);\n            vertexData = new NativeArray\u003cCustomVertexLayout\u003e(vertexCount, Allocator.Persistent);\n            var success = false;\n            using (BinaryReader reader = new BinaryReader(File.Open(filePath, FileMode.Open)))\n            {\n                reader.Read(new Span\u003cbyte\u003e(magicCode.GetUnsafePtr(), magicCode.Length));\n                if (magicCode[0] == 'c' \u0026\u0026 magicCode[1] == 'a' \u0026\u0026 magicCode[2] == 'w')\n                {\n                    reader.Read(new Span\u003cbyte\u003e(vertexCounts.GetUnsafePtr(), vertexCounts.Length * sizeof(int)));\n                    reader.Read(new Span\u003cbyte\u003e(vertexData.GetUnsafePtr(), vertexData.Length * sizeof(CustomVertexLayout)));\n                    success = true;\n                }\n                else\n                {\n                    vertexData.Dispose();\n                    vertexCounts.Dispose();\n                }\n            }\n            magicCode.Dispose();\n            return success;\n        }\n\n        /// \u003csummary\u003e\n        /// ボクセルデータの作成\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"voxelData\"\u003e0が空気255が充填を意味する、関数外で width * height * depth サイズで確保済み\u003c/param\u003e\n        /// \u003cparam name=\"width\"\u003e幅\u003c/param\u003e\n        /// \u003cparam name=\"height\"\u003e高さ\u003c/param\u003e\n        /// \u003cparam name=\"depth\"\u003e奥行き\u003c/param\u003e\n        /// \u003creturns\u003e作成ジョブのハンドル\u003c/returns\u003e\n        JobHandle GenerateVoxelData(NativeArray\u003cbyte\u003e voxelData, int width, int height, int depth)\n        {\n            return new VoxelGenerationJob\n            {\n                VoxelData = voxelData,\n                Width = width,\n                Height = height,\n                Depth = depth,\n                DensityThreshold = densityThreshold\n            }.Schedule(voxelData.Length, 64);\n        }\n    }\n}\n\n```\n\n## License\nThis project is licensed under the MIT License.\n\n## Contribution\nIf you find a bug, have an enhancement idea, or want to contribute in any other way, please open an issue or submit a pull request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplestargame%2Fsimplemeshchunksample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimplestargame%2Fsimplemeshchunksample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplestargame%2Fsimplemeshchunksample/lists"}