### Run the development demo Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Install dependencies and start the development server to view the demo at http://localhost:5173/. ```bash npm install npm run dev ``` -------------------------------- ### Install three-pinata Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Use npm to install the package. Requires Three.js version 0.158.0 or higher as a peer dependency. ```bash npm install @dgreenheck/three-pinata ``` -------------------------------- ### Quick Start with DestructibleMesh Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Demonstrates setting up a scene, creating a DestructibleMesh with distinct outer and inner materials, and performing a Voronoi fracture. ```typescript import * as THREE from "three"; import { DestructibleMesh, FractureOptions } from "@dgreenheck/three-pinata"; // Setup scene const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000, ); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Create materials const outerMaterial = new THREE.MeshStandardMaterial({ color: 0x4a90e2 }); const innerMaterial = new THREE.MeshStandardMaterial({ color: 0xff6b6b }); // Create destructible mesh with both materials const geometry = new THREE.SphereGeometry(1, 32, 32); const mesh = new DestructibleMesh(geometry, outerMaterial, innerMaterial); scene.add(mesh); // Fracture the mesh const options = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: 50, voronoiOptions: { mode: "3D", }, }); const fragments = mesh.fracture(options, (fragment) => { // Setup each fragment (material already set automatically) scene.add(fragment); }); // Hide original mesh mesh.visible = false; // Render renderer.render(scene, camera); ``` -------------------------------- ### MeshVertex Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Documentation for the MeshVertex class, including its constructor, methods, and usage example. ```APIDOC ## `MeshVertex` ### Description Represents a vertex in a mesh with position, normal, and texture coordinates. ### Constructor ```typescript constructor( position: Vector3 = new Vector3(), normal: Vector3 = new Vector3(), uv: Vector2 = new Vector2(), ) ``` #### Parameters - `position` (Vector3) - Optional - 3D position. Default: null vector. - `normal` (Vector3) - Optional - Vertex normal. Default: null vector. - `uv` (Vector2) - Optional - Texture coordinates. Default: (0,0). ### Methods #### `equals(other: MeshVertex): boolean` Checks position equality under Cantor-pairing hash with 1e-9 tolerance. #### `clone(): MeshVertex` Returns a deep copy, cloning position, normal, and uv. #### `toString(): string` Returns a string representation: `"Position = x, y, z, Normal = x, y, z, UV = x, y"`. ### Usage Example ```typescript const v = new MeshVertex(new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector2(0.5, 0.5)); const copy = v.clone(); assert(copy.equals(new MeshVertex(new THREE.Vector3(1, 0, 0)))); ``` ``` -------------------------------- ### fracture(options, onFragment?, onComplete?) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/DestructibleMesh.md Documentation for the fracture method of DestructibleMesh, including parameters, return type, behavior, and usage examples. ```APIDOC ## `fracture(options, onFragment?, onComplete?)` ### Description Fractures the DestructibleMesh into multiple fragments based on the provided options. Returns an array of new DestructibleMesh instances, one per fragment geometry. ### Method fracture ### Signature ```typescript fracture( options: FractureOptions, onFragment?: (fragment: DestructibleMesh, index: number) => void, onComplete?: () => void, ): DestructibleMesh[] ``` ### Parameters - **options** (`FractureOptions`) - Required - Fracture configuration (method, fragment count, Voronoi options, seed, UV mapping) - **onFragment** (`(fragment: DestructibleMesh, index: number) => void`) - Optional - Callback invoked for each fragment, after its transform/materials are set, before it is returned - **onComplete** (`() => void`) - Optional - Callback invoked once after all fragments are created ### Returns `DestructibleMesh[]` - one new mesh per fragment geometry. Fragments are positioned so each fragment's geometry is centered at the origin and the fragment node is placed at the fragment's former world-space center. ### Behavior by method - `options.fractureMethod === "voronoi"` (default): requires `options.voronoiOptions`; converts `FractureOptions` fields into an internal options object (with `projectionAxis || "auto"`, `useApproximation || false`, `approximationNeighborCount || 12`) and calls `voronoiFracture`. - otherwise (`"simple"`): calls `fracture` → `fractureFragment`, which recursively slices along randomized planes until the fragment count is reached. ### Throws - `Error("DestructibleMesh has no geometry to fracture")` - if `this.geometry` is falsy - `Error("voronoiOptions is required when fractureMethod is 'voronoi'")` - if `fractureMethod === "voronoi"` and `options.voronoiOptions` is unset ### Usage Example ```typescript import * as THREE from "three"; import { DestructibleMesh, FractureOptions } from "@dgreenheck/three-pinata"; const outer = new THREE.MeshStandardMaterial({ color: 0x4a90e2 }); const inner = new THREE.MeshStandardMaterial({ color: 0xff6b6b }); const mesh = new DestructibleMesh( new THREE.SphereGeometry(1, 32, 32), outer, inner, ); scene.add(mesh); const options = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: 50, voronoiOptions: { mode: "3D", impactPoint: new THREE.Vector3(0, 1, 0), impactRadius: 0.5, }, }); const fragments = mesh.fracture(options, (fragment, index) => { fragment.userData.index = index; // caller-owned bookkeeping scene.add(fragment); }); mesh.visible = false; ``` ``` -------------------------------- ### Fragment usage example Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Shows how to create a Fragment with positions, normals, uvs, and indices arrays, then add a triangle and calculate bounds. This is a typical usage inside the engine. ```typescript import { Fragment, SlicedMeshSubmesh } from "../entities/Fragment"; const fragment = new Fragment({ positions: positionsArray, // Float32Array, 3 per vertex normals: normalsArray, // Float32Array, 3 per vertex uvs: uvArray, // Float32Array, 2 per vertex indices: indexArray, // Uint32Array }); fragment.addTriangle(0, 1, 2, SlicedMeshSubmesh.Default); fragment.calculateBounds(); ``` -------------------------------- ### SeedPointGenerator usage example Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/utils.md Example usage of generateImpactBased with a seeded random for reproducible results. ```typescript const seeds = SeedPointGenerator.generateImpactBased( meshBounds, 40, new THREE.Vector3(0, 1, 0), // impact point 0.5, // impact radius new SeededRandom(42), // reproducible ); ``` -------------------------------- ### MeshVertex usage example Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Creates a MeshVertex, clones it, and asserts equality with a new vertex using only position. ```typescript const v = new MeshVertex(new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector2(0.5, 0.5)); const copy = v.clone(); assert(copy.equals(new MeshVertex(new THREE.Vector3(1, 0, 0)))); ``` -------------------------------- ### World-space slice usage example Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/DestructibleMesh.md Example of using sliceWorld with a world-space cut plane. Creates a DestructibleMesh, positions it, defines a world-space normal and origin, then adds the resulting pieces to the scene. ```typescript const mesh = new DestructibleMesh(geometry, outer, inner); mesh.position.set(5, 0, 0); scene.add(mesh); const worldNormal = new THREE.Vector3(0, 1, 0); const worldOrigin = new THREE.Vector3(5, 0.25, 0); // world-space cut point const pieces = mesh.sliceWorld(worldNormal, worldOrigin); pieces.forEach((p) => scene.add(p)); ``` -------------------------------- ### Voronoi fracture with seed and custom UV mapping Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/FractureOptions.md Creates a FractureOptions instance for Voronoi fracture with 24 fragments, 2.5D mode, Y-axis projection, texture scale, and a seed for reproducible results. The example shows that using the same seed produces the same fragments regardless of when the fracture runs. ```typescript import * as THREE from "three"; import { FractureOptions } from "@dgreenheck/three-pinata"; const options = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: 24, voronoiOptions: { mode: "2.5D", projectionAxis: "y", }, textureScale: new THREE.Vector2(2, 2), seed: 1337, }); // Reproducible: same seed → same fragments, regardless of when it runs const a = mesh.fracture(options); const b = mesh.fracture(new FractureOptions({ seed: 1337, voronoiOptions: { mode: "2.5D" } })); ``` -------------------------------- ### Simple fracture with restricted planes Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/FractureOptions.md Creates a FractureOptions instance for simple fracture with 8 fragments, restricting the fracture planes to only slice along the Y axis. The example shows how to pass the options to the fracture method. ```typescript const options = new FractureOptions({ fractureMethod: "simple", fragmentCount: 8, fracturePlanes: { x: false, y: true, z: false }, // only slice along Y }); const fragments = mesh.fracture(options); ``` -------------------------------- ### findStartingEdge(vertexTriangles, constraint) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/triangulators.md Locates the first edge intersected by the constraint, used as a starting point for edge removal. ```APIDOC ## Method: findStartingEdge ### Signature ```typescript findStartingEdge(vertexTriangles: number[], constraint: EdgeConstraint) ``` ### Description Locates the first edge intersected by the constraint. ### Parameters - **vertexTriangles** (number[]) - Required - Acceleration structure mapping vertices to triangles - **constraint** (EdgeConstraint) - Required - The constraint edge to find the starting intersection for ``` -------------------------------- ### Build the library Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Run this command to generate ES and UMD modules along with type declarations. ```bash npm run build:lib ``` -------------------------------- ### constructor Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Creates a new Fragment instance. If args are provided, vertices are created from positions, normals, and uvs, and indices are copied into the first submesh. If args are omitted, an empty fragment is created. ```APIDOC ## `constructor` ### Description Creates a new Fragment instance. If args are provided, vertices are created from positions, normals, and uvs, and indices are copied into the first submesh. If args are omitted, an empty fragment is created. ### Signature ```typescript constructor(args: FragmentArgs | undefined = undefined) ``` ### Parameters - `args` (FragmentArgs | undefined) - Optional - Raw typed-array data `{ positions: Float32Array; normals: Float32Array; uvs?: Float32Array; indices?: Uint32Array }`. When omitted an empty fragment is created. ### Behavior With `args`, vertices are created from `positions`/`normals`/`uvs` (omitted `uvs` → `Vector2(0,0)`); `indices` are copied into `triangles[0]` (submesh 1 stays empty), or sequential indices are generated when `indices` is absent; `calculateBounds()` runs at the end. Without `args`: `triangles` initialised as `[[], []]`, all other collections empty, `bounds = new Box3()`. ``` -------------------------------- ### get triangleCount Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Returns the total number of triangles across all submeshes. ```APIDOC ## `get triangleCount` ### Description Returns the total number of triangles across all submeshes. ### Signature ```typescript get triangleCount(): number ``` ### Returns - `number` - Total triangles across all submeshes: `(triangles[0].length + triangles[1].length) / 3`. ``` -------------------------------- ### Get triangleCount Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Returns the total number of triangles across all submeshes. ```typescript get triangleCount(): number ``` -------------------------------- ### Using MathUtils functions Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/utils.md Demonstrates importing and using linePlaneIntersection, isPointAbovePlane, and hash3 for geometric calculations. ```typescript import { linePlaneIntersection, isPointAbovePlane, hash3 } from "./lib/src/utils/MathUtils"; const hit = linePlaneIntersection(a, b, planeNormal, planeOrigin); if (hit) { // hit.x = Vector3 intersection point, hit.s = parameter such that x = a + (b - a) * s } const above = isPointAbovePlane(point, planeNormal, planeOrigin); // true = normal side const key = hash3(cutVertex.position); // 1e-9 grid tolerance ``` -------------------------------- ### Basic Fracturing with DestructibleMesh and FractureOptions Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Creates a DestructibleMesh with outer and inner materials, adds it to the scene, fractures it using the voronoi method with 16 fragments in 3D mode, adds each fragment to the scene, and hides the original mesh. ```TypeScript import { DestructibleMesh, FractureOptions } from "@dgreenheck/three-pinata"; const outerMaterial = new THREE.MeshStandardMaterial({ color: 0x4a90e2 }); const innerMaterial = new THREE.MeshStandardMaterial({ color: 0xff6b6b }); const mesh = new DestructibleMesh(geometry, outerMaterial, innerMaterial); scene.add(mesh); const options = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: 16, voronoiOptions: { mode: "3D", }, }); const fragments = mesh.fracture(options); fragments.forEach((fragment) => scene.add(fragment)); mesh.visible = false; ``` -------------------------------- ### get vertexCount Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Returns the total number of vertices, including cut vertices. ```APIDOC ## `get vertexCount` ### Description Returns the total number of vertices, including cut vertices. ### Signature ```typescript get vertexCount(): number ``` ### Returns - `number` - `vertices.length + cutVertices.length`. ``` -------------------------------- ### Get vertexCount Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Returns the total number of vertices, combining vertices and cutVertices. ```typescript get vertexCount(): number ``` -------------------------------- ### findIntersectingEdges(constraint, vertexTriangles) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/triangulators.md Walks the triangulation from a starting edge and collects all edges that cross the given constraint. ```APIDOC ## Method: findIntersectingEdges ### Signature ```typescript findIntersectingEdges(constraint: EdgeConstraint, vertexTriangles: number[]): EdgeConstraint[] ``` ### Description Walks the triangulation from a starting edge, collecting edges that intersect the constraint. ### Parameters - **constraint** (EdgeConstraint) - Required - The constraint edge to check intersections against - **vertexTriangles** (number[]) - Required - Acceleration structure mapping vertices to triangles ### Returns - **EdgeConstraint[]** - List of edges that intersect the constraint ``` -------------------------------- ### fracture(geometry, options) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/fracture-functions.md Implements the simple fracture method. Converts a THREE.BufferGeometry to a Fragment, applies the fracture algorithm, and maps the results back to THREE.BufferGeometry objects. ```APIDOC ## fracture(geometry, options) ### Description Implements the **simple** method: converts `geometry` with `geometryToFragment`, calls `fractureFragment`, maps results back with `fragmentToGeometry`. ### Method Function ### Signature ```typescript export function fracture( geometry: THREE.BufferGeometry, options: FractureOptions, ): THREE.BufferGeometry[] ``` ### Parameters - **geometry** (`THREE.BufferGeometry`) - Required - Source mesh to fracture - **options** (`FractureOptions`) - Required - Fracture configuration (only the simple-method fields are read) ### Returns `THREE.BufferGeometry[]` — fragment geometries (each with two material groups). ``` -------------------------------- ### findStartingEdge() method signature Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/triangulators.md Method signature for findStartingEdge() that locates the first edge intersected by the constraint. ```typescript findStartingEdge(vertexTriangles: number[], constraint: EdgeConstraint) ``` -------------------------------- ### Import VoronoiOptions from @dgreenheck/three-pinata Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/types.md Shows how to import the only public type, VoronoiOptions, from the package entry point. ```typescript import type { VoronoiOptions } from "@dgreenheck/three-pinata"; ``` -------------------------------- ### Create VoronoiFractureOptions for 3D fracture Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/VoronoiFractureOptions.md Shows how to construct a VoronoiFractureOptions instance with typical 3D fracture settings and pass it to voronoiFracture(). The options object is created with fragmentCount, mode, impactPoint, impactRadius, useApproximation, and seed. ```typescript import { voronoiFracture } from "../../fracture/VoronoiFracture"; import { VoronoiFractureOptions } from "../entities/VoronoiFractureOptions"; const options = new VoronoiFractureOptions({ fragmentCount: 40, mode: "3D", impactPoint: new THREE.Vector3(0, 1, 0), impactRadius: 0.75, useApproximation: false, seed: 42, }); const geometries = voronoiFracture(sourceGeometry, options); ``` -------------------------------- ### Throw Error when DestructibleMesh has no geometry to slice Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/errors.md Thrown at the start of slice() (and therefore sliceWorld(), which delegates) when this.geometry is falsy. Source: lib/src/DestructibleMesh.ts:168-169. ```typescript if (!this.geometry) { throw new Error("DestructibleMesh has no geometry to slice"); } ``` -------------------------------- ### new SliceOptions() Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/SliceOptions.md Creates a new SliceOptions instance with default field values: textureScale = (1,1) and textureOffset = (0,0). The constructor takes no parameters. ```APIDOC ## Constructor: new SliceOptions() ### Description Creates a new SliceOptions instance with default field values: textureScale = (1,1) and textureOffset = (0,0). The constructor takes no parameters. ### Method Constructor ### Signature ```typescript constructor() ``` ### Parameters None. ### Return Type none (constructor) ### Example ```typescript const options = new SliceOptions(); ``` ``` -------------------------------- ### Using Callbacks with fracture Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Shows how to pass callbacks to the fracture method: one called for each fragment (materials already set) and one called when fracturing completes. ```TypeScript const mesh = new DestructibleMesh(geometry, outerMaterial, innerMaterial); const fragments = mesh.fracture( options, (fragment, index) => { // Called for each fragment (materials already set) fragment.castShadow = true; // Add physics, apply forces, etc. physics.add(fragment, { type: "dynamic" }); }, () => { // Called when complete console.log("Fracturing complete!"); }, ); ``` -------------------------------- ### Throw Error when DestructibleMesh has no geometry to fracture Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/errors.md Thrown at the start of fracture() when this.geometry is falsy, e.g. the mesh was constructed without a geometry or its geometry was disposed. Source: lib/src/DestructibleMesh.ts:73-74. ```typescript if (!this.geometry) { throw new Error("DestructibleMesh has no geometry to fracture"); } ``` -------------------------------- ### Basic fracture usage with Voronoi method Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/DestructibleMesh.md Creates a DestructibleMesh with outer and inner materials, then fractures it using the Voronoi method with 50 fragments. The onFragment callback adds each fragment to the scene and stores the index in userData. The original mesh is hidden after fracturing. ```typescript import * as THREE from "three"; import { DestructibleMesh, FractureOptions } from "@dgreenheck/three-pinata"; const outer = new THREE.MeshStandardMaterial({ color: 0x4a90e2 }); const inner = new THREE.MeshStandardMaterial({ color: 0xff6b6b }); const mesh = new DestructibleMesh( new THREE.SphereGeometry(1, 32, 32), outer, inner, ); scene.add(mesh); const options = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: 50, voronoiOptions: { mode: "3D", impactPoint: new THREE.Vector3(0, 1, 0), impactRadius: 0.5, }, }); const fragments = mesh.fracture(options, (fragment, index) => { fragment.userData.index = index; // caller-owned bookkeeping scene.add(fragment); }); mesh.visible = false; ``` -------------------------------- ### Usage: disposing mesh and fragments Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/DestructibleMesh.md Example usage of dispose() to free geometry and material buffers of the original mesh after fracturing, and to free only geometry of a fragment while keeping materials shared. Call after fracturing and when despawning fragments. ```typescript const fragments = mesh.fracture(options); scene.remove(mesh); mesh.dispose(); // frees geometry + material buffers of the original mesh // later: remove a fragment and free only its geometry (keep materials shared) scene.remove(fragments[0]); fragments[0].geometry.dispose(); ``` -------------------------------- ### Integrate Rapier Physics with three-pinata Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Demonstrates initializing a Rapier world, creating rigid bodies and convex hull colliders for mesh fragments, and stepping the physics simulation in an animation loop. ```typescript import RAPIER from "@dimforge/rapier3d"; // Initialize physics world await RAPIER.init(); const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 }); // Add physics to fragments const fragments = mesh.fracture(options, (fragment) => { // Create rigid body const rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation( fragment.position.x, fragment.position.y, fragment.position.z, ); const rigidBody = world.createRigidBody(rigidBodyDesc); // Create convex hull collider const vertices = fragment.geometry.getAttribute("position").array; const colliderDesc = RAPIER.ColliderDesc.convexHull(vertices) .setRestitution(0.3) .setFriction(0.5); world.createCollider(colliderDesc, rigidBody); }); // Update physics each frame function animate() { world.step(); // Sync Three.js objects with physics... } ``` -------------------------------- ### Import SliceOptions Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/SliceOptions.md Import the SliceOptions class from the library. ```typescript import { SliceOptions } from "@dgreenheck/three-pinata"; ``` -------------------------------- ### USAGE — local-space slice Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/DestructibleMesh.md Example of slicing a DestructibleMesh in local space. Creates a mesh, defines a horizontal cut plane, calls slice() with a per-piece callback that adds each piece to the scene, and hides the original mesh after slicing. The returned pieces are not automatically added to the scene. ```typescript const mesh = new DestructibleMesh(geometry, outer, inner); scene.add(mesh); const normal = new THREE.Vector3(0, 1, 0); // horizontal cut const origin = new THREE.Vector3(0, 0, 0); const pieces = mesh.slice( normal, origin, new SliceOptions(), (piece) => scene.add(piece), () => console.log("slice complete"), ); mesh.visible = false; ``` -------------------------------- ### ConstrainedTriangulator instantiation and triangulate call Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/triangulators.md The only production call site for ConstrainedTriangulator, located in fillCutFaces (SliceFragment.ts:108-122). It constructs the triangulator with topSlice.cutVertices, topSlice.constraints, and sliceNormal, then calls triangulate() to get triangle indices. The resulting triangles are used for both slices with reversed winding for the bottom piece, and UVs are derived from triangulator.points[i].coords scaled by normalizationScaleFactor * textureScale + textureOffset. ```typescript const triangulator = new ConstrainedTriangulator( topSlice.cutVertices, // MeshVertex[] topSlice.constraints, // EdgeConstraint[] (cut-face boundary run, CCW) sliceNormal, // Vector3 ); const triangles: number[] = triangulator.triangulate(); ``` -------------------------------- ### fragmentToGeometry(fragment) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/utils.md Builds a fresh THREE.BufferGeometry from a Fragment, setting position, normal, and UV attributes, and a flat index buffer. Declares two material groups based on the fragment's triangle arrays. ```APIDOC ## fragmentToGeometry(fragment) ### Description Builds a fresh `THREE.BufferGeometry` from a `Fragment`, setting position, normal, and UV attributes, and a flat index buffer. Declares two material groups based on the fragment's triangle arrays. ### Method Function ### Signature ```typescript export function fragmentToGeometry(fragment: Fragment): THREE.BufferGeometry ``` ### Parameters - **fragment** (Fragment) - Required - The fragment to convert ### Returns - **THREE.BufferGeometry** - The resulting geometry with position/normal/uv attributes and a flat index buffer. Two material groups are declared: ```typescript geometry.addGroup(0, fragment.triangles[0].length, 0); geometry.addGroup(fragment.triangles[0].length, fragment.triangles[1].length, 1); ``` ``` -------------------------------- ### Simple Fracturing with fracturePlanes Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Uses the simple fracture method with 50 fragments and enables fracture planes along x, y, and z axes. ```TypeScript const options = new FractureOptions({ fractureMethod: "simple", fragmentCount: 50, fracturePlanes: { x: true, y: true, z: true }, }); const fragments = mesh.fracture(options); ``` -------------------------------- ### fracture(options, onFragment?, onComplete?) Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Fractures the mesh into fragments. Accepts fracture options and optional callbacks for each fragment and completion. ```APIDOC ## Method: fracture ### Description Fractures the mesh into fragments. ### Signature fracture(options: FractureOptions, onFragment?: (fragment: DestructibleMesh, index: number) => void, onComplete?: () => void): DestructibleMesh[] ### Parameters - **options** (FractureOptions) - Required - Fracture configuration - **onFragment** (function) - Optional - Callback for each fragment, receives fragment and index - **onComplete** (function) - Optional - Callback when fracturing is complete ### Returns DestructibleMesh[] - Array of fragment meshes ``` -------------------------------- ### Import FractureOptions and VoronoiOptions Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/FractureOptions.md Import the FractureOptions and VoronoiOptions types from the three-pinata package before using them. ```typescript import { FractureOptions, VoronoiOptions } from "@dgreenheck/three-pinata"; ``` -------------------------------- ### FractureOptions constructor Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/FractureOptions.md Configuration for DestructibleMesh.fracture(). Supports two fracture methods: voronoi (Voronoi tessellation; requires voronoiOptions) and simple (randomized plane-based recursive slicing). The constructor accepts a single optional destructured config object; every property is also a public mutable field, so options can be changed after construction. ```APIDOC ## Constructor ### Description Creates a new `FractureOptions` instance used to configure `DestructibleMesh.fracture()`. Supports two fracture methods: `voronoi` (Voronoi tessellation) and `simple` (randomized plane-based recursive slicing). ### Signature ```typescript constructor({ fractureMethod, fragmentCount, voronoiOptions, fracturePlanes, textureScale, textureOffset, seed, }: { fractureMethod?: "voronoi" | "simple"; fragmentCount?: number; voronoiOptions?: VoronoiOptions; fracturePlanes?: { x: boolean; y: boolean; z: boolean; }; textureScale?: Vector2; textureOffset?: Vector2; seed?: number; } = {}) ``` ### Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `fractureMethod` | `"voronoi" \| "simple"` | no | `"voronoi"` | Fracture algorithm to use | | `fragmentCount` | `number` | no | `50` | Target number of fragments. Actual count can be higher for non-convex meshes (isolated islands are split out) | | `voronoiOptions` | `VoronoiOptions` | no | `undefined` (see below) | Voronoi-specific options; **required when `fractureMethod` is `"voronoi"`** | | `fracturePlanes` | `{ x: boolean; y: boolean; z: boolean }` | no | `{ x: true, y: true, z: true }` | Which axes the simple method may fracture along | | `textureScale` | `Vector2` | no | `new Vector2(1, 1)` | UV scale applied to cut-face texture coordinates | | `textureOffset` | `Vector2` | no | `new Vector2()` (`0,0`) | UV offset applied to cut-face texture coordinates | | `seed` | `number` | no | `undefined` (random seed generated) | Random seed; same seed ⇒ same fracture pattern | ### Notes - The constructor accepts a single optional destructured config object; every property is also a public mutable field, so options can be changed after construction. - **Constructor side effect:** after assigning fields, if `fractureMethod === "voronoi"` and `voronoiOptions` is still falsy, the constructor auto-fills `this.voronoiOptions = { mode: "3D" }` (`FractureOptions.ts:163-166`). Therefore the "voronoiOptions is required" error in `DestructibleMesh.fracture()` is defensive and cannot normally be reached through this constructor. ``` -------------------------------- ### FractureOptions Constructor Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Creates a new FractureOptions instance for configuring fracturing operations. Supports both Voronoi and simple plane-based fracturing methods. ```APIDOC ## FractureOptions Constructor ### Description Creates a new FractureOptions instance for configuring fracturing operations. Supports both Voronoi and simple plane-based fracturing methods. ### Constructor ```typescript new FractureOptions({ fractureMethod?: "voronoi" | "simple"; fragmentCount?: number; voronoiOptions?: VoronoiOptions; fracturePlanes?: { x: boolean; y: boolean; z: boolean }; textureScale?: THREE.Vector2; textureOffset?: THREE.Vector2; seed?: number; }) ``` ### Parameters - **fractureMethod** ("voronoi" | "simple") - Optional - Fracture method to use (default: "voronoi"). "voronoi" uses Voronoi tessellation, "simple" uses plane-based fracturing. - **fragmentCount** (number) - Optional - Number of fragments to create (default: 50). Actual count may be higher for non-convex meshes. - **voronoiOptions** (VoronoiOptions) - Optional - Voronoi-specific options (required when fractureMethod is "voronoi"). - **fracturePlanes** ({ x: boolean; y: boolean; z: boolean }) - Optional - Simple fracture: which axes to fracture along (default: all true). - **textureScale** (THREE.Vector2) - Optional - UV scale for internal faces (default: 1,1). - **textureOffset** (THREE.Vector2) - Optional - UV offset for internal faces (default: 0,0). - **seed** (number) - Optional - Random seed for reproducibility. ### Properties - **fractureMethod** ("voronoi" | "simple") - Fracture method to use (default: "voronoi"). - **fragmentCount** (number) - Number of fragments to create (default: 50). - **voronoiOptions** (VoronoiOptions) - Voronoi-specific options (required when fractureMethod is "voronoi"). - **fracturePlanes** ({ x: boolean; y: boolean; z: boolean }) - Simple fracture: which axes to fracture along (default: all true). - **textureScale** (THREE.Vector2) - UV scale for internal faces (default: 1,1). - **textureOffset** (THREE.Vector2) - UV offset for internal faces (default: 0,0). - **seed** (number) - Random seed for reproducibility. ``` -------------------------------- ### Fragment constructor Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/entities.md Creates a Fragment instance. Accepts optional raw typed-array data. When omitted, an empty fragment is created with empty collections and default bounds. ```typescript constructor(args: FragmentArgs | undefined = undefined) ``` -------------------------------- ### Fracturing with Impact Point (voronoi, 3D) Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Configures FractureOptions with an impact point in local space and an impact radius. Smaller impact radius concentrates fragments more. ```TypeScript const options = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: 16, voronoiOptions: { mode: "3D", impactPoint: new THREE.Vector3(0, 1, 0), // Local space impactRadius: 0.5, // Smaller = more concentrated }, }); const fragments = mesh.fracture(options); ``` -------------------------------- ### UnionFind constructor Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/utils.md Creates a new UnionFind instance with a given number of elements, each initially its own parent with rank 1. ```APIDOC ## UnionFind ### Description Disjoint-set data structure with path compression and union-by-rank. Used by `findIsolatedGeometry`. ### Constructor `constructor(size: number)` ### Parameters - **size** (number) - Required - Element count; starts every element as its own parent with rank 1. ``` -------------------------------- ### voronoiFracture(geometry, options) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/fracture-functions.md Implements the Voronoi fracture method. Supports 3D and 2.5D modes, with optional approximation using nearest neighbors. ```APIDOC ## voronoiFracture(geometry, options) ### Description Implements the **Voronoi** method. Creates a `SeededRandom(options.seed)`; if `options.seed` was undefined the generated seed is written back into `options.seed`. Converts `geometry` → `Fragment`, dispatches on `options.mode`: - `"3D"` → `voronoiFracture3D` (private): seeds via `generateSeeds`, then per-seed sequential half-space intersections via `computeVoronoiCell`, then `findIsolatedGeometry` per cell (non-convex mode is hardcoded; `const convex = false`). - `"2.5D"` → `voronoiFracture2D` (private): determines projection axis from `projectionNormal` (dominant axis) or `projectionAxis`/auto, generates seeds on a plane, then uses the same half-space intersection loop seeded entirely on that plane. When `options.useApproximation` is true, both paths call `console.warn(...)` (e.g. `⚠️ Voronoi approximation enabled (k=… neighbors)…`, `VoronoiFracture.ts:77` and `:201`), and cell computation is limited to `k = Math.min(options.approximationNeighborCount, seeds.length - 1)` nearest neighbors via `findKNearestNeighbors`. ### Parameters - Parameter | Type | Required | Description - `geometry` | `THREE.BufferGeometry` | yes | Source mesh to fracture - `options` | `VoronoiFractureOptions` | yes | Voronoi configuration (see [VoronoiFractureOptions.md](VoronoiFractureOptions.md)) ### Returns `THREE.BufferGeometry[]` — non-empty Voronoi cell geometries. ``` -------------------------------- ### Direct engine call: voronoiFracture and slice Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/fracture-functions.md Direct engine call for source-tree consumers. Imports the fracture functions and demonstrates voronoiFracture with fragmentCount 64 and mode "2.5D", plus slice with a plane normal, origin, and UV scale/offset. Both functions return only non-empty results; counting fragments is the caller's responsibility. ```typescript import { slice } from "./lib/src/fracture/Slice"; import { voronoiFracture } from "./lib/src/fracture/VoronoiFracture"; import { VoronoiFractureOptions } from "./lib/src/entities/VoronoiFractureOptions"; const geometries = voronoiFracture( sourceGeometry, new VoronoiFractureOptions({ fragmentCount: 64, mode: "2.5D" }), ); const pieces = slice( sourceGeometry, new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 0), new THREE.Vector2(1, 1), new THREE.Vector2(0, 0), ); ``` -------------------------------- ### hash3(v, tolerance?) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/utils.md Computes a grid-quantized Cantor-pairing hash for a 3D point, used for vertex welding and equality checks. ```APIDOC ## hash3(v, tolerance?) ### Description Computes a grid-quantized Cantor-pairing hash for a 3D point, used for vertex welding and equality checks. ### Method Function ### Signature `hash3(v: Vector3, tolerance: number = 1e-9): number` ### Parameters - **v** (Vector3) - Required - The 3D point to hash. - **tolerance** (number) - Optional - Grid cell size for quantization. Defaults to `1e-9`. ### Returns - **number** - The grid-quantized Cantor-pairing hash value. ``` -------------------------------- ### FractureOptions Constructor Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md The constructor for configuring fracturing operations, supporting both Voronoi and simple plane-based methods. ```typescript new FractureOptions({ fractureMethod?: "voronoi" | "simple"; fragmentCount?: number; voronoiOptions?: VoronoiOptions; fracturePlanes?: { x: boolean; y: boolean; z: boolean }; textureScale?: THREE.Vector2; textureOffset?: THREE.Vector2; seed?: number; }) ``` -------------------------------- ### Refracturing with External Generation Tracking Source: https://github.com/dgreenheck/three-pinata/blob/main/README.md Shows progressive destruction by refracturing fragments. Generation is tracked externally via userData. Includes configuration, initial fracture, and a click handler that checks the generation limit, fractures the fragment, adds new fragments, and disposes the old geometry. ```TypeScript // Configuration const maxGeneration = 3; const fragmentCounts = { 1: 32, // First fracture: 32 fragments 2: 16, // Second fracture: 16 fragments 3: 8, // Third fracture: 8 fragments }; // Initial fracture const mesh = new DestructibleMesh(geometry, outerMaterial, innerMaterial); mesh.userData.generation = 0; // Track generation externally const options1 = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: fragmentCounts[1], voronoiOptions: { mode: "3D", }, }); const fragments = mesh.fracture(options1, (fragment) => { // Track generation in each fragment fragment.userData.generation = 1; scene.add(fragment); }); // Later, refracture a fragment when clicked function onFragmentClick(fragment: DestructibleMesh) { const currentGeneration = fragment.userData.generation || 0; // Check generation limit (external refracture control) if (currentGeneration >= maxGeneration) { return; // Max generation reached } // Determine fragment count for next generation const nextGeneration = currentGeneration + 1; const fragmentCount = fragmentCounts[nextGeneration]; const options = new FractureOptions({ fractureMethod: "voronoi", fragmentCount: fragmentCount, voronoiOptions: { mode: "3D", }, }); const newFragments = fragment.fracture(options, (newFragment) => { // Track generation externally newFragment.userData.generation = nextGeneration; scene.add(newFragment); }); // Clean up old fragment scene.remove(fragment); fragment.geometry.dispose(); } ``` -------------------------------- ### sliceWorld(worldNormal, worldOrigin, options?, onSlice?, onComplete?) Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/DestructibleMesh.md Slices the DestructibleMesh along a plane defined in world space. Transforms the plane into local space and delegates to the slice method, returning an array of new DestructibleMesh pieces. ```APIDOC ## sliceWorld(worldNormal, worldOrigin, options?, onSlice?, onComplete?) ### Description Slices the DestructibleMesh along a plane defined in world space. Transforms the plane into local space and delegates to the slice method, returning an array of new DestructibleMesh pieces. ### Method sliceWorld ### Signature ```typescript sliceWorld( worldNormal: THREE.Vector3, worldOrigin: THREE.Vector3, options?: SliceOptions, onSlice?: (piece: DestructibleMesh, index: number) => void, onComplete?: () => void, ): DestructibleMesh[] ``` ### Parameters - **worldNormal** (THREE.Vector3) - Required - Slice plane normal in **world space** - **worldOrigin** (THREE.Vector3) - Required - Point on the slice plane in **world space** - **options** (SliceOptions) - Optional - Passed through to `slice()` - **onSlice** ((piece: DestructibleMesh, index: number) => void) - Optional - Passed through to `slice()` - **onComplete** (() => void) - Optional - Passed through to `slice()` ### Returns - **DestructibleMesh[]** - Same as `slice()`. ### Behavior Calls `this.updateMatrixWorld(true)` to refresh the world matrix, inverts it (`new THREE.Matrix4().copy(this.matrixWorld).invert()`), transforms the normal with `transformDirection` (normalized) and the origin with `applyMatrix4` into local space, then delegates to `slice(localNormal, localOrigin, ...)`. Since the delegated `slice()` copies the parent's transform onto pieces, positions remain correct in world space. ### Throws - **Error** - `"DestructibleMesh has no geometry to slice"` when `this.geometry` is falsy. ### Usage Example ```typescript const mesh = new DestructibleMesh(geometry, outer, inner); mesh.position.set(5, 0, 0); scene.add(mesh); const worldNormal = new THREE.Vector3(0, 1, 0); const worldOrigin = new THREE.Vector3(5, 0.25, 0); // world-space cut point const pieces = mesh.sliceWorld(worldNormal, worldOrigin); pieces.forEach((p) => scene.add(p)); ``` ``` -------------------------------- ### Import DestructibleMesh Source: https://github.com/dgreenheck/three-pinata/blob/main/_autodocs/api-reference/DestructibleMesh.md Import the DestructibleMesh class from the @dgreenheck/three-pinata package. ```typescript import { DestructibleMesh } from "@dgreenheck/three-pinata"; ```