@dgreenheck/three-pinata

中文文档
2.0.1发布于 2025/11/11MITTypeScriptESM + CJS零依赖近 30 天下载 2万

Three.js library for fracturing and slicing meshes in real time.

原始使用文档
文档目录27 节
  1. Features
  2. Live Demo
  3. Installation
  4. Quick Start
  5. API Reference
  6. Classes
  7. Options
  8. Usage Examples
  9. Basic Fracturing
  10. Fracturing with Impact Point
  11. Refracturing
  12. Simple Fracturing
  13. Slicing
  14. Using Callbacks
  15. Dual Materials
  16. Important Requirements
  17. Manifold/Watertight Meshes
  18. Physics Integration
  19. Basic Physics Pattern
  20. Performance Tips
  21. Limitations
  22. Building
  23. Running the Demo
  24. License
  25. Acknowledgments
  26. Support
  27. Contributing

three-pinata

NPM Version NPM Downloads GitHub Repo stars X (formerly Twitter) Follow YouTube Channel Subscribers

Real-time mesh fracturing and slicing for Three.js

Three-pinata is a library that enables you to fracture and slice 3D meshes in real-time directly in the browser. Whether you're building a destruction physics game, an interactive art piece, or a scientific visualization, three-pinata provides the tools you need to break things beautifully.

Features

  • Voronoi Fracturing - Natural-looking fracture patterns with 3D and 2.5D modes
  • Impact-Based Fracturing - Concentrate fragments around impact points for realistic destruction
  • Refracturing - Fragments can be fractured again for progressive destruction (generation tracking handled externally)
  • Plane Slicing - Slice meshes along arbitrary planes in local or world space
  • Dual Materials - Separate materials for outer surfaces and internal fracture faces
  • Custom Seed Points - Full control over fracture patterns with custom Voronoi seeds
  • UV Mapping - Automatic UV generation for internal faces with configurable scale and offset

Live Demo

Check out the interactive demo: https://three-pinata-demo.vercel.app/

Installation

npm install @dgreenheck/three-pinata

Requires Three.js >= 0.158.0 as a peer dependency.

Quick Start

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);

API Reference

Classes

DestructibleMesh

Extends THREE.Mesh with built-in fracturing and slicing capabilities.

Constructor:

new DestructibleMesh(
  geometry?: THREE.BufferGeometry,
  outerMaterial?: THREE.Material,
  innerMaterial?: THREE.Material
)

Parameters:

  • geometry - The geometry for the mesh
  • outerMaterial - Material for the original outer surfaces
  • innerMaterial - Material for newly created internal fracture/slice faces (optional, defaults to outerMaterial)

Methods:

fracture(options, onFragment?, onComplete?)

Fractures the mesh into fragments.

  • Parameters:
    • options: FractureOptions - Fracture configuration
    • onFragment?: (fragment: DestructibleMesh, index: number) => void - Optional callback for each fragment
    • onComplete?: () => void - Optional callback when fracturing is complete
  • Returns: DestructibleMesh[] - Array of fragment meshes
slice(sliceNormal, sliceOrigin, options?, onSlice?, onComplete?)

Slices the mesh along a plane (local space).

  • Parameters:
    • sliceNormal: THREE.Vector3 - Slice plane normal (local space)
    • sliceOrigin: THREE.Vector3 - Point on slice plane (local space)
    • options?: SliceOptions - Slice configuration
    • onSlice?: (piece: DestructibleMesh, index: number) => void - Optional callback for each piece
    • onComplete?: () => void - Optional callback when slicing is complete
  • Returns: DestructibleMesh[] - Array of fragment meshes
sliceWorld(worldNormal, worldOrigin, options?, onSlice?, onComplete?)

Slices the mesh along a plane (world space).

  • Parameters:
    • worldNormal: THREE.Vector3 - Slice plane normal (world space)
    • worldOrigin: THREE.Vector3 - Point on slice plane (world space)
    • options?: SliceOptions - Slice configuration
    • onSlice?: (piece: DestructibleMesh, index: number) => void - Optional callback for each piece
    • onComplete?: () => void - Optional callback when slicing is complete
  • Returns: DestructibleMesh[] - Array of fragment meshes
dispose()

Disposes the mesh geometry and material to free up memory.

  • Parameters: None
  • Returns: void

Options

FractureOptions

