three-zoo 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,152 +1,210 @@
1
1
  <p align="center">
2
- <h1 align="center">🦁 three-zoo</h1>
3
- <p align="center">
4
- A small collection of Three.js utilities I use in my daily work with 3D development.
5
- </p>
2
+ <h1 align="center">🦁 🐘 🦊 three-zoo</h1>
3
+ <p align="center">Reusable Three.js utilities.</p>
6
4
  </p>
7
5
 
8
6
  <p align="center">
9
7
  <a href="https://www.npmjs.com/package/three-zoo"><img src="https://img.shields.io/npm/v/three-zoo.svg" alt="npm version"></a>
10
- <a href="https://bundlephobia.com/package/three-zoo"><img src="https://badgen.net/bundlephobia/min/three-zoo" alt="bundle size (min)"></a>
11
8
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
12
9
  <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-%5E5.8.0-blue" alt="TypeScript"></a>
13
10
  <a href="https://threejs.org/"><img src="https://img.shields.io/badge/Three.js-%5E0.175.0-green" alt="Three.js"></a>
14
11
  </p>
15
12
 
16
- ## What's included
17
-
18
- - 📷 **DualFovCamera** - Camera with separate horizontal and vertical FOV controls
19
- - ☀️ **Sun** - Directional light with spherical positioning
20
- - 🔍 **SceneTraversal** - Helper functions for finding objects and materials in scenes
21
- - 🎭 **SkinnedMeshBaker** - Converts animated meshes to static geometry
22
- - 🎨 **StandardToLambertConverter** - Converts PBR materials to Lambert materials
23
- - ✨ **StandardToBasicConverter** - Converts PBR materials to Basic materials
24
-
25
13
  ## Installation
26
14
 
27
15
  ```bash
28
16
  npm install three-zoo
29
17
  ```
30
18
 
31
- ## DualFovCamera
19
+ ## Contents
32
20
 
33
- A camera that lets you control horizontal and vertical field of view separately:
21
+ - **IK** - `TwoBoneIK`, `AimChainIK`
22
+ - **Instanced Mesh Pool** - `InstancedMeshPool`, `InstancedMeshInstance`, `InstancedMeshGroup`
23
+ - **Lighting** - `Sun`, `SkyLight`
24
+ - **Material Converters** - Standard to Basic / Lambert / Phong / Toon / Physical, Basic to Physical
25
+ - **Miscellaneous** - `DualFovCamera`, `SceneTraversal`, `SceneSorter`, `SkinnedMeshBaker`
34
26
 
35
- ```typescript
36
- const camera = new DualFovCamera(90, 60);
27
+ ---
37
28
 
38
- // Set FOV values independently
39
- camera.horizontalFov = 100;
40
- camera.verticalFov = 70;
29
+ ## IK
41
30
 
42
- // Fit objects in view
43
- camera.fitVerticalFovToPoints(vertices);
44
- camera.fitVerticalFovToBox(boundingBox);
45
- camera.fitVerticalFovToMesh(skinnedMesh);
31
+ ### TwoBoneIK
46
32
 
47
- // Position camera based on mesh center
48
- camera.lookAtMeshCenterOfMass(skinnedMesh);
33
+ Analytical two-bone IK solver. Chain: `root -> middle -> end`. Pole controls bend direction. Writes local quaternions to `root` and `middle`.
34
+
35
+ ```typescript
36
+ const twoBoneIK = new TwoBoneIK(upperArm, foreArm, hand, poleObject, targetObject);
37
+
38
+ // call after AnimationMixer.update() each frame
39
+ twoBoneIK.solve();
40
+
41
+ // tune pole twist per bone
42
+ twoBoneIK.rootPoleAxis.set(0, 1, 0);
43
+ twoBoneIK.middlePoleTwist = false;
49
44
  ```
50
45
 
51
- ## Sun
46
+ ### AimChainIK
52
47
 
53
- A directional light with spherical positioning:
48
+ Distributes aim rotation across a bone chain according to per-bone weights.
54
49
 
55
50
  ```typescript
56
- const sun = new Sun();
51
+ const aimChainIK = new AimChainIK([spine1, spine2, spine3, head]);
57
52
 
58
- // Position using spherical coordinates
59
- sun.elevation = Math.PI / 4; // 45° above horizon
60
- sun.azimuth = Math.PI / 2; // 90° rotation
61
- sun.distance = 100; // Distance from origin
53
+ aimChainIK.curve = [0.2, 0.5, 0.8, 1.0]; // root gets least, tip gets most
54
+ aimChainIK.weight = 0.8; // global blend
62
55
 
