UNPKG

@shopware-ag/dive

Version:

Shopware Spatial Framework

212 lines (172 loc) 6.23 kB
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import DIVEPerspectiveCamera from '../camera/PerspectiveCamera.ts'; import { DIVERenderer } from '../renderer/Renderer.ts'; import { type Box3, MathUtils, Vector3, Vector3Like } from 'three'; import { Easing } from '@tweenjs/tween.js'; import { type DIVEAnimationSystem } from '../animation/AnimationSystem.ts'; export type DIVEOrbitControlsSettings = { enableDamping: boolean; dampingFactor: number; }; export const DIVEOrbitControlsDefaultSettings: DIVEOrbitControlsSettings = { enableDamping: true, dampingFactor: 0.04, }; /** * Orbit Controls. Basic functionality to orbit around a given target point in the scene. * * @module */ export default class DIVEOrbitControls extends OrbitControls { public static readonly DEFAULT_ZOOM_FACTOR = 1; private _animationSystem: DIVEAnimationSystem; private last: { pos: Vector3Like; target: Vector3Like } | null = null; private animating: boolean = false; private locked: boolean = false; private stopMoveTo: () => void = () => {}; private stopRevertLast: () => void = () => {}; public object: DIVEPerspectiveCamera; public domElement: HTMLCanvasElement; private _removePreRenderCallback: () => void = () => {}; constructor( camera: DIVEPerspectiveCamera, renderer: DIVERenderer, animationSystem: DIVEAnimationSystem, settings: Partial<DIVEOrbitControlsSettings> = DIVEOrbitControlsDefaultSettings, ) { super(camera, renderer.domElement); this._animationSystem = animationSystem; this.domElement = renderer.domElement; this.object = camera; const id = renderer.AddPreRenderCallback(() => { this.preRenderCallback(); }); this._removePreRenderCallback = () => { renderer.RemovePreRenderCallback(id); }; this.enableDamping = settings.enableDamping || DIVEOrbitControlsDefaultSettings.enableDamping; this.dampingFactor = settings.dampingFactor || DIVEOrbitControlsDefaultSettings.dampingFactor; // initialize camera transformation this.object.position.set(0, 2, 2); this.target.copy({ x: 0, y: 0.5, z: 0 }); this.update(); } public Dispose(): void { this._removePreRenderCallback(); this.dispose(); } public ComputeEncompassingView(bb: Box3): { position: Vector3Like; target: Vector3Like; } { const center = bb.getCenter(new Vector3()); const size = bb.getSize(new Vector3()); const distance = Math.max(size.x, size.y, size.z) * 1.25; const direction = this.object.position.clone().normalize(); return { position: direction.multiplyScalar(distance), target: center, }; } public ZoomIn(by?: number): void { const zoomBy = by || DIVEOrbitControls.DEFAULT_ZOOM_FACTOR; const { minDistance, maxDistance } = this; this.minDistance = this.maxDistance = MathUtils.clamp( this.getDistance() - zoomBy, minDistance + zoomBy, maxDistance - zoomBy, ); this.update(); this.minDistance = minDistance; this.maxDistance = maxDistance; } public ZoomOut(by?: number): void { const zoomBy = by || DIVEOrbitControls.DEFAULT_ZOOM_FACTOR; const { minDistance, maxDistance } = this; this.minDistance = this.maxDistance = MathUtils.clamp( this.getDistance() + zoomBy, minDistance + zoomBy, maxDistance - zoomBy, ); this.update(); this.minDistance = minDistance; this.maxDistance = maxDistance; } public MoveTo( pos: Vector3Like | undefined, target: Vector3Like | undefined, duration: number, lock: boolean, ): void { if (this.animating) return; const toPosition = pos || this.object.position.clone(); const toTarget = target || this.target.clone(); this.stopRevertLast(); if (!this.locked) this.last = { pos: this.object.position.clone(), target: this.target.clone(), }; this.animating = duration > 0; this.locked = lock; this.enabled = false; const tweenPos = this._animationSystem .Animate(this.object.position) .to(toPosition, duration) .easing(Easing.Quadratic.Out) .start(); const tweenQuat = this._animationSystem .Animate(this.target) .to(toTarget, duration) .easing(Easing.Quadratic.Out) .onUpdate(() => { this.object.lookAt(this.target); }) .onComplete(() => { this.animating = false; this.enabled = !lock; }) .start(); this.stopMoveTo = () => { tweenPos.stop(); tweenQuat.stop(); }; } public RevertLast(duration: number): void { if (this.animating || !this.locked) return; this.stopMoveTo(); this.animating = duration > 0; this.enabled = false; const { pos, target } = this.last!; const tweenPos = this._animationSystem .Animate(this.object.position) .to(pos, duration) .easing(Easing.Quadratic.Out) .start(); const tweenQuat = this._animationSystem .Animate(this.target) .to(target, duration) .easing(Easing.Quadratic.Out) .onUpdate(() => { this.object.lookAt(this.target); }) .onComplete(() => { this.animating = false; this.locked = false; this.enabled = true; }) .start(); this.stopRevertLast = () => { tweenPos.stop(); tweenQuat.stop(); }; } private preRenderCallback = (): void => { if (this.locked) return; this.update(); }; }