Game.Rendering.ObjectMeshHelpers
Assembly: Game (Assembly-CSharp)
Namespace: Game.Rendering
Type: static class
Base: System.Object
Summary:
Utility helpers for converting Unity Meshes and in-game GeometryAsset data into ECS-friendly buffers and spatial indexing structures used by the rendering pipeline. This class provides:
- Burst-compiled jobs that cache mesh vertex/index/normal data and build a spatial tree (octree-like) of MeshNode entries for efficient culling and lookup.
- Special handling for skinned/procedural meshes that contain bone indices/weights (building per-bone triangle lists and bounds).
- Convenience functions to cache/un-cache data immediately from UnityEngine.Mesh and to create simple default placeholder meshes used by the engine.
This class is intended for use with Unity.Entities (ECS), Unity.Jobs, and Burst. It writes into DynamicBuffers on an Entity: - MeshVertex - MeshIndex - MeshNode - MeshNormal (optional)
Fields
private struct TreeNode
Represents a temporary node used when building the internal tree (spatial index). Contains:- Bounds3 m_Bounds — accumulated bounds of contained triangles
- int m_FirstTriangle — head index into a linked list of triangles for the node
- int m_ItemCount — number of triangles/items in the node
-
int m_NodeIndex — index used when converting temporary nodes into final MeshNode table
-
private struct CacheMeshDataJob
(BurstCompile, IJob)
Burst job that reads GeometryAsset.Data or raw slices and fills DynamicBuffers MeshVertex, MeshIndex, MeshNode and optionally MeshNormal on an Entity via an EntityCommandBuffer. It: - Unpacks positions (and normals if requested) into MeshVertex/MeshNormal buffers.
- Converts indices to 32-bit when dealing with geometry assets.
- Builds a spatial tree (treeDepth/treeSize) and converts it into MeshNode / MeshIndex buffers.
-
Uses temporary NativeArray allocations; disposes them when done.
-
private struct CacheProceduralMeshDataJob
(BurstCompile, IJob)
Burst job variant for procedural/skinned meshes (boneCount != 0). In addition to CacheMeshDataJob responsibilities it: - Unpacks bone ID and weight attributes.
- Builds per-bone triangle ranges (BoneData) and creates per-bone MeshNode ranges in the final buffers.
-
Handles complex allocation patterns to produce final combined buffers suitable for rendering/skinning.
-
private struct BoneData
Temporary structure used by the procedural path to track per-bone bounds and triangle ranges: - Bounds3 m_Bounds
- int2 m_TriangleRange (start,end)
- int m_TriangleCount
Properties
- (None public on this static helper type.)
Note: the class exposes static methods only — there are no public properties to consume.
Constructors
- (None — static helper class)
There is no instance constructor. All functionality is exposed via static methods.
Methods
Public API (most important):
-
public static Mesh CreateDefaultMesh()
Creates a 24-vertex cube-like default "object" mesh (with normals, tangents, uv and triangles) typically used as a placeholder object mesh. -
public static Mesh CreateDefaultBaseMesh()
Creates a simpler default base mesh (used as a base geometry placeholder) with its own vertices/tangents/normals/uv/triangles. -
public static JobHandle CacheMeshData(RenderPrefab meshPrefab, GeometryAsset meshData, Entity entity, int boneCount, bool cacheNormals, EntityCommandBuffer commandBuffer)
Schedules and returns a JobHandle for caching mesh data from a GeometryAsset (or the procedural path if boneCount != 0). When boneCount is non-zero the CacheProceduralMeshDataJob is scheduled; otherwise CacheMeshDataJob is scheduled. The job will add and fill DynamicBuffers on the passed Entity via the provided EntityCommandBuffer. -
public static void CacheMeshData(Mesh mesh, Entity entity, bool cacheNormals, EntityCommandBuffer commandBuffer)
Immediate (main-thread) method that reads data from a UnityEngine.Mesh (using Mesh.AcquireReadOnlyMeshData) and writes to the Entity's DynamicBuffers MeshVertex, MeshIndex and optionally MeshNormal. Useful when you don't want to schedule a job (or when the source is a standard Mesh rather than a GeometryAsset). -
public static void UncacheMeshData(Entity entity, EntityCommandBuffer commandBuffer)
Removes MeshVertex, MeshNormal, MeshIndex and MeshNode components from the Entity via the commandBuffer. Use this to clean up previously cached data.
Internal/private helpers (high-level descriptions):
-
AddFace / AddBaseFace
Create quad faces used by the default mesh builders; populate vertex arrays (positions, normals, tangents, uv) and triangle indices. -
InitializeBones(NativeArray
bones, int indexCount)
Initialize per-bone data arrays used for procedural/skinned meshes. -
FillBoneData(...)
Parse bone ID and weight vertex attributes and populate BoneData and a per-triangle bone index array. Handles different attribute formats (UInt8/UInt32 for bone IDs, Float32/UNorm8 for weights) and both 16/32-bit index buffers. -
AddTriangle(...) (several overloads)
- For bone assignment: Determines which bone (if any) owns a triangle and accumulates bounds/triangle counts into BoneData.
-
For spatial tree insertion: Insert a triangle into the appropriate TreeNode bucket, updating bounds and linked lists.
-
FillIndices / FillTreeNodes (overloads for ushort/int index buffers)
Copy/transform indices into temporary arrays used for building trees for the IndexFormat in question. -
CalculateTreeSize(int indexCount, Bounds3 bounds, out int treeDepth, out int treeSize, out float3 sizeFactor, out float3 sizeOffset)
Compute tree depth and size for the spatial index based on triangle count and bounds. Also computes normalization factors (sizeFactor/sizeOffset) used to map triangle centers into grid coordinates. -
InitializeTree(NativeArray
treeNodes, int treeSize)
Prepare temporary tree node array (set bounds to extreme values and m_FirstTriangle = -1). -
UpdateNodes(NativeArray
treeNodes, int treeDepth, int* depthOffsets)
Walk upward through tree levels, aggregate child bounds into parents, assign node indices for nodes that will become MeshNode entries, and compute offsets for each depth level. Uses unsafe stack pointers for performance. -
AddBounds(ref TreeNode targetNode, ref int sourceSize, NativeArray
treeNodes, int sourceIndex)
Helper used during UpdateNodes to merge child node bounds into parent and assign source node indices. -
FillMeshData(...) (multiple overloads for int/ushort indices and different input arrays)
Convert the temporary TreeNode/triangle linked list representation into final MeshNode and MeshIndex buffers. This method produces the compact MeshNode table and flattens triangle indices into MeshIndex components used by the renderer. Handles packing subnode indices into two int4 fields (m_SubNodes1 / m_SubNodes2) and assigns IndexRange for triangle ranges.
Notes on implementation and behavior: - The two major jobs (CacheMeshDataJob and CacheProceduralMeshDataJob) are Burst-compiled, use unsafe pointers and low-level NativeArray operations to maximize throughput. - When handling GeometryAsset.Data, indices are converted to 32-bit to simplify processing (GeometryAsset.ConvertAllIndicesTo32). - Temporary NativeArray allocations (e.g., for converted indices, TreeNode arrays, triangle-next lists, bone arrays) are allocated with Allocator.Temp and disposed inside the jobs — be careful to not leak if modifying code. - The algorithm builds an octree-like regular grid tree (deciding depth based on triangle count) and then compacts it into a node table suitable for per-entity rendering culling.
Usage Example
Scheduling the job (geometry asset path):
// Assume meshPrefab (RenderPrefab) and meshData (GeometryAsset) are available,
// entity is an Entity to receive mesh buffers, and ecb is an EntityCommandBuffer.
int boneCount = 0; // >0 for skinned/procedural meshes
bool cacheNormals = true;
JobHandle handle = ObjectMeshHelpers.CacheMeshData(meshPrefab, meshData, entity, boneCount, cacheNormals, ecb);
// You should manage dependencies and complete the handle appropriately.
Immediate main-thread caching (UnityEngine.Mesh):
// Assume mesh is a UnityEngine.Mesh, entity and ecb are available
bool cacheNormals = true;
ObjectMeshHelpers.CacheMeshData(mesh, entity, cacheNormals, ecb);
Uncaching:
ObjectMeshHelpers.UncacheMeshData(entity, ecb);
Additional notes: - The ECS DynamicBuffer element types (MeshVertex, MeshIndex, MeshNode, MeshNormal) are expected to be defined by the rendering system. The helper writes into these buffers — ensure your Entity archetype or systems expect/use those buffers. - Because the Burst jobs use unsafe code and temporary NativeArray allocations, ensure you follow Unity's job safety and memory rules; schedule/combine job dependencies correctly and Dispose any allocated arrays if you extend the code. - For modding Cities: Skylines 2, use existing game-provided GeometryAsset/RenderPrefab APIs; mismatches in attribute formats (missing positions/normals, unsupported bone formats) will throw exceptions in the helper routines — handle these cases when preparing assets.