63
- // Set up shadows for a bounding box
64
- sun.configureShadowsForBoundingBox(sceneBounds);
56
+ // sample directions before calling - mutates bone quaternions
57
+ aimChainIK.solve(currentForward, targetDirection);
58
+ ```
65
59
 
66
- // Position based on HDR environment map
67
- sun.setDirectionFromHDRTexture(hdrTexture, 50);
60
+ ---
61
+
62
+ ## Instanced Mesh Pool
63
+
64
+ Manages `InstancedMesh` instances keyed by geometry+material. Grows capacity automatically.
65
+
66
+ ```typescript
67
+ const pool = new InstancedMeshPool(scene, { initialCapacity: 32, capacityStep: 16 });
68
+
69
+ // allocate / update / release individual instances
70
+ const instance = new InstancedMeshInstance(pool, geometry, material);
71
+ instance.setPosition3f(1, 0, 0).setScale3f(2, 2, 2).flushTransform();
72
+ instance.destroy();
73
+
74
+ // group multiple instances under a shared Object3D transform
75
+ const group = new InstancedMeshGroup([instanceA, instanceB]);
76
+ scene.add(group);
77
+ group.position.set(10, 0, 0);
78
+ group.flushTransform(); // propagates group world matrix to all instances
79
+ group.destroy();
68
80
  ```
69
81
 
70
- ## SceneTraversal
82
+ ---
83
+
84
+ ## Lighting
71
85
 
72
- Functions for working with Three.js scene graphs:
86
+ ### Sun
87
+
88
+ `DirectionalLight` with spherical positioning and shadow auto-configuration.
73
89
 
74
90
  ```typescript
75
- // Find by name
76
- const player = SceneTraversal.getObjectByName(scene, 'Player');
77
- const metal = SceneTraversal.getMaterialByName(scene, 'MetalMaterial');
91
+ const sun = new Sun();
92
+ sun.elevation = Math.PI / 4;
93
+ sun.azimuth = Math.PI / 2;
94
+ sun.distance = 100;
78
95
 
79
- // Find with patterns
80
- const enemies = SceneTraversal.filterObjects(scene, /^enemy_/);
81
- const glassMats = SceneTraversal.filterMaterials(scene, /glass/i);
96
+ sun.configureShadowsForBoundingBox(sceneBounds);
97
+ sun.setDirectionFromHDRTexture(hdrTexture, 50);
98
+ ```
82
99
 
83
- // Find objects using specific materials
84
- const glassObjects = SceneTraversal.findMaterialUsers(scene, /glass/i);
100
+ ### SkyLight
85
101
 
86
- // Process all materials in a scene
87
- SceneTraversal.enumerateMaterials(scene, (material) => {
88
- if ('roughness' in material) material.roughness = 0.8;
102
+ `HemisphereLight` that extracts sky and ground colors from an HDR environment map.
103
+
104
+ ```typescript
105
+ const skyLight = new SkyLight();
106
+ skyLight.setColorsFromHDRTexture(hdrTexture, {
107
+ skySampleCount: 100,
108
+ groundSampleCount: 100,
89
109
  });
90
110
  ```
91
111
 
92
- ## SkinnedMeshBaker
112
+ ---
113
+
114
+ ## Material Converters
115
+
116
+ All converters expose a single static `convert(material, options?)`. Common options:
93
117
 
94
- Convert animated meshes to static geometry:
118
+ | Option | Default | Description |
119
+ |------------------|---------|--------------------------------------|
120
+ | `preserveName` | `true` | Copy `.name` to the new material |
121
+ | `copyUserData` | `true` | Copy `.userData` |
122
+ | `disposeOriginal`| `false` | Dispose source material after conversion |
95
123
 
96
124
  ```typescript
97
- // Bake current pose
98
- const staticMesh = SkinnedMeshBaker.bakePose(skinnedMesh);
125
+ // Standard -> unlit
126
+ const basicMaterial = StandardToBasicConverter.convert(standardMaterial, { brightnessFactor: 1.3, combineEmissive: true });
127
+
128
+ // Standard -> diffuse-only lit
129
+ const lambertMaterial = StandardToLambertConverter.convert(standardMaterial);
130
+ const phongMaterial = StandardToPhongConverter.convert(standardMaterial);
131
+ const toonMaterial = StandardToToonConverter.convert(standardMaterial);
99
132
 
