SurfaceTool
Build one surface incrementally: vertices, attributes, indices and mesh arrays.
SurfaceTool is an owned RefCounted builder rather than an Entity or completed Mesh. Typical order: Begin → attributes before AddVertex → normals/tangents → optional Index → CommitToArrays or Commit. Begin clears old geometry, material, custom formats and skin count; another Begin does not append a second surface. Dispose an owned tool after use.
using var tool = new SurfaceTool();
tool.Begin(Mesh.PrimitiveType.Triangles);
tool.SetUV(Vector2.Zero);
tool.AddVertex(Vector3.Zero);
tool.SetUV(new Vector2(1, 0));
tool.AddVertex(new Vector3(1, 0, 0));
tool.SetUV(new Vector2(0, 1));
tool.AddVertex(new Vector3(0, 1, 0));
tool.GenerateNormals();
tool.GenerateTangents();
using var arrays = tool.CommitToArrays();SetUV/SetNormal/SetColor and other attributes apply to the next AddVertex and remain latched until replaced. Enable every required channel before the first vertex: UV/color cannot first appear after the format has started. Setters do not edit previously added vertices. For triangles each corner triple defines a face; winding determines normals/front-face visibility. GenerateNormals(true) changes normals without reordering indices.
CommitToArrays returns a separate Array of Mesh.ArrayType.Max slots; unused slots contain Nil rather than necessarily empty typed arrays. Slots contain packed arrays rather than one Vertex object each. Commit creates/appends a new ArrayMesh surface and needs separate rendering validation. Assign material with SetMaterial; vertex color only affects output when the material/shader uses it.
Index merges vertices only when their full attributes and smooth group match, not merely positions. UV/normal/color seams retain separate vertices. GenerateNormals averages by matching position and smooth group; uint.MaxValue disables smoothing for a corner. GenerateTangents requires triangles, UVs and normals. The current public binding does not enable tangent split mode; inspect actual-model normal-map seams.
Current skinning limitations: SetSkinWeightCount accepts a value only before begun, but Begin calls Clear and restores four influences. Therefore “SetSkinWeightCount(SurfaceTool.SkinWeightCount.Skin8Weights), then Begin” does not retain eight. Excess influences are sorted ascending and the smaller weights retained. Supply exactly four preselected, normalized influences rather than relying on automatic selection. These cases were reproduced without changing the engine; skeleton animation was not tested here.
CreateFrom/CreateFromArrays replace working state; a following Begin discards the import. AppendFrom does not transfer every source-mesh feature: do not assume preserved blend shapes, LODs or correct normals for arbitrary nonuniform transforms. GenerateLod is deprecated and only returns indices; consider ImporterMesh.GenerateLods for a new pipeline.
25 Windows headless checks established CPU arrays, attribute order, normals/tangents, indexing, simple array import and skinning limitations. GPU Commit/readback, visible surfaces, normal-map seams, skeleton deformation, LODs and rendering performance were not accepted by this suite.
Declarations checked against C# SDK 1.0.0. Expand a name to see declarations and documentation.
Constructors1
SurfaceToolConstructor
public SurfaceTool();Creates an owned RefCounted with no vertices; initial primitive is Lines and skin count is 4. Call Begin before adding vertices.
Methods38
AddIndexvoid
public void AddIndex(int index);Appends a nonnegative index. The setter does not check index < vertexCount: ensure upper bounds and triangle-count divisibility yourself. Creates no new vertex.
| Name | Type | Default |
|---|---|---|
index | int | — |
indexNonnegative existing-vertex index; must be less than vertexCount.
AddTriangleFan3 overloads
public void AddTriangleFan(Vector3[] vertices, Vector2[] uvs, Color[] colors, Vector2[] uv2S, Vector3[] normals, Reckfell.Collections.Array tangents);Expands fan (0,1,2), (0,2,3)… into independent Triangles corners without indices. Requires Begin(Triangles) and at least 3 points. Empty optional channels use latched values; short nonempty arrays can retain latched leftovers, so supply a full value per input vertex. Span overload requires every argument.
| Name | Type | Default |
|---|---|---|
vertices | Vector3[] | — |
uvs | Vector2[] | — |
colors | Color[] | — |
uv2S | Vector2[] | — |
normals | Vector3[] | — |
tangents | Reckfell.Collections.Array | — |
verticesAt least three fan vertices; successive triangles reuse vertex 0.
uvsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
colorsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
uv2SOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
normalsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
tangentsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
public void AddTriangleFan(Vector3[] vertices, Vector2[] uvs = null, Color[] colors = null, Vector2[] uv2S = null, Vector3[] normals = null, Reckfell.Collections.Array<Plane> tangents = null);Expands fan (0,1,2), (0,2,3)… into independent Triangles corners without indices. Requires Begin(Triangles) and at least 3 points. Empty optional channels use latched values; short nonempty arrays can retain latched leftovers, so supply a full value per input vertex. Span overload requires every argument.
| Name | Type | Default |
|---|---|---|
vertices | Vector3[] | — |
uvs | Vector2[] | null |
colors | Color[] | null |
uv2S | Vector2[] | null |
normals | Vector3[] | null |
tangents | Reckfell.Collections.Array<Plane> | null |
verticesAt least three fan vertices; successive triangles reuse vertex 0.
uvsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
colorsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
uv2SOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
normalsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
tangentsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
public void AddTriangleFan(ReadOnlySpan<Vector3> vertices, ReadOnlySpan<Vector2> uvs, ReadOnlySpan<Color> colors, ReadOnlySpan<Vector2> uv2S, ReadOnlySpan<Vector3> normals, Reckfell.Collections.Array<Plane> tangents);Expands fan (0,1,2), (0,2,3)… into independent Triangles corners without indices. Requires Begin(Triangles) and at least 3 points. Empty optional channels use latched values; short nonempty arrays can retain latched leftovers, so supply a full value per input vertex. Span overload requires every argument.
| Name | Type | Default |
|---|---|---|
vertices | ReadOnlySpan<Vector3> | — |
uvs | ReadOnlySpan<Vector2> | — |
colors | ReadOnlySpan<Color> | — |
uv2S | ReadOnlySpan<Vector2> | — |
normals | ReadOnlySpan<Vector3> | — |
tangents | Reckfell.Collections.Array<Plane> | — |
verticesAt least three fan vertices; successive triangles reuse vertex 0.
uvsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
colorsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
uv2SOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
normalsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
tangentsOptional fan channel: empty/supported null uses latched values, otherwise supply one value per input vertex. Tangents use Plane xyz with handedness in D.
AddVertexvoid
public void AddVertex(Vector3 vertex);Copies position and currently latched attributes into the next corner. Requires Begin; channel presence is fixed after the first vertex. Skin bones/weights must have equal lengths; prefer exactly four preprocessed influences.
| Name | Type | Default |
|---|---|---|
vertex | Vector3 | — |
vertexNext-corner position in the generated surface local space.
AppendFromvoid
public void AppendFrom(Mesh existing, int surface, Transform3D transform);Appends one source surface, transforming positions by Transform3D and offsetting indices. Normals use the basis directly rather than inverse transpose, requiring separate correction for nonuniform scale. Reflection flips tangent handedness. Prepare compatible primitive/formats and indexed/nonindexed data before combining.
| Name | Type | Default |
|---|---|---|
existing | Mesh | — |
surface | int | — |
transform | Transform3D | — |
existingNonnull Mesh with accessible data for the selected surface.
surfaceExisting source Mesh surface index rather than a material ID.
transformSource-to-working transform; nonuniform basis does not automatically inverse-transpose normals.
Beginvoid
public void Begin(Mesh.PrimitiveType primitive);Clears state, sets primitive and enables AddVertex. Resets material/custom formats/skin count, so configure them again at the appropriate stage. Does not append a surface to previous geometry.
| Name | Type | Default |
|---|---|---|
primitive | Mesh.PrimitiveType | — |
primitiveMesh primitive mode; normals/tangents/fan require Triangles.
Clearvoid
public void Clear();Clears geometry, indices, material and formats; begun=false, primitive=Lines, skin count=4. Not equivalent to Begin. Call Begin before another AddVertex. Explicitly set new-surface attributes instead of assuming every latched value is reset to zero.
Commit2 overloads
public ArrayMesh Commit(ArrayMesh existing, uint flags);Creates an ArrayMesh or appends a new surface to existing, preserving old surfaces. An empty tool returns the mesh without adding one. Uses current material; neither clears the tool nor supplies blend shapes/LODs. Do not separately dispose an existing alias still used by its owner. Rendering was not tested by the CPU suite.
| Name | Type | Default |
|---|---|---|
existing | ArrayMesh | — |
flags | uint | — |
existingArrayMesh to append to, or null for a new one; result may be the same object.
flagsInteger surface/compression bit mask; primary overload uses ulong with a uint adapter. Not shader/render-feature switches.
ArrayMeshNew owned ArrayMesh or the same existing with an appended surface, not a copy of existing.
public ArrayMesh Commit(ArrayMesh existing = null, ulong flags = 0);Creates an ArrayMesh or appends a new surface to existing, preserving old surfaces. An empty tool returns the mesh without adding one. Uses current material; neither clears the tool nor supplies blend shapes/LODs. Do not separately dispose an existing alias still used by its owner. Rendering was not tested by the CPU suite.
| Name | Type | Default |
|---|---|---|
existing | ArrayMesh | null |
flags | ulong | 0 |
existingArrayMesh to append to, or null for a new one; result may be the same object.
flagsInteger surface/compression bit mask; primary overload uses ulong with a uint adapter. Not shader/render-feature switches.
ArrayMeshNew owned ArrayMesh or the same existing with an appended surface, not a copy of existing.
CommitToArraysReckfell.Collections.Array
public Reckfell.Collections.Array CommitToArrays();Creates a separate untyped Array of Mesh.ArrayType.Max packed-array slots for active channels. Unused slots are Nil. Neither clears the tool nor creates a GPU surface; dispose the returned Array after use.
Reckfell.Collections.ArrayOwned Reckfell.Collections.Array of fixed Mesh.ArrayType slots; dispose after use.
CreateFromvoid
public void CreateFrom(Mesh existing, int surface);Replaces working data with one source Mesh surface, primitive and material; requires accessible arrays and a valid index. Neither begins AddVertex mode nor imports every surface/LOD. A following Begin clears the import.
| Name | Type | Default |
|---|---|---|
existing | Mesh | — |
surface | int | — |
existingNonnull Mesh with accessible data for the selected surface.
surfaceExisting source Mesh surface index rather than a material ID.
CreateFromArraysvoid
public void CreateFromArrays(Reckfell.Collections.Array arrays, Mesh.PrimitiveType primitiveType = Mesh.PrimitiveType.Triangles);Replaces state with coherent Mesh.ArrayType slots; primitiveType defaults to Triangles. Not an arbitrary vertex list. Channel types/counts must match vertexCount. Does not enable begun for subsequent AddVertex.
| Name | Type | Default |
|---|---|---|
arrays | Reckfell.Collections.Array | — |
primitiveType | Mesh.PrimitiveType | Mesh.PrimitiveType.Triangles |
arraysCoherent Mesh.ArrayType slots as returned by CommitToArrays, not an arbitrary vector list.
primitiveTypeMesh primitive mode; normals/tangents/fan require Triangles.
CreateFromBlendShapevoid
public void CreateFromBlendShape(Mesh existing, int surface, string blendShape);Loads one named blend shape from one surface. Positions/normals/tangents come from the shape, remaining channels/indices from the base surface. Creates no complete animation of all shapes; name/index must exist. GPU-backed import was not tested here.
| Name | Type | Default |
|---|---|---|
existing | Mesh | — |
surface | int | — |
blendShape | string | — |
existingNonnull Mesh with accessible data for the selected surface.
surfaceExisting source Mesh surface index rather than a material ID.
blendShapeExact existing source Mesh blend-shape name.
Deindexvoid
public void Deindex();Expands indexed corners into a full vertex list and removes the index channel. No-op without indices. Requires valid indices: upper-bound errors are diagnosed after mutation begins.
GenerateLodint[] · Deprecated
public int[] GenerateLod(float ndThreshold, int targetIndexCount = 3);Deprecated index simplifier; source recommends ImporterMesh.GenerateLods. Requires vertex/index arrays, triangle-divisible indices and 0 ≤ targetIndexCount ≤ currentIndexCount. Locks topological borders; neither installs an LOD in a Mesh nor replaces working geometry. Not executed here because it emits deprecation diagnostics.
| Name | Type | Default |
|---|---|---|
ndThreshold | float | — |
targetIndexCount | int | 3 |
ndThresholdSimplifier error tolerance rather than a normal angle; current call forwards it to Meshoptimizer and locks borders.
targetIndexCountTarget index count rather than triangle count; default 3, actual output depends on simplifier constraints.
int[]int[] alternate triangle indices or empty/diagnosed-failure output, not a finished LOD resource.
GenerateNormalsvoid
public void GenerateNormals(bool flip = false);Recalculates normals for Triangles with corner count divisible by three. Smooth groups use matching positions; uint.MaxValue keeps face normals. flip reverses normals rather than triangle order. Indexed input is temporarily deindexed and reindexed, so vertex count/order can change.
| Name | Type | Default |
|---|---|---|
flip | bool | false |
fliptrue reverses generated normals rather than index winding.
GenerateTangentsvoid
public void GenerateTangents();Generates tangent xyzw through current Meshoptimizer. Requires Triangles, normals, UVs and a corner count divisible by three; degenerate UVs are not repaired into a meaningful unwrap. Public binding uses no-split mode; verify GPU/normal-map quality separately.
GetAabbAabb
public Aabb GetAabb();Computes Aabb from all stored positions, including vertices unused by indices. An empty tool is diagnosed with a zero fallback. Not a MeshInstance3D world-space bound.
AabbLocal Aabb of working vertices; a diagnosed zero fallback for empty input.
GetCustomFormatSurfaceTool.CustomFormat
public SurfaceTool.CustomFormat GetCustomFormat(int channelIndex);Returns custom-channel 0…3 format; Max means unconfigured. A selected format does not establish SetCustom data.
| Name | Type | Default |
|---|---|---|
channelIndex | int | — |
channelIndexCustom-channel index 0…3 rather than a bit mask.
SurfaceTool.CustomFormatChannel CustomFormat; Max if unconfigured or on a diagnosed invalid index.
GetPrimitiveTypeMesh.PrimitiveType
public Mesh.PrimitiveType GetPrimitiveType();Returns current primitive; a new/cleared tool has Lines. Does not validate corner counts for that mode.
Mesh.PrimitiveTypeCurrent Mesh.PrimitiveType; Lines after Clear.
GetSkinWeightCountSurfaceTool.SkinWeightCount
public SurfaceTool.SkinWeightCount GetSkinWeightCount();Returns selected influence count rather than last input-array length. Begin/Clear restore Skin4Weights. Skin8Weights before Begin does not establish a working 8-weight surface.
SurfaceTool.SkinWeightCountCurrent Skin4Weights/Skin8Weights enum rather than successful GPU-skinning evidence.
Indexvoid
public void Index();If no indices exist, merges exact full-Vertex duplicates and creates indices. Compares attributes/smooth group rather than position alone; not tolerance welding. Existing indices make this a no-op.
OptimizeIndicesForCachevoid
public void OptimizeIndicesForCache();Reorders triangle indices for vertex-cache use. Requires nonempty valid indices, Triangles, divisibility by three and initialized Meshoptimizer. Promises neither fewer vertices nor speedup on a particular GPU.
SetBones2 overloads
public void SetBones(int[] bones);Sets next-vertex bone indices. Requires SetWeights of matching length; short pairs zero-pad, while current excess handling retains smaller weights. Supply exactly four valid influences in the ordinary Begin workflow.
| Name | Type | Default |
|---|---|---|
bones | int[] | — |
bonesBone indices matching weights length; the ordinary safe workflow uses exactly 4.
public void SetBones(ReadOnlySpan<int> bones);Sets next-vertex bone indices. Requires SetWeights of matching length; short pairs zero-pad, while current excess handling retains smaller weights. Supply exactly four valid influences in the ordinary Begin workflow.
| Name | Type | Default |
|---|---|---|
bones | ReadOnlySpan<int> | — |
bonesBone indices matching weights length; the ordinary safe workflow uses exactly 4.
SetColorvoid
public void SetColor(Color color);Enables COLOR before the first vertex and sets color for subsequent AddVertex calls. After the first vertex only an already-enabled channel can change. Does not enable vertex-color use in the material.
| Name | Type | Default |
|---|---|---|
color | Color | — |
colorNext-corner color; material must explicitly use vertex color.
SetCustomvoid
public void SetCustom(int channelIndex, Color customColor);Sets next-vertex components through Color without implying color semantics. Requires SetCustomFormat and channel enablement before the first vertex. Format determines components, precision and array encoding.
| Name | Type | Default |
|---|---|---|
channelIndex | int | — |
customColor | Color | — |
channelIndexCustom-channel index 0…3 rather than a bit mask.
customColorCustom-data components in RGBA fields; format defines encoding rather than color space.
SetCustomFormatvoid
public void SetCustomFormat(int channelIndex, SurfaceTool.CustomFormat format);Sets custom-channel 0…3 storage format after Begin and before the first vertex. Does not enable the channel itself: call SetCustom before AddVertex. Do not reinterpret an already-populated channel; Max denotes an unconfigured format.
| Name | Type | Default |
|---|---|---|
channelIndex | int | — |
format | SurfaceTool.CustomFormat | — |
channelIndexCustom-channel index 0…3 rather than a bit mask.
formatStorage CustomFormat; set after Begin before enabling/populating the channel.
SetMaterialvoid
public void SetMaterial(Material material);Stores a shared Material for the next Commit. Neither enables COLOR/UV channels nor updates existing surfaces. Begin/Clear resets the reference.
| Name | Type | Default |
|---|---|---|
material | Material | — |
materialShared Material or null; assigned to the next committed surface.
SetNormalvoid
public void SetNormal(Vector3 normal);Sets the next vertex normal without editing previous vertices. Supply a unit vector; the setter does not normalize automatically. Enable the channel before the first vertex or generate it with GenerateNormals.
| Name | Type | Default |
|---|---|---|
normal | Vector3 | — |
normalUnit next-corner normal in position coordinates.
SetSkinWeightCountvoid
public void SetSkinWeightCount(SurfaceTool.SkinWeightCount count);Accepts the enum only while begun=false. However Begin calls Clear and restores Skin4Weights: setting Skin8Weights before Begin does not provide eight influences. Reproduced without an engine fix; a working 8-weight workflow is not established.
| Name | Type | Default |
|---|---|---|
count | SurfaceTool.SkinWeightCount | — |
countSkinWeightCount enum; account for Begin resetting it to 4.
SetSmoothGroupvoid
public void SetSmoothGroup(uint index);Sets the uint group of subsequent vertices for GenerateNormals and exact indexing. Default 0; uint.MaxValue means flat shading. Does not immediately change normals or material.
| Name | Type | Default |
|---|---|---|
index | uint | — |
indexuint smooth-group ID; uint.MaxValue disables smoothing.
SetTangentvoid
public void SetTangent(Plane tangent);Plane is a tangent container: normal → xyz and D → handedness w, normally ±1. Not a geometric surface plane. Enable the channel before the first vertex.
| Name | Type | Default |
|---|---|---|
tangent | Plane | — |
tangentPlane: Normal stores tangent xyz, D the handedness sign, normally ±1.
SetUVvoid
public void SetUV(Vector2 uV);Sets the next vertex UV; enable the channel before the first AddVertex. Neither unwraps automatically nor scales positions. UVs are normally normalized, but not clamped to 0…1.
| Name | Type | Default |
|---|---|---|
uV | Vector2 | — |
uVNext-corner UV rather than a vertex position or completed unwrap.
SetUV2void
public void SetUV2(Vector2 uV2);Sets the next vertex second UV channel; enable before the first vertex. Neither generates a lightmap unwrap nor bakes lighting.
| Name | Type | Default |
|---|---|---|
uV2 | Vector2 | — |
uV2Next-corner UV rather than a vertex position or completed unwrap.
SetWeights2 overloads
public void SetWeights(ReadOnlySpan<float> weights);Sets next-vertex weights; neither selects a skeleton nor automatically normalizes exact/short lists. Requires SetBones with matching length. Prenormalize exactly four influences instead of relying on faulty excess selection.
| Name | Type | Default |
|---|---|---|
weights | ReadOnlySpan<float> | — |
weightsNonnegative normalized weights paired with bones, normally exactly 4.
public void SetWeights(float[] weights);Sets next-vertex weights; neither selects a skeleton nor automatically normalizes exact/short lists. Requires SetBones with matching length. Prenormalize exactly four influences instead of relying on faulty excess selection.
| Name | Type | Default |
|---|---|---|
weights | float[] | — |
weightsNonnegative normalized weights paired with bones, normally exactly 4.
SurfaceTool.CustomFormat
| Value | Description |
|---|---|
Max = 8 | Used to indicate a disabled custom channel. |
RFloat = 4 | Stores data passed to |
RgFloat = 5 | Stores data passed to |
RgHalf = 2 | Stores data passed to |
RgbFloat = 6 | Stores data passed to |
Rgba8Snorm = 1 | Limits range of data passed to |
Rgba8Unorm = 0 | Limits range of data passed to |
RgbaFloat = 7 | Stores data passed to |
RgbaHalf = 3 | Stores data passed to |
SurfaceTool.SkinWeightCount
| Value | Description |
|---|---|
Skin4Weights = 0 | Each individual vertex can be influenced by only 4 bone weights. |
Skin8Weights = 1 | Each individual vertex can be influenced by up to 8 bone weights. |