Configuration for fracturing operations. Supports both Voronoi and simple plane-based fracturing.

Constructor:

new FractureOptions({
  fractureMethod?: "voronoi" | "simple";
  fragmentCount?: number;
  voronoiOptions?: VoronoiOptions;
  fracturePlanes?: { x: boolean; y: boolean; z: boolean };
  textureScale?: THREE.Vector2;
  textureOffset?: THREE.Vector2;
  seed?: number;
})

Properties:

  • fractureMethod: "voronoi" | "simple" - Fracture method to use (default: "voronoi")
    • "voronoi": Natural-looking fracture using Voronoi tessellation (requires voronoiOptions)
    • "simple": Simple plane-based fracturing (fast, lower quality)
  • fragmentCount: number - Number of fragments to create (default: 50). Note that actual fragment count may be higher when fracturing non-convex meshes.
  • 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

VoronoiOptions

Voronoi-specific fracture configuration (used within FractureOptions).

Interface:

{
  mode: "3D" | "2.5D";
  seedPoints?: THREE.Vector3[];
  impactPoint?: THREE.Vector3;
  impactRadius?: number;
  projectionAxis?: "x" | "y" | "z" | "auto";
  projectionNormal?: THREE.Vector3;
  useApproximation?: boolean;
  approximationNeighborCount?: number;
}

Properties:

  • mode: "3D" | "2.5D" - Voronoi fracture mode (required)
    • "3D": Full 3D Voronoi tessellation - most realistic, slower
    • "2.5D": 2D Voronoi projected through mesh - faster, good for flat objects
  • seedPoints?: THREE.Vector3[] - Custom seed points for Voronoi cells in local space (auto-generated if not provided)
  • impactPoint?: THREE.Vector3 - Impact location in local space to concentrate fragments around
  • impactRadius?: number - Radius around impact point for fragment density
  • projectionAxis?: "x" | "y" | "z" | "auto" - For 2.5D mode: projection axis (default: "auto")
  • projectionNormal?: THREE.Vector3 - For 2.5D mode: optional projection plane normal
  • useApproximation?: boolean - Use K-nearest neighbor approximation for performance (default: false)
    • Warning: May cause fragment overlap when enabled
  • approximationNeighborCount?: number - Neighbors to consider when using approximation (default: 12)

SliceOptions

Configuration for slicing operations.

Constructor:

new SliceOptions();

Properties:

  • textureScale: THREE.Vector2 - UV scale for internal faces (default: 1,1)
  • textureOffset: THREE.Vector2 - UV offset for internal faces (default: 0,0)

Usage Examples

Basic Fracturing

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;

Fracturing with Impact Point

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);

Refracturing

Fragments can be fractured again for progressive destruction. Generation tracking is handled externally using userData:

// 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();
}

This external approach gives you complete control over refracture behavior, allowing you to implement custom strategies like:

  • Progressive weakening (fewer fragments per generation)
  • Energy-based refracturing (only if impact force exceeds threshold)
  • Material-based limits (glass refractures more than wood)
  • Performance-based throttling (stop refracturing when FPS drops)

Simple Fracturing

const options = new FractureOptions({
  fractureMethod: "simple",
  fragmentCount: 50,
  fracturePlanes: { x: true, y: true, z: true },
});

const fragments = mesh.fracture(options);

Slicing

// Create mesh with outer and inner materials
const mesh = new DestructibleMesh(geometry, outerMaterial, innerMaterial);

const sliceNormal = new THREE.Vector3(0, 1, 0); // Horizontal cut
const sliceOrigin = new THREE.Vector3(0, 0, 0); // At origin

const options = new SliceOptions();
const pieces = mesh.slice(sliceNormal, sliceOrigin, options);

// Materials are already set automatically
pieces.forEach((piece) => scene.add(piece));
mesh.visible = false;

Using Callbacks

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!");
  },
);

Dual Materials

Fragments support two materials - one for the original surface, one for internal fracture faces.

Recommended approach: Pass both materials to the constructor:

const outerMaterial = new THREE.MeshStandardMaterial({ color: 0xff6644 });
const innerMaterial = new THREE.MeshStandardMaterial({ color: 0xdddddd });

// Materials are automatically inherited by fragments
const mesh = new DestructibleMesh(geometry, outerMaterial, innerMaterial);

