# まえがき
これらの記事シリーズの続きです。
今回は作業メモになっているので、開発者以外が読むと辛い文章かも…
simplestar-tech.hatenablog.com
simplestar-tech.hatenablog.com
シリーズ名は CubeWalkGame として、前回はチャンクをまたぐキューブ情報の取得を確認しました。
前回はマクロ視点でしたが、今回はミクロ視点ということで、キューブ単体のテクスチャ座標と回転表現をやります。
## 無回転でテクスチャ貼り
テクスチャ座標はこれなんだけど、どうやって貼り付け?
new Vector2[]{ // Top new Vector2 (0, 1), new Vector2 (1, 1), new Vector2 (1, 0), // Bottom new Vector2 (1, 0), new Vector2 (0, 0), new Vector2 (0, 1), // Right up new Vector2 (1, 1), new Vector2 (2, 1), new Vector2 (2, 0), // Right down new Vector2 (2, 0), new Vector2 (1, 0), new Vector2 (1, 1), // Forward up new Vector2 (2, 1), new Vector2 (3, 1), new Vector2 (3, 0), // Forward down new Vector2 (3, 0), new Vector2 (2, 0), new Vector2 (2, 1), // Cross up new Vector2 (3, 1), new Vector2 (4.4142f, 1), new Vector2 (4.4142f, 0), // Cross down new Vector2 (4.4142f, 0), new Vector2 (3, 0), new Vector2 (3, 1), }
ひとまず頂点位置と同じようにセットしてみます。
マジックコードを発見しましたが、コレなんでしょう?
const float mtf = 232f / 4096f; float mtoffu = col / 4.0f; float mtoffv = mtf * (16 - row % 17) % 17 + 0.0371f; mesh.uv[vertCount * 2 + 0] = uvSrc[i * 2 + 0] * mtf + mtoffu; mesh.uv[vertCount * 2 + 1] = uvSrc[i * 2 + 1] * mtf + mtoffv;
こちらの level 4 との対となるマジックコードでした。
github.com
ロジックの肝心な箇所はこちら
int row = 10; int col = 0; const float mtf = 232f / 4096f; float mtoffu = col / 4.0f; float mtoffv = mtf * (16 - row % 17) % 17 + 0.0371f; for (int vertexIndex = 0; vertexIndex < vertexCount * 2; vertexIndex += 2) { var uvXIndex = uvOffset + vertexIndex; meshData.pUVVector2[uvXIndex + 0] *= mtf; meshData.pUVVector2[uvXIndex + 0] += mtoffu; meshData.pUVVector2[uvXIndex + 1] *= mtf; meshData.pUVVector2[uvXIndex + 1] += mtoffv; } verticesOffset += vertexCount * 3; uvOffset += vertexCount * 2;
いずれテクスチャの row, col の値は ChunkData から取らないといけませんね。
## 頂点を回転
この記事で試しておかなければならないこと、それが mathematics でその場で頂点回転できないか?というもの
キューブの頂点座標は原点中心で定義しているので、90度または パイ/2 ラジアンで回転する quaternion とかあれば良いのですが
ECS で使う Mathematics を確認します。
こういうときは過去記事を引っ張ってきて、関連リンクを読み漁ります。
simplestar-tech.hatenablog.com
そして発見!
using static Unity.Mathematics.math; /// <summary>Returns the result of rotating a vector by a unit quaternion.</summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float3 rotate(quaternion q, float3 v) { float3 t = 2 * cross(q.value.xyz, v); return v + q.value.w * t + cross(q.value.xyz, t); }
仮で、すべての頂点を X 軸で 90 度回転させてみます。
コードはこんな感じかな?
for (int vertexIndex = 0; vertexIndex < vertexCount * 3; vertexIndex += 3) { var positionXIndex = verticesOffset + vertexIndex; var pPosition = meshData.pVerticesVector3 + positionXIndex; var q = Unity.Mathematics.quaternion.RotateX(PI / 2); var p = float3(pPosition[0], pPosition[1], pPosition[2]); var r = rotate(q, p); pPosition[0] = r.x + x * ChunkManager.CubeSide; pPosition[1] = r.y + y * ChunkManager.CubeSide; pPosition[2] = r.z + z * ChunkManager.CubeSide; }
変化は?
期待通りですね!
回転ごとに周辺チャンクの参照先を変えなければならないので、ここが頑張りどころです。
## 回転タイプを byte 値で判別
ここは強引にこう定義してみました。
internal enum CubeRotationType : byte { Top000 = 0, Top090, Top180, Top270, Bottom000, Bottom090, Bottom180, Bottom270, Forward000, Forward090, Forward180, Forward270, Back000, Back090, Back180, Back270, Right000, Right090, Right180, Right270, Left000, Left090, Left180, Left270, Max }
以前の回転結果図を参考に決めてみたけど、これでうまくいくか
/// <summary> /// byte の回転タイプから Quaternion を作って返す /// </summary> static Unity.Mathematics.quaternion GetRotationQuaternion(CubeRotationType rotationType) { // @TODO: インデックスアクセスに var rotationQuaternion = Unity.Mathematics.quaternion.identity; switch (rotationType) { case CubeRotationType.Top000: break; case CubeRotationType.Top090: rotationQuaternion = Unity.Mathematics.quaternion.RotateY(PI / 2); break; case CubeRotationType.Top180: rotationQuaternion = Unity.Mathematics.quaternion.RotateY(PI); break; case CubeRotationType.Top270: rotationQuaternion = Unity.Mathematics.quaternion.RotateY(PI * 3 / 2); break; case CubeRotationType.Bottom000: rotationQuaternion = Unity.Mathematics.quaternion.RotateX(PI); break; case CubeRotationType.Bottom090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Bottom180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Bottom270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Forward000: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Forward090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Forward180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(0)); break; case CubeRotationType.Forward270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Back000: rotationQuaternion = Unity.Mathematics.quaternion.RotateX(-PI / 2); break; case CubeRotationType.Back090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Back180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Back270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Right000: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Right090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(0)); break; case CubeRotationType.Right180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Right270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Left000: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Left090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Left180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Left270: rotationQuaternion = Unity.Mathematics.quaternion.RotateZ(PI / 2); break; case CubeRotationType.Max: break; default: break; } return rotationQuaternion; }
次に周辺ブロックを見るためのインデックスを回転タイプから求めます。
いっぱい switch 文を書いていて気づく、enum が巨大な連番だったら switch 文の分岐書くより
配列のインデックスアクセスにした方が、結果取得早いしプログラミングも楽です。(読解し辛いのでコメント必須ですが)
前回のポインターのポインターを活用することにして、次の通り定義してみました。
#region 回転タイプと面タイプごとの方向 var top = int3(0, 1, 0); var bottom = int3(0, -1, 0); var right = int3(1, 0, 0); var left = int3(-1, 0, 0); var forward = int3(0, 0, 1); var back = int3(0, 0, -1); this.ppRotationFaceDirections = (int3**)(UnsafeUtility.Malloc(sizeof(int3*) * (int)CubeRotationType.Max, sizeof(int3*), Allocator.Persistent)); #region Top this.ppRotationFaceDirections[(int)CubeRotationType.Top000] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Top000] = new NativeArray<int3>( new int3[] { top, bottom, right, forward, bottom, top, left, back }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Top090] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Top090] = new NativeArray<int3>( new int3[] { top, bottom, back, right, bottom, top, forward, left }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Top180] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Top180] = new NativeArray<int3>( new int3[] { top, bottom, left, back, bottom, top, right, forward }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Top270] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Top270] = new NativeArray<int3>( new int3[] { top, bottom, forward, left, bottom, top, back, right }, Allocator.Persistent)).GetUnsafePtr(); #endregion #region Bottom this.ppRotationFaceDirections[(int)CubeRotationType.Bottom000] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Bottom000] = new NativeArray<int3>( new int3[] { bottom, top, right, back, top, bottom, left, forward }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Bottom090] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Bottom090] = new NativeArray<int3>( new int3[] { bottom, top, forward, right, top, bottom, back, left }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Bottom180] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Bottom180] = new NativeArray<int3>( new int3[] { bottom, top, left, forward, top, bottom, right, back }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Bottom270] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Bottom270] = new NativeArray<int3>( new int3[] { bottom, top, back, left, top, bottom, forward, right }, Allocator.Persistent)).GetUnsafePtr(); #endregion #region Forward this.ppRotationFaceDirections[(int)CubeRotationType.Forward000] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Forward000] = new NativeArray<int3>( new int3[] { forward, back, left, top, back, forward, right, bottom }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Forward090] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Forward090] = new NativeArray<int3>( new int3[] { forward, back, bottom, left, back, forward, top, right }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Forward180] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Forward180] = new NativeArray<int3>( new int3[] { forward, back, right, bottom, back, forward, left, top }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Forward270] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Forward270] = new NativeArray<int3>( new int3[] { forward, back, top, right, back, forward, bottom, left }, Allocator.Persistent)).GetUnsafePtr(); #endregion #region Back this.ppRotationFaceDirections[(int)CubeRotationType.Back000] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Back000] = new NativeArray<int3>( new int3[] { back, forward, right, top, forward, back, left, bottom }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Back090] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Back090] = new NativeArray<int3>( new int3[] { back, forward, bottom, right, forward, back, top, left }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Back180] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Back180] = new NativeArray<int3>( new int3[] { back, forward, left, bottom, forward, back, right, top }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Back270] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Back270] = new NativeArray<int3>( new int3[] { back, forward, top, left, forward, back, bottom, right }, Allocator.Persistent)).GetUnsafePtr(); #endregion #region Right this.ppRotationFaceDirections[(int)CubeRotationType.Right000] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Right000] = new NativeArray<int3>( new int3[] { right, left, forward, top, left, right, back, bottom }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Right090] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Right090] = new NativeArray<int3>( new int3[] { right, left, bottom, forward, left, right, top, back }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Right180] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Right180] = new NativeArray<int3>( new int3[] { right, left, back, bottom, left, right, forward, top }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Right270] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Right270] = new NativeArray<int3>( new int3[] { right, left, top, back, left, right, bottom, forward }, Allocator.Persistent)).GetUnsafePtr(); #endregion #region Left this.ppRotationFaceDirections[(int)CubeRotationType.Left000] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Left000] = new NativeArray<int3>( new int3[] { left, right, back, top, right, left, forward, bottom }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Left090] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Left090] = new NativeArray<int3>( new int3[] { left, right, bottom, back, right, left, top, forward }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Left180] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Left180] = new NativeArray<int3>( new int3[] { left, right, forward, bottom, right, left, back, top }, Allocator.Persistent)).GetUnsafePtr(); this.ppRotationFaceDirections[(int)CubeRotationType.Left270] = (int3*)(this.rotationFaceDirectionArray[(int)CubeRotationType.Left270] = new NativeArray<int3>( new int3[] { left, right, top, forward, right, left, bottom, back }, Allocator.Persistent)).GetUnsafePtr(); #endregion #endregion } protected override void OnDestroy() { for (int cubeRotationIndex = 0; cubeRotationIndex < (int)CubeRotationType.Max; cubeRotationIndex++) { this.rotationFaceDirectionArray[cubeRotationIndex].Dispose(); } UnsafeUtility.Free(this.ppRotationFaceDirections, Allocator.Persistent);
こちらのポインターのポインターを参照することで、キューブの回転タイプから正しい方向の周囲のキューブ情報の取得が行えることが確認できました。
頂点情報を作成するコードをリファクタリングして、同じようなコードをまとめた結果がこちら
/// <summary> /// 頂点バッファに頂点データをコピー書き込み /// </summary> [BurstCompile] unsafe struct CopyWriteVerticesJob : IJobParallelFor { [NativeDisableUnsafePtrRestriction] [ReadOnly] internal int3** ppRotationFaceDirection; [NativeDisableUnsafePtrRestriction] [ReadOnly] internal float* pVerticesSource; [NativeDisableUnsafePtrRestriction] [ReadOnly] internal float* pUVSource; [ReadOnly] internal int sourceCount; internal NativeArray<ChunkMeshData> meshDataArray; public void Execute(int entityIndex) { var meshData = this.meshDataArray[entityIndex]; this.verticesOffset = 0; this.uvOffset = 0; for (var x = 0; x < ChunkManager.ChunkSizeX; x++) { for (var z = 0; z < ChunkManager.ChunkSizeZ; z++) { for (var y = 0; y < ChunkManager.ChunkSizeY; y++) { var pData = GetDataPtr(meshData, x, y, z); if (1 == pData[(int)ChunkDataType.Category]) { var rotationType = (CubeRotationType)pData[(int)ChunkDataType.Rotation]; this.sourceOffset = 0; this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.TopA); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.BottomA); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.RightA); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.ForwardA); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.CrossA); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.BottomB); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.TopB); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.LeftB); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.BackB); this.MakeFace(meshData, x, y, z, rotationType, CubeFaceType.CrossB); } } } } } private int MakeFace(ChunkMeshData meshData, int x, int y, int z, CubeRotationType rotationType, CubeFaceType faceType) { int faceCount = 0; int dataSideIndex = -1; int crossFlag = 0; #region 面タイプごとの変数を決定 switch (faceType) { case CubeFaceType.TopA: case CubeFaceType.BottomA: { dataSideIndex = (int)ChunkDataType.SideA; faceCount = 1; } break; case CubeFaceType.BottomB: case CubeFaceType.TopB: { dataSideIndex = (int)ChunkDataType.SideB; faceCount = 1; } break; case CubeFaceType.RightA: case CubeFaceType.ForwardA: { dataSideIndex = (int)ChunkDataType.SideA; faceCount = 2; } break; case CubeFaceType.LeftB: case CubeFaceType.BackB: { dataSideIndex = (int)ChunkDataType.SideB; faceCount = 2; } break; case CubeFaceType.CrossA: { dataSideIndex = (int)ChunkDataType.SideA; faceCount = 2; crossFlag = 1; } break; case CubeFaceType.CrossB: { dataSideIndex = (int)ChunkDataType.SideB; faceCount = 2; crossFlag = 1; } break; default: break; } #endregion var vertexCount = 0; #region 周囲チャンクデータをチェックして、面作成が必要なときのみコピー var sideCubeOffset = crossFlag == 0 ? this.ppRotationFaceDirection[(int)rotationType][(int)faceType] : int3(0, 0, 0); var pData = GetDataPtr(meshData, x + sideCubeOffset.x, y + sideCubeOffset.y, z + sideCubeOffset.z); if (!(null != pData && 1 == pData[(int)ChunkDataType.Category] && 1 == pData[dataSideIndex])) { UnsafeUtility.MemCpy(meshData.pVerticesVector3 + this.verticesOffset, this.pVerticesSource + this.sourceOffset * 9, size: 9 * sizeof(float) * faceCount); UnsafeUtility.MemCpy(meshData.pUVVector2 + this.uvOffset, this.pUVSource + this.sourceOffset * 6, size: 6 * sizeof(float) * faceCount); vertexCount = faceCount * 3; } this.sourceOffset += faceCount; #endregion #region コピーしたものを回転タイプとチャンク内インデックスから、正しい頂点位置へ移動 for (int vertexIndex = 0; vertexIndex < vertexCount * 3; vertexIndex += 3) { // コピーした頂点情報を取得 var pPosition = meshData.pVerticesVector3 + this.verticesOffset + vertexIndex; // 回転タイプによる回転 var quaternion = GetRotationQuaternion(rotationType); var position = float3(pPosition[0], pPosition[1], pPosition[2]); var resultPosition = rotate(quaternion, position); // チャンク内オフセットを足して格納 pPosition[0] = resultPosition.x + x * ChunkManager.CubeSide; pPosition[1] = resultPosition.y + y * ChunkManager.CubeSide; pPosition[2] = resultPosition.z + z * ChunkManager.CubeSide; } this.verticesOffset += vertexCount * 3; #endregion #region UV 設定 int row = 10; // データから決めるべき int col = 0; const float mtf = 232f / 4096f; float mtoffu = col / 4.0f; float mtoffv = mtf * (16 - row % 17) % 17 + 0.0371f; for (int vertexIndex = 0; vertexIndex < vertexCount * 2; vertexIndex += 2) { var pUV = meshData.pUVVector2 + this.uvOffset + vertexIndex; pUV[0] = pUV[0] * mtf + mtoffu; pUV[1] = pUV[1] * mtf + mtoffv; } this.uvOffset += vertexCount * 2; #endregion return vertexCount; } int sourceOffset; int verticesOffset; int uvOffset; }
このジョブの関数の面を描くべきかどうかの判定は再利用して頂点カウントのジョブ側でも使いたいところ
切り出し方を考えてみると
具体的には vertexCount を計算する部分を共通化できるといい
できた。
言葉にし難いけど、System は以下の通り、だいぶコンパクトにたたむことができた
using System.Collections.Generic; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using UnityEngine; using static Unity.Mathematics.math; internal unsafe class CreateMeshSystem : ComponentSystem { /// <summary> /// チャンク情報から頂点数をカウント /// </summary> [BurstCompile] unsafe struct CountVerticesJob : IJobParallelFor { [NativeDisableUnsafePtrRestriction] [ReadOnly] internal int3** ppRotationFaceDirection; internal NativeArray<ChunkMeshData> meshDataArray; internal int sourceCount; public void Execute(int entityIndex) { var meshData = this.meshDataArray[entityIndex]; meshData.vertexCount = 0; for (var x = 0; x < ChunkManager.ChunkSizeX; x++) { for (var z = 0; z < ChunkManager.ChunkSizeZ; z++) { for (var y = 0; y < ChunkManager.ChunkSizeY; y++) { var pData = GetDataPtr(meshData, x, y, z); if (1 == pData[(int)ChunkDataType.Category]) // @debug 1 とは? { var rotationType = (CubeRotationType)pData[(int)ChunkDataType.Rotation]; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.TopA).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.BottomA).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.RightA).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.ForwardA).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.CrossA).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.BottomB).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.TopB).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.LeftB).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.BackB).activeVertexCount; meshData.vertexCount += CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.CrossB).activeVertexCount; } } } } this.meshDataArray[entityIndex] = meshData; } } /// <summary> /// 頂点バッファに頂点データをコピー書き込み /// </summary> [BurstCompile] unsafe struct CopyWriteVerticesJob : IJobParallelFor { [NativeDisableUnsafePtrRestriction] [ReadOnly] internal int3** ppRotationFaceDirection; [NativeDisableUnsafePtrRestriction] [ReadOnly] internal float* pVerticesSource; [NativeDisableUnsafePtrRestriction] [ReadOnly] internal float* pUVSource; internal NativeArray<ChunkMeshData> meshDataArray; public void Execute(int entityIndex) { var meshData = this.meshDataArray[entityIndex]; this.verticesOffset = 0; this.uvOffset = 0; for (var x = 0; x < ChunkManager.ChunkSizeX; x++) { for (var z = 0; z < ChunkManager.ChunkSizeZ; z++) { for (var y = 0; y < ChunkManager.ChunkSizeY; y++) { var pData = GetDataPtr(meshData, x, y, z); if (1 == pData[(int)ChunkDataType.Category]) // @debug 1 とは? { var rotationType = (CubeRotationType)pData[(int)ChunkDataType.Rotation]; this.sourceFaceOffset = 0; this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.TopA)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.BottomA)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.RightA)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.ForwardA)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.CrossA)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.BottomB)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.TopB)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.LeftB)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.BackB)); this.CalculateMeshData(meshData, x, y, z, rotationType, CountFace(meshData, x, y, z, rotationType, this.ppRotationFaceDirection, CubeFaceType.CrossB)); } } } } } /// <summary> /// メッシュ情報をコピー&計算 /// </summary> private void CalculateMeshData(ChunkMeshData meshData, int x, int y, int z, CubeRotationType rotationType, CountData countData) { if (0 < countData.activeVertexCount) { // 作成対象の頂点データをコピー UnsafeUtility.MemCpy(meshData.pVerticesVector3 + this.verticesOffset, this.pVerticesSource + 9 * this.sourceFaceOffset, size: 3 * sizeof(float) * countData.activeVertexCount); UnsafeUtility.MemCpy(meshData.pUVVector2 + this.uvOffset, this.pUVSource + 6 * this.sourceFaceOffset, size: 2 * sizeof(float) * countData.activeVertexCount); #region コピーしたものを回転タイプとチャンク内インデックスから、正しい頂点位置へ移動 for (int vertexIndex = 0; vertexIndex < countData.activeVertexCount * 3; vertexIndex += 3) { // コピーした頂点情報を取得 var pPosition = meshData.pVerticesVector3 + this.verticesOffset + vertexIndex; // 回転タイプによる回転 var quaternion = GetRotationQuaternion(rotationType); var position = float3(pPosition[0], pPosition[1], pPosition[2]); var resultPosition = rotate(quaternion, position); // チャンク内オフセットを足して格納 pPosition[0] = resultPosition.x + x * ChunkManager.CubeSide; pPosition[1] = resultPosition.y + y * ChunkManager.CubeSide; pPosition[2] = resultPosition.z + z * ChunkManager.CubeSide; } this.verticesOffset += countData.activeVertexCount * 3; #endregion #region UV 設定 int row = 10; // @debug データから決めるべき あと変数名キモ int col = 0; const float mtf = 232f / 4096f; float mtoffu = col / 4.0f; float mtoffv = mtf * (16 - row % 17) % 17 + 0.0371f; for (int vertexIndex = 0; vertexIndex < countData.activeVertexCount * 2; vertexIndex += 2) { var pUV = meshData.pUVVector2 + this.uvOffset + vertexIndex; pUV[0] = pUV[0] * mtf + mtoffu; pUV[1] = pUV[1] * mtf + mtoffv; } this.uvOffset += countData.activeVertexCount * 2; #endregion } // 作成しなくても面タイプごとの面数オフセットを加算 this.sourceFaceOffset += countData.reservedFaceOffset; } int sourceFaceOffset; int verticesOffset; int uvOffset; } /// <summary> /// 回転タイプと面タイプとチャンク内の座標から、作成するべき頂点数をカウント /// </summary> static CountData CountFace(ChunkMeshData meshData, int x, int y, int z, CubeRotationType rotationType, int3** ppRotationFaceDirection, CubeFaceType faceType) { int faceCount = 0; int dataSideIndex = -1; int crossFlag = 0; #region 面タイプごとの変数を決定 switch (faceType) { case CubeFaceType.TopA: case CubeFaceType.BottomA: { dataSideIndex = (int)ChunkDataType.SideA; faceCount = 1; } break; case CubeFaceType.BottomB: case CubeFaceType.TopB: { dataSideIndex = (int)ChunkDataType.SideB; faceCount = 1; } break; case CubeFaceType.RightA: case CubeFaceType.ForwardA: { dataSideIndex = (int)ChunkDataType.SideA; faceCount = 2; } break; case CubeFaceType.LeftB: case CubeFaceType.BackB: { dataSideIndex = (int)ChunkDataType.SideB; faceCount = 2; } break; case CubeFaceType.CrossA: { dataSideIndex = (int)ChunkDataType.SideB; faceCount = 2; crossFlag = 1; } break; case CubeFaceType.CrossB: { dataSideIndex = (int)ChunkDataType.SideA; faceCount = 2; crossFlag = 1; } break; default: break; } #endregion #region 周囲チャンクデータをチェックして、面作成が必要なときのみコピー var vertexCount = 0; var sideCubeOffset = crossFlag == 0 ? ppRotationFaceDirection[(int)rotationType][(int)faceType] : int3(0, 0, 0); var pData = GetDataPtr(meshData, x + sideCubeOffset.x, y + sideCubeOffset.y, z + sideCubeOffset.z); if (!(null != pData && 1 == pData[(int)ChunkDataType.Category] && 1 == pData[dataSideIndex])) // @debug 1 とは? 相手の回転も見ないと駄目なのでは? { vertexCount = faceCount * 3; } #endregion return new CountData { activeVertexCount = vertexCount, reservedFaceOffset = faceCount }; } static byte* GetDataPtr(ChunkMeshData meshData, int x, int y, int z) { if (0 <= x && ChunkManager.ChunkSizeX > x && 0 <= y && ChunkManager.ChunkSizeY > y && 0 <= z && ChunkManager.ChunkSizeZ > z) { #region チャンク内のデータを返す var byteMax = (byte.MaxValue + 1); int chunkIndex = meshData.chunkKeyX * byteMax * byteMax + meshData.chunkKeyZ * byteMax + meshData.chunkKeyY; var pChunkData = meshData.ppChunkData[chunkIndex]; var dataIndex = x * ChunkManager.ChunkSizeZ * ChunkManager.ChunkSizeY + z * ChunkManager.ChunkSizeY + y; return (byte*)(pChunkData + dataIndex); #endregion } else { #region チャンク越えカウント var overChunkCountX = x / ChunkManager.ChunkSizeX; if (0 > x) overChunkCountX -= 1; var overChunkCountY = y / ChunkManager.ChunkSizeY; if (0 > y) overChunkCountY -= 1; var overChunkCountZ = z / ChunkManager.ChunkSizeZ; if (0 > z) overChunkCountZ -= 1; #endregion #region byte オーバーフローによる値ループ var chunkKeyX = (byte)(meshData.chunkKeyX + overChunkCountX); var chunkKeyY = (byte)(meshData.chunkKeyY + overChunkCountY); var chunkKeyZ = (byte)(meshData.chunkKeyZ + overChunkCountZ); #endregion #region チャンクの特定 var byteMax = (byte.MaxValue + 1); int chunkIndex = chunkKeyX * byteMax * byteMax + chunkKeyZ * byteMax + chunkKeyY; var pChunkData = meshData.ppChunkData[chunkIndex]; #endregion if (null != pChunkData) { #region チャンク内のデータインデックスへ変換 x -= overChunkCountX * ChunkManager.ChunkSizeX; y -= overChunkCountY * ChunkManager.ChunkSizeY; z -= overChunkCountZ * ChunkManager.ChunkSizeZ; var dataIndex = x * ChunkManager.ChunkSizeZ * ChunkManager.ChunkSizeY + z * ChunkManager.ChunkSizeY + y; #endregion return (byte*)(pChunkData + dataIndex); } } return null; } /// <summary> /// byte の回転タイプから Quaternion を作って返す /// </summary> static Unity.Mathematics.quaternion GetRotationQuaternion(CubeRotationType rotationType) { // @TODO: インデックスアクセスに var rotationQuaternion = Unity.Mathematics.quaternion.identity; switch (rotationType) { case CubeRotationType.Top000: break; case CubeRotationType.Top090: rotationQuaternion = Unity.Mathematics.quaternion.RotateY(PI / 2); break; case CubeRotationType.Top180: rotationQuaternion = Unity.Mathematics.quaternion.RotateY(PI); break; case CubeRotationType.Top270: rotationQuaternion = Unity.Mathematics.quaternion.RotateY(PI * 3 / 2); break; case CubeRotationType.Bottom000: rotationQuaternion = Unity.Mathematics.quaternion.RotateX(PI); break; case CubeRotationType.Bottom090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Bottom180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Bottom270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Forward000: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Forward090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Forward180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(0)); break; case CubeRotationType.Forward270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Back000: rotationQuaternion = Unity.Mathematics.quaternion.RotateX(-PI / 2); break; case CubeRotationType.Back090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Back180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Back270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateX(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Right000: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Right090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(0)); break; case CubeRotationType.Right180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Right270: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(-PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Left000: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(PI / 2), Unity.Mathematics.quaternion.RotateY(PI / 2)); break; case CubeRotationType.Left090: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(PI / 2), Unity.Mathematics.quaternion.RotateY(PI)); break; case CubeRotationType.Left180: rotationQuaternion = mul(Unity.Mathematics.quaternion.RotateZ(PI / 2), Unity.Mathematics.quaternion.RotateY(PI * 3 / 2)); break; case CubeRotationType.Left270: rotationQuaternion = Unity.Mathematics.quaternion.RotateZ(PI / 2); break; case CubeRotationType.Max: break; default: break; } return rotationQuaternion; } protected override unsafe void OnCreate() { base.OnCreate(); this.query = GetEntityQuery(new EntityQueryDesc { All = new[] { ComponentType.ReadOnly<CreateMeshMarker>(), ComponentType.ReadWrite<ChunkMeshData>() }, }); #region モデルの形状を定義→NativeArray確保 // 省略 #endregion } protected override void OnDestroy() { for (int cubeRotationIndex = 0; cubeRotationIndex < (int)CubeRotationType.Max; cubeRotationIndex++) { this.rotationFaceDirectionArray[cubeRotationIndex].Dispose(); } UnsafeUtility.Free(this.ppRotationFaceDirection, Allocator.Persistent); this.nativeUVSource.Dispose(); this.nativeVerticesSource.Dispose(); base.OnDestroy(); } protected unsafe override void OnUpdate() { #region NativeArray 確保 var entities = this.query.ToEntityArray(Allocator.TempJob); var meshDataArray = this.query.ToComponentDataArray<ChunkMeshData>(Allocator.TempJob); #endregion #region メッシュの頂点数をカウント var countVerticesJob = new CountVerticesJob { ppRotationFaceDirection = this.ppRotationFaceDirection, sourceCount = this.nativeVerticesSource.Length, meshDataArray = meshDataArray }; var countJobHandle = countVerticesJob.Schedule(arrayLength: meshDataArray.Length, innerloopBatchCount: 1); countJobHandle.Complete(); #endregion #region カウント数→頂点バッファを確保→バッファポインタを ComponentData に代入 this.entityMeshDataList.Clear(); for (int entityIndex = 0; entityIndex < entities.Length; entityIndex++) { var meshData = meshDataArray[entityIndex]; var vertexCount = meshData.vertexCount; var entityMeshData = new EntityMeshData { nativeVertices = new NativeArray<Vector3>(vertexCount, Allocator.TempJob), nativeUV = new NativeArray<Vector2>(vertexCount, Allocator.TempJob), }; this.entityMeshDataList.Add(entityMeshData); meshData.pVerticesVector3 = (float*)entityMeshData.nativeVertices.GetUnsafePtr(); meshData.pUVVector2 = (float*)entityMeshData.nativeUV.GetUnsafePtr(); meshDataArray[entityIndex] = meshData; } #endregion #region 頂点バッファに頂点データをコピー var copyVerticesJob = new CopyWriteVerticesJob { ppRotationFaceDirection = this.ppRotationFaceDirection, pVerticesSource = this.pVerticesSource, pUVSource = this.pUVSource, meshDataArray = meshDataArray }; var copyJobHandle = copyVerticesJob.Schedule(arrayLength: meshDataArray.Length, innerloopBatchCount: 1); copyJobHandle.Complete(); #endregion #region 頂点バッファ→マネージド配列→メッシュ作成→メッシュ法線・接線の計算 for (int entityIndex = 0; entityIndex < entities.Length; entityIndex++) { var entity = entities[entityIndex]; var entityMeshData = this.entityMeshDataList[entityIndex]; var vertices = entityMeshData.nativeVertices.ToArray(); var uv = entityMeshData.nativeUV.ToArray(); var mesh = new Mesh(); mesh.Clear(); mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; mesh.vertices = vertices; mesh.uv = uv; int[] triangles = new int[vertices.Length]; for (int vertexIndex = 0; vertexIndex < vertices.Length; vertexIndex++) { triangles[vertexIndex] = vertexIndex; } mesh.SetIndices(triangles, MeshTopology.Triangles, submesh: 0, calculateBounds: true); mesh.RecalculateNormals(); mesh.RecalculateTangents(); var meshFilter = EntityManager.GetComponentObject<MeshFilter>(entity); meshFilter.mesh = mesh; entityMeshData.nativeUV.Dispose(); entityMeshData.nativeVertices.Dispose(); } this.entityMeshDataList.Clear(); #endregion #region entity から marker の除去 for (int entityIndex = 0; entityIndex < entities.Length; entityIndex++) { var entity = entities[entityIndex]; EntityManager.RemoveComponent(entity, ComponentType.ReadOnly<CreateMeshMarker>()); } #endregion #region NativeArray 開放 meshDataArray.Dispose(); entities.Dispose(); #endregion } EntityQuery query; NativeArray<float> nativeVerticesSource; float* pVerticesSource; NativeArray<float> nativeUVSource; float* pUVSource; List<EntityMeshData> entityMeshDataList = new List<EntityMeshData>(); NativeArray<int3>[] rotationFaceDirectionArray = new NativeArray<int3>[(int)CubeRotationType.Max]; int3** ppRotationFaceDirection; class EntityMeshData { public NativeArray<Vector3> nativeVertices; public NativeArray<Vector2> nativeUV; } struct CountData { public int activeVertexCount; public int reservedFaceOffset; } } internal enum ChunkDataType : int { Category = 0, Rotation, SideA, SideB, Max } internal enum CubeFaceType : byte { TopA = 0, BottomA, RightA, ForwardA, BottomB, TopB, LeftB, BackB, CrossA, CrossB, Max, } internal enum CubeRotationType : byte { Top000 = 0, Top090, Top180, Top270, Bottom000, Bottom090, Bottom180, Bottom270, Forward000, Forward090, Forward180, Forward270, Back000, Back090, Back180, Back270, Right000, Right090, Right180, Right270, Left000, Left090, Left180, Left270, Max }
## 動作確認
回転対応ができたので random に CubeRotationType を ChunkData に与えて、結果のメッシュを確認します。
余計な面が大きなキューブの内側に入っていなければ OK
結果はどうか?
残念 回転タイプ 0~7 までは問題なかったが
8から問題あり
11 まで修正(180度回転ずらせばよかった)
15 まではすでに正しかった。
16は、こちらも不具合あったが 180度回転ずらして修正を確認
19までOK
20から、すでに正しかった。
これでもう一度ランダムに回転を与えて結果を確認します。
上記コードの方、正しいものに修正しておきました。
動くサンプルはこちら
github.com