100
- // Bake specific animation frame
101
- const frameMesh = SkinnedMeshBaker.bakeAnimationFrame(
102
- armature,
103
- skinnedMesh,
104
- 1.5, // Time in seconds
105
- animationClip
106
- );
133
+ // Standard <-> Physical
134
+ const physicalMaterial = StandardToPhysicalConverter.convert(standardMaterial);
135
+ const standardMaterial2 = BasicToPhysicalConverter.convert(basicMaterial);
107
136
  ```
108
137
 
109
- ## Material Converters
138
+ ---
139
+
140
+ ## Miscellaneous
110
141
 
111
- Convert PBR materials to simpler types. Useful for performance optimization:
142
+ ### DualFovCamera
112
143
 
113
- ### StandardToLambertConverter
144
+ `PerspectiveCamera` with independent horizontal and vertical FOV.
114
145
 
115
146
  ```typescript
116
- // Basic conversion
117
- const lambertMaterial = StandardToLambertConverter.convert(standardMaterial);
118
-
119
- // With options
120
- const lambertMaterial = StandardToLambertConverter.convert(standardMaterial, {
121
- preserveName: true,
122
- roughnessColorFactor: 0.9,
123
- disposeOriginal: true
124
- });
147
+ const camera = new DualFovCamera(90, 60);
148
+ camera.horizontalFov = 100;
149
+ camera.verticalFov = 70;
150
+
151
+ camera.fitVerticalFovToPoints(vertices);
152
+ camera.fitVerticalFovToBox(boundingBox);
153
+ camera.fitVerticalFovToMesh(skinnedMesh);
154
+ camera.lookAtMeshCenterOfMass(skinnedMesh);
125
155
  ```
126
156
 
127
- ### StandardToBasicConverter
157
+ ### SceneTraversal
158
+
159
+ Static helpers for depth-first scene graph traversal.
128
160
 
129
161
  ```typescript
