3d-scene-creator
Version:
A simple utility to create and manage 3D scenes
250 lines (249 loc) • 10.8 kB
TypeScript
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { LightingOptions, AnimatedModel, AnimatedModelOptions, PhysicsOptions, PhysicsBodyOptions } from '../types';
import * as THREE from "three";
import type * as CANNON from "cannon-es";
export declare class SceneCreator {
scene: THREE.Scene;
renderer: THREE.WebGLRenderer;
container: HTMLElement | undefined;
cWidth: number;
cHeight: number;
camera: THREE.PerspectiveCamera;
initialCamPos: THREE.Vector3;
initialTargetPos: THREE.Vector3;
prevCamPos: THREE.Vector3;
controls: OrbitControls | undefined;
additionalRenderFn: (() => void) | undefined;
stopLoop: boolean;
scale: number;
animating: number;
private tweens;
private mixers;
private lastFrameTime?;
physicsWorld?: CANNON.World;
private physicsBodies;
private physicsFixedStep;
private physicsMaxSubSteps;
private cannon?;
raycaster: THREE.Raycaster;
mouse: THREE.Vector2;
selectedObject: THREE.Object3D | null;
pickingEnabled: boolean;
onObjectClick: ((obj: THREE.Object3D) => void) | undefined;
onObjectHover: ((obj: THREE.Object3D | null) => void) | undefined;
onObjectContextMenu: ((obj: THREE.Object3D) => void) | undefined;
private gltfLoader?;
private gltfCache;
clickDragThreshold: number;
private pointerDownX;
private pointerDownY;
private pointerDragging;
/**
* Initialize a 3D scene with Three.js and tween.js
* @param container - Optional HTML element to attach the renderer to
* @param scale - Scale factor for the scene (default: 1)
* @param camPos - Initial camera position (default: 10, 10, 10)
* @param targetPos - Initial camera target (default: 0, 0, 0)
*/
constructor(container?: HTMLElement, scale?: number, camPos?: THREE.Vector3, targetPos?: THREE.Vector3);
/**
* Add orbit controls to the camera
* @param overrides - Optional configuration overrides
* @returns this for method chaining
*/
addControls(overrides?: {}): this;
/**
* Set a callback function to be executed on each render frame
* @param fn - Callback function to run each frame
* @returns this for method chaining
*/
setAdditionalRenderFn(fn: () => void): this;
resetSizes(): void;
/**
* Tween numeric properties of a target object. Durations are given in seconds;
* the render loop keeps drawing while at least one tween is active, then stops once they complete.
* @param target - Object whose numeric props are animated
* @param props - Destination values
* @param duration - Duration in seconds
* @param onComplete - Optional callback fired when the tween finishes
* @returns the created Tween
*/
private tween;
/**
* Animate the color of a 3D model
* @param name - Name of the object in the scene
* @param color - Target color (hex, rgb, or color name)
* @param duration - Animation duration in seconds (default: 2)
* @returns this for method chaining
*/
animateModelColor(name: string, color: string | number, duration?: number): this;
/**
* Animate the opacity of a 3D model
* @param name - Name of the object in the scene
* @param value - Target opacity value (0-1)
* @param duration - Animation duration in seconds (default: 2)
* @returns this for method chaining
*/
animateModelOpacity(name: string, value: number, duration?: number): this;
/**
* Animate the position of a 3D model
* @param name - Name of the object in the scene
* @param newPosition - Target position vector
* @param duration - Animation duration in seconds (default: 2)
* @returns this for method chaining
*/
animateModelPosition(name: string, newPosition: THREE.Vector3, duration?: number): this;
/**
* Attach renderer to a DOM element and start rendering
* @param container - HTML element to attach the canvas to
* @returns this for method chaining
*/
attachRenderer(container: HTMLElement): this;
/**
* Add a game-ready lighting rig: a hemisphere fill, a shadow-casting key
* light, and an opposite fill light, with optional ACES tone mapping.
* All parts are configurable; sensible defaults are used when omitted.
* @param options - Lighting configuration overrides
* @returns this for method chaining
*/
addLighting(options?: LightingOptions): this;
/**
* Enable shadow casting/receiving on every mesh currently in the scene.
* Call after adding your meshes (and after {@link addLighting} with shadows on).
* @param cast - Whether meshes cast shadows (default: true)
* @param receive - Whether meshes receive shadows (default: true)
* @returns this for method chaining
*/
applyShadows(cast?: boolean, receive?: boolean): this;
/**
* Add a skybox to the scene (360° background)
* @param url - Optional URL to a 360° image texture
* @param color - Fallback color if no texture URL provided
* @param name - Optional name for the skybox object
* @returns this for method chaining
*/
addSkybox(url?: string, color?: THREE.ColorRepresentation, name?: string): this;
resizeListener(): void;
stopRenderLoop(): this;
startRenderLoop(): this;
renderLoop(): void;
/**
* Reset camera to initial position with animation
* @returns this for method chaining
*/
resetCameraPosition(): this;
/**
* Animate camera to a new position
* @param newPosCam - Target camera position
* @param newPosTarget - Optional target position for orbit controls
* @param callback - Optional callback on animation complete
* @returns this for method chaining
*/
moveCamera(newPosCam: THREE.Vector3, newPosTarget?: THREE.Vector3, callback?: () => void): this;
/**
* Load a 3D model from a URL
* @param url - URL to the model file
* @param loader - Optional THREE.js loader (default: ObjectLoader)
* @returns Promise that resolves with the loaded object
*/
loadModel(url: string, loader?: THREE.Loader): Promise<THREE.Object3D>;
/**
* Load a glTF/glb model and return a fresh clone of its scene (not added to
* the scene graph). Results are cached per URL, so loading the same model
* many times only fetches/parses it once. Ideal for instancing buildings,
* units, props, etc. in a game.
* @param url - URL to the .glb/.gltf file
* @returns Promise resolving to a cloneable Group ready to position/add
*/
loadGLTF(url: string): Promise<THREE.Group>;
/**
* Register an AnimationMixer so it's advanced automatically every frame by
* the render loop. The loop keeps drawing while any mixer is registered.
* @param mixer - The mixer to drive
* @returns this for method chaining
*/
addMixer(mixer: THREE.AnimationMixer): this;
/**
* Stop driving a previously-registered AnimationMixer.
* @param mixer - The mixer to remove
* @returns this for method chaining
*/
removeMixer(mixer: THREE.AnimationMixer): this;
/**
* Load a rigged glTF/glb model, add it to the scene, and wire its animation
* clips into the render loop. Unlike {@link loadGLTF} (which returns a clone
* for static instancing), this keeps the original object so its skeleton
* animates correctly, and returns a small handle to control playback.
* @param url - URL to the .glb/.gltf file
* @param options - Load options (add to scene, shadows, autoplay)
* @returns Promise resolving to an {@link AnimatedModel} handle
*/
loadAnimatedModel(url: string, options?: AnimatedModelOptions): Promise<AnimatedModel>;
/**
* Enable rigid-body physics (powered by cannon-es). The world is stepped every
* frame and each body added with {@link addBody} keeps its mesh in sync.
*
* cannon-es is imported lazily here, so projects that never call this method
* don't bundle it. This makes the method asynchronous: `await` it before
* calling {@link addBody} or {@link addGround}.
* @param options - Gravity and default contact material settings
* @returns Promise resolving to this (for chaining)
*/
enablePhysics(options?: PhysicsOptions): Promise<this>;
/**
* Give a mesh a rigid body and keep the two in sync each frame. The collision
* shape is derived from the mesh's bounding box (or sphere) and its scale.
* Requires {@link enablePhysics} to have been called first.
* @param mesh - The mesh to simulate (its transform is driven by the body)
* @param options - Mass, shape and damping
* @returns The created cannon-es Body
*/
addBody(mesh: THREE.Object3D, options?: PhysicsBodyOptions): CANNON.Body;
/**
* Add a static, infinite ground plane at the given height (default y = 0).
* @param y - Height of the ground plane
* @returns The created cannon-es Body
*/
addGround(y?: number): CANNON.Body;
/**
* Remove a body from the physics world and stop syncing its mesh.
* @param body - The body returned by {@link addBody} / {@link addGround}
* @returns this for method chaining
*/
removeBody(body: CANNON.Body): this;
/** Build a cannon-es collision shape from a mesh's geometry and scale. */
private shapeFromMesh;
/** * Enable interactive object picking with mouse events
* @param onClickCallback - Callback when object is clicked
* @param onContextMenuCallback - Callback when object is right-clicked
* @returns this for method chaining
*/
enablePicking(onClickCallback?: (object: THREE.Object3D) => void, onHoverCallback?: (object: THREE.Object3D | null) => void, onContextMenuCallback?: (object: THREE.Object3D) => void): this;
/**
* Disable interactive object picking
* @returns this for method chaining
*/
disablePicking(): this;
/**
* Get currently hovered/selected object
* @returns The selected object or null
*/
getSelectedObject(): THREE.Object3D | null;
/**
* Pick object at specific mouse position
* @param mouseX - Normalized X coordinate (-1 to 1)
* @param mouseY - Normalized Y coordinate (-1 to 1)
* @returns PickingResult if object found, null otherwise
*/
pickAt(mouseX: number, mouseY: number): {
object: any;
distance: any;
point: any;
normal: any;
uv: any;
} | null;
/** * Clean up scene resources and stop rendering
* Call this when you're done with the scene to prevent memory leaks
*/
dispose(): void;
}