From b52875d68ff865a14dc9f3990a8f4b54a4b077c0 Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Sun, 13 Mar 2022 01:30:50 +0000 Subject: [PATCH] Refactored camera smoothing --- src/camera.ts | 90 +++++++++++++++++++++------------------------------ src/util.ts | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 53 deletions(-) diff --git a/src/camera.ts b/src/camera.ts index 7ba1d38..1d4ba00 100644 --- a/src/camera.ts +++ b/src/camera.ts @@ -1,7 +1,9 @@ import { m4, v3 } from 'twgl.js'; import { MouseManager } from './mouse'; -import { degreesToRadians, clamp } from './math'; +import { degreesToRadians } from './math'; import { Renderer } from './renderer'; +import { SmoothVariable, SmoothVectorVariable } from './util'; +import { Vector3 } from './vector'; export class ArcballCamera { public isUserRotating = false; @@ -10,28 +12,19 @@ export class ArcballCamera { private readonly fov: number; private readonly zNear: number; private readonly zFar: number; - private readonly cameraSmoothing = 0.025; - public aspect: number; - private actualDistance = 18.0; - private actualAzimuth = -1.0; - private actualElevation = 1.3; + + private _distance = new SmoothVariable(18.0, 0.025); + private _azimuth = new SmoothVariable(-1.0, 0.025); + private _elevation = new SmoothVariable(1.3, 0.025); + private _target = new SmoothVectorVariable(new Vector3(0, 0, 0), 0.025); - private targetDistance: number; - private targetAzimuth: number; - private targetElevation: number; - private targetTarget: v3.Vec3; - - private readonly actualTarget: v3.Vec3 = [0, 0, 0]; private readonly up: v3.Vec3 = [0, 1, 0]; private eye: v3.Vec3 = [0, 0, 0]; private mouseSensitivity = 0.005; private scrollSensitivity = 0.005; - private zoomDistMin = 1.0; - private zoomDistMax = 100.0; - private gl: WebGLRenderingContext; private static _instance: ArcballCamera; @@ -47,64 +40,56 @@ export class ArcballCamera { this.gl = Renderer.Get._gl; this.aspect = this.gl.canvas.width / this.gl.canvas.height; - this.targetDistance = this.actualDistance; - this.targetAzimuth = this.actualAzimuth; - this.targetElevation = this.actualElevation; - this.targetTarget = [0, 0, 0]; + this._elevation.setClamp(0.01, Math.PI - 0.01); + this._distance.setClamp(1.0, 100.0); } public updateCamera() { this.aspect = this.gl.canvas.width / this.gl.canvas.height; - // Update target location if user is rotating camera const mouseDelta = MouseManager.Get.getMouseDelta(); if (this.isUserRotating) { - this.targetAzimuth += mouseDelta.dx * this.mouseSensitivity; - this.targetElevation += mouseDelta.dy * this.mouseSensitivity; - - // Prevent the camera going upside-down - const eps = 0.01; - this.targetElevation = Math.max(Math.min(Math.PI - eps, this.targetElevation), eps); + this._azimuth.addToTarget(mouseDelta.dx * this.mouseSensitivity); + this._elevation.addToTarget(mouseDelta.dy * this.mouseSensitivity); } if (this.isUserTranslating) { const my = mouseDelta.dy * this.mouseSensitivity; const mx = mouseDelta.dx * this.mouseSensitivity; // Up-down - const dy = -Math.cos(this.targetElevation - Math.PI/2); - const df = Math.sin(this.targetElevation - Math.PI/2); - this.targetTarget[0] += -Math.sin(this.targetAzimuth - Math.PI/2) * my * df; - this.targetTarget[1] += dy * my; - this.targetTarget[2] += Math.cos(this.targetAzimuth - Math.PI/2) * my * df; + const dy = -Math.cos(this._elevation.getTarget() - Math.PI/2); + const df = Math.sin(this._elevation.getTarget() - Math.PI/2); + this._target.addToTarget(new Vector3( + -Math.sin(this._azimuth.getTarget() - Math.PI/2) * my * df, + dy * my, + Math.cos(this._azimuth.getTarget() - Math.PI/2) * my * df, + )); // Left-right - const dx = Math.sin(this.targetAzimuth); - const dz = -Math.cos(this.targetAzimuth); - this.targetTarget[0] += dx * mx; - this.targetTarget[2] += dz * mx; + const dx = Math.sin(this._azimuth.getTarget()); + const dz = -Math.cos(this._azimuth.getTarget()); + this._target.addToTarget(new Vector3(dx * mx, 0.0, dz * mx)); } // Move camera towards target location - this.actualDistance += (this.targetDistance - this.actualDistance ) * this.cameraSmoothing; - this.actualAzimuth += (this.targetAzimuth - this.actualAzimuth ) * this.cameraSmoothing; - this.actualElevation += (this.targetElevation - this.actualElevation) * this.cameraSmoothing; - - this.actualTarget[0] += (this.targetTarget[0] - this.actualTarget[0]) * this.cameraSmoothing; - this.actualTarget[1] += (this.targetTarget[1] - this.actualTarget[1]) * this.cameraSmoothing; - this.actualTarget[2] += (this.targetTarget[2] - this.actualTarget[2]) * this.cameraSmoothing; + this._distance.tick(); + this._azimuth.tick(); + this._elevation.tick(); + this._target.tick(); + const target = this._target.getActual().toArray(); this.eye = [ - this.actualDistance * Math.cos(this.actualAzimuth) * -Math.sin(this.actualElevation) + this.actualTarget[0], - this.actualDistance * Math.cos(this.actualElevation) + this.actualTarget[1], - this.actualDistance * Math.sin(this.actualAzimuth) * -Math.sin(this.actualElevation) + this.actualTarget[2], + this._distance.getActual() * Math.cos(this._azimuth.getActual()) * -Math.sin(this._elevation.getActual()) + target[0], + this._distance.getActual() * Math.cos(this._elevation.getActual()) + target[1], + this._distance.getActual() * Math.sin(this._azimuth.getActual()) * -Math.sin(this._elevation.getActual()) + target[2], ]; } getCameraPosition(azimuthOffset: number, elevationOffset: number) { - const azimuth = this.actualAzimuth + azimuthOffset; - const elevation = this.actualElevation + elevationOffset; + const azimuth = this._azimuth.getActual() + azimuthOffset; + const elevation = this._elevation.getActual() + elevationOffset; return [ - this.actualDistance * Math.cos(azimuth ) * -Math.sin(elevation), - this.actualDistance * Math.cos(elevation), - this.actualDistance * Math.sin(azimuth) * -Math.sin(elevation), + this._distance.getActual() * Math.cos(azimuth ) * -Math.sin(elevation), + this._distance.getActual() * Math.cos(elevation), + this._distance.getActual() * Math.sin(azimuth) * -Math.sin(elevation), ]; } @@ -122,8 +107,7 @@ export class ArcballCamera { } public onWheelScroll(e: WheelEvent) { - this.targetDistance += e.deltaY * this.scrollSensitivity; - this.targetDistance = clamp(this.targetDistance, this.zoomDistMin, this.zoomDistMax); + this._distance.addToTarget(e.deltaY * this.scrollSensitivity); } public getProjectionMatrix() { @@ -131,7 +115,7 @@ export class ArcballCamera { } public getCameraMatrix() { - return m4.lookAt(this.eye, this.actualTarget, this.up); + return m4.lookAt(this.eye, this._target.getActual().toArray(), this.up); } public getViewMatrix() { diff --git a/src/util.ts b/src/util.ts index 7f42ab4..4e1b463 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,6 @@ import { AppConfig } from './config'; import { Vector3 } from './vector'; +import { clamp } from './math'; const convert = require('color-convert'); @@ -252,3 +253,68 @@ export class Warnable { export function getRandomID(): string { return (Math.random() + 1).toString(36).substring(7); } + +export class SmoothVariable { + private _actual: number; + private _target: number; + private _smoothing: number; + private _min: number; + private _max: number; + + public constructor(value: number, smoothing: number) { + this._actual = value; + this._target = value; + this._smoothing = smoothing; + this._min = -Infinity; + this._max = Infinity; + } + + public setClamp(min: number, max: number) { + this._min = min; + this._max = max; + } + + public addToTarget(delta: number) { + this._target = clamp(this._target + delta, this._min, this._max); + } + + public tick() { + this._actual += (this._target - this._actual) * this._smoothing; + } + + public getActual() { + return this._actual; + } + + public getTarget() { + return this._target; + } +} + +export class SmoothVectorVariable { + private _actual: Vector3; + private _target: Vector3; + private _smoothing: number; + + public constructor(value: Vector3, smoothing: number) { + this._actual = value; + this._target = value; + this._smoothing = smoothing; + } + + public addToTarget(delta: Vector3) { + this._target = Vector3.add(this._target, delta); + } + + public tick() { + this._actual.add(Vector3.sub(this._target, this._actual).mulScalar(this._smoothing)); + } + + public getActual() { + return this._actual; + } + + public getTarget() { + return this._target; + } +}