three-zoo 0.13.0 → 0.14.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 +18 -199
- package/dist/camera/EnhancedPerspectiveCamera.d.ts +23 -0
- package/dist/camera/EnhancedPerspectiveCamera.js +66 -0
- package/dist/camera/fovPolicies/FovPolicy.d.ts +4 -0
- package/dist/camera/fovPolicies/FovPolicy.js +5 -0
- package/dist/camera/fovPolicies/FovPolicyCover.d.ts +26 -0
- package/dist/camera/fovPolicies/FovPolicyCover.js +44 -0
- package/dist/camera/fovPolicies/FovPolicyFit.d.ts +26 -0
- package/dist/camera/fovPolicies/FovPolicyFit.js +44 -0
- package/dist/camera/fovPolicies/FovPolicyFixedHorizontal.d.ts +13 -0
- package/dist/camera/fovPolicies/FovPolicyFixedHorizontal.js +27 -0
- package/dist/camera/fovPolicies/FovPolicyFixedVertical.d.ts +13 -0
- package/dist/camera/fovPolicies/FovPolicyFixedVertical.js +27 -0
- package/dist/camera/fovPolicies/FovPolicyHybrid.d.ts +25 -0
- package/dist/camera/fovPolicies/FovPolicyHybrid.js +45 -0
- package/dist/camera/fovPolicies/FovPolicyHybridInverted.d.ts +21 -0
- package/dist/camera/fovPolicies/FovPolicyHybridInverted.js +40 -0
- package/dist/camera/fovPolicies/fovMath.d.ts +5 -0
- package/dist/camera/fovPolicies/fovMath.js +14 -0
- package/dist/ik/AimChainIK.d.ts +3 -3
- package/dist/ik/AimChainIK.js +2 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +8 -1
- package/dist/instancedMeshPool/InstancedMeshInstance.d.ts +1 -1
- package/dist/instancedMeshPool/InstancedMeshInstance.js +5 -5
- package/dist/instancedMeshPool/InstancedMeshPool.d.ts +1 -2
- package/dist/instancedMeshPool/InstancedMeshPool.js +3 -1
- package/dist/lighting/Sun.d.ts +2 -2
- package/dist/lighting/Sun.js +2 -2
- package/dist/miscellaneous/SceneResolver.d.ts +6 -0
- package/package.json +1 -1
- package/dist/miscellaneous/DualFovCamera.d.ts +0 -43
- package/dist/miscellaneous/DualFovCamera.js +0 -170
package/README.md
CHANGED
|
@@ -1,211 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
<h1 align="center">🦁 🐘 🦊 three-zoo</h1>
|
|
3
|
-
<p align="center">Reusable Three.js utilities.</p>
|
|
4
|
-
</p>
|
|
1
|
+
# three-zoo
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
9
|
-
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-%5E5.8.0-blue" alt="TypeScript"></a>
|
|
10
|
-
<a href="https://threejs.org/"><img src="https://img.shields.io/badge/Three.js-%5E0.175.0-green" alt="Three.js"></a>
|
|
11
|
-
</p>
|
|
3
|
+
[](https://www.npmjs.com/package/three-zoo)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
12
5
|
|
|
13
|
-
|
|
6
|
+
Reusable Three.js utilities. WebGL 1 compatible.
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
npm install three-zoo
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Contents
|
|
20
|
-
|
|
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`
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## IK
|
|
30
|
-
|
|
31
|
-
### TwoBoneIK
|
|
32
|
-
|
|
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;
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### AimChainIK
|
|
47
|
-
|
|
48
|
-
Distributes aim rotation across a bone chain according to per-bone weights.
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
const aimChainIK = new AimChainIK([spine1, spine2, spine3, head]);
|
|
52
|
-
|
|
53
|
-
aimChainIK.curve = [0.2, 0.5, 0.8, 1.0]; // root gets least, tip gets most
|
|
54
|
-
aimChainIK.weight = 0.8; // global blend
|
|
55
|
-
|
|
56
|
-
// sample directions before calling - mutates bone quaternions
|
|
57
|
-
aimChainIK.solve(currentForward, targetDirection);
|
|
58
|
-
```
|
|
59
|
-
|
|
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();
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Lighting
|
|
85
|
-
|
|
86
|
-
### Sun
|
|
87
|
-
|
|
88
|
-
`DirectionalLight` with spherical positioning and shadow auto-configuration.
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
const sun = new Sun();
|
|
92
|
-
sun.elevation = Math.PI / 4;
|
|
93
|
-
sun.azimuth = Math.PI / 2;
|
|
94
|
-
sun.distance = 100;
|
|
95
|
-
|
|
96
|
-
sun.configureShadowsForBoundingBox(sceneBounds);
|
|
97
|
-
sun.setDirectionFromHDRTexture(hdrTexture, 50);
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### SkyLight
|
|
8
|
+
## API
|
|
101
9
|
|
|
102
|
-
`
|
|
10
|
+
- **IK**: `TwoBoneIK` (analytical two-bone solver), `AimChainIK` (distributes aim across a bone chain with per-bone weights).
|
|
11
|
+
- **Instanced Mesh Pool**: `InstancedMeshPool`, `InstancedMeshInstance`, `InstancedMeshGroup`. Keyed by geometry+material, auto-grows capacity.
|
|
12
|
+
- **Lighting**: `Sun` (DirectionalLight with spherical positioning, shadow auto-config from bounding box or HDR), `SkyLight` (HemisphereLight that samples sky/ground from HDR).
|
|
13
|
+
- **Material Converters**: Standard to Basic/Lambert/Phong/Toon/Physical, Basic to Physical. Single static `convert()` call each.
|
|
14
|
+
- **DualFovCamera**: PerspectiveCamera with independent horizontal and vertical FOV. Can fit FOV to points, boxes, or skinned meshes.
|
|
15
|
+
- **EnhancedPerspectiveCamera**: PerspectiveCamera whose vertical FOV is derived from a `FovPolicy` and the current aspect ratio. Policies: `FovPolicyFixedVertical`, `FovPolicyFixedHorizontal`, `FovPolicyHybrid`, `FovPolicyHybridInverted`, `FovPolicyCover`, `FovPolicyFit`.
|
|
16
|
+
- **SceneTraversal**: static helpers for finding/filtering objects and materials by name, regex, or predicate.
|
|
17
|
+
- **SceneSorter**: assigns `renderOrder` by distance to a point (front-to-back or back-to-front).
|
|
18
|
+
- **SkinnedMeshBaker**: bakes a SkinnedMesh to static geometry at current pose or a specific animation frame.
|
|
103
19
|
|
|
104
|
-
|
|
105
|
-
const skyLight = new SkyLight();
|
|
106
|
-
skyLight.setColorsFromHDRTexture(hdrTexture, {
|
|
107
|
-
skySampleCount: 100,
|
|
108
|
-
groundSampleCount: 100,
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Material Converters
|
|
115
|
-
|
|
116
|
-
All converters expose a single static `convert(material, options?)`. Common options:
|
|
117
|
-
|
|
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 |
|
|
20
|
+
## Install
|
|
123
21
|
|
|
124
|
-
```typescript
|
|
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);
|
|
132
|
-
|
|
133
|
-
// Standard <-> Physical
|
|
134
|
-
const physicalMaterial = StandardToPhysicalConverter.convert(standardMaterial);
|
|
135
|
-
const standardMaterial2 = BasicToPhysicalConverter.convert(basicMaterial);
|
|
136
22
|
```
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Miscellaneous
|
|
141
|
-
|
|
142
|
-
### DualFovCamera
|
|
143
|
-
|
|
144
|
-
`PerspectiveCamera` with independent horizontal and vertical FOV.
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
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);
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### SceneTraversal
|
|
158
|
-
|
|
159
|
-
Static helpers for depth-first scene graph traversal.
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
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;
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// find meshes that use specific materials
|
|
176
|
-
const glassMeshes = SceneTraversal.findMaterialUsers(scene, glassMaterials);
|
|
177
|
-
```
|
|
178
|
-
|
|
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
|
-
```
|
|
190
|
-
|
|
191
|
-
### SkinnedMeshBaker
|
|
192
|
-
|
|
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);
|
|
23
|
+
npm install three-zoo
|
|
201
24
|
```
|
|
202
25
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
## Requirements
|
|
206
|
-
|
|
207
|
-
- `three` >=0.157.0 <0.180.0 (peer dependency)
|
|
26
|
+
Peer dep: `three` >=0.157.0 <0.180.0
|
|
208
27
|
|
|
209
28
|
## License
|
|
210
29
|
|
|
211
|
-
MIT
|
|
30
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SkinnedMesh } from "three";
|
|
2
|
+
import { PerspectiveCamera } from "three";
|
|
3
|
+
import type { FovPolicy } from "./fovPolicies/FovPolicy";
|
|
4
|
+
/**
|
|
5
|
+
* PerspectiveCamera whose vertical FOV is derived from a {@link FovPolicy} and the current aspect
|
|
6
|
+
* ratio. The policy is applied on every `updateProjectionMatrix()` call, so the standard resize
|
|
7
|
+
* flow (`camera.aspect = w / h; camera.updateProjectionMatrix()`) is all that is needed.
|
|
8
|
+
*/
|
|
9
|
+
export declare class EnhancedPerspectiveCamera extends PerspectiveCamera {
|
|
10
|
+
private fovPolicyInternal;
|
|
11
|
+
constructor(fovPolicy: FovPolicy, aspect?: number, near?: number, far?: number);
|
|
12
|
+
get fovPolicy(): FovPolicy;
|
|
13
|
+
/** Replacing the policy re-applies it immediately. */
|
|
14
|
+
set fovPolicy(value: FovPolicy);
|
|
15
|
+
/** @override */
|
|
16
|
+
updateProjectionMatrix(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Orients the camera toward the mesh's vertex centroid (mean of the skinned vertices in world
|
|
19
|
+
* space). Calls `skeleton.update()` internally before sampling vertices.
|
|
20
|
+
*/
|
|
21
|
+
lookAtMeshCenterOfMass(skinnedMesh: SkinnedMesh): void;
|
|
22
|
+
clone(): this;
|
|
23
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PerspectiveCamera, Vector3 } from 'three';
|
|
2
|
+
import { clampFov } from './fovPolicies/fovMath.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ASPECT = 1;
|
|
5
|
+
const DEFAULT_NEAR = 1;
|
|
6
|
+
const DEFAULT_FAR = 1000;
|
|
7
|
+
/**
|
|
8
|
+
* PerspectiveCamera whose vertical FOV is derived from a {@link FovPolicy} and the current aspect
|
|
9
|
+
* ratio. The policy is applied on every `updateProjectionMatrix()` call, so the standard resize
|
|
10
|
+
* flow (`camera.aspect = w / h; camera.updateProjectionMatrix()`) is all that is needed.
|
|
11
|
+
*/
|
|
12
|
+
class EnhancedPerspectiveCamera extends PerspectiveCamera {
|
|
13
|
+
constructor(fovPolicy, aspect = DEFAULT_ASPECT, near = DEFAULT_NEAR, far = DEFAULT_FAR) {
|
|
14
|
+
super(clampFov(fovPolicy.calculateFov(aspect)), aspect, near, far);
|
|
15
|
+
this._private_fovPolicyInternal = fovPolicy;
|
|
16
|
+
this.updateProjectionMatrix();
|
|
17
|
+
}
|
|
18
|
+
get fovPolicy() {
|
|
19
|
+
return this._private_fovPolicyInternal;
|
|
20
|
+
}
|
|
21
|
+
/** Replacing the policy re-applies it immediately. */
|
|
22
|
+
set fovPolicy(value) {
|
|
23
|
+
this._private_fovPolicyInternal = value;
|
|
24
|
+
this.updateProjectionMatrix();
|
|
25
|
+
}
|
|
26
|
+
/** @override */
|
|
27
|
+
updateProjectionMatrix() {
|
|
28
|
+
// The PerspectiveCamera constructor calls this before fovPolicyInternal is assigned, so the
|
|
29
|
+
// field may briefly be undefined despite its declared type.
|
|
30
|
+
const policy = this._private_fovPolicyInternal;
|
|
31
|
+
if (policy !== undefined) {
|
|
32
|
+
this.fov = clampFov(policy.calculateFov(this.aspect));
|
|
33
|
+
}
|
|
34
|
+
super.updateProjectionMatrix();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Orients the camera toward the mesh's vertex centroid (mean of the skinned vertices in world
|
|
38
|
+
* space). Calls `skeleton.update()` internally before sampling vertices.
|
|
39
|
+
*/
|
|
40
|
+
lookAtMeshCenterOfMass(skinnedMesh) {
|
|
41
|
+
skinnedMesh.updateWorldMatrix(true, true);
|
|
42
|
+
skinnedMesh.skeleton.update();
|
|
43
|
+
const position = skinnedMesh.geometry.attributes["position"];
|
|
44
|
+
if (position.count === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const vertex = new Vector3();
|
|
48
|
+
const centroid = new Vector3();
|
|
49
|
+
for (let i = 0; i < position.count; i++) {
|
|
50
|
+
vertex.fromBufferAttribute(position, i);
|
|
51
|
+
skinnedMesh.applyBoneTransform(i, vertex);
|
|
52
|
+
vertex.applyMatrix4(skinnedMesh.matrixWorld);
|
|
53
|
+
centroid.add(vertex);
|
|
54
|
+
}
|
|
55
|
+
centroid.divideScalar(position.count);
|
|
56
|
+
this.lookAt(centroid);
|
|
57
|
+
}
|
|
58
|
+
clone() {
|
|
59
|
+
const camera = new EnhancedPerspectiveCamera(this._private_fovPolicyInternal.clone(), this.aspect, this.near, this.far);
|
|
60
|
+
camera.copy(this, true);
|
|
61
|
+
camera.updateProjectionMatrix();
|
|
62
|
+
return camera;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { EnhancedPerspectiveCamera };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FovPolicy } from "./FovPolicy";
|
|
2
|
+
/**
|
|
3
|
+
* Guarantees the target frustum is fully visible at any aspect ratio.
|
|
4
|
+
*
|
|
5
|
+
* Takes the wider of the two vertical FOVs implied by the horizontal and vertical targets, so
|
|
6
|
+
* both target angles are always contained (the view may show more than the target rect).
|
|
7
|
+
*/
|
|
8
|
+
export declare class FovPolicyCover extends FovPolicy {
|
|
9
|
+
private fovHorizontalInternal;
|
|
10
|
+
private fovVerticalInternal;
|
|
11
|
+
/**
|
|
12
|
+
* @param fovHorizontal - Target horizontal FOV. Clamped to 1-179 degrees.
|
|
13
|
+
* @param fovVertical - Target vertical FOV. Clamped to 1-179 degrees.
|
|
14
|
+
*/
|
|
15
|
+
constructor(fovHorizontal: number, fovVertical: number);
|
|
16
|
+
/** Target horizontal FOV. */
|
|
17
|
+
get fovHorizontal(): number;
|
|
18
|
+
/** Target vertical FOV. */
|
|
19
|
+
get fovVertical(): number;
|
|
20
|
+
/** Clamped to 1-179 degrees. */
|
|
21
|
+
set fovHorizontal(value: number);
|
|
22
|
+
/** Clamped to 1-179 degrees. */
|
|
23
|
+
set fovVertical(value: number);
|
|
24
|
+
calculateFov(aspect: number): number;
|
|
25
|
+
clone(): FovPolicyCover;
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { clampFov, verticalFovFromHorizontal } from './fovMath.js';
|
|
2
|
+
import { FovPolicy } from './FovPolicy.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Guarantees the target frustum is fully visible at any aspect ratio.
|
|
6
|
+
*
|
|
7
|
+
* Takes the wider of the two vertical FOVs implied by the horizontal and vertical targets, so
|
|
8
|
+
* both target angles are always contained (the view may show more than the target rect).
|
|
9
|
+
*/
|
|
10
|
+
class FovPolicyCover extends FovPolicy {
|
|
11
|
+
/**
|
|
12
|
+
* @param fovHorizontal - Target horizontal FOV. Clamped to 1-179 degrees.
|
|
13
|
+
* @param fovVertical - Target vertical FOV. Clamped to 1-179 degrees.
|
|
14
|
+
*/
|
|
15
|
+
constructor(fovHorizontal, fovVertical) {
|
|
16
|
+
super();
|
|
17
|
+
this._private_fovHorizontalInternal = clampFov(fovHorizontal);
|
|
18
|
+
this._private_fovVerticalInternal = clampFov(fovVertical);
|
|
19
|
+
}
|
|
20
|
+
/** Target horizontal FOV. */
|
|
21
|
+
get fovHorizontal() {
|
|
22
|
+
return this._private_fovHorizontalInternal;
|
|
23
|
+
}
|
|
24
|
+
/** Target vertical FOV. */
|
|
25
|
+
get fovVertical() {
|
|
26
|
+
return this._private_fovVerticalInternal;
|
|
27
|
+
}
|
|
28
|
+
/** Clamped to 1-179 degrees. */
|
|
29
|
+
set fovHorizontal(value) {
|
|
30
|
+
this._private_fovHorizontalInternal = clampFov(value);
|
|
31
|
+
}
|
|
32
|
+
/** Clamped to 1-179 degrees. */
|
|
33
|
+
set fovVertical(value) {
|
|
34
|
+
this._private_fovVerticalInternal = clampFov(value);
|
|
35
|
+
}
|
|
36
|
+
calculateFov(aspect) {
|
|
37
|
+
return Math.max(this._private_fovVerticalInternal, verticalFovFromHorizontal(this._private_fovHorizontalInternal, aspect));
|
|
38
|
+
}
|
|
39
|
+
clone() {
|
|
40
|
+
return new FovPolicyCover(this._private_fovHorizontalInternal, this._private_fovVerticalInternal);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { FovPolicyCover };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FovPolicy } from "./FovPolicy";
|
|
2
|
+
/**
|
|
3
|
+
* Fills the viewport with the target frustum at any aspect ratio.
|
|
4
|
+
*
|
|
5
|
+
* Takes the narrower of the two vertical FOVs implied by the horizontal and vertical targets, so
|
|
6
|
+
* neither target angle is ever exceeded (content outside the target rect is cropped).
|
|
7
|
+
*/
|
|
8
|
+
export declare class FovPolicyFit extends FovPolicy {
|
|
9
|
+
private fovHorizontalInternal;
|
|
10
|
+
private fovVerticalInternal;
|
|
11
|
+
/**
|
|
12
|
+
* @param fovHorizontal - Target horizontal FOV. Clamped to 1-179 degrees.
|
|
13
|
+
* @param fovVertical - Target vertical FOV. Clamped to 1-179 degrees.
|
|
14
|
+
*/
|
|
15
|
+
constructor(fovHorizontal: number, fovVertical: number);
|
|
16
|
+
/** Target horizontal FOV. */
|
|
17
|
+
get fovHorizontal(): number;
|
|
18
|
+
/** Target vertical FOV. */
|
|
19
|
+
get fovVertical(): number;
|
|
20
|
+
/** Clamped to 1-179 degrees. */
|
|
21
|
+
set fovHorizontal(value: number);
|
|
22
|
+
/** Clamped to 1-179 degrees. */
|
|
23
|
+
set fovVertical(value: number);
|
|
24
|
+
calculateFov(aspect: number): number;
|
|
25
|
+
clone(): FovPolicyFit;
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { clampFov, verticalFovFromHorizontal } from './fovMath.js';
|
|
2
|
+
import { FovPolicy } from './FovPolicy.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fills the viewport with the target frustum at any aspect ratio.
|
|
6
|
+
*
|
|
7
|
+
* Takes the narrower of the two vertical FOVs implied by the horizontal and vertical targets, so
|
|
8
|
+
* neither target angle is ever exceeded (content outside the target rect is cropped).
|
|
9
|
+
*/
|
|
10
|
+
class FovPolicyFit extends FovPolicy {
|
|
11
|
+
/**
|
|
12
|
+
* @param fovHorizontal - Target horizontal FOV. Clamped to 1-179 degrees.
|
|
13
|
+
* @param fovVertical - Target vertical FOV. Clamped to 1-179 degrees.
|
|
14
|
+
*/
|
|
15
|
+
constructor(fovHorizontal, fovVertical) {
|
|
16
|
+
super();
|
|
17
|
+
this._private_fovHorizontalInternal = clampFov(fovHorizontal);
|
|
18
|
+
this._private_fovVerticalInternal = clampFov(fovVertical);
|
|
19
|
+
}
|
|
20
|
+
/** Target horizontal FOV. */
|
|
21
|
+
get fovHorizontal() {
|
|
22
|
+
return this._private_fovHorizontalInternal;
|
|
23
|
+
}
|
|
24
|
+
/** Target vertical FOV. */
|
|
25
|
+
get fovVertical() {
|
|
26
|
+
return this._private_fovVerticalInternal;
|
|
27
|
+
}
|
|
28
|
+
/** Clamped to 1-179 degrees. */
|
|
29
|
+
set fovHorizontal(value) {
|
|
30
|
+
this._private_fovHorizontalInternal = clampFov(value);
|
|
31
|
+
}
|
|
32
|
+
/** Clamped to 1-179 degrees. */
|
|
33
|
+
set fovVertical(value) {
|
|
34
|
+
this._private_fovVerticalInternal = clampFov(value);
|
|
35
|
+
}
|
|
36
|
+
calculateFov(aspect) {
|
|
37
|
+
return Math.min(this._private_fovVerticalInternal, verticalFovFromHorizontal(this._private_fovHorizontalInternal, aspect));
|
|
38
|
+
}
|
|
39
|
+
clone() {
|
|
40
|
+
return new FovPolicyFit(this._private_fovHorizontalInternal, this._private_fovVerticalInternal);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { FovPolicyFit };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FovPolicy } from "./FovPolicy";
|
|
2
|
+
/** Keeps the horizontal FOV constant; derives the vertical FOV from the aspect ratio. */
|
|
3
|
+
export declare class FovPolicyFixedHorizontal extends FovPolicy {
|
|
4
|
+
private fovHorizontalInternal;
|
|
5
|
+
/** @param fovHorizontal - Clamped to 1-179 degrees. */
|
|
6
|
+
constructor(fovHorizontal: number);
|
|
7
|
+
/** Target horizontal FOV. */
|
|
8
|
+
get fovHorizontal(): number;
|
|
9
|
+
/** Clamped to 1-179 degrees. */
|
|
10
|
+
set fovHorizontal(value: number);
|
|
11
|
+
calculateFov(aspect: number): number;
|
|
12
|
+
clone(): FovPolicyFixedHorizontal;
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { clampFov, verticalFovFromHorizontal } from './fovMath.js';
|
|
2
|
+
import { FovPolicy } from './FovPolicy.js';
|
|
3
|
+
|
|
4
|
+
/** Keeps the horizontal FOV constant; derives the vertical FOV from the aspect ratio. */
|
|
5
|
+
class FovPolicyFixedHorizontal extends FovPolicy {
|
|
6
|
+
/** @param fovHorizontal - Clamped to 1-179 degrees. */
|
|
7
|
+
constructor(fovHorizontal) {
|
|
8
|
+
super();
|
|
9
|
+
this._private_fovHorizontalInternal = clampFov(fovHorizontal);
|
|
10
|
+
}
|
|
11
|
+
/** Target horizontal FOV. */
|
|
12
|
+
get fovHorizontal() {
|
|
13
|
+
return this._private_fovHorizontalInternal;
|
|
14
|
+
}
|
|
15
|
+
/** Clamped to 1-179 degrees. */
|
|
16
|
+
set fovHorizontal(value) {
|
|
17
|
+
this._private_fovHorizontalInternal = clampFov(value);
|
|
18
|
+
}
|
|
19
|
+
calculateFov(aspect) {
|
|
20
|
+
return verticalFovFromHorizontal(this._private_fovHorizontalInternal, aspect);
|
|
21
|
+
}
|
|
22
|
+
clone() {
|
|
23
|
+
return new FovPolicyFixedHorizontal(this._private_fovHorizontalInternal);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { FovPolicyFixedHorizontal };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FovPolicy } from "./FovPolicy";
|
|
2
|
+
/** Keeps the vertical FOV constant regardless of aspect (default three.js behavior). */
|
|
3
|
+
export declare class FovPolicyFixedVertical extends FovPolicy {
|
|
4
|
+
private fovVerticalInternal;
|
|
5
|
+
/** @param fovVertical - Clamped to 1-179 degrees. */
|
|
6
|
+
constructor(fovVertical: number);
|
|
7
|
+
/** Target vertical FOV. */
|
|
8
|
+
get fovVertical(): number;
|
|
9
|
+
/** Clamped to 1-179 degrees. */
|
|
10
|
+
set fovVertical(value: number);
|
|
11
|
+
calculateFov(): number;
|
|
12
|
+
clone(): FovPolicyFixedVertical;
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { clampFov } from './fovMath.js';
|
|
2
|
+
import { FovPolicy } from './FovPolicy.js';
|
|
3
|
+
|
|
4
|
+
/** Keeps the vertical FOV constant regardless of aspect (default three.js behavior). */
|
|
5
|
+
class FovPolicyFixedVertical extends FovPolicy {
|
|
6
|
+
/** @param fovVertical - Clamped to 1-179 degrees. */
|
|
7
|
+
constructor(fovVertical) {
|
|
8
|
+
super();
|
|
9
|
+
this._private_fovVerticalInternal = clampFov(fovVertical);
|
|
10
|
+
}
|
|
11
|
+
/** Target vertical FOV. */
|
|
12
|
+
get fovVertical() {
|
|
13
|
+
return this._private_fovVerticalInternal;
|
|
14
|
+
}
|
|
15
|
+
/** Clamped to 1-179 degrees. */
|
|
16
|
+
set fovVertical(value) {
|
|
17
|
+
this._private_fovVerticalInternal = clampFov(value);
|
|
18
|
+
}
|
|
19
|
+
calculateFov() {
|
|
20
|
+
return this._private_fovVerticalInternal;
|
|
21
|
+
}
|
|
22
|
+
clone() {
|
|
23
|
+
return new FovPolicyFixedVertical(this._private_fovVerticalInternal);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { FovPolicyFixedVertical };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FovPolicy } from "./FovPolicy";
|
|
2
|
+
/**
|
|
3
|
+
* Preserves the horizontal FOV in landscape (aspect > 1) and the vertical FOV in portrait.
|
|
4
|
+
*
|
|
5
|
+
* Equivalent to DualFovCamera's projection behavior.
|
|
6
|
+
*/
|
|
7
|
+
export declare class FovPolicyHybrid extends FovPolicy {
|
|
8
|
+
private fovHorizontalInternal;
|
|
9
|
+
private fovVerticalInternal;
|
|
10
|
+
/**
|
|
11
|
+
* @param fovHorizontal - Preserved in landscape. Clamped to 1-179 degrees.
|
|
12
|
+
* @param fovVertical - Preserved in portrait. Clamped to 1-179 degrees.
|
|
13
|
+
*/
|
|
14
|
+
constructor(fovHorizontal: number, fovVertical: number);
|
|
15
|
+
/** Horizontal FOV preserved in landscape. */
|
|
16
|
+
get fovHorizontal(): number;
|
|
17
|
+
/** Vertical FOV preserved in portrait. */
|
|
18
|
+
get fovVertical(): number;
|
|
19
|
+
/** Clamped to 1-179 degrees. */
|
|
20
|
+
set fovHorizontal(value: number);
|
|
21
|
+
/** Clamped to 1-179 degrees. */
|
|
22
|
+
set fovVertical(value: number);
|
|
23
|
+
calculateFov(aspect: number): number;
|
|
24
|
+
clone(): FovPolicyHybrid;
|
|
25
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { clampFov, verticalFovFromHorizontal } from './fovMath.js';
|
|
2
|
+
import { FovPolicy } from './FovPolicy.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Preserves the horizontal FOV in landscape (aspect > 1) and the vertical FOV in portrait.
|
|
6
|
+
*
|
|
7
|
+
* Equivalent to DualFovCamera's projection behavior.
|
|
8
|
+
*/
|
|
9
|
+
class FovPolicyHybrid extends FovPolicy {
|
|
10
|
+
/**
|
|
11
|
+
* @param fovHorizontal - Preserved in landscape. Clamped to 1-179 degrees.
|
|
12
|
+
* @param fovVertical - Preserved in portrait. Clamped to 1-179 degrees.
|
|
13
|
+
*/
|
|
14
|
+
constructor(fovHorizontal, fovVertical) {
|
|
15
|
+
super();
|
|
16
|
+
this._private_fovHorizontalInternal = clampFov(fovHorizontal);
|
|
17
|
+
this._private_fovVerticalInternal = clampFov(fovVertical);
|
|
18
|
+
}
|
|
19
|
+
/** Horizontal FOV preserved in landscape. */
|
|
20
|
+
get fovHorizontal() {
|
|
21
|
+
return this._private_fovHorizontalInternal;
|
|
22
|
+
}
|
|
23
|
+
/** Vertical FOV preserved in portrait. */
|
|
24
|
+
get fovVertical() {
|
|
25
|
+
return this._private_fovVerticalInternal;
|
|
26
|
+
}
|
|
27
|
+
/** Clamped to 1-179 degrees. */
|
|
28
|
+
set fovHorizontal(value) {
|
|
29
|
+
this._private_fovHorizontalInternal = clampFov(value);
|
|
30
|
+
}
|
|
31
|
+
/** Clamped to 1-179 degrees. */
|
|
32
|
+
set fovVertical(value) {
|
|
33
|
+
this._private_fovVerticalInternal = clampFov(value);
|
|
34
|
+
}
|
|
35
|
+
calculateFov(aspect) {
|
|
36
|
+
return aspect > 1
|
|
37
|
+
? verticalFovFromHorizontal(this._private_fovHorizontalInternal, aspect)
|
|
38
|
+
: this._private_fovVerticalInternal;
|
|
39
|
+
}
|
|
40
|
+
clone() {
|
|
41
|
+
return new FovPolicyHybrid(this._private_fovHorizontalInternal, this._private_fovVerticalInternal);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { FovPolicyHybrid };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FovPolicy } from "./FovPolicy";
|
|
2
|
+
/** Preserves the vertical FOV in landscape (aspect > 1) and the horizontal FOV in portrait. */
|
|
3
|
+
export declare class FovPolicyHybridInverted extends FovPolicy {
|
|
4
|
+
private fovHorizontalInternal;
|
|
5
|
+
private fovVerticalInternal;
|
|
6
|
+
/**
|
|
7
|
+
* @param fovHorizontal - Preserved in portrait. Clamped to 1-179 degrees.
|
|
8
|
+
* @param fovVertical - Preserved in landscape. Clamped to 1-179 degrees.
|
|
9
|
+
*/
|
|
10
|
+
constructor(fovHorizontal: number, fovVertical: number);
|
|
11
|
+
/** Horizontal FOV preserved in portrait. */
|
|
12
|
+
get fovHorizontal(): number;
|
|
13
|
+
/** Vertical FOV preserved in landscape. */
|
|
14
|
+
get fovVertical(): number;
|
|
15
|
+
/** Clamped to 1-179 degrees. */
|
|
16
|
+
set fovHorizontal(value: number);
|
|
17
|
+
/** Clamped to 1-179 degrees. */
|
|
18
|
+
set fovVertical(value: number);
|
|
19
|
+
calculateFov(aspect: number): number;
|
|
20
|
+
clone(): FovPolicyHybridInverted;
|
|
21
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { clampFov, verticalFovFromHorizontal } from './fovMath.js';
|
|
2
|
+
import { FovPolicy } from './FovPolicy.js';
|
|
3
|
+
|
|
4
|
+
/** Preserves the vertical FOV in landscape (aspect > 1) and the horizontal FOV in portrait. */
|
|
5
|
+
class FovPolicyHybridInverted extends FovPolicy {
|
|
6
|
+
/**
|
|
7
|
+
* @param fovHorizontal - Preserved in portrait. Clamped to 1-179 degrees.
|
|
8
|
+
* @param fovVertical - Preserved in landscape. Clamped to 1-179 degrees.
|
|
9
|
+
*/
|
|
10
|
+
constructor(fovHorizontal, fovVertical) {
|
|
11
|
+
super();
|
|
12
|
+
this._private_fovHorizontalInternal = clampFov(fovHorizontal);
|
|
13
|
+
this._private_fovVerticalInternal = clampFov(fovVertical);
|
|
14
|
+
}
|
|
15
|
+
/** Horizontal FOV preserved in portrait. */
|
|
16
|
+
get fovHorizontal() {
|
|
17
|
+
return this._private_fovHorizontalInternal;
|
|
18
|
+
}
|
|
19
|
+
/** Vertical FOV preserved in landscape. */
|
|
20
|
+
get fovVertical() {
|
|
21
|
+
return this._private_fovVerticalInternal;
|
|
22
|
+
}
|
|
23
|
+
/** Clamped to 1-179 degrees. */
|
|
24
|
+
set fovHorizontal(value) {
|
|
25
|
+
this._private_fovHorizontalInternal = clampFov(value);
|
|
26
|
+
}
|
|
27
|
+
/** Clamped to 1-179 degrees. */
|
|
28
|
+
set fovVertical(value) {
|
|
29
|
+
this._private_fovVerticalInternal = clampFov(value);
|
|
30
|
+
}
|
|
31
|
+
calculateFov(aspect) {
|
|
32
|
+
return aspect > 1
|
|
33
|
+
? this._private_fovVerticalInternal : verticalFovFromHorizontal(this._private_fovHorizontalInternal, aspect);
|
|
34
|
+
}
|
|
35
|
+
clone() {
|
|
36
|
+
return new FovPolicyHybridInverted(this._private_fovHorizontalInternal, this._private_fovVerticalInternal);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { FovPolicyHybridInverted };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const MIN_FOV = 1;
|
|
2
|
+
export declare const MAX_FOV = 179;
|
|
3
|
+
export declare function clampFov(value: number): number;
|
|
4
|
+
/** Vertical FOV (degrees) that yields the given horizontal FOV (degrees) at aspect = width / height. */
|
|
5
|
+
export declare function verticalFovFromHorizontal(horizontalFov: number, aspect: number): number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MathUtils } from 'three';
|
|
2
|
+
|
|
3
|
+
const MIN_FOV = 1;
|
|
4
|
+
const MAX_FOV = 179;
|
|
5
|
+
function clampFov(value) {
|
|
6
|
+
return MathUtils.clamp(value, MIN_FOV, MAX_FOV);
|
|
7
|
+
}
|
|
8
|
+
/** Vertical FOV (degrees) that yields the given horizontal FOV (degrees) at aspect = width / height. */
|
|
9
|
+
function verticalFovFromHorizontal(horizontalFov, aspect) {
|
|
10
|
+
const radians = MathUtils.degToRad(horizontalFov);
|
|
11
|
+
return MathUtils.radToDeg(Math.atan(Math.tan(radians / 2) / aspect) * 2);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { MAX_FOV, MIN_FOV, clampFov, verticalFovFromHorizontal };
|
package/dist/ik/AimChainIK.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare class AimChainIK {
|
|
|
14
14
|
epsilon: number;
|
|
15
15
|
/**
|
|
16
16
|
* Global blend weight. 0 = solver has no effect, 1 = full effect.
|
|
17
|
-
* Clamped to 0
|
|
17
|
+
* Clamped to 0-1 internally.
|
|
18
18
|
*/
|
|
19
19
|
weight: number;
|
|
20
20
|
/**
|
|
@@ -22,7 +22,7 @@ export declare class AimChainIK {
|
|
|
22
22
|
*
|
|
23
23
|
* `[1, 1, 1, 1]` = uniform. `[0.2, 0.5, 0.8, 1.0]` = root gets least, tip gets most.
|
|
24
24
|
*
|
|
25
|
-
* Compared by reference
|
|
25
|
+
* Compared by reference - mutating values in-place won't trigger
|
|
26
26
|
* renormalization. Assign a new array to update.
|
|
27
27
|
*
|
|
28
28
|
* Length must match bone count; mismatches throw on `solve()`.
|
|
@@ -43,7 +43,7 @@ export declare class AimChainIK {
|
|
|
43
43
|
*
|
|
44
44
|
* Both vectors are in world space and are not mutated.
|
|
45
45
|
*
|
|
46
|
-
* Sample directions **before** calling
|
|
46
|
+
* Sample directions **before** calling - this method mutates bone quaternions,
|
|
47
47
|
* so any direction derived from the chain will be stale after the call.
|
|
48
48
|
*
|
|
49
49
|
* @param currentDirection - Where the chain currently aims.
|
package/dist/ik/AimChainIK.js
CHANGED
|
@@ -18,7 +18,7 @@ class AimChainIK {
|
|
|
18
18
|
this.epsilon = 1e-5;
|
|
19
19
|
/**
|
|
20
20
|
* Global blend weight. 0 = solver has no effect, 1 = full effect.
|
|
21
|
-
* Clamped to 0
|
|
21
|
+
* Clamped to 0-1 internally.
|
|
22
22
|
*/
|
|
23
23
|
this.weight = 1;
|
|
24
24
|
this._private_swingAxis = new Vector3();
|
|
@@ -38,7 +38,7 @@ class AimChainIK {
|
|
|
38
38
|
*
|
|
39
39
|
* Both vectors are in world space and are not mutated.
|
|
40
40
|
*
|
|
41
|
-
* Sample directions **before** calling
|
|
41
|
+
* Sample directions **before** calling - this method mutates bone quaternions,
|
|
42
42
|
* so any direction derived from the chain will be stale after the call.
|
|
43
43
|
*
|
|
44
44
|
* @param currentDirection - Where the chain currently aims.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export { EnhancedPerspectiveCamera } from "./camera/EnhancedPerspectiveCamera";
|
|
2
|
+
export { FovPolicy } from "./camera/fovPolicies/FovPolicy";
|
|
3
|
+
export { FovPolicyCover } from "./camera/fovPolicies/FovPolicyCover";
|
|
4
|
+
export { FovPolicyFit } from "./camera/fovPolicies/FovPolicyFit";
|
|
5
|
+
export { FovPolicyFixedHorizontal } from "./camera/fovPolicies/FovPolicyFixedHorizontal";
|
|
6
|
+
export { FovPolicyFixedVertical } from "./camera/fovPolicies/FovPolicyFixedVertical";
|
|
7
|
+
export { FovPolicyHybrid } from "./camera/fovPolicies/FovPolicyHybrid";
|
|
8
|
+
export { FovPolicyHybridInverted } from "./camera/fovPolicies/FovPolicyHybridInverted";
|
|
1
9
|
export { AimChainIK } from "./ik/AimChainIK";
|
|
2
10
|
export { TwoBoneIK } from "./ik/TwoBoneIK";
|
|
3
11
|
export { InstancedMeshGroup } from "./instancedMeshPool/InstancedMeshGroup";
|
|
@@ -11,7 +19,6 @@ export { StandardToLambertConverter } from "./materialConverters/StandardToLambe
|
|
|
11
19
|
export { StandardToPhongConverter } from "./materialConverters/StandardToPhongConverter";
|
|
12
20
|
export { StandardToPhysicalConverter } from "./materialConverters/StandardToPhysicalConverter";
|
|
13
21
|
export { StandardToToonConverter } from "./materialConverters/StandardToToonConverter";
|
|
14
|
-
export { DualFovCamera } from "./miscellaneous/DualFovCamera";
|
|
15
22
|
export { SceneSorter } from "./miscellaneous/SceneSorter";
|
|
16
23
|
export { SceneTraversal } from "./miscellaneous/SceneTraversal";
|
|
17
24
|
export { SkinnedMeshBaker } from "./miscellaneous/SkinnedMeshBaker";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export { EnhancedPerspectiveCamera } from './camera/EnhancedPerspectiveCamera.js';
|
|
2
|
+
export { FovPolicy } from './camera/fovPolicies/FovPolicy.js';
|
|
3
|
+
export { FovPolicyCover } from './camera/fovPolicies/FovPolicyCover.js';
|
|
4
|
+
export { FovPolicyFit } from './camera/fovPolicies/FovPolicyFit.js';
|
|
5
|
+
export { FovPolicyFixedHorizontal } from './camera/fovPolicies/FovPolicyFixedHorizontal.js';
|
|
6
|
+
export { FovPolicyFixedVertical } from './camera/fovPolicies/FovPolicyFixedVertical.js';
|
|
7
|
+
export { FovPolicyHybrid } from './camera/fovPolicies/FovPolicyHybrid.js';
|
|
8
|
+
export { FovPolicyHybridInverted } from './camera/fovPolicies/FovPolicyHybridInverted.js';
|
|
1
9
|
export { AimChainIK } from './ik/AimChainIK.js';
|
|
2
10
|
export { TwoBoneIK } from './ik/TwoBoneIK.js';
|
|
3
11
|
export { InstancedMeshGroup } from './instancedMeshPool/InstancedMeshGroup.js';
|
|
@@ -11,7 +19,6 @@ export { StandardToLambertConverter } from './materialConverters/StandardToLambe
|
|
|
11
19
|
export { StandardToPhongConverter } from './materialConverters/StandardToPhongConverter.js';
|
|
12
20
|
export { StandardToPhysicalConverter } from './materialConverters/StandardToPhysicalConverter.js';
|
|
13
21
|
export { StandardToToonConverter } from './materialConverters/StandardToToonConverter.js';
|
|
14
|
-
export { DualFovCamera } from './miscellaneous/DualFovCamera.js';
|
|
15
22
|
export { SceneSorter } from './miscellaneous/SceneSorter.js';
|
|
16
23
|
export { SceneTraversal } from './miscellaneous/SceneTraversal.js';
|
|
17
24
|
export { SkinnedMeshBaker } from './miscellaneous/SkinnedMeshBaker.js';
|
|
@@ -12,9 +12,9 @@ export declare class InstancedMeshInstance {
|
|
|
12
12
|
private needsUpdateInstancedMatrixFromLocalMatrix;
|
|
13
13
|
private handler;
|
|
14
14
|
constructor(pool: InstancedMeshPool, geometry: BufferGeometry, material: Material, tag?: string);
|
|
15
|
+
get isAlive(): boolean;
|
|
15
16
|
static fromInstancedMesh(pool: InstancedMeshPool, mesh: InstancedMesh<BufferGeometry, Material>, tag?: string): InstancedMeshInstance[];
|
|
16
17
|
destroy(): void;
|
|
17
|
-
isDestroyed(): boolean;
|
|
18
18
|
setPosition(source: Vector3, flushTransform?: boolean): this;
|
|
19
19
|
setPosition3f(x: number, y: number, z: number, flushTransform?: boolean): this;
|
|
20
20
|
setQuaternion(source: Quaternion, flushTransform?: boolean): this;
|
|
@@ -12,6 +12,9 @@ class InstancedMeshInstance {
|
|
|
12
12
|
this._private_needsUpdateInstancedMatrixFromLocalMatrix = false;
|
|
13
13
|
this._private_handler = this._private_pool.allocate(geometry, material, tag);
|
|
14
14
|
}
|
|
15
|
+
get isAlive() {
|
|
16
|
+
return this._private_handler >= 0;
|
|
17
|
+
}
|
|
15
18
|
static fromInstancedMesh(pool, mesh, tag = "") {
|
|
16
19
|
const matrix = new Matrix4();
|
|
17
20
|
const instances = [];
|
|
@@ -24,14 +27,11 @@ class InstancedMeshInstance {
|
|
|
24
27
|
return instances;
|
|
25
28
|
}
|
|
26
29
|
destroy() {
|
|
27
|
-
if (this.
|
|
30
|
+
if (this.isAlive) {
|
|
28
31
|
this._private_pool.deallocate(this._private_handler);
|
|
29
32
|
this._private_handler = -1;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
isDestroyed() {
|
|
33
|
-
return this._private_handler < 0;
|
|
34
|
-
}
|
|
35
35
|
setPosition(source, flushTransform = false) {
|
|
36
36
|
this._private_updateTransformFromMatrix();
|
|
37
37
|
if (!this._private_position.equals(source)) {
|
|
@@ -114,7 +114,7 @@ class InstancedMeshInstance {
|
|
|
114
114
|
return this;
|
|
115
115
|
}
|
|
116
116
|
flushTransform() {
|
|
117
|
-
if (this.
|
|
117
|
+
if (!this.isAlive) {
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
this._private_updateMatrixFromTransform();
|
|
@@ -15,8 +15,7 @@ export declare class InstancedMeshPool {
|
|
|
15
15
|
isValidHandler(handler: number): boolean;
|
|
16
16
|
sortMeshes(baseRenderOrder: number, compare: (a: InstancedMesh, aTag: string, b: InstancedMesh, bTag: string) => number): void;
|
|
17
17
|
sortInstances(compare: (matrixA: Matrix4, matrixB: Matrix4, tag: string) => number): void;
|
|
18
|
-
|
|
19
|
-
protected getTransformMatrix(handler: number, target: Matrix4): Matrix4 | undefined;
|
|
18
|
+
destroy(): void;
|
|
20
19
|
private getOrCreateEntry;
|
|
21
20
|
private growEntry;
|
|
22
21
|
private sortEntryInstances;
|
|
@@ -33,7 +33,7 @@ class InstancedMeshPool {
|
|
|
33
33
|
this._private_sortEntryInstances(entry, compare);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
destroy() {
|
|
37
37
|
for (const entry of this._private_entries.values()) {
|
|
38
38
|
this._private_scene.remove(entry.mesh);
|
|
39
39
|
entry.mesh.dispose();
|
|
@@ -87,6 +87,7 @@ class InstancedMeshPool {
|
|
|
87
87
|
descriptor.entry.mesh.setMatrixAt(descriptor.index, matrix);
|
|
88
88
|
descriptor.entry.mesh.instanceMatrix.needsUpdate = true;
|
|
89
89
|
}
|
|
90
|
+
/** @internal */
|
|
90
91
|
getTransformMatrix(handler, target) {
|
|
91
92
|
const descriptor = this._private_descriptors.get(handler);
|
|
92
93
|
if (descriptor === undefined) {
|
|
@@ -150,6 +151,7 @@ class InstancedMeshPool {
|
|
|
150
151
|
}
|
|
151
152
|
return result;
|
|
152
153
|
});
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- needsReorder is set by the sort callback
|
|
153
155
|
if (!needsReorder) {
|
|
154
156
|
return;
|
|
155
157
|
}
|
package/dist/lighting/Sun.d.ts
CHANGED
|
@@ -50,8 +50,8 @@ export declare class Sun extends DirectionalLight {
|
|
|
50
50
|
/** @param texture - Must have image data. */
|
|
51
51
|
setDirectionFromHDRTexture(texture: Texture, distance?: number): void;
|
|
52
52
|
private findBrightestPixel;
|
|
53
|
-
/** Stores world-space frustum corners in `tempVector3D0
|
|
53
|
+
/** Stores world-space frustum corners in `tempVector3D0`-`tempVector3D7`. */
|
|
54
54
|
private computeFrustumPoints;
|
|
55
|
-
/** Stores world-space frustum corners in `tempVector3D0
|
|
55
|
+
/** Stores world-space frustum corners in `tempVector3D0`-`tempVector3D7`. */
|
|
56
56
|
private computeOrthographicPoints;
|
|
57
57
|
}
|
package/dist/lighting/Sun.js
CHANGED
|
@@ -186,7 +186,7 @@ class Sun extends DirectionalLight {
|
|
|
186
186
|
}
|
|
187
187
|
return { index: maxIndex, luminance: maxLuminance };
|
|
188
188
|
}
|
|
189
|
-
/** Stores world-space frustum corners in `tempVector3D0
|
|
189
|
+
/** Stores world-space frustum corners in `tempVector3D0`-`tempVector3D7`. */
|
|
190
190
|
_private_computeFrustumPoints(camera) {
|
|
191
191
|
const fovRad = camera.fov * MathUtils.DEG2RAD;
|
|
192
192
|
const halfTanFov = Math.tan(fovRad / 2);
|
|
@@ -211,7 +211,7 @@ class Sun extends DirectionalLight {
|
|
|
211
211
|
this._private_tempVector3D6.applyMatrix4(camera.matrixWorld);
|
|
212
212
|
this._private_tempVector3D7.applyMatrix4(camera.matrixWorld);
|
|
213
213
|
}
|
|
214
|
-
/** Stores world-space frustum corners in `tempVector3D0
|
|
214
|
+
/** Stores world-space frustum corners in `tempVector3D0`-`tempVector3D7`. */
|
|
215
215
|
_private_computeOrthographicPoints(camera) {
|
|
216
216
|
this._private_tempVector3D0.set(camera.left, camera.bottom, -camera.near);
|
|
217
217
|
this._private_tempVector3D1.set(camera.right, camera.bottom, -camera.near);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Material, Mesh, Object3D } from "three";
|
|
2
|
+
export declare class SceneResolver {
|
|
3
|
+
static resolveObject3DByName(scene: Object3D, name: string): Object3D;
|
|
4
|
+
static resolveMeshByName(scene: Object3D, name: string): Mesh;
|
|
5
|
+
static resolveMaterialByName(scene: Object3D, name: string): Material;
|
|
6
|
+
}
|
package/package.json
CHANGED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Box3, SkinnedMesh } from "three";
|
|
2
|
-
import { PerspectiveCamera, Vector3 } from "three";
|
|
3
|
-
/**
|
|
4
|
-
* Camera with independent horizontal and vertical FOV settings.
|
|
5
|
-
*/
|
|
6
|
-
export declare class DualFovCamera extends PerspectiveCamera {
|
|
7
|
-
private horizontalFovInternal;
|
|
8
|
-
private verticalFovInternal;
|
|
9
|
-
/**
|
|
10
|
-
* @param horizontalFov - Clamped to 1–179°.
|
|
11
|
-
* @param verticalFov - Clamped to 1–179°.
|
|
12
|
-
*/
|
|
13
|
-
constructor(horizontalFov?: number, verticalFov?: number, aspect?: number, near?: number, far?: number);
|
|
14
|
-
get horizontalFov(): number;
|
|
15
|
-
get verticalFov(): number;
|
|
16
|
-
/** Clamped to 1–179°. */
|
|
17
|
-
set horizontalFov(value: number);
|
|
18
|
-
/** Clamped to 1–179°. */
|
|
19
|
-
set verticalFov(value: number);
|
|
20
|
-
/** Both values clamped to 1–179°. */
|
|
21
|
-
setFov(horizontal: number, vertical: number): void;
|
|
22
|
-
copyFovSettings(source: DualFovCamera): void;
|
|
23
|
-
/**
|
|
24
|
-
* Landscape (aspect > 1): preserves horizontal FOV.
|
|
25
|
-
* Portrait (aspect ≤ 1): preserves vertical FOV.
|
|
26
|
-
*
|
|
27
|
-
* @override
|
|
28
|
-
*/
|
|
29
|
-
updateProjectionMatrix(): void;
|
|
30
|
-
/** Effective horizontal FOV accounting for current aspect ratio. */
|
|
31
|
-
getActualHorizontalFov(): number;
|
|
32
|
-
/** Effective vertical FOV accounting for current aspect ratio. */
|
|
33
|
-
getActualVerticalFov(): number;
|
|
34
|
-
/** @param vertices - World-space points. */
|
|
35
|
-
fitVerticalFovToPoints(vertices: Vector3[]): void;
|
|
36
|
-
/** @param box - World-space box. */
|
|
37
|
-
fitVerticalFovToBox(box: Box3): void;
|
|
38
|
-
/** Calls `skeleton.update()` internally before sampling vertices. */
|
|
39
|
-
fitVerticalFovToMesh(skinnedMesh: SkinnedMesh): void;
|
|
40
|
-
/** Uses iterative vertex clustering to approximate center of mass. */
|
|
41
|
-
lookAtMeshCenterOfMass(skinnedMesh: SkinnedMesh): void;
|
|
42
|
-
clone(): this;
|
|
43
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { PerspectiveCamera, MathUtils, Vector3 } from 'three';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_HORIZONTAL_FOV = 90;
|
|
4
|
-
const DEFAULT_VERTICAL_FOV = 90;
|
|
5
|
-
const DEFAULT_ASPECT = 1;
|
|
6
|
-
const DEFAULT_NEAR = 1;
|
|
7
|
-
const DEFAULT_FAR = 1000;
|
|
8
|
-
const MIN_FOV = 1;
|
|
9
|
-
const MAX_FOV = 179;
|
|
10
|
-
/**
|
|
11
|
-
* Camera with independent horizontal and vertical FOV settings.
|
|
12
|
-
*/
|
|
13
|
-
class DualFovCamera extends PerspectiveCamera {
|
|
14
|
-
/**
|
|
15
|
-
* @param horizontalFov - Clamped to 1–179°.
|
|
16
|
-
* @param verticalFov - Clamped to 1–179°.
|
|
17
|
-
*/
|
|
18
|
-
constructor(horizontalFov = DEFAULT_HORIZONTAL_FOV, verticalFov = DEFAULT_VERTICAL_FOV, aspect = DEFAULT_ASPECT, near = DEFAULT_NEAR, far = DEFAULT_FAR) {
|
|
19
|
-
super(verticalFov, aspect, near, far);
|
|
20
|
-
this._private_horizontalFovInternal = horizontalFov;
|
|
21
|
-
this._private_verticalFovInternal = verticalFov;
|
|
22
|
-
this.updateProjectionMatrix();
|
|
23
|
-
}
|
|
24
|
-
get horizontalFov() {
|
|
25
|
-
return this._private_horizontalFovInternal;
|
|
26
|
-
}
|
|
27
|
-
get verticalFov() {
|
|
28
|
-
return this._private_verticalFovInternal;
|
|
29
|
-
}
|
|
30
|
-
/** Clamped to 1–179°. */
|
|
31
|
-
set horizontalFov(value) {
|
|
32
|
-
this._private_horizontalFovInternal = MathUtils.clamp(value, MIN_FOV, MAX_FOV);
|
|
33
|
-
this.updateProjectionMatrix();
|
|
34
|
-
}
|
|
35
|
-
/** Clamped to 1–179°. */
|
|
36
|
-
set verticalFov(value) {
|
|
37
|
-
this._private_verticalFovInternal = MathUtils.clamp(value, MIN_FOV, MAX_FOV);
|
|
38
|
-
this.updateProjectionMatrix();
|
|
39
|
-
}
|
|
40
|
-
/** Both values clamped to 1–179°. */
|
|
41
|
-
setFov(horizontal, vertical) {
|
|
42
|
-
this._private_horizontalFovInternal = MathUtils.clamp(horizontal, MIN_FOV, MAX_FOV);
|
|
43
|
-
this._private_verticalFovInternal = MathUtils.clamp(vertical, MIN_FOV, MAX_FOV);
|
|
44
|
-
this.updateProjectionMatrix();
|
|
45
|
-
}
|
|
46
|
-
copyFovSettings(source) {
|
|
47
|
-
this._private_horizontalFovInternal = source.horizontalFov;
|
|
48
|
-
this._private_verticalFovInternal = source.verticalFov;
|
|
49
|
-
this.updateProjectionMatrix();
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Landscape (aspect > 1): preserves horizontal FOV.
|
|
53
|
-
* Portrait (aspect ≤ 1): preserves vertical FOV.
|
|
54
|
-
*
|
|
55
|
-
* @override
|
|
56
|
-
*/
|
|
57
|
-
updateProjectionMatrix() {
|
|
58
|
-
if (this.aspect > 1) {
|
|
59
|
-
const radians = MathUtils.degToRad(this._private_horizontalFovInternal);
|
|
60
|
-
this.fov = MathUtils.radToDeg(Math.atan(Math.tan(radians / 2) / this.aspect) * 2);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
this.fov = this._private_verticalFovInternal;
|
|
64
|
-
}
|
|
65
|
-
super.updateProjectionMatrix();
|
|
66
|
-
}
|
|
67
|
-
/** Effective horizontal FOV accounting for current aspect ratio. */
|
|
68
|
-
getActualHorizontalFov() {
|
|
69
|
-
if (this.aspect >= 1) {
|
|
70
|
-
return this._private_horizontalFovInternal;
|
|
71
|
-
}
|
|
72
|
-
const verticalRadians = MathUtils.degToRad(this._private_verticalFovInternal);
|
|
73
|
-
return MathUtils.radToDeg(Math.atan(Math.tan(verticalRadians / 2) * this.aspect) * 2);
|
|
74
|
-
}
|
|
75
|
-
/** Effective vertical FOV accounting for current aspect ratio. */
|
|
76
|
-
getActualVerticalFov() {
|
|
77
|
-
if (this.aspect < 1) {
|
|
78
|
-
return this._private_verticalFovInternal;
|
|
79
|
-
}
|
|
80
|
-
const horizontalRadians = MathUtils.degToRad(this._private_horizontalFovInternal);
|
|
81
|
-
return MathUtils.radToDeg(Math.atan(Math.tan(horizontalRadians / 2) / this.aspect) * 2);
|
|
82
|
-
}
|
|
83
|
-
/** @param vertices - World-space points. */
|
|
84
|
-
fitVerticalFovToPoints(vertices) {
|
|
85
|
-
const up = new Vector3(0, 1, 0).applyQuaternion(this.quaternion);
|
|
86
|
-
let maxVerticalAngle = 0;
|
|
87
|
-
for (const vertex of vertices) {
|
|
88
|
-
const vertexToCam = this.position.clone().sub(vertex);
|
|
89
|
-
const vertexDirection = vertexToCam.normalize();
|
|
90
|
-
const verticalAngle = Math.asin(Math.abs(vertexDirection.dot(up))) * Math.sign(vertexDirection.dot(up));
|
|
91
|
-
if (Math.abs(verticalAngle) > maxVerticalAngle) {
|
|
92
|
-
maxVerticalAngle = Math.abs(verticalAngle);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const requiredFov = MathUtils.radToDeg(2 * maxVerticalAngle);
|
|
96
|
-
this._private_verticalFovInternal = MathUtils.clamp(requiredFov, MIN_FOV, MAX_FOV);
|
|
97
|
-
this.updateProjectionMatrix();
|
|
98
|
-
}
|
|
99
|
-
/** @param box - World-space box. */
|
|
100
|
-
fitVerticalFovToBox(box) {
|
|
101
|
-
this.fitVerticalFovToPoints([
|
|
102
|
-
new Vector3(box.min.x, box.min.y, box.min.z),
|
|
103
|
-
new Vector3(box.min.x, box.min.y, box.max.z),
|
|
104
|
-
new Vector3(box.min.x, box.max.y, box.min.z),
|
|
105
|
-
new Vector3(box.min.x, box.max.y, box.max.z),
|
|
106
|
-
new Vector3(box.max.x, box.min.y, box.min.z),
|
|
107
|
-
new Vector3(box.max.x, box.min.y, box.max.z),
|
|
108
|
-
new Vector3(box.max.x, box.max.y, box.min.z),
|
|
109
|
-
new Vector3(box.max.x, box.max.y, box.max.z),
|
|
110
|
-
]);
|
|
111
|
-
}
|
|
112
|
-
/** Calls `skeleton.update()` internally before sampling vertices. */
|
|
113
|
-
fitVerticalFovToMesh(skinnedMesh) {
|
|
114
|
-
skinnedMesh.updateWorldMatrix(true, true);
|
|
115
|
-
skinnedMesh.skeleton.update();
|
|
116
|
-
const bakedGeometry = skinnedMesh.geometry;
|
|
117
|
-
const position = bakedGeometry.attributes["position"];
|
|
118
|
-
const target = new Vector3();
|
|
119
|
-
const points = [];
|
|
120
|
-
for (let i = 0; i < position.count; i++) {
|
|
121
|
-
target.fromBufferAttribute(position, i);
|
|
122
|
-
skinnedMesh.applyBoneTransform(i, target);
|
|
123
|
-
points.push(target.clone());
|
|
124
|
-
}
|
|
125
|
-
this.fitVerticalFovToPoints(points);
|
|
126
|
-
}
|
|
127
|
-
/** Uses iterative vertex clustering to approximate center of mass. */
|
|
128
|
-
lookAtMeshCenterOfMass(skinnedMesh) {
|
|
129
|
-
skinnedMesh.updateWorldMatrix(true, true);
|
|
130
|
-
skinnedMesh.skeleton.update();
|
|
131
|
-
const bakedGeometry = skinnedMesh.geometry;
|
|
132
|
-
const position = bakedGeometry.attributes.position;
|
|
133
|
-
const target = new Vector3();
|
|
134
|
-
const points = [];
|
|
135
|
-
for (let i = 0; i < position.count; i++) {
|
|
136
|
-
target.fromBufferAttribute(position, i);
|
|
137
|
-
skinnedMesh.applyBoneTransform(i, target);
|
|
138
|
-
points.push(target.clone());
|
|
139
|
-
}
|
|
140
|
-
const findMainCluster = (points, iterations = 3) => {
|
|
141
|
-
if (points.length === 0) {
|
|
142
|
-
return new Vector3();
|
|
143
|
-
}
|
|
144
|
-
let center = points[Math.floor(points.length / 2)].clone();
|
|
145
|
-
for (let i = 0; i < iterations; i++) {
|
|
146
|
-
let total = new Vector3();
|
|
147
|
-
let count = 0;
|
|
148
|
-
for (const point of points) {
|
|
149
|
-
if (point.distanceTo(center) < point.distanceTo(total) || count === 0) {
|
|
150
|
-
total.add(point);
|
|
151
|
-
count++;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if (count > 0) {
|
|
155
|
-
center = total.divideScalar(count);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return center;
|
|
159
|
-
};
|
|
160
|
-
const centerOfMass = findMainCluster(points);
|
|
161
|
-
this.lookAt(centerOfMass);
|
|
162
|
-
}
|
|
163
|
-
clone() {
|
|
164
|
-
const camera = new DualFovCamera(this._private_horizontalFovInternal, this._private_verticalFovInternal, this.aspect, this.near, this.far);
|
|
165
|
-
camera.copy(this, true);
|
|
166
|
-
return camera;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export { DualFovCamera };
|