three-zoo 0.4.6 → 0.5.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,6 +1,6 @@
1
1
  # three-zoo
2
2
 
3
- A few Three.js utilities to handle common 3D tasks.
3
+ A modest collection of Three.js utilities designed to simplify common 3D development tasks.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,102 +8,108 @@ A few Three.js utilities to handle common 3D tasks.
8
8
  npm install three-zoo
9
9
  ```
10
10
 
11
+ ## Overview
12
+
13
+ **three-zoo** provides focused solutions for recurring challenges in 3D web development:
14
+
15
+ - **Advanced camera controls** with independent FOV management and auto-fitting
16
+ - **Intuitive lighting** with spherical positioning and HDR integration
17
+ - **Scene graph utilities** for finding and manipulating objects and materials
18
+ - **Animation baking** to convert skinned meshes to static geometry
19
+
20
+ Each utility is designed to work seamlessly with existing Three.js workflows without imposing architectural constraints.
21
+
11
22
  ## Tools
12
23
 
13
- ### BiFovCamera
24
+ ### DualFovCamera
14
25
 
15
- Camera with separate horizontal and vertical FOV control:
26
+ Camera with independent horizontal and vertical field of view control, plus advanced fitting capabilities:
16
27
 
17
28
  ```typescript
18
- const camera = new BiFovCamera(90, 60); // hFov, vFov
29
+ const camera = new DualFovCamera(90, 60); // hFov, vFov
19
30
  camera.horizontalFov = 100; // Change horizontal FOV
20
31
  camera.verticalFov = 70; // Change vertical FOV
21
- ```
22
32
 
23
- ### Bounds
33
+ // Automatically adjust FOV to fit objects
34
+ camera.fitVerticalFovToPoints(vertices);
35
+ camera.fitVerticalFovToBox(boundingBox);
36
+ camera.fitVerticalFovToMesh(skinnedMesh);
24
37
 
25
- Extra bounding box calculations:
38
+ // Point camera at mesh center of mass
39
+ camera.lookAtMeshCenterOfMass(skinnedMesh);
26
40
 
27
- ```typescript
28
- const bounds = new Bounds(mesh);
29
- console.log(bounds.width); // x-axis length
30
- console.log(bounds.depth); // z-axis length
31
- console.log(bounds.getVolume()); // volume
41
+ // Get actual FOV after aspect ratio calculations
42
+ const actualHFov = camera.getActualHorizontalFov();
43
+ const actualVFov = camera.getActualVerticalFov();
32
44
  ```
33
45
 
34
- ### InstanceAssembler
46
+ ### Sun
35
47
 
36
- Combines identical meshes into instances:
48
+ Directional light with intuitive spherical positioning and automatic shadow configuration:
37
49
 
38
50
  ```typescript
39
- // Basic - combine all identical meshes
40
- InstanceAssembler.assemble(scene);
41
-
42
- // Custom - only specific meshes
43
- InstanceAssembler.assemble(scene, {
44
- filter: mesh => mesh.name.startsWith('Tree_'),
45
- geometryTolerance: 0.001
46
- });
47
- ```
51
+ const sun = new Sun();
48
52
 
49
- ### SceneProcessor
53
+ // Spherical positioning
54
+ sun.elevation = Math.PI / 4; // 45° above horizon
55
+ sun.azimuth = Math.PI / 2; // 90° rotation
56
+ sun.distance = 100; // Distance from origin
50
57
 
51
- Sets up materials and shadows based on naming patterns:
58
+ // Automatically configure shadows for optimal coverage
59
+ sun.configureShadowsForBoundingBox(sceneBounds);
52
60
 
53
- ```typescript
54
- SceneProcessor.process(scene, {
55
- castShadowExpressions: [/^Tree_.*/],
56
- receiveShadwoExpressions: [/Ground/],
57
- transparentMaterialExpressions: [/Glass/],
58
- });
61
+ // Position sun based on brightest point in HDR environment map
62
+ sun.setDirectionFromHDRTexture(hdrTexture, 50);
59
63
  ```
60
64
 
61
65
  ### SceneTraversal
62
66
 
63
- Scene graph utilities:
67
+ Scene graph navigation and batch operations for finding and manipulating objects:
64
68
 
65
69
  ```typescript