130
- // Convert to unlit material
131
- const basicMaterial = StandardToBasicConverter.convert(standardMaterial);
132
-
133
- // With brightness adjustment
134
- const basicMaterial = StandardToBasicConverter.convert(standardMaterial, {
135
- brightnessFactor: 1.5,
136
- combineEmissive: true,
137
- preserveName: true,
138
- disposeOriginal: false
162
+ // find by name
163
+ const playerObject = SceneTraversal.getObjectByName(scene, 'Player');
164
+ const metalMaterial = SceneTraversal.getMaterialByName(scene, 'Metal');
165
+
166
+ // filter by regex or predicate
167
+ const enemies = SceneTraversal.filterObjects(scene, /^enemy_/);
168
+ const glassMaterials = SceneTraversal.filterMaterials(scene, /glass/i);
169
+
170
+ // enumerate with callback
171
+ SceneTraversal.enumerateMaterials(scene, (material) => {
172
+ material.needsUpdate = true;
139
173
  });
174
+
175
+ // find meshes that use specific materials
176
+ const glassMeshes = SceneTraversal.findMaterialUsers(scene, glassMaterials);
140
177
  ```
141
178
 
142
- ## Requirements
179
+ ### SceneSorter
180
+
181
+ Assigns sequential `renderOrder` values sorted by distance to a point. Useful for transparent meshes.
182
+
183
+ ```typescript
184
+ // front-to-back
185
+ SceneSorter.sortByDistanceToPoint(object, cameraPosition, 0);
186
+
187
+ // back-to-front (transparent objects)
188
+ SceneSorter.sortByDistanceToPoint(object, cameraPosition, 0, true);
189
+ ```
143
190
 
144
- - Three.js ^0.175.0 (peer dependency)
145
- - TypeScript support included
191
+ ### SkinnedMeshBaker
146
192
 
147
- ## Contributing
193
+ Bakes a `SkinnedMesh` to static geometry.
194
+
195
+ ```typescript
196
+ // current pose
197
+ const staticMesh = SkinnedMeshBaker.bakePose(skinnedMesh);
198
+
199
+ // specific animation frame
200
+ const frameMesh = SkinnedMeshBaker.bakeAnimationFrame(armature, skinnedMesh, 1.5, animationClip);
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Requirements
148
206
 
149
- Feel free to submit issues and pull requests if you find these utilities helpful.
207
+ - `three` >=0.157.0 <0.180.0 (peer dependency)
150
208
 
151
209
  ## License
152
210
 
@@ -0,0 +1,58 @@
1
+ import type { Object3D } from "three";
2
+ import { Vector3 } from "three";
3
+ /**
4
+ * Distributes aim rotation across a chain of bones.
5
+ *
6
+ * Computes the swing rotation from `currentDirection` to `targetDirection`
7
+ * and splits it across bones according to `curve` weights.
8
+ *
9
+ * Mutates `bone.quaternion` directly. Call after AnimationMixer update.
10
+ */
11
+ export declare class AimChainIK {
12
+ readonly bones: readonly Object3D[];
13
+ /** Numerical stability threshold. */
14
+ epsilon: number;
15
+ /**
16
+ * Global blend weight. 0 = solver has no effect, 1 = full effect.
17
+ * Clamped to 0–1 internally.
18
+ */
19
+ weight: number;
20
+ /**
21
+ * Per-bone rotation weights. Normalized internally so their sum equals 1.
22
+ *
23
+ * `[1, 1, 1, 1]` = uniform. `[0.2, 0.5, 0.8, 1.0]` = root gets least, tip gets most.
24
+ *
25
+ * Compared by reference — mutating values in-place won't trigger
26
+ * renormalization. Assign a new array to update.
27
+ *
28
+ * Length must match bone count; mismatches throw on `solve()`.
29
+ */
30
+ curve: readonly number[];
31
+ private readonly swingAxis;
32
+ private readonly deltaRotation;
33
+ private readonly boneWorldQuaternion;
34
+ private readonly parentWorldQuaternion;
35
+ private normalizedCurve;
36
+ private lastCurveReference;
37
+ /**
38
+ * @param bones - Ordered from root to tip. Must contain at least one bone.
39
+ */
40
+ constructor(bones: readonly Object3D[]);
41
+ /**
42
+ * Rotate the chain so that `currentDirection` aligns with `targetDirection`.
43
+ *
44
+ * Both vectors are in world space and are not mutated.
45
+ *
46
+ * Sample directions **before** calling — this method mutates bone quaternions,
47
+ * so any direction derived from the chain will be stale after the call.
48
+ *
49
+ * @param currentDirection - Where the chain currently aims.
50
+ * @param targetDirection - Where it should aim.
51
+ */
52
+ solve(currentDirection: Vector3, targetDirection: Vector3): void;
53
+ /**
54
+ * Returns normalized curve (sums to 1).
55
+ * Rebuilds only when the `curve` reference changes.
56
+ */
57
+ private getNormalizedCurve;
58
+ }
@@ -0,0 +1,130 @@
1
+ import { Vector3, Quaternion, MathUtils } from 'three';
2
+
3
+ /**
4
+ * Distributes aim rotation across a chain of bones.
5
+ *
6
+ * Computes the swing rotation from `currentDirection` to `targetDirection`
7
+ * and splits it across bones according to `curve` weights.
8
+ *
9
+ * Mutates `bone.quaternion` directly. Call after AnimationMixer update.
10
+ */
11
+ class AimChainIK {
12
+ /**
13
+ * @param bones - Ordered from root to tip. Must contain at least one bone.
14
+ */
15
+ constructor(bones) {
16
+ this.bones = bones;
17
+ /** Numerical stability threshold. */
18
+ this.epsilon = 1e-5;
19
+ /**
20
+ * Global blend weight. 0 = solver has no effect, 1 = full effect.
21
+ * Clamped to 0–1 internally.
22
+ */
23
+ this.weight = 1;
24
+ this._private_swingAxis = new Vector3();
25
+ this._private_deltaRotation = new Quaternion();
26
+ this._private_boneWorldQuaternion = new Quaternion();
27
+ this._private_parentWorldQuaternion = new Quaternion();
28
+ this._private_normalizedCurve = [];
29
+ this._private_lastCurveReference = undefined;
30
+ if (bones.length === 0) {
31
+ throw new Error("AimChainIK requires at least one bone.");
32
+ }
33
+ const uniform = Array(bones.length).fill(1);
34
+ this.curve = uniform;
35
+ }
36
+ /**
37
+ * Rotate the chain so that `currentDirection` aligns with `targetDirection`.
38
+ *
39
+ * Both vectors are in world space and are not mutated.
40
+ *
41
+ * Sample directions **before** calling — this method mutates bone quaternions,
42
+ * so any direction derived from the chain will be stale after the call.
43
+ *
44
+ * @param currentDirection - Where the chain currently aims.
45
+ * @param targetDirection - Where it should aim.
46
+ */
47
+ solve(currentDirection, targetDirection) {
48
+ const effectiveWeight = MathUtils.clamp(this.weight, 0, 1);
49
+ if (effectiveWeight < this.epsilon) {
50
+ return;
51
+ }
52
+ const currentLength = currentDirection.length();
53
+ const targetLength = targetDirection.length();
54
+ if (currentLength < this.epsilon || targetLength < this.epsilon) {
55
+ return;
56
+ }
57
+ const dotProduct = MathUtils.clamp(currentDirection.dot(targetDirection) / (currentLength * targetLength), -1, 1);
58
+ const totalAngle = Math.acos(dotProduct);
59
+ if (totalAngle < this.epsilon) {
60
+ return;
61
+ }
62
+ this._private_swingAxis.copy(currentDirection).cross(targetDirection).normalize();
63
+ // Near-opposite directions: pick an arbitrary perpendicular axis.
64
+ if (this._private_swingAxis.lengthSq() < this.epsilon) {
65
+ this._private_swingAxis.set(0, 1, 0);
66
+ this._private_swingAxis.addScaledVector(currentDirection, -this._private_swingAxis.dot(currentDirection) / (currentLength * currentLength));
67
+ if (this._private_swingAxis.lengthSq() < this.epsilon) {
68
+ this._private_swingAxis.set(1, 0, 0);
69
+ }
70
+ this._private_swingAxis.normalize();
71
+ }
72
+ const curve = this._private_getNormalizedCurve();
73
+ const weightedAngle = totalAngle * effectiveWeight;
74
+ for (let index = 0; index < this.bones.length; index++) {
75
+ const bone = this.bones[index];
76
+ const boneAngle = weightedAngle * curve[index];
77
+ if (boneAngle < this.epsilon) {
78
+ continue;
79
+ }
80
+ this._private_deltaRotation.setFromAxisAngle(this._private_swingAxis, boneAngle);
81
+ if (bone.parent) {
82
+ bone.parent.getWorldQuaternion(this._private_parentWorldQuaternion);
83
+ }
84
+ else {
85
+ this._private_parentWorldQuaternion.identity();
86
+ }
87
+ // localDelta = parentInverse * worldDelta * parentWorld
88
+ this._private_boneWorldQuaternion.copy(this._private_parentWorldQuaternion)
89
+ .invert()
90
+ .multiply(this._private_deltaRotation)
91
+ .multiply(this._private_parentWorldQuaternion);
92
+ bone.quaternion.premultiply(this._private_boneWorldQuaternion);
93
+ }
94
+ }
95
+ /**
96
+ * Returns normalized curve (sums to 1).
97
+ * Rebuilds only when the `curve` reference changes.
98
+ */
99
+ _private_getNormalizedCurve() {
100
+ if (this._private_lastCurveReference === this.curve) {
101
+ return this._private_normalizedCurve;
102
+ }
103
+ if (this.curve.length !== this.bones.length) {
104
+ throw new Error(`AimChainIK: curve length (${this.curve.length}) must match bone count (${this.bones.length}).`);
105
+ }
106
+ let sum = 0;
107
+ for (const value of this.curve) {
108
+ sum += value;
109
+ }
110
+ if (this._private_normalizedCurve.length !== this.curve.length) {
111
+ this._private_normalizedCurve = new Array(this.curve.length);
112
+ }
113
+ if (sum < this.epsilon) {
114
+ const uniform = 1 / this.bones.length;
115
+ for (let index = 0; index < this._private_normalizedCurve.length; index++) {
116
+ this._private_normalizedCurve[index] = uniform;
117
+ }
118
+ }
119
+ else {
120
+ const inverseSum = 1 / sum;
121
+ for (let index = 0; index < this.curve.length; index++) {
122
+ this._private_normalizedCurve[index] = this.curve[index] * inverseSum;
123
+ }
124
+ }
125
+ this._private_lastCurveReference = this.curve;
126
+ return this._private_normalizedCurve;
127
+ }
128
+ }
129
+
130
+ export { AimChainIK };
@@ -91,8 +91,7 @@ class TwoBoneIK {
91
91
  const middlePerpendicular = upperLength * Math.sin(angleRoot);
92
92
  this._private_perpendicularDirection.subVectors(this._private_polePosition, this._private_rootPosition);
93
93
  this._private_perpendicularDirection.addScaledVector(this._private_chainDirection, -this._private_perpendicularDirection.dot(this._private_chainDirection));
94
- // Guard: pole collinear with chain perpendicular is degenerate.
95
- // Pick an arbitrary axis perpendicular to chain.
94
+ // If the pole is collinear with the chain then we must choose an arbitrary axis perpendicular to the chain.
96
95
  if (this._private_perpendicularDirection.lengthSq() < this.epsilon) {
97
96
  this._private_perpendicularDirection.set(0, 1, 0);
98
97
  this._private_perpendicularDirection.addScaledVector(this._private_chainDirection, -this._private_perpendicularDirection.dot(this._private_chainDirection));
@@ -121,11 +120,10 @@ class TwoBoneIK {
121
120
  * which constrains aim direction but not roll.
122
121
  */
123
122
  _private_twistBoneTowardPole(bone, child, poleAxis) {
124
- const epsilon = this.epsilon;
125
123
  bone.getWorldPosition(this._private_bonePosition);
126
124
  child.getWorldPosition(this._private_childPosition);
127
125
  this._private_aimAxis.subVectors(this._private_childPosition, this._private_bonePosition);
128
- if (this._private_aimAxis.lengthSq() < epsilon) {
126
+ if (this._private_aimAxis.lengthSq() < this.epsilon) {
129
127
  return;
130
128
  }
131
129
  this._private_aimAxis.normalize();
@@ -133,12 +131,12 @@ class TwoBoneIK {
133
131
  this._private_currentUp.copy(poleAxis).applyQuaternion(this._private_boneWorldQuaternion);
134
132
  this._private_desiredUp.subVectors(this._private_polePosition, this._private_bonePosition);
135
133
  this._private_desiredUp.addScaledVector(this._private_aimAxis, -this._private_desiredUp.dot(this._private_aimAxis));
136
- if (this._private_desiredUp.lengthSq() < epsilon) {
134
+ if (this._private_desiredUp.lengthSq() < this.epsilon) {
137
135
  return;
138
136
  }
139
137
  this._private_desiredUp.normalize();
140
138
  this._private_currentUp.addScaledVector(this._private_aimAxis, -this._private_currentUp.dot(this._private_aimAxis));
141
- if (this._private_currentUp.lengthSq() < epsilon) {
139
+ if (this._private_currentUp.lengthSq() < this.epsilon) {
142
140
  console.warn(`TwoBoneIK: poleAxis is parallel to bone's aim axis - twist is undefined.`);
143
141
  return;
144
142
  }
@@ -148,7 +146,7 @@ class TwoBoneIK {
148
146
  if (this._private_currentDirection.dot(this._private_aimAxis) < 0) {
149
147
  angle = -angle;
150
148
  }
151
- if (Math.abs(angle) < epsilon) {
149
+ if (Math.abs(angle) < this.epsilon) {
152
150
  return;
153
151
  }
154
152
  this._private_twistRotation.setFromAxisAngle(this._private_aimAxis, angle);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { AimChainIK } from "./ik/AimChainIK";
2
+ export { TwoBoneIK } from "./ik/TwoBoneIK";
1
3
  export { InstancedMeshGroup } from "./instancedMeshPool/InstancedMeshGroup";
2
4
  export { InstancedMeshInstance } from "./instancedMeshPool/InstancedMeshInstance";
3
5
  export { InstancedMeshPool } from "./instancedMeshPool/InstancedMeshPool";
@@ -13,4 +15,3 @@ export { DualFovCamera } from "./miscellaneous/DualFovCamera";
13
15
  export { SceneSorter } from "./miscellaneous/SceneSorter";
14
16
  export { SceneTraversal } from "./miscellaneous/SceneTraversal";
15
17
  export { SkinnedMeshBaker } from "./miscellaneous/SkinnedMeshBaker";
16
- export { TwoBoneIK } from "./miscellaneous/TwoBoneIK";
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ export { AimChainIK } from './ik/AimChainIK.js';
2
+ export { TwoBoneIK } from './ik/TwoBoneIK.js';
1
3
  export { InstancedMeshGroup } from './instancedMeshPool/InstancedMeshGroup.js';
2
4
  export { InstancedMeshInstance } from './instancedMeshPool/InstancedMeshInstance.js';
3
5
  export { InstancedMeshPool } from './instancedMeshPool/InstancedMeshPool.js';
@@ -13,4 +15,3 @@ export { DualFovCamera } from './miscellaneous/DualFovCamera.js';
13
15
  export { SceneSorter } from './miscellaneous/SceneSorter.js';
14
16
  export { SceneTraversal } from './miscellaneous/SceneTraversal.js';
15
17
  export { SkinnedMeshBaker } from './miscellaneous/SkinnedMeshBaker.js';
16
- export { TwoBoneIK } from './miscellaneous/TwoBoneIK.js';
@@ -1,4 +1,4 @@
1
- import type { BufferGeometry, Material } from "three";
1
+ import type { BufferGeometry, InstancedMesh, Material } from "three";
2
2
  import { Matrix4, Quaternion, Vector3 } from "three";
3
3
  import type { InstancedMeshPool } from "./InstancedMeshPool";
4
4
  export declare class InstancedMeshInstance {
@@ -12,6 +12,7 @@ export declare class InstancedMeshInstance {
12
12
  private needsUpdateInstancedMatrixFromLocalMatrix;
13
13
  private handler;
14
14
  constructor(pool: InstancedMeshPool, geometry: BufferGeometry, material: Material, tag?: string);
15
+ static fromInstancedMesh(pool: InstancedMeshPool, mesh: InstancedMesh<BufferGeometry, Material>, tag?: string): InstancedMeshInstance[];
15
16
  destroy(): void;
16
17
  isDestroyed(): boolean;
17
18
  setPosition(source: Vector3, flushTransform?: boolean): this;
@@ -12,6 +12,17 @@ class InstancedMeshInstance {
12
12
  this._private_needsUpdateInstancedMatrixFromLocalMatrix = false;
13
13
  this._private_handler = this._private_pool.allocate(geometry, material, tag);
14
14
  }
15
+ static fromInstancedMesh(pool, mesh, tag = "") {
16
+ const matrix = new Matrix4();
17
+ const instances = [];
18
+ for (let i = 0; i < mesh.count; i++) {
19
+ const instance = new InstancedMeshInstance(pool, mesh.geometry, mesh.material, tag);
20
+ mesh.getMatrixAt(i, matrix);
21
+ instance.setTransform(matrix, true);
22
+ instances.push(instance);
23
+ }
24
+ return instances;
25
+ }
15
26
  destroy() {
16
27
  if (this._private_handler >= 0) {
17
28
  this._private_pool.deallocate(this._private_handler);
@@ -2,8 +2,7 @@ import type { AnimationClip, Object3D, SkinnedMesh } from "three";
2
2
  import { Mesh } from "three";
3
3
  export declare class SkinnedMeshBaker {
4
4
  /**
5
- * Does not call `skeleton.update()` internally skeleton must be up to date before calling.
6
- * The returned mesh shares the original material (not cloned).
5
+ * Does not call the skeleton update, assuming it is already in a state ready for baking.
7
6
  */
8
7
  static bakePose(skinnedMesh: SkinnedMesh): Mesh;
9
8
  /**
@@ -3,8 +3,7 @@ import { Vector3, BufferAttribute, Mesh, AnimationMixer } from 'three';
3
3
  const COMPONENT_COUNT = 3;
4
4
  class SkinnedMeshBaker {
5
5
  /**
6
- * Does not call `skeleton.update()` internally skeleton must be up to date before calling.
7
- * The returned mesh shares the original material (not cloned).
6
+ * Does not call the skeleton update, assuming it is already in a state ready for baking.
8
7
  */
9
8
  static bakePose(skinnedMesh) {
10
9
  const bakedGeometry = skinnedMesh.geometry.clone();
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "three-zoo",
3
- "version": "0.12.0",
4
- "description": "Some reusable bits for building things with Three.js ",
3
+ "version": "0.13.0",
4
+ "description": "Some reusable bits for building things with Three.js",
5
5
  "keywords": [
6
6
  "three.js",
7
7
  "typescript",
8
- "3d"
8
+ "3d",
9
+ "webgl",
10
+ "ik",
11
+ "inverse-kinematics",
12
+ "instanced-mesh",
13
+ "material",
14
+ "scene",
15
+ "lighting"
9
16
  ],
10
17
  "author": "jango",
11
18
  "license": "MIT",
File without changes