---
title: @vgpu/render/edit — Mesh Editing
description: 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.
---

# @vgpu/render/edit — Mesh Editing



## Index

* Creation and conversion: [EditableMesh](#editablemesh), [toEditable](#toeditable), [toEditableWithDiagnostics](#toeditablewithdiagnostics)
* Selection and views: [EditableMeshValue](#editablemeshvalue), [ElementDomain](#elementdomain), [ElementSelection](#elementselection), [ElementSet](#elementset), [ScoredSelection](#scoredselection), [VertexView](#vertexview), [EdgeView](#edgeview), [FaceView](#faceview), [KernelHandle](#kernelhandle)
* Shape editing: [extrude](#extrude), [bevel](#bevel), [inset](#inset)
* Subdivision and cuts: [subdivideEdges](#subdivideedges), [subdivideFaces](#subdividefaces), [loopCut](#loopcut)
* Topology fill/bridge: [bridge](#bridge), [fillHole](#fillhole), [gridFill](#gridfill)
* Dissolve/weld/heal/bake: [dissolveVertices](#dissolvevertices), [dissolveEdges](#dissolveedges), [dissolveFaces](#dissolvefaces), [mergeByDistance](#mergebydistance), [healManifold](#healmanifold), [recomputeNormals](#recomputenormals)
* Diagnostics: [MeshEditError](#meshediterror), [MeshEditWarning](#mesheditwarning)

All imports in this file use the public edit entrypoint:

```ts
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

```ts
import { EditableMesh } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { toEditable } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { toEditableWithDiagnostics } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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_STRIPPED` when `mesh.attributes.tangent` exists.
* **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

```ts
import type { EditableMeshValue } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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 `EditableMeshValue` objects; 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

```ts
import type { ElementDomain } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import type { ElementSelection } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import type { ElementSet } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

* `loop` and `ring` are typed for edge sets; do not call them on `vertices` or `faces`.
* **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

```ts
import type { ScoredSelection } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

* `bottomN` re-sorts ascending at call time; `entries` remains descending.
* **See also:** `ElementSet`, `ElementSelection`.

## VertexView

Read-only per-vertex data passed to vertex `where`/`scoreBy` callbacks.

## Import

```ts
import type { VertexView } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import type { EdgeView } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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.hardEdges` when 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

```ts
import type { FaceView } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

* `vertexCount` is 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

```ts
import type { KernelHandle } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { extrude } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { bevel } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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_SKIPPED` warnings.
* **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

```ts
import { inset } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { subdivideEdges } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { subdivideFaces } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { loopCut } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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_CONTINUATION` and 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

```ts
import { bridge } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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_MISMATCH` is emitted.
* **See also:** `fillHole`, `gridFill`, `ElementSelection`.

## fillHole

Fills an ordered boundary loop with a triangle fan.

## Import

```ts
import { fillHole } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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_BOUNDARY` and 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

```ts
import { gridFill } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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 with `FILL_NON_PLANAR_BOUNDARY` wording.
* **See also:** `fillHole`, `bridge`, `MeshEditWarning`.

## dissolveVertices

Dissolves selected non-boundary vertices by dissolving their surrounding faces.

## Import

```ts
import { dissolveVertices } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { dissolveEdges } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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_RETRIANGULATED` because 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

```ts
import { dissolveFaces } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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 warn `DISSOLVE_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

```ts
import { mergeByDistance } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { healManifold } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { recomputeNormals } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { MeshEditError } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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

```ts
import { MeshEditWarning } from "@vgpu/render/edit";
```

## Signature

```ts
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

```ts
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`.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)