66
- // Find objects
70
+ // Find objects and materials by name
67
71
  const obj = SceneTraversal.getObjectByName(scene, 'player');
68
- const objects = SceneTraversal.filterObjects(scene, /^enemy_/);
72
+ const material = SceneTraversal.getMaterialByName(scene, 'metal');
69
73
 
70
- // Configure shadows
71
- SceneTraversal.setShadowRecursive(scene, true, true);
74
+ // Filter with patterns or custom functions
75
+ const enemies = SceneTraversal.filterObjects(scene, /^enemy_/);
76
+ const glassMaterials = SceneTraversal.filterMaterials(scene, /glass/i);
77
+
78
+ // Find objects that use specific materials
79
+ const meshesWithGlass = SceneTraversal.findMaterialUsers(scene, /glass/i);
80
+
81
+ // Batch operations on specific object types
82
+ SceneTraversal.enumerateObjectsByType(scene, Mesh, (mesh) => {
83
+ mesh.castShadow = true;
84
+ });
85
+
86
+ // Process all materials in the scene
87
+ SceneTraversal.enumerateMaterials(scene, (material) => {
88
+ if ('roughness' in material) material.roughness = 0.8;
89
+ });
72
90
  ```
73
91
 
74
92
  ### SkinnedMeshBaker
75
93
 
76
- Converts skinned meshes to static geometry:
94
+ Converts animated skinned meshes to static geometry:
77
95
 
78
96
  ```typescript
79
- // Bake current pose
97
+ // Bake current pose to static mesh
80
98
  const staticMesh = SkinnedMeshBaker.bakePose(skinnedMesh);
81
99
 
82
- // Bake animation frame
100
+ // Bake specific animation frame
83
101
  const frameMesh = SkinnedMeshBaker.bakeAnimationFrame(
84
- armature,
85
- skinnedMesh,
86
- 1.5, // time
87
- clip // animation
102
+ armature, // Root object with bones
103
+ skinnedMesh, // Mesh to bake
104
+ 1.5, // Time in seconds
105
+ animationClip // Animation to sample
88
106
  );
