Symbols in this topic
bevelfunctionbridgefunctiondissolveEdgesfunctiondissolveFacesfunctiondissolveVerticesfunctionEdgeViewtypeEditableMeshclassEditableMeshValuetypeElementDomaintypeElementSelectiontypeElementSettypeextrudefunctionFaceViewtypefillHolefunctiongridFillfunctionhealManifoldfunctioninsetfunctionKernelHandletypeloopCutfunctionmergeByDistancefunctionMeshEditErrortypeMeshEditWarningtyperecomputeNormalsfunctionScoredSelectiontypesubdivideEdgesfunctionsubdivideFacesfunctiontoEditablefunctiontoEditableWithDiagnosticsfunctionVertexViewtype@vgpu/render/edit — Mesh Editing
CPU-side triangle mesh editing built on the render package's half-edge kernel. Use this entrypoint to convert render meshes into EditableMesh, select topology, run edit operators, and bake back to render meshes after the edit pipeline.
Index
- Creation and conversion: EditableMesh, toEditable, toEditableWithDiagnostics
- Selection and views: EditableMeshValue, ElementDomain, ElementSelection, ElementSet, ScoredSelection, VertexView, EdgeView, FaceView, KernelHandle
- Shape editing: extrude, bevel, inset
- Subdivision and cuts: subdivideEdges, subdivideFaces, loopCut
- Topology fill/bridge: bridge, fillHole, gridFill
- Dissolve/weld/heal/bake: dissolveVertices, dissolveEdges, dissolveFaces, mergeByDistance, healManifold, recomputeNormals
- Diagnostics: MeshEditError, MeshEditWarning
All imports in this file use the public edit entrypoint:
import { EditableMesh } from "@vgpu/render/edit";EditableMesh
Factory object for creating and baking EditableMeshValue instances. Use fromArrays when you already have typed geometry arrays; use EditableMesh.toRenderMesh or mesh.toRenderMesh only after the final edit step.
Import
import { EditableMesh } from "@vgpu/render/edit";Signature
import type { Device } from "@vgpu/core";
import type { EditableMeshValue } from "@vgpu/render/edit";
declare const EditableMesh: {
fromArrays(opts: {
readonly positions: Float32Array;
readonly normals?: Float32Array;
readonly uvs?: Float32Array;
readonly colors?: Float32Array;
readonly indices?: Uint16Array | Uint32Array;
readonly sharpEdges?: Uint8Array;
readonly useSmooth?: Uint8Array;
readonly creaseAngle?: number;
}): EditableMeshValue;
toRenderMesh(em: EditableMeshValue, opts: { readonly device: Device }): unknown;
};Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| opts.positions | Float32Array | ✔ | — | XYZ triples. Vertices with identical XYZ are welded by position during kernel build. |
| opts.indices | Uint16Array | Uint32Array | ✖ | sequential 0..positions.length / 3 - 1 | Triangle indices; length must represent triangles. |
| opts.normals | Float32Array | ✖ | omitted | Preserved only as hasNormals; edit operators recompute face topology. |
| opts.uvs | Float32Array | ✖ | omitted | Preserved only as hasUVs; operators may drop seams. |
| opts.colors | Float32Array | ✖ | omitted | Preserved only as hasVertexColors. |
| opts.sharpEdges | Uint8Array | ✖ | auto from creaseAngle | Per-edge sharp mask in kernel edge order; if present it overrides auto-sharp detection. |
| opts.useSmooth | Uint8Array | ✖ | all faces smooth (1) | Per-face smoothing flags. |
| opts.creaseAngle | number | ✖ | Math.PI / 6 | Radians used to auto-mark sharp edges when sharpEdges is omitted. |
| em | EditableMeshValue | ✔ | — | Mesh to bake for static EditableMesh.toRenderMesh. |
| opts.device | Device | ✔ | — | Device used to create the render mesh buffers. |
Returns: EditableMeshValue from fromArrays; render Mesh from toRenderMesh.
Throws: — no MeshEditError is thrown directly. Invalid or mismatched raw arrays can still produce invalid geometry at JavaScript/WebGPU boundaries.
Examples
import { EditableMesh } from "@vgpu/render/edit";
const editableMeshExample = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
indices: new Uint32Array([0, 1, 2]),
});
const editableFaceCount: number = editableMeshExample.faceCount;Notes
- The editable kernel is triangle-only; higher-order fills are represented as deterministic triangles.
- Bake once at the end of a pipeline instead of after every operator.
- See also:
toEditable,toEditableWithDiagnostics,EditableMeshValue,recomputeNormals.
toEditable
Converts a render Mesh into an editable half-edge mesh and discards diagnostics. Use when warnings are not important; otherwise call toEditableWithDiagnostics.
Import
import { toEditable } from "@vgpu/render/edit";Signature
import { toEditable } from "@vgpu/render/edit";
import type { EditableMeshValue } from "@vgpu/render/edit";
type EditableInputMesh = Parameters<typeof toEditable>[0];
declare function toEditableSignature(mesh: EditableInputMesh, opts?: { readonly creaseAngle?: number }): EditableMeshValue;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| mesh | Mesh | ✔ | — | Render mesh-like object with attributes and bounds. Source arrays are used when available; otherwise a bbox box fallback is built. |
| opts.creaseAngle | number | ✖ | Math.PI / 6 through EditableMesh.fromArrays | Radians for auto-sharp edge detection. |
Returns: EditableMeshValue — editable mesh ready for selections/operators.
Throws: — no MeshEditError is thrown directly.
Examples
import { toEditable } from "@vgpu/render/edit";
const renderMeshForEdit = {
vertexBuffer: {},
vertexCount: 3,
attributes: { stride: 12, position: { offset: 0, format: "float32x3" as const } },
bbox: { min: new Float32Array([0, 0, 0]), max: new Float32Array([1, 1, 1]) },
} as unknown as Parameters<typeof toEditable>[0];
const toEditableMesh = toEditable(renderMeshForEdit, { creaseAngle: Math.PI / 4 });Notes
- Tangent-stripping warnings are hidden by this convenience function.
- See also:
toEditableWithDiagnostics,EditableMesh,MeshEditWarning.
toEditableWithDiagnostics
Converts a render Mesh and returns warnings such as stripped tangents. Use this at import/conversion boundaries so LLM-generated pipelines do not silently lose render-layer data.
Import
import { toEditableWithDiagnostics } from "@vgpu/render/edit";Signature
import { toEditableWithDiagnostics } from "@vgpu/render/edit";
import type { EditableMeshValue, MeshEditWarning } from "@vgpu/render/edit";
type DiagnosticInputMesh = Parameters<typeof toEditableWithDiagnostics>[0];
declare function toEditableWithDiagnosticsSignature(
mesh: DiagnosticInputMesh,
opts?: { readonly creaseAngle?: number },
): { readonly mesh: EditableMeshValue; readonly warnings: readonly MeshEditWarning[] };Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| mesh | Mesh | ✔ | — | Render mesh-like object. If edit-source arrays are absent, bbox fallback arrays are generated. |
| opts.creaseAngle | number | ✖ | Math.PI / 6 through EditableMesh.fromArrays | Radians for auto-sharp edge detection. |
Returns: { mesh, warnings } — warnings is an array of MeshEditWarning objects.
Throws: — no MeshEditError is thrown directly.
Examples
import { toEditableWithDiagnostics } from "@vgpu/render/edit";
const renderMeshWithDiagnostics = {
vertexBuffer: {},
vertexCount: 3,
attributes: { stride: 12, position: { offset: 0, format: "float32x3" as const } },
bbox: { min: new Float32Array([0, 0, 0]), max: new Float32Array([1, 1, 1]) },
} as unknown as Parameters<typeof toEditableWithDiagnostics>[0];
const diagnostics = toEditableWithDiagnostics(renderMeshWithDiagnostics);
const diagnosticsWarnings = diagnostics.warnings.map((warning) => warning.code);Notes
- Current explicit warning source is
TANGENTS_STRIPPEDwhenmesh.attributes.tangentexists. - See also:
toEditable,MeshEditWarning,EditableMesh.
EditableMeshValue
Runtime shape of an editable mesh. It exposes counts, bounds, typed element sets, material/topology flags, hard-edge selection, an opaque kernel handle, and toRenderMesh.
Import
import type { EditableMeshValue } from "@vgpu/render/edit";Signature
import type { Device } from "@vgpu/core";
import type { ElementSelection, ElementSet, KernelHandle } from "@vgpu/render/edit";
type Vec3 = Float32Array;
declare interface EditableMeshValue {
readonly vertexCount: number;
readonly edgeCount: number;
readonly faceCount: number;
readonly bounds: { readonly min: Vec3; readonly max: Vec3 };
readonly vertices: ElementSet<"vertex">;
readonly edges: ElementSet<"edge">;
readonly faces: ElementSet<"face">;
readonly isManifold: boolean;
readonly hasUVs: boolean;
readonly hasNormals: boolean;
readonly hasVertexColors: boolean;
readonly hardEdges: ElementSelection;
readonly gpu: { readonly halfEdgeKernel: KernelHandle };
toRenderMesh(opts: { readonly device: Device }): unknown;
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| vertexCount / edgeCount / faceCount | number | ✔ | — | Counts in the current immutable editable mesh value. |
| bounds.min / bounds.max | Vec3 | ✔ | — | Axis-aligned bounds computed from positions. |
| vertices / edges / faces | ElementSet | ✔ | — | Selection factories and traversal helpers for each domain. |
| isManifold | boolean | ✔ | — | true only when every edge has two incident faces in the current kernel. |
| hasUVs / hasNormals / hasVertexColors | boolean | ✔ | — | Flags copied from input arrays; most operators rebuild topology arrays. |
| hardEdges | ElementSelection | ✔ | — | Edge selection where the kernel isSharp mask is nonzero. |
| gpu.halfEdgeKernel | KernelHandle | ✔ | — | Opaque handle; do not construct or mutate directly. |
| toRenderMesh | function | ✔ | — | Bakes the editable mesh with { device }. |
Returns: N/A — this is an interface/type export.
Throws: N/A — toRenderMesh can fail at render/WebGPU boundaries if passed an invalid Device.
Examples
import { EditableMesh, type EditableMeshValue } from "@vgpu/render/edit";
const editableValueExample: EditableMeshValue = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const editableBoundsMin = editableValueExample.bounds.min;Notes
- Operators return new
EditableMeshValueobjects; do not assume selections from an old mesh are valid on a new mesh unless the result explicitly returns them. - See also:
ElementSet,ElementSelection,KernelHandle,EditableMesh.
ElementDomain
String union naming the selectable topology domains.
Import
import type { ElementDomain } from "@vgpu/render/edit";Signature
export type ElementDomain = "vertex" | "edge" | "face" | "loop";Parameters
| Variant | Type | Required | Default | Notes |
|---|---|---|---|---|
"vertex" | ElementDomain | ✔ | — | Vertex selections and VertexView. |
"edge" | ElementDomain | ✔ | — | Edge selections, loops, rings, bridge/fill boundaries. |
"face" | ElementDomain | ✔ | — | Face selections for extrusion/inset/dissolve. |
"loop" | ElementDomain | ✔ | — | Declared domain variant; public element sets currently operate on vertex/edge/face. |
Returns: N/A — type alias. Throws: N/A.
Examples
import type { ElementDomain } from "@vgpu/render/edit";
const selectedDomain: ElementDomain = "edge";Notes
- Operator validation is strict: passing a selection with the wrong domain throws
WRONG_DOMAIN. - See also:
ElementSelection,ElementSet.
ElementSelection
Immutable selection object passed to operators. Use ElementSet helpers (mesh.faces.byIndex, mesh.edges.loop, etc.) instead of hand-building selections unless you need an ordered boundary loop.
Import
import type { ElementSelection } from "@vgpu/render/edit";Signature
import type { ElementDomain } from "@vgpu/render/edit";
declare interface ElementSelection {
readonly domain: ElementDomain;
readonly indices: ReadonlyArray<number>;
readonly count: number;
readonly ordered?: boolean;
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| domain | ElementDomain | ✔ | — | Must match the operator target domain. |
| indices | ReadonlyArray<number> | ✔ | — | Element indices in the mesh that owns the selection. |
| count | number | ✔ | — | Usually indices.length; operators check count === 0 for empty selections. |
| ordered | boolean | ✖ | omitted / false | Required as true for loop-boundary operators (bridge, fillHole, gridFill). |
Returns: N/A — interface. Throws: N/A.
Examples
import { EditableMesh, type ElementSelection } from "@vgpu/render/edit";
const selectionMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const oneFaceSelection: ElementSelection = selectionMesh.faces.byIndex([0]);Notes
- Selections are mesh-local. Do not reuse a selection from the input mesh against an operator result unless the operator returned that selection for the new mesh.
- See also:
ElementSet,ScoredSelection,MeshEditError.
ElementSet
Domain-specific helper collection available as mesh.vertices, mesh.edges, and mesh.faces. Use it to create validated selections and compute simple adjacency expansions.
Import
import type { ElementSet } from "@vgpu/render/edit";Signature
import type { ElementDomain, ElementSelection, EdgeView, FaceView, ScoredSelection, VertexView } from "@vgpu/render/edit";
type ElementView<D extends ElementDomain> = D extends "vertex" ? VertexView : D extends "edge" ? EdgeView : D extends "face" ? FaceView : never;
declare interface ElementSet<D extends ElementDomain> {
readonly domain: D;
readonly count: number;
where(pred: (e: ElementView<D>) => boolean): ElementSelection;
scoreBy(score: (e: ElementView<D>) => number): ScoredSelection;
byIndex(indices: readonly number[]): ElementSelection;
all(): ElementSelection;
none(): ElementSelection;
loop(seedEdge: number): D extends "edge" ? ElementSelection : never;
ring(seedEdge: number): D extends "edge" ? ElementSelection : never;
grow(sel: ElementSelection, layers?: number): ElementSelection;
shrink(sel: ElementSelection, layers?: number): ElementSelection;
boundaryOf(sel: ElementSelection): ElementSelection;
connectedComponentOf(seed: number): ElementSelection;
}Parameters
| Method/Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| domain | D | ✔ | — | "vertex", "edge", or "face" for the owning set. |
| count | number | ✔ | — | Number of elements in the owning domain. |
| where.pred | function | ✔ | — | Called with VertexView, EdgeView, or FaceView; returned indices are sorted/deduped. |
| scoreBy.score | function | ✔ | — | Produces a ScoredSelection sorted by score descending. |
| byIndex.indices | readonly number[] | ✔ | — | Out-of-range indices are filtered out. |
| loop.seedEdge / ring.seedEdge | number | ✔ | — | For edge sets only; current implementation returns connected edge walk with ordered: true. |
| grow.layers / shrink.layers | number | ✖ | 1 | Number of adjacency layers to expand or contract. |
| boundaryOf.sel | ElementSelection | ✔ | — | Returns an edge selection around face/vertex selections; edge input returns itself. |
| connectedComponentOf.seed | number | ✔ | — | Flood-fills adjacent elements in the same domain. |
Returns: ElementSelection or ScoredSelection depending on the method.
Throws: — no MeshEditError is thrown directly by the public methods.
Examples
import { EditableMesh } from "@vgpu/render/edit";
const elementSetMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const longEdges = elementSetMesh.edges.where((edge) => edge.length > 0.5);Notes
loopandringare typed for edge sets; do not call them onverticesorfaces.- See also:
ElementSelection,ScoredSelection,VertexView,EdgeView,FaceView.
ScoredSelection
Ranked selection helper returned by ElementSet.scoreBy. Use it to pick strongest or weakest candidates without manually sorting indices.
Import
import type { ScoredSelection } from "@vgpu/render/edit";Signature
import type { ElementDomain, ElementSelection } from "@vgpu/render/edit";
declare interface ScoredSelection {
readonly domain: ElementDomain;
readonly entries: ReadonlyArray<{ readonly index: number; readonly score: number }>;
top(): ElementSelection;
topN(n: number): ElementSelection;
threshold(min: number): ElementSelection;
bottom(): ElementSelection;
bottomN(n: number): ElementSelection;
}Parameters
| Method/Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| domain | ElementDomain | ✔ | — | Domain of all ranked entries. |
| entries | ReadonlyArray | ✔ | — | Sorted by score descending, then index ascending. |
| top / bottom | function | ✔ | — | Equivalent to topN(1) / bottomN(1). |
| topN.n / bottomN.n | number | ✔ | — | Negative values clamp to 0. |
| threshold.min | number | ✔ | — | Keeps entries with score >= min. |
Returns: ElementSelection from ranking methods.
Throws: — no MeshEditError is thrown directly.
Examples
import { EditableMesh, type ScoredSelection } from "@vgpu/render/edit";
const scoredMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 2, 0, 0, 0, 1, 0]),
});
const scoredEdges: ScoredSelection = scoredMesh.edges.scoreBy((edge) => edge.length);
const longestEdge = scoredEdges.top();Notes
bottomNre-sorts ascending at call time;entriesremains descending.- See also:
ElementSet,ElementSelection.
VertexView
Read-only per-vertex data passed to vertex where/scoreBy callbacks.
Import
import type { VertexView } from "@vgpu/render/edit";Signature
type Vec3 = Float32Array;
declare interface VertexView {
readonly index: number;
readonly position: Vec3;
readonly normal: Vec3;
readonly valence: number;
readonly isBoundary: boolean;
readonly isManifold: boolean;
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| index | number | ✔ | — | Vertex index in the current mesh. |
| position | Vec3 | ✔ | — | XYZ position. |
| normal | Vec3 | ✔ | — | Kernel-computed vertex normal. |
| valence | number | ✔ | — | Number of incident edges. |
| isBoundary | boolean | ✔ | — | True if any incident edge is boundary. |
| isManifold | boolean | ✔ | — | True when local incident topology is manifold. |
Returns: N/A — interface. Throws: N/A.
Examples
import { EditableMesh, type VertexView } from "@vgpu/render/edit";
const vertexViewMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const boundaryVertices = vertexViewMesh.vertices.where((vertex: VertexView) => vertex.isBoundary);Notes
- Views are snapshots produced by callbacks; do not store them as stable handles across edits.
- See also:
ElementSet,EdgeView,FaceView.
EdgeView
Read-only per-edge data passed to edge where/scoreBy callbacks.
Import
import type { EdgeView } from "@vgpu/render/edit";Signature
type Vec3 = Float32Array;
declare interface EdgeView {
readonly index: number;
readonly midpoint: Vec3;
readonly length: number;
readonly direction: Vec3;
readonly vertexA: number;
readonly vertexB: number;
readonly faceA: number | null;
readonly faceB: number | null;
readonly isBoundary: boolean;
readonly isManifold: boolean;
readonly isSharp: boolean;
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| index | number | ✔ | — | Edge index in current mesh. |
| midpoint / direction | Vec3 | ✔ | — | Derived from endpoints. |
| length | number | ✔ | — | Euclidean length. |
| vertexA / vertexB | number | ✔ | — | Endpoint vertex indices. |
| faceA / faceB | number | null | ✔ | — | Incident faces; faceB === null indicates boundary. |
| isBoundary | boolean | ✔ | — | True for one-sided edges. |
| isManifold | boolean | ✔ | — | True when the edge has manifold incidence. |
| isSharp | boolean | ✔ | — | True when the kernel sharp mask is set. |
Returns: N/A — interface. Throws: N/A.
Examples
import { EditableMesh, type EdgeView } from "@vgpu/render/edit";
const edgeViewMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const sharpEdges = edgeViewMesh.edges.where((edge: EdgeView) => edge.isSharp);Notes
- Use
mesh.hardEdgeswhen you just need the current sharp-edge selection. - See also:
VertexView,FaceView,bevel,subdivideEdges.
FaceView
Read-only per-face data passed to face where/scoreBy callbacks.
Import
import type { FaceView } from "@vgpu/render/edit";Signature
type Vec3 = Float32Array;
declare interface FaceView {
readonly index: number;
readonly center: Vec3;
readonly normal: Vec3;
readonly area: number;
readonly vertexCount: number;
readonly vertexIndices: ReadonlyArray<number>;
readonly edgeIndices: ReadonlyArray<number>;
readonly useSmooth: boolean;
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| index | number | ✔ | — | Face index in current mesh. |
| center / normal | Vec3 | ✔ | — | Derived from triangle vertices and face normal. |
| area | number | ✔ | — | Triangle area. |
| vertexCount | number | ✔ | — | Always 3 for the triangle-only editable kernel. |
| vertexIndices / edgeIndices | ReadonlyArray<number> | ✔ | — | Triangle vertex/edge indices. |
| useSmooth | boolean | ✔ | — | Per-face smoothing flag. |
Returns: N/A — interface. Throws: N/A.
Examples
import { EditableMesh, type FaceView } from "@vgpu/render/edit";
const faceViewMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const upwardFaces = faceViewMesh.faces.where((face: FaceView) => face.normal[2] > 0);Notes
vertexCountis still exposed so future kernels can remain source-compatible with code that checks it.- See also:
ElementSet,extrude,inset,dissolveFaces.
KernelHandle
Opaque branded handle to the internal half-edge kernel. It exists so editable values can carry kernel data without exposing mutation APIs.
Import
import type { KernelHandle } from "@vgpu/render/edit";Signature
declare const kernelBrand: unique symbol;
export type KernelHandle = { readonly [kernelBrand]: never };Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| branded property | unique symbol | ✔ | — | Compile-time brand only; not constructible through public API. |
Returns: N/A — type alias. Throws: N/A.
Examples
import { EditableMesh, type KernelHandle } from "@vgpu/render/edit";
const kernelHandleMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const kernelHandle: KernelHandle = kernelHandleMesh.gpu.halfEdgeKernel;Notes
- Do not serialize or construct a
KernelHandle; use public operators instead. - See also:
EditableMeshValue,EditableMesh.
extrude
Extrudes selected faces along their face normals or an explicit direction. Use it for raised panels, shells, and block-out modeling.
Import
import { extrude } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface ExtrudeOptions {
readonly distance: number;
readonly inset?: number;
readonly direction?: readonly [number, number, number];
readonly mode?: "region" | "individual";
}
declare interface ExtrudeResult {
readonly mesh: EditableMeshValue;
readonly sideFaces: ElementSelection;
readonly capFaces: ElementSelection;
readonly boundaryEdges: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function extrude(em: EditableMeshValue, faces: ElementSelection, opts: ExtrudeOptions): ExtrudeResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| faces | ElementSelection | ✔ | — | Must be a non-empty face selection from em. |
| opts.distance | number | ✔ | — | Offset distance along selected face normal or normalized direction. |
| opts.inset | number | ✖ | 0 | Fraction toward each face center before lifting. No clamp is applied. |
| opts.direction | [number, number, number] | ✖ | selected face normal | Normalized internally; zero vector behaves as length 1 denominator. |
| opts.mode | "region" | "individual" | ✖ | accepted but not used in v1 | Current implementation extrudes each selected triangle independently. |
Returns: ExtrudeResult — edited mesh plus side, cap, and boundary-edge selections on the result mesh.
Throws: MeshEditError WRONG_DOMAIN if faces.domain !== "face"; EMPTY_SELECTION if faces.count === 0.
Examples
import { EditableMesh, extrude } from "@vgpu/render/edit";
const extrudeMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const extruded = extrude(extrudeMesh, extrudeMesh.faces.byIndex([0]), { distance: 0.2, inset: 0.1 });Notes
- Source faces are removed; new side and cap faces are returned for highlighting/chaining.
- See also:
inset,bevel,recomputeNormals.
bevel
Bevels selected edges by shrinking incident faces and inserting strip faces. Use it to soften hard edges; v1 supports a single segment.
Import
import { bevel } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface BevelOptions {
readonly offset: number;
readonly segments?: number;
readonly profile?: number;
readonly affect?: "edges" | "vertices";
readonly markSharp?: boolean;
}
declare interface BevelResult {
readonly mesh: EditableMeshValue;
readonly newFaces: ElementSelection;
readonly originalFaces: ElementSelection;
readonly profileLoops: readonly ElementSelection[];
readonly warnings?: readonly MeshEditWarning[];
}
declare function bevel(em: EditableMeshValue, edges: ElementSelection, opts: BevelOptions): BevelResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| edges | ElementSelection | ✔ | — | Must be a non-empty edge selection. |
| opts.offset | number | ✔ | — | Fraction toward each incident face center; clamped to [0, 0.49]. |
| opts.segments | number | ✖ | 1 | Any value other than 1 emits BEVEL_SEGMENTS_CLAMPED; geometry remains one segment. |
| opts.profile | number | ✖ | accepted but not used in v1 | Present in type for future bevel profiles. |
| opts.affect | "edges" | "vertices" | ✖ | accepted but not used in v1 | Current implementation bevels selected edges/incident faces. |
| opts.markSharp | boolean | ✖ | true | Marks selected original/profile edges sharp when true. |
Returns: BevelResult — edited mesh, strip faces, shrunken original faces, and profile loop edge selections.
Throws: MeshEditError WRONG_DOMAIN if edges.domain !== "edge"; EMPTY_SELECTION if edges.count === 0.
Examples
import { EditableMesh, bevel } from "@vgpu/render/edit";
const bevelMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const beveled = bevel(bevelMesh, bevelMesh.edges.byIndex([0]), { offset: 0.05 });Notes
- Boundary edges are processed on their one incident face and reported with
NON_MANIFOLD_EDGE_SKIPPEDwarnings. - See also:
extrude,inset,EdgeView.
inset
Insets selected faces by adding an inner triangle and boundary rim faces. Use it before extrude for panel-like forms.
Import
import { inset } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface InsetOptions {
readonly thickness: number;
readonly depth?: number;
readonly individual?: boolean;
}
declare interface InsetResult {
readonly mesh: EditableMeshValue;
readonly insetFaces: ElementSelection;
readonly boundaryFaces: ElementSelection;
readonly rimEdges: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function inset(em: EditableMeshValue, faces: ElementSelection, opts: InsetOptions): InsetResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| faces | ElementSelection | ✔ | — | Must be a non-empty face selection. |
| opts.thickness | number | ✔ | — | Fraction toward face center; clamped to [0, 0.49]. Clamp emits INSET_OVERLAP_CLAMPED. |
| opts.depth | number | ✖ | 0 | Offset along each face normal after insetting. |
| opts.individual | boolean | ✖ | accepted but not used in v1 | Current implementation processes selected triangles individually. |
Returns: InsetResult — edited mesh plus inner faces, rim/boundary faces, and rim edges on the result mesh.
Throws: MeshEditError WRONG_DOMAIN if faces.domain !== "face"; EMPTY_SELECTION if faces.count === 0.
Examples
import { EditableMesh, inset } from "@vgpu/render/edit";
const insetMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const insetResult = inset(insetMesh, insetMesh.faces.all(), { thickness: 0.2, depth: 0.05 });Notes
- Follow with
extrude(result.mesh, result.insetFaces, ...)for raised or recessed panels. - See also:
extrude,bevel,FaceView.
subdivideEdges
Splits selected edges and retriangulates incident triangles. Use it to add local resolution before detailed edits.
Import
import { subdivideEdges } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection } from "@vgpu/render/edit";
declare interface SubdivideEdgesOptions { readonly cuts?: number }
declare interface SubdivideEdgesResult {
readonly mesh: EditableMeshValue;
readonly newVertices: ElementSelection;
readonly newEdges: ElementSelection;
}
declare function subdivideEdges(em: EditableMeshValue, edges: ElementSelection, opts?: SubdivideEdgesOptions): SubdivideEdgesResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| edges | ElementSelection | ✔ | — | Must be a non-empty edge selection. |
| opts | SubdivideEdgesOptions | ✖ | {} | Options object may be omitted. |
| opts.cuts | number | ✖ | 1 | Floored and clamped to minimum 1; inserts this many points per selected edge. |
Returns: SubdivideEdgesResult — edited mesh with selections for inserted vertices and child edges.
Throws: MeshEditError WRONG_DOMAIN if edges.domain !== "edge"; EMPTY_SELECTION if edges.count === 0.
Examples
import { EditableMesh, subdivideEdges } from "@vgpu/render/edit";
const subdivideEdgesMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const edgeSubdivision = subdivideEdges(subdivideEdgesMesh, subdivideEdgesMesh.edges.byIndex([0]), { cuts: 2 });Notes
- Sharp selected edges propagate sharpness to child edges.
- See also:
subdivideFaces,loopCut,bevel.
subdivideFaces
Subdivides selected triangles into four triangles per cut iteration using edge midpoints.
Import
import { subdivideFaces } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection } from "@vgpu/render/edit";
declare interface SubdivideFacesOptions { readonly cuts?: number }
declare interface SubdivideFacesResult {
readonly mesh: EditableMeshValue;
readonly newFaces: ElementSelection;
readonly newEdges: ElementSelection;
}
declare function subdivideFaces(em: EditableMeshValue, faces: ElementSelection, opts?: SubdivideFacesOptions): SubdivideFacesResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| faces | ElementSelection | ✔ | — | Must be a non-empty face selection. |
| opts | SubdivideFacesOptions | ✖ | {} | Options object may be omitted. |
| opts.cuts | number | ✖ | 1 | Floored and clamped to minimum 1; repeated cut iterations apply to newly-created faces. |
Returns: SubdivideFacesResult — edited mesh with selections for descendant faces and new edges.
Throws: MeshEditError WRONG_DOMAIN if faces.domain !== "face"; EMPTY_SELECTION if faces.count === 0.
Examples
import { EditableMesh, subdivideFaces } from "@vgpu/render/edit";
const subdivideFacesMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const faceSubdivision = subdivideFaces(subdivideFacesMesh, subdivideFacesMesh.faces.all(), { cuts: 1 });Notes
- Original sharp face edges are split into sharp child edges.
- See also:
subdivideEdges,loopCut,FaceView.
loopCut
Attempts to cut an edge loop/ring through coplanar triangle pairs. Falls back to cutting only the seed edge when continuation is ambiguous.
Import
import { loopCut } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface LoopCutOptions {
readonly cuts?: number;
readonly slide?: number;
readonly markSharp?: boolean;
}
declare interface LoopCutResult {
readonly mesh: EditableMeshValue;
readonly insertedLoop: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function loopCut(em: EditableMeshValue, seedEdge: number, opts?: LoopCutOptions): LoopCutResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| seedEdge | number | ✔ | — | Edge index; must be 0 <= seedEdge < em.edgeCount. |
| opts | LoopCutOptions | ✖ | {} | Options object may be omitted. |
| opts.cuts | number | ✖ | 1 in fallback only | Used only when ambiguous continuation falls back to subdivideEdges. |
| opts.slide | number | ✖ | 0 | Maps to split factor 0.5 + slide * 0.5, clamped to [0.001, 0.999]. |
| opts.markSharp | boolean | ✖ | false | Marks inserted loop edges sharp only in successful ring cuts. |
Returns: LoopCutResult — edited mesh plus ordered inserted-loop edge selection.
Throws: MeshEditError EMPTY_SELECTION when seedEdge is out of range.
Examples
import { EditableMesh, loopCut } from "@vgpu/render/edit";
const loopCutMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const loopCutResult = loopCut(loopCutMesh, 0, { slide: 0.25 });Notes
- Ambiguous topology returns
LOOP_CUT_AMBIGUOUS_CONTINUATIONand cuts only the seed edge. - See also:
subdivideEdges,subdivideFaces,EdgeView.
bridge
Creates faces between two ordered edge loops in one selection. Use for connecting holes or separated boundary rings.
Import
import { bridge } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface BridgeOptions {
readonly twist?: number;
readonly mode?: "faces" | "merge";
}
declare interface BridgeResult {
readonly mesh: EditableMeshValue;
readonly bridgeFaces: ElementSelection;
readonly chosenTwist: number;
readonly warnings?: readonly MeshEditWarning[];
}
declare function bridge(em: EditableMeshValue, sel: ElementSelection, opts?: BridgeOptions): BridgeResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| sel | ElementSelection | ✔ | — | Must be a non-empty ordered edge selection containing two loops. |
| opts | BridgeOptions | ✖ | {} | Options object may be omitted. |
| opts.twist | number | ✖ | auto by shortest squared endpoint distance | Shift applied to second loop correspondence; returned as positive modulo loop length. |
| opts.mode | "faces" | "merge" | ✖ | "faces" | "merge" throws UNSUPPORTED_INPUT in the triangle-only kernel. |
Returns: BridgeResult — edited mesh, bridge face selection, chosen twist, optional length-mismatch warnings.
Throws: MeshEditError WRONG_DOMAIN, EMPTY_SELECTION, or NOT_ORDERED from loop validation; AMBIGUOUS_TOPOLOGY when two loops cannot be split; UNSUPPORTED_INPUT for mode: "merge"; DEGENERATE_RESULT can propagate from invalid loop vertices.
Examples
import { EditableMesh, bridge, type ElementSelection } from "@vgpu/render/edit";
const bridgeMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1]),
indices: new Uint32Array([0, 1, 2, 3, 4, 5]),
});
const twoTriangleLoops: ElementSelection = { domain: "edge", indices: [0, 1, 2, 3, 4, 5], count: 6, ordered: true };
const bridged = bridge(bridgeMesh, twoTriangleLoops, { twist: 0 });Notes
- Different loop lengths are allowed; modulo correspondence is used and
BRIDGE_LOOP_LENGTH_MISMATCHis emitted. - See also:
fillHole,gridFill,ElementSelection.
fillHole
Fills an ordered boundary loop with a triangle fan.
Import
import { fillHole } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface FillHoleOptions { readonly method?: "triangulate" | "ngon" | "beautify" }
declare interface FillHoleResult {
readonly mesh: EditableMeshValue;
readonly newFaces: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function fillHole(em: EditableMeshValue, boundary: ElementSelection, opts?: FillHoleOptions): FillHoleResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| boundary | ElementSelection | ✔ | — | Must be a non-empty ordered edge loop. |
| opts | FillHoleOptions | ✖ | {} | Options object may be omitted. |
| opts.method | "triangulate" | "ngon" | "beautify" | ✖ | "triangulate" | Non-triangulate methods still emit a triangle fan and warn FILL_HOLE_TRIANGULATED. |
Returns: FillHoleResult — edited mesh and newly-created faces.
Throws: MeshEditError WRONG_DOMAIN, EMPTY_SELECTION, or NOT_ORDERED from loop validation; AMBIGUOUS_TOPOLOGY/DEGENERATE_RESULT can propagate from invalid loop vertices.
Examples
import { EditableMesh, fillHole, type ElementSelection } from "@vgpu/render/edit";
const fillHoleMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const triangleBoundary: ElementSelection = { domain: "edge", indices: [0, 1, 2], count: 3, ordered: true };
const filledHole = fillHole(fillHoleMesh, triangleBoundary);Notes
- Non-planar loops warn
FILL_NON_PLANAR_BOUNDARYand are still triangulated. - See also:
gridFill,bridge,ElementSet.boundaryOf.
gridFill
Deterministically represents a grid fill as triangles around the boundary center. Use it when callers request grid-fill semantics but the triangle-only kernel is acceptable.
Import
import { gridFill } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface GridFillOptions { readonly spanMode?: "auto" | number }
declare interface GridFillResult {
readonly mesh: EditableMeshValue;
readonly newFaces: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function gridFill(em: EditableMeshValue, boundary: ElementSelection, opts?: GridFillOptions): GridFillResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| boundary | ElementSelection | ✔ | — | Must be a non-empty ordered edge loop. |
| opts | GridFillOptions | ✖ | {} | Options object may be omitted. |
| opts.spanMode | "auto" | number | ✖ | "auto" for warning text | Numeric values < 1 throw DEGENERATE_RESULT; all modes triangulate. |
Returns: GridFillResult — edited mesh, new fan faces, and warnings.
Throws: MeshEditError WRONG_DOMAIN, EMPTY_SELECTION, or NOT_ORDERED from loop validation; DEGENERATE_RESULT for numeric spanMode < 1; AMBIGUOUS_TOPOLOGY can propagate from invalid loop vertices.
Examples
import { EditableMesh, gridFill, type ElementSelection } from "@vgpu/render/edit";
const gridFillMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const gridBoundary: ElementSelection = { domain: "edge", indices: [0, 1, 2], count: 3, ordered: true };
const gridFilled = gridFill(gridFillMesh, gridBoundary, { spanMode: "auto" });Notes
- Always emits
GRID_FILL_TRIANGULATED; odd loop lengths also warn withFILL_NON_PLANAR_BOUNDARYwording. - See also:
fillHole,bridge,MeshEditWarning.
dissolveVertices
Dissolves selected non-boundary vertices by dissolving their surrounding faces.
Import
import { dissolveVertices } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface DissolveVerticesOptions {
readonly useFaceSplit?: boolean;
readonly useBoundaryTear?: boolean;
}
declare interface DissolveVerticesResult {
readonly mesh: EditableMeshValue;
readonly surroundingFaces: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function dissolveVertices(em: EditableMeshValue, vertices: ElementSelection, opts?: DissolveVerticesOptions): DissolveVerticesResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| vertices | ElementSelection | ✔ | — | Must be a non-empty vertex selection. |
| opts | DissolveVerticesOptions | ✖ | {} | Options object may be omitted. |
| opts.useFaceSplit | boolean | ✖ | accepted but not used in v1 | Present in type only. |
| opts.useBoundaryTear | boolean | ✖ | accepted but not used in v1 | Boundary vertices are skipped with warnings. |
Returns: DissolveVerticesResult — edited mesh and resulting surrounding face selection.
Throws: MeshEditError WRONG_DOMAIN if vertices.domain !== "vertex"; EMPTY_SELECTION if vertices.count === 0.
Examples
import { EditableMesh, dissolveVertices } from "@vgpu/render/edit";
const dissolveVerticesMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const dissolvedVertices = dissolveVertices(dissolveVerticesMesh, dissolveVerticesMesh.vertices.byIndex([0]));Notes
- Boundary vertices are skipped with
NON_MANIFOLD_VERTEX_SKIPPED. - See also:
dissolveEdges,dissolveFaces,mergeByDistance.
dissolveEdges
Removes selected internal edges by merging each adjacent face pair and retriangulating with a deterministic diagonal.
Import
import { dissolveEdges } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface DissolveEdgesOptions { readonly useVerts?: boolean }
declare interface DissolveEdgesResult {
readonly mesh: EditableMeshValue;
readonly mergedFaces: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function dissolveEdges(em: EditableMeshValue, edges: ElementSelection, opts?: DissolveEdgesOptions): DissolveEdgesResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| edges | ElementSelection | ✔ | — | Must be a non-empty edge selection. Boundary/overlapping jobs are skipped with warnings. |
| opts | DissolveEdgesOptions | ✖ | {} | Options object may be omitted. |
| opts.useVerts | boolean | ✖ | accepted but not used in v1 | Present in type only. |
Returns: DissolveEdgesResult — edited mesh and merged face selection.
Throws: MeshEditError WRONG_DOMAIN if edges.domain !== "edge"; EMPTY_SELECTION if edges.count === 0.
Examples
import { EditableMesh, dissolveEdges } from "@vgpu/render/edit";
const dissolveEdgesMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const dissolvedEdges = dissolveEdges(dissolveEdgesMesh, dissolveEdgesMesh.edges.byIndex([0]));Notes
- Successful jobs emit
DISSOLVE_FACES_RETRIANGULATEDbecause the merged quad remains two triangles. - See also:
dissolveVertices,dissolveFaces,EdgeView.
dissolveFaces
Removes selected face regions and retriangulates each region boundary as a fan.
Import
import { dissolveFaces } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface DissolveFacesResult {
readonly mesh: EditableMeshValue;
readonly resultFace: ElementSelection;
readonly warnings?: readonly MeshEditWarning[];
}
declare function dissolveFaces(em: EditableMeshValue, faces: ElementSelection): DissolveFacesResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| faces | ElementSelection | ✔ | — | Must be a non-empty face selection. Connected components are dissolved independently. |
Returns: DissolveFacesResult — edited mesh and result face selection.
Throws: MeshEditError WRONG_DOMAIN if faces.domain !== "face"; EMPTY_SELECTION if faces.count === 0.
Examples
import { EditableMesh, dissolveFaces } from "@vgpu/render/edit";
const dissolveFacesMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const dissolvedFaces = dissolveFaces(dissolveFacesMesh, dissolveFacesMesh.faces.all());Notes
- Degenerate regions warn
DEGENERATE_FACE_DROPPED; multi-face or n-gon regions warnDISSOLVE_FACES_RETRIANGULATED. - See also:
dissolveEdges,dissolveVertices,fillHole.
mergeByDistance
Welds vertices whose positions are within a threshold, removes collapsed faces, and returns an old-to-new vertex map.
Import
import { mergeByDistance } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, ElementSelection, MeshEditWarning } from "@vgpu/render/edit";
declare interface MergeByDistanceOptions {
readonly threshold?: number;
readonly selection?: ElementSelection;
readonly key?: "position" | "full-vertex";
}
declare interface MergeByDistanceResult {
readonly mesh: EditableMeshValue;
readonly mergeMap: ReadonlyMap<number, number>;
readonly weldedCount: number;
readonly warnings?: readonly MeshEditWarning[];
}
declare function mergeByDistance(em: EditableMeshValue, opts?: MergeByDistanceOptions): MergeByDistanceResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
| opts | MergeByDistanceOptions | ✖ | {} | Options object may be omitted. |
| opts.threshold | number | ✖ | 1e-4 | Euclidean position distance for clustering. |
| opts.selection | ElementSelection | ✖ | em.vertices.all() | Must be a vertex selection. Only selected vertices are clustered; all faces are remapped. |
| opts.key | "position" | "full-vertex" | ✖ | warning mode equivalent to "full-vertex" | Clustering is position-based in v1. "position" emits SEAM_DESTROYED when UV/normal/color flags exist. |
Returns: MergeByDistanceResult — welded mesh, old vertex index to new vertex index map (-1 for unused), and welded count.
Throws: MeshEditError WRONG_DOMAIN if opts.selection.domain !== "vertex"; EMPTY_SELECTION if opts.selection.count === 0.
Examples
import { EditableMesh, mergeByDistance } from "@vgpu/render/edit";
const mergeMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 0.00001, 0, 0, 0, 1, 0]),
});
const merged = mergeByDistance(mergeMesh, { threshold: 0.001 });Notes
- Collapsed faces are removed and reported with
MERGE_DEGENERATE_FACES_REMOVED. - See also:
healManifold,dissolveVertices,recomputeNormals.
healManifold
Deterministic cleanup pass that removes duplicate, degenerate, and overused-edge faces where possible.
Import
import { healManifold } from "@vgpu/render/edit";Signature
import type { EditableMeshValue, MeshEditWarning } from "@vgpu/render/edit";
declare interface HealManifoldReport {
readonly nonManifoldEdgesFixed: number;
readonly nonManifoldVerticesFixed: number;
readonly holesFixed: number;
readonly duplicateFacesRemoved: number;
}
declare interface HealManifoldResult {
readonly mesh: EditableMeshValue;
readonly report: HealManifoldReport;
readonly warnings?: readonly MeshEditWarning[];
}
declare function healManifold(em: EditableMeshValue): HealManifoldResult;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. |
Returns: HealManifoldResult — cleaned mesh plus report. nonManifoldVerticesFixed and holesFixed are currently always 0.
Throws: — no MeshEditError is thrown directly.
Examples
import { EditableMesh, healManifold } from "@vgpu/render/edit";
const healMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
indices: new Uint32Array([0, 1, 2, 0, 1, 2]),
});
const healed = healManifold(healMesh);Notes
- Remaining non-manifold residue is reported as
HEAL_NON_MANIFOLD_RESIDUE. - See also:
mergeByDistance,recomputeNormals,MeshEditWarning.
recomputeNormals
Rebuilds the editable mesh and recomputes face normals using smoothing components and sharp edges or a new crease angle.
Import
import { recomputeNormals } from "@vgpu/render/edit";Signature
import type { EditableMeshValue } from "@vgpu/render/edit";
declare interface RecomputeNormalsOptions {
readonly weighting?: "angle" | "area" | "uniform";
readonly creaseAngle?: number;
}
declare function recomputeNormals(em: EditableMeshValue, opts?: RecomputeNormalsOptions): EditableMeshValue;Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| em | EditableMeshValue | ✔ | — | Source mesh. Empty meshes are returned unchanged. |
| opts | RecomputeNormalsOptions | ✖ | {} | Options object may be omitted. |
| opts.weighting | "angle" | "area" | "uniform" | ✖ | "angle" | Weighting mode for smoothing component normals. |
| opts.creaseAngle | number | ✖ | preserve current sharp-edge mask | If provided, rebuilds sharp edges from the crease angle instead of preserving isSharp. |
Returns: EditableMeshValue — new mesh with recomputed kernel face normals, or the same mesh when faceCount === 0.
Throws: — no MeshEditError is thrown directly.
Examples
import { EditableMesh, recomputeNormals } from "@vgpu/render/edit";
const normalsMesh = EditableMesh.fromArrays({
positions: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]),
});
const normalsRecomputed = recomputeNormals(normalsMesh, { weighting: "area" });Notes
- Run after topology-changing operators if downstream code relies on smooth normals.
- See also:
EditableMesh,mergeByDistance,healManifold.
MeshEditError
Error class thrown by validation and topology operators. Catch by instanceof MeshEditError and branch on code.
Import
import { MeshEditError } from "@vgpu/render/edit";Signature
export type MeshEditErrorCode =
| "NON_MANIFOLD"
| "STALE_SELECTION"
| "EMPTY_SELECTION"
| "WRONG_DOMAIN"
| "NOT_ORDERED"
| "DEGENERATE_RESULT"
| "AMBIGUOUS_TOPOLOGY"
| "UNSUPPORTED_INPUT";
declare class MeshEditError extends Error {
readonly code: MeshEditErrorCode;
readonly suggestion?: string;
constructor(opts: { readonly code: MeshEditErrorCode; readonly message?: string; readonly suggestion?: string });
}Parameters
| Param/Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| opts.code | MeshEditErrorCode | ✔ | — | Machine-readable error code. |
| opts.message | string | ✖ | opts.code | Error message passed to Error. |
| opts.suggestion | string | ✖ | omitted | Optional recovery hint. |
| code | MeshEditErrorCode | ✔ | — | Public readonly field copied from constructor. |
| suggestion | string | ✖ | omitted | Public readonly optional field. |
| name | string | ✔ | "MeshEditError" | Set by constructor. |
Returns: MeshEditError instance from new MeshEditError(...).
Throws: — constructor does not throw.
Examples
import { MeshEditError } from "@vgpu/render/edit";
const meshEditError = new MeshEditError({ code: "EMPTY_SELECTION", suggestion: "Select at least one face." });
const meshEditErrorCode = meshEditError.code;Notes
- Common validation codes:
WRONG_DOMAIN,EMPTY_SELECTION,NOT_ORDERED. - See also:
ElementSelection,bridge,gridFill.
MeshEditWarning
Non-fatal diagnostic emitted in operator result warnings arrays. Use warnings to explain deterministic fallbacks and data loss.
Import
import { MeshEditWarning } from "@vgpu/render/edit";Signature
export type MeshEditWarningCode =
| "NON_MANIFOLD_EDGE_SKIPPED"
| "NON_MANIFOLD_VERTEX_SKIPPED"
| "DEGENERATE_FACE_DROPPED"
| "TANGENTS_STRIPPED"
| "BEVEL_ACUTE_CLAMPED"
| "BEVEL_SEGMENTS_CLAMPED"
| "INSET_OVERLAP_CLAMPED"
| "SEAM_DESTROYED"
| "BRIDGE_LOOP_LENGTH_MISMATCH"
| "FILL_NON_PLANAR_BOUNDARY"
| "LOOP_CUT_AMBIGUOUS_CONTINUATION"
| "FILL_HOLE_TRIANGULATED"
| "GRID_FILL_TRIANGULATED"
| "DISSOLVE_FACES_RETRIANGULATED"
| "MERGE_DEGENERATE_FACES_REMOVED"
| "HEAL_NON_MANIFOLD_RESIDUE";
declare class MeshEditWarning {
readonly code: MeshEditWarningCode;
readonly reason: string;
readonly element?: { readonly domain: "vertex" | "edge" | "face"; readonly index: number };
constructor(
code: MeshEditWarningCode,
reason: string,
element?: { readonly domain: "vertex" | "edge" | "face"; readonly index: number },
);
}Parameters
| Param/Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| code | MeshEditWarningCode | ✔ | — | Machine-readable warning code. |
| reason | string | ✔ | — | Human-readable explanation. |
| element | { domain: "vertex" | "edge" | "face"; index: number } | ✖ | omitted | Optional source element associated with the warning. |
Returns: MeshEditWarning instance from new MeshEditWarning(...).
Throws: — constructor does not throw.
Examples
import { MeshEditWarning } from "@vgpu/render/edit";
const meshEditWarning = new MeshEditWarning("GRID_FILL_TRIANGULATED", "Triangle-only output was used.");
const warningReason = meshEditWarning.reason;Notes
- Treat warnings as actionable diagnostics, not failures; operators still return a mesh.
- See also:
toEditableWithDiagnostics,bevel,gridFill,healManifold.