{"id":19923741,"url":"https://github.com/simplestargame/simplemeshworldsample","last_synced_at":"2025-08-02T20:08:41.910Z","repository":{"id":196330389,"uuid":"695750781","full_name":"simplestargame/SimpleMeshWorldSample","owner":"simplestargame","description":"Simple Mesh World Sample","archived":false,"fork":false,"pushed_at":"2023-09-24T06:38:17.000Z","size":761,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T07:35:17.168Z","etag":null,"topics":["mesh","unity"],"latest_commit_sha":null,"homepage":"","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-24T05:23:21.000Z","updated_at":"2025-02-25T15:21:28.000Z","dependencies_parsed_at":"2023-09-24T14:45:58.107Z","dependency_job_id":null,"html_url":"https://github.com/simplestargame/SimpleMeshWorldSample","commit_stats":null,"previous_names":["simplestargame/simplemeshworldsample"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/simplestargame/SimpleMeshWorldSample","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshWorldSample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshWorldSample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshWorldSample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshWorldSample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simplestargame","download_url":"https://codeload.github.com/simplestargame/SimpleMeshWorldSample/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshWorldSample/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268448221,"owners_count":24251999,"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","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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:17.573Z","updated_at":"2025-08-02T20:08:41.881Z","avatar_url":"https://github.com/simplestargame.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Runtime Mesh World Sample\n\nWelcome to the Runtime Mesh World Sample repository!\nThis repository is a script sample that takes a three-dimensional byte array as input and places cubes on non-zero elements to construct a building world.  \n\n![サンプルシーン](main.png)\n\n## Features\n- **Optimization:** The use of the Job System and async Task syntax is employed to maximize background multithreading, minimizing the load on the main thread.\n\n## Getting Started\nFollow these steps to try out the 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.\n1. Pressing the space key triggers world generation with shifted positions.\n\n# ランタイム メッシュワールドサンプル\n\n**ランタイム メッシュワールドサンプル** リポジトリへようこそ！  \nこのリポジトリは三次元 byte 配列を入力に、0 でない要素にキューブを配置して、建築世界を構築するスクリプトサンプルです。  \n\n## 特徴\n\n- **最適化:** Job System と async Task 構文を用いて極力バックグラウンドマルチスレッド化がほどこされ、メインスレッドに負荷を与えません。\n\n## 始め方\n\n以下の手順に従って、サンプルをお試しできます。\n\n1. このリポジトリをクローンします。\n1. Unity Hub 経由でクローンしたフォルダからプロジェクトを開きます。\n1. サンプルシーンを開いて再生することでメッシュの結合サンプル処理を理解します。\n1. スペースキーを押すと、位置をずらした世界生成が行われます\n\n## How to use\n\n```csharp\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Threading.Tasks;\nusing Unity.Collections;\nusing Unity.Jobs;\nusing Unity.Mathematics;\nusing UnityEngine;\nusing UnityEngine.Rendering;\n\nnamespace SimplestarGame\n{\n    public class SimpleMeshWorldSample : MonoBehaviour\n    {\n        [SerializeField] string meshFileName = \"BasicCube.caw\";\n        [SerializeField] string dataFileName = \"world000.gz\";\n        [Range(0, 3)][SerializeField] int chunkCount = 0;\n        [SerializeField] Material material;\n        [SerializeField] Transform[] parents;\n\n        int paretentIndex = 0;\n        const int dataEdgeCubeCount = 256;\n\n        class WorldData\n        {\n            public NativeArray\u003cbyte\u003e voxelData;\n            public NativeArray\u003cXYZ\u003e xyz;\n            public NativeArray\u003cint\u003e countOffsets;\n        }\n\n        void Awake()\n        {\n            Application.targetFrameRate = 60;\n            GraphicsSettings.useScriptableRenderPipelineBatching = true;\n        }\n\n        async void Start()\n        {\n            // キューブのメッシュデータソースを読み込み\n            var cubeData = await Task.Run(() =\u003e CAWFile.ReadCAWFile(Path.Combine(Application.streamingAssetsPath, this.meshFileName)));\n            // ワールドのボクセルデータ読み込み\n            var compressedData = await Task.Run(() =\u003e File.ReadAllBytes(Path.Combine(Application.streamingAssetsPath, this.dataFileName)));\n            // ワールドのデータ解凍\n            var worldDataBytes = await Task.Run(() =\u003e GZipCompressor.Unzip(compressedData));\n            // Job用NativeArray確保\n            var edgeCubeCount = Mathf.RoundToInt(16 * Mathf.Pow(2, this.chunkCount));\n            var worldData = await this.AllocateDataAsync(worldDataBytes, edgeCubeCount);\n            // Meshオブジェクト作成\n            List\u003cGameObject\u003e meshObjectList = new List\u003cGameObject\u003e();\n            var edgeChunkCount = dataEdgeCubeCount / edgeCubeCount;\n            for (int chunkX = 0; chunkX \u003c edgeChunkCount; chunkX++)\n            {\n                for (int chunkY = 0; chunkY \u003c edgeChunkCount; chunkY++)\n                {\n                    for (int chunkZ = 0; chunkZ \u003c edgeChunkCount; chunkZ++)\n                    {\n                        var chunkInt3 = new Vector3Int(chunkX, chunkY, chunkZ);\n                        meshObjectList.Add(await this.CreateChunkObjectAsync(worldData, cubeData, chunkInt3, edgeCubeCount));\n                    }\n                }\n            }\n            // 確保したものを開放\n            worldData.countOffsets.Dispose();\n            worldData.xyz.Dispose();\n            worldData.voxelData.Dispose();\n            cubeData.fileVertexData.Dispose();\n            cubeData.vertexCounts.Dispose();\n            // BakeMesh\n            await this.BakeMeshAsync(meshObjectList);\n            // 配置換え\n            this.paretentIndex++;\n            if (this.parents.Length == this.paretentIndex)\n            {\n                this.paretentIndex = 0;\n            }\n        }\n\n        void Update()\n        {\n            // Space キーを押すと、配置換えしてワールドを構築し直します\n            if (Input.GetKeyDown(KeyCode.Space))\n            {\n                this.Start();\n            }\n        }\n\n        /// \u003csummary\u003e\n        /// 計算で毎回使うバッファ、使いまわすために最初に確保\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"worldDataBytes\"\u003e世界データ\u003c/param\u003e\n        /// \u003cparam name=\"edgeCubeCount\"\u003eチャンクの辺キューブ数\u003c/param\u003e\n        /// \u003creturns\u003e確保したバッファ\u003c/returns\u003e\n        async Task\u003cWorldData\u003e AllocateDataAsync(byte[] worldDataBytes, int edgeCubeCount)\n        {\n            return await Task.Run(() =\u003e {\n                var voxelData = new NativeArray\u003cbyte\u003e(worldDataBytes, Allocator.Persistent);\n                var xyz = new NativeArray\u003cXYZ\u003e(edgeCubeCount * edgeCubeCount * edgeCubeCount, Allocator.Persistent);\n                var countOffsets = new NativeArray\u003cint\u003e(xyz.Length, Allocator.Persistent);\n                for (byte x = 0; x \u003c edgeCubeCount; x++)\n                {\n                    for (byte y = 0; y \u003c edgeCubeCount; y++)\n                    {\n                        for (byte z = 0; z \u003c edgeCubeCount; z++)\n                        {\n                            var index = x * edgeCubeCount * edgeCubeCount + y * edgeCubeCount + z;\n                            xyz[index] = new XYZ { x = x, y = y, z = z };\n                            countOffsets[index] = 0;\n                        }\n                    }\n                }\n                return new WorldData { voxelData = voxelData, xyz = xyz, countOffsets = countOffsets };\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        async Task\u003cMesh\u003e CreateMesh(Mesh.MeshDataArray meshDataArray, int vertexIndexCount, float3x2 bounds)\n        {\n            var newMesh = new Mesh();\n            newMesh.name = \"CustomLayoutMesh\";\n            var meshBounds = newMesh.bounds = new Bounds((bounds.c0 + bounds.c1) * 0.5f, bounds.c1 - bounds.c0);\n            await Task.Run(() =\u003e {\n                meshDataArray[0].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 = meshBounds\n                }, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers);\n            });\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=\"worldData\"\u003eワールド全体データ\u003c/param\u003e\n        /// \u003cparam name=\"cubeData\"\u003eキューブの頂点情報\u003c/param\u003e\n        /// \u003cparam name=\"chunkOffset\"\u003eワールド内のローカル塊オフセット\u003c/param\u003e\n        /// \u003cparam name=\"edgeCubeCount\"\u003e塊の辺キューブ数\u003c/param\u003e\n        /// \u003creturns\u003e作成したゲームオブジェクト\u003c/returns\u003e\n        async Task\u003cGameObject\u003e CreateChunkObjectAsync(\n            WorldData worldData,\n            CAWFile.CubeData cubeData,\n            Vector3Int chunkOffset,\n            int edgeCubeCount)\n        {\n            // カウント\n            await Task.Run(() =\u003e {\n                var countJobHandle = new CountChunkVertexJob()\n                {\n                    vertexCounts = cubeData.vertexCounts,\n                    voxelData = worldData.voxelData,\n                    xyz = worldData.xyz,\n                    results = worldData.countOffsets,\n                    heightDepth = edgeCubeCount * edgeCubeCount,\n                    width = edgeCubeCount,\n                    height = edgeCubeCount,\n                    depth = edgeCubeCount,\n                    chunkOffset = chunkOffset,\n                    dataEdgeCubeCount = dataEdgeCubeCount,\n                }.Schedule(worldData.xyz.Length, 8);\n                countJobHandle.Complete(); });\n            // 集計\n            var vertexIndexCount = await Task.Run(() =\u003e\n            {\n                int vertexIndexCount = 0;\n                for (int index = 0; index \u003c worldData.countOffsets.Length; index++)\n                {\n                    var counts = worldData.countOffsets[index];\n                    worldData.countOffsets[index] = vertexIndexCount;\n                    vertexIndexCount += counts;\n                }\n                return vertexIndexCount;\n            });\n            if (vertexIndexCount == 0)\n            {\n                return null;\n            }\n            // 確保\n            var meshDataArray = Mesh.AllocateWritableMeshData(1);\n            Mesh.MeshData meshData = meshDataArray[0];\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            // インデックス書き込み\n            var indexJobHandle = new WriteIndexDataJob() { indexData = indexData }.Schedule(indexData.Length, 128);\n            indexJobHandle.Complete();\n            // 頂点データ書き込み\n            NativeArray\u003cCustomVertexLayout\u003e vertexData = meshData.GetVertexData\u003cCustomVertexLayout\u003e(stream: 0);\n            await Task.Run(() =\u003e {\n                var writeJobHandle = new WriteChunkDataJob()\n                {\n                    vertexCounts = cubeData.vertexCounts,\n                    voxelData = worldData.voxelData,\n                    xyz = worldData.xyz,\n                    countOffsets = worldData.countOffsets,\n                    width = edgeCubeCount,\n                    height = edgeCubeCount,\n                    depth = edgeCubeCount,\n                    fileVertexData = cubeData.fileVertexData,\n                    vertexData = vertexData,\n                    chunkOffset = chunkOffset,\n                    dataEdgeCubeCount = dataEdgeCubeCount,\n                }.Schedule(worldData.xyz.Length, 8);\n                writeJobHandle.Complete(); });\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(edgeCubeCount + 0.5f, edgeCubeCount + 0.5f, edgeCubeCount + 0.5f));\n            // オブジェクト作成\n            Mesh newMesh = await this.CreateMesh(meshDataArray, vertexIndexCount, bounds);\n            vertexData.Dispose();\n            indexData.Dispose();\n            GameObject newGameObject = new GameObject(\"TestCubeMeshObject\");\n            newGameObject.transform.SetParent(this.parents[this.paretentIndex], false);\n            newGameObject.transform.localPosition = chunkOffset * edgeCubeCount;\n            newGameObject.isStatic = true;\n            newGameObject.AddComponent\u003cMeshFilter\u003e().sharedMesh = newMesh;\n            newGameObject.AddComponent\u003cMeshRenderer\u003e().sharedMaterial = this.material;\n            return newGameObject;\n        }\n\n        /// \u003csummary\u003e\n        /// MeshCollider 作成\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"meshObjectList\"\u003eMeshFilter の sharedMesh を入力に MeshCollider を計算します\u003c/param\u003e\n        /// \u003creturns\u003eTask\u003c/returns\u003e\n        async Task BakeMeshAsync(List\u003cGameObject\u003e meshObjectList)\n        {\n            NativeArray\u003cint\u003e meshIds = new NativeArray\u003cint\u003e(meshObjectList.Count, Allocator.Persistent);\n            var meshIdx = 0;\n            foreach (var meshObject in meshObjectList)\n            {\n                var mesh = meshObject.GetComponent\u003cMeshFilter\u003e().sharedMesh;\n                meshIds[meshIdx++] = mesh.GetInstanceID();\n            }\n            await Task.Run(() =\u003e\n            {\n                var bakeMeshJob = new BakeMeshJob(meshIds);\n                var bakeMeshJobHandle = bakeMeshJob.Schedule(meshIds.Length, 1);\n                bakeMeshJobHandle.Complete();\n                meshIds.Dispose();\n            });\n            // Set MeshCollider\n            foreach (var meshObject in meshObjectList)\n            {\n                meshObject.AddComponent\u003cMeshCollider\u003e().sharedMesh = meshObject.GetComponent\u003cMeshFilter\u003e().sharedMesh;\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%2Fsimplemeshworldsample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimplestargame%2Fsimplemeshworldsample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplestargame%2Fsimplemeshworldsample/lists"}