@shopware-ag/dive
Version:
Shopware Spatial Framework
212 lines (172 loc) • 6.23 kB
text/typescript
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();
};
}