UNPKG

3d-scene-creator

Version:

A simple utility to create and manage 3D scenes

250 lines (249 loc) 10.8 kB
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; }