89
107
  ```
90
108
 
91
- ### Sun
92
-
93
- Directional light with spherical positioning:
94
-
95
- ```typescript
96
- const sun = new Sun();
97
- sun.elevation = Math.PI / 4; // 45°
98
- sun.azimuth = Math.PI / 2; // 90°
99
-
100
- // Set up shadows
101
- sun.setShadowMapFromBox3(new Bounds().setFromObject(scene));
102
- ```
103
-
104
109
  ## Requirements
105
110
 
106
- - three >= 0.150.0
111
+ - Three.js >= 0.150.0
112
+ - TypeScript support included
107
113
 
108
114
  ## License
109
115
 
@@ -0,0 +1,142 @@
1
+ import type { Box3, SkinnedMesh } from "three";
2
+ import { PerspectiveCamera, Vector3 } from "three";
3
+ /**
4
+ * A camera that supports independent horizontal and vertical FOV settings.
5
+ * Extends Three.js PerspectiveCamera to allow separate control over horizontal
6
+ * and vertical fields of view.
7
+ */
8
+ export declare class DualFovCamera extends PerspectiveCamera {
9
+ /** Internal storage for horizontal field of view in degrees */
10
+ private horizontalFovInternal;
11
+ /** Internal storage for vertical field of view in degrees */
12
+ private verticalFovInternal;
13
+ /**
14
+ * Creates a new DualFovCamera instance with independent horizontal and vertical FOV control.
15
+ *
16
+ * @param horizontalFov - Horizontal field of view in degrees. Must be between 1° and 179°. Defaults to 90°.
17
+ * @param verticalFov - Vertical field of view in degrees. Must be between 1° and 179°. Defaults to 90°.
18
+ * @param aspect - Camera aspect ratio (width/height). Defaults to 1.
19
+ * @param near - Near clipping plane distance. Must be greater than 0. Defaults to 1.
20
+ * @param far - Far clipping plane distance. Must be greater than near plane. Defaults to 1000.
21
+ */
22
+ constructor(horizontalFov?: number, verticalFov?: number, aspect?: number, near?: number, far?: number);
23
+ /**
24
+ * Gets the current horizontal field of view in degrees.
25
+ *
26
+ * @returns The horizontal FOV value between 1° and 179°
27
+ */
28
+ get horizontalFov(): number;
29
+ /**
30
+ * Gets the current vertical field of view in degrees.
31
+ *
32
+ * @returns The vertical FOV value between 1° and 179°
33
+ */
34
+ get verticalFov(): number;
35
+ /**
36
+ * Sets the horizontal field of view in degrees.
37
+ *
38
+ * @param value - The horizontal FOV value in degrees. Will be clamped between 1° and 179°.
39
+ */
40
+ set horizontalFov(value: number);
41
+ /**
42
+ * Sets the vertical field of view in degrees.
43
+ *
44
+ * @param value - The vertical FOV value in degrees. Will be clamped between 1° and 179°.
45
+ */
46
+ set verticalFov(value: number);
47
+ /**
48
+ * Updates both horizontal and vertical field of view values simultaneously.
49
+ *
50
+ * @param horizontal - Horizontal FOV in degrees. Will be clamped between 1° and 179°.
51
+ * @param vertical - Vertical FOV in degrees. Will be clamped between 1° and 179°.
52
+ */
53
+ setFov(horizontal: number, vertical: number): void;
54
+ /**
55
+ * Copies the field of view settings from another DualFovCamera instance.
56
+ *
57
+ * @param source - The DualFovCamera instance to copy FOV settings from.
58
+ */
59
+ copyFovSettings(source: DualFovCamera): void;
60
+ /**
61
+ * Updates the projection matrix based on current FOV settings and aspect ratio.
62
+ *
63
+ * The behavior differs based on orientation:
64
+ * - **Landscape mode (aspect > 1)**: Preserves horizontal FOV, calculates vertical FOV
65
+ * - **Portrait mode (aspect ≤ 1)**: Preserves vertical FOV, calculates horizontal FOV
66
+ *
67
+ * This method is automatically called when FOV values or aspect ratio changes.
68
+ *
69
+ * @override
70
+ */
71
+ updateProjectionMatrix(): void;
72
+ /**
73
+ * Gets the actual horizontal field of view after aspect ratio adjustments.
74
+ *
75
+ * In landscape mode, this returns the set horizontal FOV.
76
+ * In portrait mode, this calculates the actual horizontal FOV based on the vertical FOV and aspect ratio.
77
+ *
78
+ * @returns The actual horizontal FOV in degrees
79
+ */
80
+ getActualHorizontalFov(): number;
81
+ /**
82
+ * Gets the actual vertical field of view after aspect ratio adjustments.
83
+ *
84
+ * In portrait mode, this returns the set vertical FOV.
85
+ * In landscape mode, this calculates the actual vertical FOV based on the horizontal FOV and aspect ratio.
86
+ *
87
+ * @returns The actual vertical FOV in degrees
88
+ */
89
+ getActualVerticalFov(): number;
90
+ /**
91
+ * Adjusts the vertical field of view to fit all specified points within the camera's view.
92
+ *
93
+ * This method calculates the required vertical FOV to ensure all provided vertices
94
+ * are visible within the vertical bounds of the camera's frustum.
95
+ *
96
+ * @param vertices - Array of 3D points (in world coordinates) that should fit within the camera's vertical view
97
+ */
98
+ fitVerticalFovToPoints(vertices: Vector3[]): void;
99
+ /**
100
+ * Adjusts the vertical field of view to fit a bounding box within the camera's view.
101
+ *
102
+ * This method calculates the required vertical FOV to ensure the entire bounding box
103
+ * is visible within the vertical bounds of the camera's frustum.
104
+ *
105
+ * @param box - The 3D bounding box (in world coordinates) that should fit within the camera's vertical view
106
+ */
107
+ fitVerticalFovToBox(box: Box3): void;
108
+ /**
109
+ * Adjusts the vertical field of view to fit a skinned mesh within the camera's view.
110
+ *
111
+ * This method updates the mesh's skeleton, applies bone transformations to all vertices,
112
+ * and then calculates the required vertical FOV to ensure the entire deformed mesh
113
+ * is visible within the vertical bounds of the camera's frustum.
114
+ *
115
+ * @param skinnedMesh - The skinned mesh (with active skeleton) that should fit within the camera's vertical view
116
+ */
117
+ fitVerticalFovToMesh(skinnedMesh: SkinnedMesh): void;
118
+ /**
119
+ * Points the camera to look at the center of mass of a skinned mesh.
120
+ *
121
+ * This method updates the mesh's skeleton, applies bone transformations to all vertices,
122
+ * calculates the center of mass using a clustering algorithm, and then orients the camera
123
+ * to look at that point.
124
+ *
125
+ * The center of mass calculation uses an iterative clustering approach to find the
126
+ * main concentration of vertices, which provides better results than a simple average
127
+ * for complex meshes.
128
+ *
129
+ * @param skinnedMesh - The skinned mesh (with active skeleton) whose center of mass should be the camera's target
130
+ */
131
+ lookAtMeshCenterOfMass(skinnedMesh: SkinnedMesh): void;
132
+ /**
133
+ * Creates a deep copy of this DualFovCamera instance.
134
+ *
135
+ * The cloned camera will have identical FOV settings, position, rotation,
136
+ * and all other camera properties.
137
+ *
138
+ * @returns A new DualFovCamera instance that is an exact copy of this one
139
+ * @override
140
+ */
141
+ clone(): this;
142
+ }
@@ -1,17 +1 @@
1
- import type { BufferGeometry } from "three";
2
- /**
3
- * Internal utility to identify identical geometries.
4
- * @internal
5
- */
6
- export declare class GeometryHasher {
7
- /**
8
- * Creates a hash for a geometry based on its vertex data.
9
- * Vertices that differ by less than tolerance are considered the same.
10
- *
11
- * @param geometry - Geometry to hash
12
- * @param tolerance - How close vertices need to be to count as identical
13
- * @returns Hash string that's the same for matching geometries
14
- * @internal
15
- */
16
- static getGeometryHash(geometry: BufferGeometry, tolerance: number): string;
17
- }
1
+ export {};
@@ -1,20 +1,114 @@
1
1
  import type { Material, Object3D } from "three";
2
- /** Constructor type for type-safe scene traversal */
2
+ import { Mesh } from "three";
3
+ /**
4
+ * Constructor type for type-safe scene traversal operations.
5
+ *
6
+ * This type represents any constructor function that can be used to create instances of type T.
7
+ * It's used for runtime type checking when filtering objects by their constructor type.
8
+ *
9
+ * @template T - The type that the constructor creates
10
+ */
3
11
  export type Constructor<T> = abstract new (...args: never[]) => T;
4
- /** Find and modify objects in a Three.js scene */
12
+ /**
13
+ * Utility class for finding and modifying objects in a Three.js scene graph.
14
+ *
15
+ * This class provides static methods for traversing Three.js scene hierarchies,
16
+ * searching for specific objects or materials, and performing batch operations
17
+ * on collections of scene objects.
18
+ *
19
+ * All methods perform depth-first traversal of the scene graph starting from
20
+ * the provided root object and recursively processing all children.
21
+ */
5
22
  export declare class SceneTraversal {
6
- /** Find first object with exact name match */
23
+ /**
24
+ * Finds the first object in the scene hierarchy with an exact name match.
25
+ *
26
+ * Performs a depth-first search through the scene graph starting from the provided
27
+ * root object. Returns the first object encountered whose name property exactly
28
+ * matches the search string.
29
+ *
30
+ * @param object - The root Object3D to start searching from
31
+ * @param name - The exact name to search for (case-sensitive)
32
+ * @returns The first matching Object3D, or null if no match is found
33
+
34
+ */
7
35
  static getObjectByName(object: Object3D, name: string): Object3D | null;
8
- /** Find first material with exact name match */
36
+ /**
37
+ * Finds the first material in the scene hierarchy with an exact name match.
38
+ *
39
+ * Performs a depth-first search through the scene graph, examining materials
40
+ * attached to Mesh objects. Handles both single materials and material arrays.
41
+ * Returns the first material encountered whose name property exactly matches
42
+ * the search string.
43
+ *
44
+ * @param object - The root Object3D to start searching from
45
+ * @param name - The exact material name to search for (case-sensitive)
46
+ * @returns The first matching Material, or null if no match is found
47
+
48
+ */
9
49
  static getMaterialByName(object: Object3D, name: string): Material | null;
10
- /** Process all objects of a specific type */
50
+ /**
51
+ * Processes all objects of a specific type in the scene hierarchy.
52
+ *
53
+ * Performs a depth-first traversal and executes the provided callback function
54
+ * for every object that is an instance of the specified type. This is useful
55
+ * for batch operations on specific object types (e.g., all lights, all meshes, etc.).
56
+ *
57
+ * @template T - The type of objects to process
58
+ * @param object - The root Object3D to start searching from
59
+ * @param type - The constructor/class to filter by (e.g., DirectionalLight, Mesh)
60
+ * @param callback - Function to execute for each matching object instance
61
+
62
+ */
11
63
  static enumerateObjectsByType<T>(object: Object3D, type: Constructor<T>, callback: (instance: T) => void): void;
12
- /** Process all materials in meshes */
64
+ /**
65
+ * Processes all materials found in mesh objects within the scene hierarchy.
66
+ *
67
+ * Performs a depth-first traversal, finding all Mesh objects and executing
68
+ * the provided callback function for each material. Handles both single
69
+ * materials and material arrays properly.
70
+ *
71
+ * @param object - The root Object3D to start searching from
72
+ * @param callback - Function to execute for each material found
73
+
74
+ */
13
75
  static enumerateMaterials(object: Object3D, callback: (material: Material) => void): void;
14
- /** Find all objects whose names match a pattern */
76
+ /**
77
+ * Finds all objects in the scene hierarchy that match the specified filter criteria.
78
+ *
79
+ * Performs a depth-first search and collects all objects that either match
80
+ * a regular expression pattern (applied to the object's name) or satisfy
81
+ * a custom predicate function.
82
+ *
83
+ * @param object - The root Object3D to start searching from
84
+ * @param filter - Either a RegExp to test against object names, or a predicate function
85
+ * @returns Array of all matching Object3D instances
86
+
87
+ */
15
88
  static filterObjects(object: Object3D, filter: RegExp | ((object: Object3D) => boolean)): Object3D[];
16
- /** Find all materials whose names match a pattern */
89
+ /**
90
+ * Finds all materials in the scene hierarchy whose names match a regular expression pattern.
91
+ *
92
+ * Performs a depth-first search through all Mesh objects and collects materials
93
+ * whose name property matches the provided regular expression. Handles both
94
+ * single materials and material arrays properly.
95
+ *
96
+ * @param object - The root Object3D to start searching from
97
+ * @param name - Regular expression pattern to test against material names
98
+ * @returns Array of all matching Material instances
99
+
100
+ */
17
101
  static filterMaterials(object: Object3D, name: RegExp): Material[];
18
- /** Set shadow properties on meshes */
19
- static setShadowRecursive(object: Object3D, castShadow?: boolean, receiveShadow?: boolean, filter?: (object: Object3D) => boolean): void;
102
+ /**
103
+ * Finds all objects (mesh users) that use materials with names matching a regular expression pattern.
104
+ *
105
+ * Performs a depth-first search through all Mesh objects and collects the mesh objects
106
+ * whose materials have names that match the provided regular expression. This is useful
107
+ * for finding all objects that use specific material types or naming patterns.
108
+ *
109
+ * @param object - The root Object3D to start searching from
110
+ * @param materialName - Regular expression pattern to test against material names
111
+ * @returns Array of all Mesh objects that use materials with matching names
112
+ */
113
+ static findMaterialUsers(object: Object3D, materialName: RegExp): Mesh[];
20
114
  }
package/dist/Sun.d.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import type { Texture } from "three";
2
2
  import { Box3, DirectionalLight } from "three";
3
- /** A directional light with spherical positioning controls */
3
+ /**
4
+ * A directional light with spherical positioning controls and advanced shadow mapping.
5
+ *
6
+ * Extends Three.js DirectionalLight to provide intuitive spherical coordinate control
7
+ * (distance, elevation, azimuth) and automatic shadow map configuration for bounding boxes.
8
+ * Also supports automatic sun direction calculation from HDR environment maps.
9
+ */
4
10
  export declare class Sun extends DirectionalLight {
5
- /** Internal vectors to avoid garbage collection */
11
+ /** Internal vectors to avoid garbage collection during calculations */
6
12
  private readonly tempVector3D0;
7
13
  private readonly tempVector3D1;
8
14
  private readonly tempVector3D2;
@@ -13,20 +19,61 @@ export declare class Sun extends DirectionalLight {
13
19
  private readonly tempVector3D7;
14
20
  private readonly tempBox3;
15
21
  private readonly tempSpherical;
16
- /** Distance from the light to its target */
22
+ /**
23
+ * Gets the distance from the light to its target (origin).
24
+ *
25
+ * @returns The distance in world units
26
+ */
17
27
  get distance(): number;
18
- /** Vertical angle from the ground in radians */
28
+ /**
29
+ * Gets the elevation angle (vertical angle from the horizontal plane).
30
+ *
31
+ * @returns The elevation angle in radians (0 = horizontal, π/2 = directly above)
32
+ */
19
33
  get elevation(): number;
20
- /** Horizontal angle around the target in radians */
34
+ /**
35
+ * Gets the azimuth angle (horizontal rotation around the target).
36
+ *
37
+ * @returns The azimuth angle in radians (0 = positive X axis, π/2 = positive Z axis)
38
+ */
21
39
  get azimuth(): number;
22
- /** Set distance while keeping current angles */
40
+ /**
41
+ * Sets the distance while preserving current elevation and azimuth angles.
42
+ *
43
+ * @param value - The new distance in world units
44
+ */
23
45
  set distance(value: number);
24
- /** Set elevation while keeping current distance and azimuth */
46
+ /**
47
+ * Sets the elevation angle while preserving current distance and azimuth.
48
+ *
49
+ * @param value - The new elevation angle in radians (0 = horizontal, π/2 = directly above)
50
+ */
25
51
  set elevation(value: number);
26
- /** Set azimuth while keeping current distance and elevation */
52
+ /**
53
+ * Sets the azimuth angle while preserving current distance and elevation.
54
+ *
55
+ * @param value - The new azimuth angle in radians (0 = positive X axis, π/2 = positive Z axis)
56
+ */
27
57
  set azimuth(value: number);
28
- /** Configure shadows to cover all corners of a bounding box */
29
- setShadowMapFromBox3(box3: Box3): void;
30
- /** Set light direction based on brightest point in an HDR texture */
31
- setDirectionFromHDR(texture: Texture, distance?: number): void;
58
+ /**
59
+ * Configures the shadow camera to optimally cover a bounding box.
60
+ *
61
+ * This method automatically adjusts the directional light's shadow camera frustum
62
+ * to perfectly encompass the provided bounding box, ensuring efficient shadow map
63
+ * usage and eliminating shadow clipping issues.
64
+ *
65
+ * @param box3 - The 3D bounding box to cover with shadows
66
+ */
67
+ configureShadowsForBoundingBox(box3: Box3): void;
68
+ /**
69
+ * Sets the sun's direction based on the brightest point in an HDR environment map.
70
+ *
71
+ * This method analyzes an HDR texture to find the pixel with the highest luminance
72
+ * value and positions the sun to shine from that direction. This is useful for
73
+ * creating realistic lighting that matches HDR environment maps.
74
+ *
75
+ * @param texture - The HDR texture to analyze (must have image data available)
76
+ * @param distance - The distance to place the sun from the origin (defaults to 1)
77
+ */
78
+ setDirectionFromHDRTexture(texture: Texture, distance?: number): void;
32
79
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,4 @@
1
- export * from "./BiFovCamera";
2
- export * from "./Bounds";
3
- export * from "./InstanceAssembler";
4
- export * from "./SceneProcessor";
1
+ export * from "./DualFovCamera";
5
2
  export * from "./SceneTraversal";
6
3
  export * from "./SkinnedMeshBaker";
7
4
  export * from "./Sun";