{"id":19923742,"url":"https://github.com/simplestargame/simplemeshminingsample","last_synced_at":"2025-05-03T07:31:14.698Z","repository":{"id":199176431,"uuid":"702286517","full_name":"simplestargame/SimpleMeshMiningSample","owner":"simplestargame","description":"Simple Mesh Mining Sample","archived":false,"fork":false,"pushed_at":"2023-10-09T08:34:17.000Z","size":1885,"stargazers_count":15,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-07T13:11:12.864Z","etag":null,"topics":["mesh","minecraft","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-10-09T02:36:58.000Z","updated_at":"2025-02-02T05:58:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"077157cf-9e0f-406c-ac9f-aca6a0339723","html_url":"https://github.com/simplestargame/SimpleMeshMiningSample","commit_stats":null,"previous_names":["simplestargame/simplemeshminingsample"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshMiningSample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshMiningSample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshMiningSample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplestargame%2FSimpleMeshMiningSample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simplestargame","download_url":"https://codeload.github.com/simplestargame/SimpleMeshMiningSample/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252156867,"owners_count":21703366,"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","minecraft","unity"],"created_at":"2024-11-12T22:15:17.585Z","updated_at":"2025-05-03T07:31:12.959Z","avatar_url":"https://github.com/simplestargame.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Simple Mesh Mining Sample\n\n[Click Demo (WebGL)](https://d1nl077dbzu98o.cloudfront.net/MeshMiningSample2/index.html)\n\nWelcome to the Simple Mesh Mining Sample repository!  \nThis repository is a sample where the surrounding area, when clicked, undergoes mesh subdivision and is cubified to demonstrate physical behavior. The key feature here is the use of large merged meshes displayed in the distance, which undergo spatial downsampling to reduce memory usage.\n\n![Sample Scene](main.png)\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.\n1. Click the screen.\n\n# ランタイムメッシュ採掘サンプル\n\n**ランタイムメッシュ採掘サンプル** リポジトリへようこそ！  \nこのリポジトリはクリックした周辺をメッシュ細分化し、キューブ化したものが物理挙動するサンプルです。  \n遠方に表示される巨大なマージメッシュが空間低解像度化してメモリ使用量を削減している点がポイントです。    \n\n## 始め方\n\n以下の手順に従って、サンプルをお試しできます。\n\n1. このリポジトリをクローンします。\n1. Unity Hub 経由でクローンしたフォルダからプロジェクトを開きます。\n1. サンプルシーンを開いて再生します。\n1. 画面をクリックします。\n\n## How to use\n\n```csharp\nusing System.Collections;\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 IndexXYZ\n    {\n        public NativeArray\u003cXYZ\u003e xyz;\n        public NativeArray\u003cint\u003e countOffsets;\n    }\n\n    public class SimpleMeshMiningSample : MonoBehaviour\n    {\n        [SerializeField] string meshFileName = \"BasicCube.caw\";\n        [SerializeField] string dataFileName = \"world000.gz\";\n        [SerializeField] Material material;\n        [SerializeField] WebGLUtil webGLUtil;\n        [SerializeField] Transform mainCamera;\n        [SerializeField] Transform[] parents;\n        List\u003cVector3\u003e interactPoints = new List\u003cVector3\u003e();\n        [SerializeField] Color[] levelColors = new Color[] {\n            Color.white * 0.2f,\n            Color.white * 0.3f,\n            Color.white * 0.4f,\n            Color.white * 0.5f,\n            Color.white * 0.6f,\n            Color.white * 0.7f,\n            Color.white * 0.8f,\n            Color.white * 0.9f,\n            Color.white * 0.95f,\n            Color.white };\n\n        /// \u003csummary\u003e\n        /// ワールドを構成する最大粒度チャンク\n        /// \u003c/summary\u003e\n        List\u003cSimpleMeshChunk\u003e chunks = new List\u003cSimpleMeshChunk\u003e();\n        SimpleMeshChunkBuilder chunkBuilder;\n\n        CAWFile.CubeData cubeData;\n        NativeArray\u003cbyte\u003e voxelData;\n        List\u003cIndexXYZ\u003e levelDataList;\n\n        Task createMeshesTask = null;\n        bool cancelCreateMeshes = false;\n\n        float timer = 0f;\n        float interval = 1f; // 秒\n        float gridSize = 128;\n        Vector3Int cameraGridPosition;\n\n        void Awake()\n        {\n            Application.targetFrameRate = 60;\n            GraphicsSettings.useScriptableRenderPipelineBatching = true;\n        }\n\n        IEnumerator Start()\n        {\n            yield return this.webGLUtil.ReadFile(Path.Combine(Application.streamingAssetsPath, this.meshFileName));\n            var cawData = this.webGLUtil.GetData();\n            yield return this.webGLUtil.ReadFile(Path.Combine(Application.streamingAssetsPath, this.dataFileName));\n            byte[] worldDataBytes = this.webGLUtil.GetData();\n#if UNITY_EDITOR || !UNITY_WEBGL\n            worldDataBytes = GZipCompressor.Decompress(worldDataBytes);\n#endif\n            this.Start2(cawData, worldDataBytes);\n        }\n\n        async void Start2(byte[] cawData, byte[] worldDataBytes)\n        {\n            this.cubeData = CAWFile.GetCAWFile(cawData);\n            this.voxelData = await Task.Run(() =\u003e new NativeArray\u003cbyte\u003e(worldDataBytes, Allocator.Persistent));\n            // Job用NativeArray確保\n            this.levelDataList = await AllocateDataAsync();\n            // ビルダーを初期化\n            this.chunkBuilder = new SimpleMeshChunkBuilder(this.cubeData, this.voxelData, this.levelDataList, this.material, this.levelColors);\n\n            // チャンクオブジェクトを作成、リスト化\n            this.chunks.Clear();\n            foreach (var parent in this.parents)\n            {\n                var myChunkLevel = ChunkLevel.Cube256;\n                var edgeCubes = SimpleMeshChunk.levelEdgeCubes[(int)myChunkLevel];\n                for (int chunkX = 0; chunkX \u003c 1; chunkX++)\n                {\n                    for (int chunkY = 0; chunkY \u003c 1; chunkY++)\n                    {\n                        for (int chunkZ = 0; chunkZ \u003c 1; chunkZ++)\n                        {\n                            var chunkOffset = new Vector3Int(chunkX, chunkY, chunkZ) * edgeCubes;\n                            var newGameObject = new GameObject($\"{chunkX}, {chunkY}, {chunkZ}\");\n                            newGameObject.transform.SetParent(parent.transform, false);\n                            newGameObject.transform.localPosition = chunkOffset;\n                            newGameObject.isStatic = true;\n                            var chunk = newGameObject.AddComponent\u003cSimpleMeshChunk\u003e();\n                            chunk.SetData(myChunkLevel, chunkOffset);\n                            this.chunks.Add(chunk);\n                        }\n                    }\n                }\n            }\n            this.createMeshesTask = this.CreateWorldMeshes(this.chunks, new Vector3[0]);\n        }\n\n        void OnDestroy()\n        {\n            // 確保したものを開放\n            foreach (var levelData in this.levelDataList)\n            {\n                levelData.countOffsets.Dispose();\n                levelData.xyz.Dispose();\n            }\n            this.voxelData.Dispose();\n            this.cubeData.vertexData.Dispose();\n            this.cubeData.vertexCounts.Dispose();\n        }\n\n        async void Update()\n        {\n            // タイマーを更新\n            this.timer += Time.deltaTime;\n\n            // タイマーが指定した間隔を超えた場合に処理を実行\n            if (this.timer \u003e= this.interval)\n            {\n                var lastGridPosition = this.cameraGridPosition;\n                this.cameraGridPosition = this.CalculateGridPosition(this.mainCamera.position);\n                if (lastGridPosition != this.cameraGridPosition)\n                {\n                    await this.ReBuildMesh();\n                }\n                // タイマーをリセット\n                this.timer = 0f;\n            }\n\n            // Space キーを押すと、興味ポイント付近のメッシュを再構築\n            if (Input.GetKeyDown(KeyCode.Space))\n            {\n                await this.ReBuildMesh();\n            }\n\n            // クリックまたはタップされたら交点を計算する\n            if (Input.GetMouseButtonDown(0)) // 左クリックで交点を計算\n            {\n                // マウスポインターの位置を取得\n                Vector3 mousePosition = Input.mousePosition;\n                var mainCamera = this.mainCamera.GetComponent\u003cCamera\u003e();\n\n                // マウスポインターの位置をカメラからの距離に変換\n                mousePosition.z = mainCamera.nearClipPlane;\n\n                // マウスポインターの位置をワールド座標に変換\n                Vector3 worldPosition = mainCamera.ScreenToWorldPoint(mousePosition);\n\n                // カメラからマウスポインターの位置に向かうレイを作成\n                Ray ray = new Ray(mainCamera.transform.position, worldPosition - mainCamera.transform.position);\n                if (Physics.Raycast(ray, out RaycastHit hit))\n                {\n                    var p = hit.point + ray.direction * 0.5f;\n                    this.interactPoints.Add(new Vector3(Mathf.RoundToInt(p.x), Mathf.RoundToInt(p.y), Mathf.RoundToInt(p.z)));\n                    if (this.interactPoints.Count \u003e 10)\n                    {\n                        this.interactPoints.RemoveAt(0);\n                    }\n                    await this.ReBuildMesh();\n                }\n            }\n        }\n\n        Vector3Int CalculateGridPosition(Vector3 position)\n        {\n            // グリッドのセルサイズに合わせて位置を切り捨てて計算\n            int x = Mathf.FloorToInt(position.x / this.gridSize);\n            int y = Mathf.FloorToInt(position.y / this.gridSize);\n            int z = Mathf.FloorToInt(position.z / this.gridSize);\n\n            return new Vector3Int(x, y, z);\n        }\n\n        async Task ReBuildMesh()\n        {\n            while (this.createMeshesTask != null \u0026\u0026 !this.createMeshesTask.IsCompleted)\n            {\n                this.cancelCreateMeshes = true;\n                await Task.Delay(100);\n            }\n            this.createMeshesTask = this.CreateWorldMeshes(this.chunks, this.interactPoints.ToArray());\n        }\n\n        /// \u003csummary\u003e\n        /// チャンクを順番にメッシュオブジェクト化\n        /// \u003c/summary\u003e\n        /// \u003cparam name=\"chunks\"\u003eソートされたチャンク一覧\u003c/param\u003e\n        /// \u003cparam name=\"interactPoints\"\u003e興味ポイント座標一覧\u003c/param\u003e\n        /// \u003creturns\u003easync Task\u003c/returns\u003e\n        async Task CreateWorldMeshes(List\u003cSimpleMeshChunk\u003e chunks, Vector3[] interactPoints)\n        {\n            this.cancelCreateMeshes = false;\n            // 現在のチャンク一覧をカメラに近い順にソート\n            var mainCamera = Camera.main.transform;\n            await this.SortChunksAsync(chunks, mainCamera.position, mainCamera.forward);\n            List\u003cGameObject\u003e meshObjectList = new List\u003cGameObject\u003e();\n            foreach (var chunk in chunks)\n            {\n                if (this.cancelCreateMeshes)\n                {\n                    break;\n                }\n                if (chunk.dot \u003c 0f \u0026\u0026 chunk.distance \u003e 256 \u0026\u0026 chunk.cubeSize \u003e= CubeSize.Size2)\n                {\n                    // 表示不要なものはスキップ\n                    continue;\n                }\n                await this.chunkBuilder.CreateChunkMesh(meshObjectList, chunk, interactPoints, true);\n\n                this.RemoveUnitCubeObjects(chunk, interactPoints);\n            }\n            await this.chunkBuilder.BakeMeshAsync(meshObjectList, true);\n        }\n\n        void RemoveUnitCubeObjects(SimpleMeshChunk chunk, Vector3[] interactPoints)\n        {\n            if (chunk.children != null)\n            {\n                foreach (var child in chunk.children)\n                {\n                    this.RemoveUnitCubeObjects(child, interactPoints);\n                }\n            }\n            if (chunk.chunkLevel == ChunkLevel.Cube1 \u0026\u0026 chunk.meshObject != null \u0026\u0026 this.chunkBuilder.IsNearInteractPoints(chunk, interactPoints, 2f))\n            {\n                this.voxelData[chunk.offset.x * SimpleMeshChunk.dataEdgeCubeCount * SimpleMeshChunk.dataEdgeCubeCount +\n                    chunk.offset.y * SimpleMeshChunk.dataEdgeCubeCount + chunk.offset.z] = 0;\n                var unitCube = chunk.meshObject;\n                unitCube.GetComponent\u003cMeshCollider\u003e().convex = true;\n                unitCube.AddComponent\u003cRigidbody\u003e();\n                unitCube.transform.SetParent(null, true);\n                chunk.meshObject = null;\n                StartCoroutine(this.CoDestroyCube(unitCube, 30f));\n            }\n        }\n\n        IEnumerator CoDestroyCube(GameObject unitCube, float delay)\n        {\n            yield return new WaitForSeconds(delay);\n            if (unitCube.TryGetComponent(out MeshFilter meshFilter))\n            {\n                if (null != meshFilter.sharedMesh)\n                {\n                    meshFilter.sharedMesh.Clear();\n                }\n                Destroy(meshFilter.sharedMesh);\n            }\n            Destroy(unitCube);\n        }\n\n        async Task SortChunksAsync(List\u003cSimpleMeshChunk\u003e chunks, Vector3 viewPoint, Vector3 viewDirection)\n        {\n            await Task.Run(() =\u003e { SortChunks(chunks, viewPoint, viewDirection); });\n        }\n\n        static void SortChunks(List\u003cSimpleMeshChunk\u003e chunks, Vector3 viewPoint, Vector3 viewDirection)\n        {\n            var points = new NativeArray\u003cfloat3\u003e(chunks.Count, Allocator.Persistent);\n            var cameraDistances = new NativeArray\u003cDotDistance\u003e(chunks.Count, Allocator.Persistent);\n            for (int i = 0; i \u003c chunks.Count; i++)\n            {\n                var chunk = chunks[i];\n                points[i] = chunk.center;\n            }\n            var calculateCameraDistanceJob = new CalculateDotDistanceJob()\n            {\n                points = points,\n                viewPoint = viewPoint,\n                viewDirection = viewDirection,\n                dotDistances = cameraDistances\n            };\n            calculateCameraDistanceJob.Schedule(points.Length, 1).Complete();\n            for (int i = 0; i \u003c cameraDistances.Length; i++)\n            {\n                var cameraDistance = cameraDistances[i];\n                chunks[i].distance = cameraDistance.distance;\n                chunks[i].dot = cameraDistance.dot;\n            }\n            cameraDistances.Dispose();\n            points.Dispose();\n            chunks.Sort((a, b) =\u003e a.distance \u003e b.distance ? 1 : -1);\n        }\n\n        /// \u003csummary\u003e\n        /// 計算で毎回使うバッファ、使いまわすために最初に確保\n        /// \u003c/summary\u003e\n        /// \u003creturns\u003e確保したバッファ\u003c/returns\u003e\n        static async Task\u003cList\u003cIndexXYZ\u003e\u003e AllocateDataAsync()\n        {\n            return await Task.Run(() =\u003e {\n                List\u003cIndexXYZ\u003e levelDataList = new List\u003cIndexXYZ\u003e();\n                for (ChunkLevel chunkLevel = ChunkLevel.Cube1; chunkLevel \u003c= ChunkLevel.Cube256; chunkLevel++)\n                {\n                    var edgeCubeCount = SimpleMeshChunk.levelEdgeCubes[(int)chunkLevel];\n                    var size = edgeCubeCount * edgeCubeCount * edgeCubeCount;\n                    var xyz = new NativeArray\u003cXYZ\u003e(size, Allocator.Persistent);\n                    var countOffsets = new NativeArray\u003cint\u003e(size, Allocator.Persistent);\n                    for (int x = 0; x \u003c edgeCubeCount; x++)\n                    {\n                        for (int y = 0; y \u003c edgeCubeCount; y++)\n                        {\n                            for (int z = 0; z \u003c edgeCubeCount; z++)\n                            {\n                                var index = x * edgeCubeCount * edgeCubeCount + y * edgeCubeCount + z;\n                                xyz[index] = new XYZ { x = (byte)x, y = (byte)y, z = (byte)z };\n                                countOffsets[index] = 0;\n                            }\n                        }\n                    }\n                    levelDataList.Add(new IndexXYZ { xyz = xyz, countOffsets = countOffsets });\n                }\n                return levelDataList;\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%2Fsimplemeshminingsample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimplestargame%2Fsimplemeshminingsample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplestargame%2Fsimplemeshminingsample/lists"}