Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/simplestargame/simplemeshworldsample
Simple Mesh World Sample
https://github.com/simplestargame/simplemeshworldsample
mesh unity
Last synced: about 2 months ago
JSON representation
Simple Mesh World Sample
- Host: GitHub
- URL: https://github.com/simplestargame/simplemeshworldsample
- Owner: simplestargame
- License: mit
- Created: 2023-09-24T05:23:21.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2023-09-24T06:38:17.000Z (over 1 year ago)
- Last Synced: 2023-09-24T14:21:13.564Z (over 1 year ago)
- Topics: mesh, unity
- Language: C#
- Homepage:
- Size: 743 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Runtime Mesh World Sample
Welcome to the Runtime Mesh World Sample repository!
This 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.![サンプルシーン](main.png)
## Features
- **Optimization:** The use of the Job System and async Task syntax is employed to maximize background multithreading, minimizing the load on the main thread.## Getting Started
Follow these steps to try out the Sample:1. Clone this repository.
1. Open the project from the cloned folder using Unity Hub.
1. Play the sample scene to understand how mesh merging is performed.
1. Pressing the space key triggers world generation with shifted positions.# ランタイム メッシュワールドサンプル
**ランタイム メッシュワールドサンプル** リポジトリへようこそ!
このリポジトリは三次元 byte 配列を入力に、0 でない要素にキューブを配置して、建築世界を構築するスクリプトサンプルです。## 特徴
- **最適化:** Job System と async Task 構文を用いて極力バックグラウンドマルチスレッド化がほどこされ、メインスレッドに負荷を与えません。
## 始め方
以下の手順に従って、サンプルをお試しできます。
1. このリポジトリをクローンします。
1. Unity Hub 経由でクローンしたフォルダからプロジェクトを開きます。
1. サンプルシーンを開いて再生することでメッシュの結合サンプル処理を理解します。
1. スペースキーを押すと、位置をずらした世界生成が行われます## How to use
```csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;namespace SimplestarGame
{
public class SimpleMeshWorldSample : MonoBehaviour
{
[SerializeField] string meshFileName = "BasicCube.caw";
[SerializeField] string dataFileName = "world000.gz";
[Range(0, 3)][SerializeField] int chunkCount = 0;
[SerializeField] Material material;
[SerializeField] Transform[] parents;int paretentIndex = 0;
const int dataEdgeCubeCount = 256;class WorldData
{
public NativeArray voxelData;
public NativeArray xyz;
public NativeArray countOffsets;
}void Awake()
{
Application.targetFrameRate = 60;
GraphicsSettings.useScriptableRenderPipelineBatching = true;
}async void Start()
{
// キューブのメッシュデータソースを読み込み
var cubeData = await Task.Run(() => CAWFile.ReadCAWFile(Path.Combine(Application.streamingAssetsPath, this.meshFileName)));
// ワールドのボクセルデータ読み込み
var compressedData = await Task.Run(() => File.ReadAllBytes(Path.Combine(Application.streamingAssetsPath, this.dataFileName)));
// ワールドのデータ解凍
var worldDataBytes = await Task.Run(() => GZipCompressor.Unzip(compressedData));
// Job用NativeArray確保
var edgeCubeCount = Mathf.RoundToInt(16 * Mathf.Pow(2, this.chunkCount));
var worldData = await this.AllocateDataAsync(worldDataBytes, edgeCubeCount);
// Meshオブジェクト作成
List meshObjectList = new List();
var edgeChunkCount = dataEdgeCubeCount / edgeCubeCount;
for (int chunkX = 0; chunkX < edgeChunkCount; chunkX++)
{
for (int chunkY = 0; chunkY < edgeChunkCount; chunkY++)
{
for (int chunkZ = 0; chunkZ < edgeChunkCount; chunkZ++)
{
var chunkInt3 = new Vector3Int(chunkX, chunkY, chunkZ);
meshObjectList.Add(await this.CreateChunkObjectAsync(worldData, cubeData, chunkInt3, edgeCubeCount));
}
}
}
// 確保したものを開放
worldData.countOffsets.Dispose();
worldData.xyz.Dispose();
worldData.voxelData.Dispose();
cubeData.fileVertexData.Dispose();
cubeData.vertexCounts.Dispose();
// BakeMesh
await this.BakeMeshAsync(meshObjectList);
// 配置換え
this.paretentIndex++;
if (this.parents.Length == this.paretentIndex)
{
this.paretentIndex = 0;
}
}void Update()
{
// Space キーを押すと、配置換えしてワールドを構築し直します
if (Input.GetKeyDown(KeyCode.Space))
{
this.Start();
}
}///
/// 計算で毎回使うバッファ、使いまわすために最初に確保
///
/// 世界データ
/// チャンクの辺キューブ数
/// 確保したバッファ
async Task AllocateDataAsync(byte[] worldDataBytes, int edgeCubeCount)
{
return await Task.Run(() => {
var voxelData = new NativeArray(worldDataBytes, Allocator.Persistent);
var xyz = new NativeArray(edgeCubeCount * edgeCubeCount * edgeCubeCount, Allocator.Persistent);
var countOffsets = new NativeArray(xyz.Length, Allocator.Persistent);
for (byte x = 0; x < edgeCubeCount; x++)
{
for (byte y = 0; y < edgeCubeCount; y++)
{
for (byte z = 0; z < edgeCubeCount; z++)
{
var index = x * edgeCubeCount * edgeCubeCount + y * edgeCubeCount + z;
xyz[index] = new XYZ { x = x, y = y, z = z };
countOffsets[index] = 0;
}
}
}
return new WorldData { voxelData = voxelData, xyz = xyz, countOffsets = countOffsets };
});
}///
/// メッシュの作成
///
/// データ設定済みメッシュデータ
/// インデックス数=頂点数
/// バウンディングボックス情報
/// 作成したメッシュ
async Task CreateMesh(Mesh.MeshDataArray meshDataArray, int vertexIndexCount, float3x2 bounds)
{
var newMesh = new Mesh();
newMesh.name = "CustomLayoutMesh";
var meshBounds = newMesh.bounds = new Bounds((bounds.c0 + bounds.c1) * 0.5f, bounds.c1 - bounds.c0);
await Task.Run(() => {
meshDataArray[0].SetSubMesh(0, new SubMeshDescriptor
{
topology = MeshTopology.Triangles,
vertexCount = vertexIndexCount,
indexCount = vertexIndexCount,
baseVertex = 0,
firstVertex = 0,
indexStart = 0,
bounds = meshBounds
}, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers);
});
Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, new[] { newMesh },
MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers);
return newMesh;
}///
/// メッシュオブジェクト作成
///
/// ワールド全体データ
/// キューブの頂点情報
/// ワールド内のローカル塊オフセット
/// 塊の辺キューブ数
/// 作成したゲームオブジェクト
async Task CreateChunkObjectAsync(
WorldData worldData,
CAWFile.CubeData cubeData,
Vector3Int chunkOffset,
int edgeCubeCount)
{
// カウント
await Task.Run(() => {
var countJobHandle = new CountChunkVertexJob()
{
vertexCounts = cubeData.vertexCounts,
voxelData = worldData.voxelData,
xyz = worldData.xyz,
results = worldData.countOffsets,
heightDepth = edgeCubeCount * edgeCubeCount,
width = edgeCubeCount,
height = edgeCubeCount,
depth = edgeCubeCount,
chunkOffset = chunkOffset,
dataEdgeCubeCount = dataEdgeCubeCount,
}.Schedule(worldData.xyz.Length, 8);
countJobHandle.Complete(); });
// 集計
var vertexIndexCount = await Task.Run(() =>
{
int vertexIndexCount = 0;
for (int index = 0; index < worldData.countOffsets.Length; index++)
{
var counts = worldData.countOffsets[index];
worldData.countOffsets[index] = vertexIndexCount;
vertexIndexCount += counts;
}
return vertexIndexCount;
});
if (vertexIndexCount == 0)
{
return null;
}
// 確保
var meshDataArray = Mesh.AllocateWritableMeshData(1);
Mesh.MeshData meshData = meshDataArray[0];
meshData.subMeshCount = 1;
meshData.SetVertexBufferParams(vertexIndexCount, CustomLayoutMesh.VERTEX_ATTRIBUTE_DESCRIPTORS);
meshData.SetIndexBufferParams(vertexIndexCount, IndexFormat.UInt32);
NativeArray indexData = meshData.GetIndexData();
// インデックス書き込み
var indexJobHandle = new WriteIndexDataJob() { indexData = indexData }.Schedule(indexData.Length, 128);
indexJobHandle.Complete();
// 頂点データ書き込み
NativeArray vertexData = meshData.GetVertexData(stream: 0);
await Task.Run(() => {
var writeJobHandle = new WriteChunkDataJob()
{
vertexCounts = cubeData.vertexCounts,
voxelData = worldData.voxelData,
xyz = worldData.xyz,
countOffsets = worldData.countOffsets,
width = edgeCubeCount,
height = edgeCubeCount,
depth = edgeCubeCount,
fileVertexData = cubeData.fileVertexData,
vertexData = vertexData,
chunkOffset = chunkOffset,
dataEdgeCubeCount = dataEdgeCubeCount,
}.Schedule(worldData.xyz.Length, 8);
writeJobHandle.Complete(); });
// バウンディングボックス
float3x2 bounds = new float3x2();
bounds.c0 = math.min(bounds.c0, new float3(-0.5f, -0.5f, -0.5f));
bounds.c1 = math.max(bounds.c1, new float3(edgeCubeCount + 0.5f, edgeCubeCount + 0.5f, edgeCubeCount + 0.5f));
// オブジェクト作成
Mesh newMesh = await this.CreateMesh(meshDataArray, vertexIndexCount, bounds);
vertexData.Dispose();
indexData.Dispose();
GameObject newGameObject = new GameObject("TestCubeMeshObject");
newGameObject.transform.SetParent(this.parents[this.paretentIndex], false);
newGameObject.transform.localPosition = chunkOffset * edgeCubeCount;
newGameObject.isStatic = true;
newGameObject.AddComponent().sharedMesh = newMesh;
newGameObject.AddComponent().sharedMaterial = this.material;
return newGameObject;
}///
/// MeshCollider 作成
///
/// MeshFilter の sharedMesh を入力に MeshCollider を計算します
/// Task
async Task BakeMeshAsync(List meshObjectList)
{
NativeArray meshIds = new NativeArray(meshObjectList.Count, Allocator.Persistent);
var meshIdx = 0;
foreach (var meshObject in meshObjectList)
{
var mesh = meshObject.GetComponent().sharedMesh;
meshIds[meshIdx++] = mesh.GetInstanceID();
}
await Task.Run(() =>
{
var bakeMeshJob = new BakeMeshJob(meshIds);
var bakeMeshJobHandle = bakeMeshJob.Schedule(meshIds.Length, 1);
bakeMeshJobHandle.Complete();
meshIds.Dispose();
});
// Set MeshCollider
foreach (var meshObject in meshObjectList)
{
meshObject.AddComponent().sharedMesh = meshObject.GetComponent().sharedMesh;
}
}
}
}
```## License
This project is licensed under the MIT License.## Contribution
If 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.