# @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](#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`.