const fragments = mesh.fracture(options, (fragment) => {
  // Material array [outer, inner] is already set automatically
  scene.add(fragment);
});

Alternative: Manually set materials on each fragment:

const mesh = new DestructibleMesh(geometry, outerMaterial);

const fragments = mesh.fracture(options, (fragment) => {
  // Manually assign material array: [outer, inner]
  fragment.material = [outerMaterial, innerMaterial];
  scene.add(fragment);
});

The fragment geometries include two material groups:

  • Group 0 (materialIndex 0): Original outer surface faces
  • Group 1 (materialIndex 1): Newly created internal fracture faces

Important Requirements

Manifold/Watertight Meshes

The library requires manifold (watertight) meshes - 3D models that form a completely closed, solid volume with no holes or self-intersecting geometry.

Valid meshes:

  • Sphere, cube, cylinder, torus
  • Closed character models
  • Properly modeled objects with no gaps

Invalid meshes:

  • Planes or single-sided surfaces
  • Meshes with holes or missing faces
  • Open-ended cylinders or boxes
  • Overlapping geometry

How to check/fix in Blender:

  1. Select mesh in Edit mode
  2. Mesh > Clean Up > Make Manifold
  3. Use "3D Print Toolbox" addon to check for issues

Why this matters:

  • Fracturing algorithms need to determine "inside" vs "outside"
  • Non-manifold meshes create ambiguity leading to missing faces, holes, and visual artifacts
  • Physics colliders will behave unpredictably with non-manifold fragments

Physics Integration

The library handles geometry only. For realistic destruction, integrate with a physics engine like Rapier.

Basic Physics Pattern

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...
}

For a complete implementation, see:

  • demo/src/physics/PhysicsWorld.ts - Complete physics wrapper
  • demo/src/physics/PhysicsBody.ts - Physics body wrapper
  • demo/src/scenes/SmashingScene.ts - Real-world example

Performance Tips

  • Fragment Count: 10-50 fragments is optimal. 100+ may cause lag on slower devices
  • 2.5D vs 3D: Use 2.5D mode when possible - significantly faster
  • Pre-fracture: Fracture ahead of time and keep fragments hidden for instant destruction
  • Approximation: For high fragment counts (>50), enable useApproximation (may cause overlap)
  • Physics: More fragments = more physics bodies. Despawn fragments after they settle

Limitations

  • Manifold Requirement: Meshes must be watertight (no holes or self-intersecting geometry)
  • Memory: Each fragment is a new geometry. Plan accordingly for many destructible objects
  • Physics Required: Library only handles geometry - you must add physics integration

Building

npm run build:lib

Generates:

  • three-pinata.es.js - ES Module
  • three-pinata.umd.js - UMD Module
  • Type declarations

Running the Demo

npm install
npm run dev

Open http://localhost:5173/

License

This project is licensed under the MIT License.

Acknowledgments

Support

For bugs or feature requests, create an issue in the issue tracker.

Contributing

Contributions welcome! Please submit a Pull Request.

版本与发布记录

@dgreenheck/three-pinata 共发布 25 个版本,首次发布于 2025/03/04;过去 12 个月发布了 0 个版本。

版本发布日期距今
2.0.1latest2025/11/111 年前
2.0.02025/11/111 年前
1.0.52025/11/051 年前
1.0.42025/11/051 年前
1.0.32025/11/051 年前
1.0.22025/11/051 年前

关于 @dgreenheck/three-pinata 的常见问题

@dgreenheck/three-pinata 是什么?

Three.js library for fracturing and slicing meshes in real time.

如何安装 @dgreenheck/three-pinata?

在项目目录运行 npm install @dgreenheck/three-pinata,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:three(>=0.158.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@dgreenheck/three-pinata 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dgreenheck/three-pinata 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dgreenheck/three-pinata 可以商用吗?

@dgreenheck/three-pinata 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dgreenheck/three-pinata 的下载量有多少?

根据 npm 官方下载统计,@dgreenheck/three-pinata 在 2026/10/12 – 2026/11/10 这 30 天内被下载 19,684 次,日均约 656 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dgreenheck/three-pinata 还在维护吗?

最新版本 2.0.1 发布于 2025/11/11(1 年前);至今共发布 25 个版本,其中过去 12 个月发布了 0 个;首次发布于 2025/03/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。