From e7dbb069dcf42dd971c02108aba62af004291fac Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Wed, 11 Oct 2023 23:01:52 +0200 Subject: [PATCH 01/12] Set spatial position when autoplay is set. --- howler-audio-source.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/howler-audio-source.js b/howler-audio-source.js index 0131abe..c81c730 100644 --- a/howler-audio-source.js +++ b/howler-audio-source.js @@ -28,27 +28,30 @@ export class HowlerAudioSource extends Component { }; start() { + this.lastPlayedAudioId = null; + this.origin = new Float32Array(3); + this.lastOrigin = new Float32Array(3); + this.audio = new Howl({ src: [this.src], loop: this.loop, volume: this.volume, - autoplay: this.autoplay, + autoplay: false, + onload:()=>{ + if (this.spatial) { + this.object.getPositionWorld(this.origin); + } + if (this.autoplay) { + this.play(); + } + } }); - - this.lastPlayedAudioId = null; - this.origin = new Float32Array(3); - this.lastOrigin = new Float32Array(3); - - if (this.spatial && this.autoplay) { - this.updatePosition(); - this.play(); - } } update() { if (!this.spatial || !this.lastPlayedAudioId) return; - this.object.getTranslationWorld(this.origin); + this.object.getPositionWorld(this.origin); /* Only call pos() if the position moved more than half a centimeter * otherwise this gets very performance heavy. * Smaller movement should only be perceivable if close to the From e64703db930b017e265cd9432c2ea7e5b2d87960 Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Thu, 8 Feb 2024 17:17:20 +0100 Subject: [PATCH 02/12] First orbital camera --- index.js | 1 + orbital-camera.ts | 174 ++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 2 +- utils/utils.ts | 4 ++ 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 orbital-camera.ts diff --git a/index.js b/index.js index b5429e5..0b719f7 100644 --- a/index.js +++ b/index.js @@ -23,3 +23,4 @@ export * from './plane-detection.js'; export * from './vrm.js'; export * from './wasd-controls.js'; export * from './input-profile.js'; +export * from './orbital-camera.js'; diff --git a/orbital-camera.ts b/orbital-camera.ts new file mode 100644 index 0000000..24e899c --- /dev/null +++ b/orbital-camera.ts @@ -0,0 +1,174 @@ +import {Component} from '@wonderlandengine/api'; +import {property} from '@wonderlandengine/api/decorators.js'; +import {deg2rad} from './utils/utils.js'; + +const preventDefault = (e: Event) => { + e.preventDefault(); +}; + +export class OrbitalCamera extends Component { + static TypeName = 'orbital-camera'; + + @property.int() + mouseButtonIndex = 0; + + @property.float(5) + radial = 5; + + @property.float() + minElevation = 0.0; + + @property.float(89.99) + maxElevation = 89.99; + + @property.float() + minZoom = 0.01; + + @property.float(10.0) + maxZoom = 10.0; + + @property.float(0.5) + xSensitivity = 0.5; + + @property.float(0.5) + ySensitivity = 0.5; + + @property.float(0.02) + zoomSensitivity = 0.02; + + private mouseDown: boolean = false; + private origin = [0, 0, 0]; + private azimuth = 0; + private polar = 45; + + private touchStartX: number = 0; + private touchStartY: number = 0; + + start(): void { + this.object.getPositionWorld(this.origin); + this.updateCamera(); + } + + onActivate(): void { + document.addEventListener('mousemove', this.onMouseMove); + + const canvas = this.engine.canvas; + + if (this.mouseButtonIndex === 2) { + canvas.addEventListener('contextmenu', preventDefault, false); + } + canvas.addEventListener('mousedown', this.onMouseDown); + canvas.addEventListener('mouseup', this.onMouseUp); + canvas.addEventListener('wheel', this.onMouseScroll); + + + canvas.addEventListener('touchstart', this.onTouchStart, {passive: false}); + canvas.addEventListener('touchmove', this.onTouchMove, {passive: false}); + canvas.addEventListener('touchend', this.onTouchEnd); + } + + onDeactivate(): void { + document.removeEventListener('mousemove', this.onMouseMove); + const canvas = this.engine.canvas; + + if (this.mouseButtonIndex === 2) { + canvas.removeEventListener('contextmenu', preventDefault, false); + } + canvas.removeEventListener('mousedown', this.onMouseDown); + canvas.removeEventListener('mouseup', this.onMouseUp); + canvas.removeEventListener('wheel', this.onMouseScroll); + + canvas.removeEventListener('touchstart', this.onTouchStart); + canvas.removeEventListener('touchmove', this.onTouchMove); + canvas.removeEventListener('touchend', this.onTouchEnd); + } + + private updateCamera() { + this.object.setPositionWorld([ + this.radial * Math.sin(deg2rad(this.azimuth)) * Math.cos(deg2rad(this.polar)), + this.radial * Math.sin(deg2rad(this.polar)), + this.radial * Math.cos(deg2rad(this.azimuth)) * Math.cos(deg2rad(this.polar)), + ]); + this.object.translateWorld(this.origin); + this.object.lookAt(this.origin); + } + + // Mouse Event Handlers + + private onMouseDown = (e: MouseEvent) => { + if (e.button === this.mouseButtonIndex) { + this.mouseDown = true; + document.body.style.cursor = 'grabbing'; + if (e.button === 1) { + e.preventDefault(); // to prevent scrolling + return false; + } + } + }; + + private onMouseUp = (e: MouseEvent) => { + if (e.button === this.mouseButtonIndex) { + this.mouseDown = false; + document.body.style.cursor = 'initial'; + } + }; + + private onMouseMove = (e: MouseEvent) => { + if (this.active && this.mouseDown) { + this.azimuth += -(e.movementX * this.xSensitivity); + this.polar += e.movementY * this.ySensitivity; + this.polar = Math.min( + this.maxElevation, + Math.max(this.minElevation, this.polar) + ); + this.updateCamera(); + } + }; + + private onMouseScroll = (e: WheelEvent) => { + this.radial *= 1 - (e.deltaY * this.zoomSensitivity * -0.001); + this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); + + this.updateCamera(); + } + + // Touch event handlers + + private onTouchStart = (e: TouchEvent) => { + if (e.touches.length === 1) { + e.preventDefault(); // to prevent scrolling and allow us to track touch movement + + this.touchStartX = e.touches[0].clientX; + this.touchStartY = e.touches[0].clientY; + this.mouseDown = true; // Treat touch like mouse down + } + }; + + private onTouchMove = (e: TouchEvent) => { + if (this.active && this.mouseDown && e.touches.length === 1) { + const deltaX = e.touches[0].clientX - this.touchStartX; + const deltaY = e.touches[0].clientY - this.touchStartY; + + // Apply the deltas as you would in onMouseMove + this.azimuth += -(deltaX * this.xSensitivity); + this.polar += deltaY * this.ySensitivity; + this.polar = Math.min( + this.maxElevation, + Math.max(this.minElevation, this.polar) + ); + + this.updateCamera(); + + // Update the start positions for the next touch move + this.touchStartX = e.touches[0].clientX; + this.touchStartY = e.touches[0].clientY; + } + }; + + private onTouchEnd = (e: TouchEvent) => { + if (e.touches.length === 0) { + // Treat touch end like mouse up + this.mouseDown = false; + } + }; +} diff --git a/tsconfig.json b/tsconfig.json index 3899aab..ddb91c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2022", - "module": "ES6", + "module": "NodeNext", "moduleResolution": "nodenext", "outDir": "dist", "declaration": true, diff --git a/utils/utils.ts b/utils/utils.ts index 3bf25cb..47a9ff5 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -53,3 +53,7 @@ export function setFirstMaterialTexture( } return false; } + +export function deg2rad(value:number){ + return value * Math.PI / 180.0; +} \ No newline at end of file From a019ca667b9b9115d3e37e42fa8c707353a3ee5f Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Thu, 8 Feb 2024 17:32:39 +0100 Subject: [PATCH 03/12] Added pinch zoom --- orbital-camera.ts | 93 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index 24e899c..81df5c3 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -50,17 +50,15 @@ export class OrbitalCamera extends Component { } onActivate(): void { - document.addEventListener('mousemove', this.onMouseMove); - const canvas = this.engine.canvas; + canvas.addEventListener('mousemove', this.onMouseMove); if (this.mouseButtonIndex === 2) { canvas.addEventListener('contextmenu', preventDefault, false); } canvas.addEventListener('mousedown', this.onMouseDown); canvas.addEventListener('mouseup', this.onMouseUp); canvas.addEventListener('wheel', this.onMouseScroll); - canvas.addEventListener('touchstart', this.onTouchStart, {passive: false}); canvas.addEventListener('touchmove', this.onTouchMove, {passive: false}); @@ -68,9 +66,9 @@ export class OrbitalCamera extends Component { } onDeactivate(): void { - document.removeEventListener('mousemove', this.onMouseMove); const canvas = this.engine.canvas; + canvas.removeEventListener('mousemove', this.onMouseMove); if (this.mouseButtonIndex === 2) { canvas.removeEventListener('contextmenu', preventDefault, false); } @@ -81,6 +79,14 @@ export class OrbitalCamera extends Component { canvas.removeEventListener('touchstart', this.onTouchStart); canvas.removeEventListener('touchmove', this.onTouchMove); canvas.removeEventListener('touchend', this.onTouchEnd); + + this.mouseDown = false; + this.initialPinchDistance = 0; + // Reset interaction states + this.mouseDown = false; + this.initialPinchDistance = 0; + this.touchStartX = 0; + this.touchStartY = 0; } private updateCamera() { @@ -100,7 +106,7 @@ export class OrbitalCamera extends Component { this.mouseDown = true; document.body.style.cursor = 'grabbing'; if (e.button === 1) { - e.preventDefault(); // to prevent scrolling + e.preventDefault(); // to prevent scrolling return false; } } @@ -126,14 +132,17 @@ export class OrbitalCamera extends Component { }; private onMouseScroll = (e: WheelEvent) => { - this.radial *= 1 - (e.deltaY * this.zoomSensitivity * -0.001); + this.radial *= 1 - e.deltaY * this.zoomSensitivity * -0.001; this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); - + this.updateCamera(); - } + }; // Touch event handlers + // Add a property to keep track of the initial pinch distance + private initialPinchDistance: number = 0; + private onTouchStart = (e: TouchEvent) => { if (e.touches.length === 1) { e.preventDefault(); // to prevent scrolling and allow us to track touch movement @@ -141,34 +150,62 @@ export class OrbitalCamera extends Component { this.touchStartX = e.touches[0].clientX; this.touchStartY = e.touches[0].clientY; this.mouseDown = true; // Treat touch like mouse down + } else if (e.touches.length === 2) { + // Calculate initial pinch distance + this.initialPinchDistance = this.getDistanceBetweenTouches(e.touches); + e.preventDefault(); // Prevent default pinch actions } }; private onTouchMove = (e: TouchEvent) => { - if (this.active && this.mouseDown && e.touches.length === 1) { - const deltaX = e.touches[0].clientX - this.touchStartX; - const deltaY = e.touches[0].clientY - this.touchStartY; - - // Apply the deltas as you would in onMouseMove - this.azimuth += -(deltaX * this.xSensitivity); - this.polar += deltaY * this.ySensitivity; - this.polar = Math.min( - this.maxElevation, - Math.max(this.minElevation, this.polar) - ); - - this.updateCamera(); - - // Update the start positions for the next touch move - this.touchStartX = e.touches[0].clientX; - this.touchStartY = e.touches[0].clientY; + if (this.active && this.mouseDown) { + if (e.touches.length === 1) { + // Handle rotation + const deltaX = e.touches[0].clientX - this.touchStartX; + const deltaY = e.touches[0].clientY - this.touchStartY; + + this.azimuth += -(deltaX * this.xSensitivity); + this.polar += deltaY * this.ySensitivity; + this.polar = Math.min( + this.maxElevation, + Math.max(this.minElevation, this.polar) + ); + + this.updateCamera(); + + this.touchStartX = e.touches[0].clientX; + this.touchStartY = e.touches[0].clientY; + } else if (e.touches.length === 2) { + // Handle pinch zoom + const currentPinchDistance = this.getDistanceBetweenTouches(e.touches); + const pinchScale = this.initialPinchDistance / currentPinchDistance; + + this.radial *= pinchScale; + this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); + + this.updateCamera(); + + // Update initial pinch distance for next move + this.initialPinchDistance = currentPinchDistance; + } } }; private onTouchEnd = (e: TouchEvent) => { - if (e.touches.length === 0) { - // Treat touch end like mouse up - this.mouseDown = false; + if (e.touches.length < 2) { + this.mouseDown = false; // Treat touch end like mouse up + } + if (e.touches.length === 1) { + // Prepare for possible single touch movement + this.touchStartX = e.touches[0].clientX; + this.touchStartY = e.touches[0].clientY; } }; + + // Helper function to calculate the distance between two touch points + private getDistanceBetweenTouches(touches: TouchList): number { + const dx = touches[0].clientX - touches[1].clientX; + const dy = touches[0].clientY - touches[1].clientY; + return Math.sqrt(dx * dx + dy * dy); + } } From ab965b23b10dd16f7962c920a71451ec8f91b3b4 Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Thu, 8 Feb 2024 17:42:45 +0100 Subject: [PATCH 04/12] Add inertia to orbital camera --- orbital-camera.ts | 59 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index 81df5c3..9eaa2f3 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -36,6 +36,9 @@ export class OrbitalCamera extends Component { @property.float(0.02) zoomSensitivity = 0.02; + @property.float(0.9) + decelerationFactor = 0.9; + private mouseDown: boolean = false; private origin = [0, 0, 0]; private azimuth = 0; @@ -44,6 +47,9 @@ export class OrbitalCamera extends Component { private touchStartX: number = 0; private touchStartY: number = 0; + private azimuthSpeed: number = 0; + private polarSpeed: number = 0; + start(): void { this.object.getPositionWorld(this.origin); this.updateCamera(); @@ -89,6 +95,30 @@ export class OrbitalCamera extends Component { this.touchStartY = 0; } + update(): void { + if (!this.mouseDown) { + // Apply deceleration only when the user is not actively dragging + this.azimuthSpeed *= this.decelerationFactor; + this.polarSpeed *= this.decelerationFactor; + + // Stop completely if the speed is very low to avoid endless tiny movements + if (Math.abs(this.azimuthSpeed) < 0.01) this.azimuthSpeed = 0; + if (Math.abs(this.polarSpeed) < 0.01) this.polarSpeed = 0; + } + + // Apply the speed to the camera angles + this.azimuth += this.azimuthSpeed; + this.polar += this.polarSpeed; + + // Clamp the polar angle + this.polar = Math.min(this.maxElevation, Math.max(this.minElevation, this.polar)); + + // Update the camera if there's any speed + if (this.azimuthSpeed !== 0 || this.polarSpeed !== 0) { + this.updateCamera(); + } + } + private updateCamera() { this.object.setPositionWorld([ this.radial * Math.sin(deg2rad(this.azimuth)) * Math.cos(deg2rad(this.polar)), @@ -121,13 +151,17 @@ export class OrbitalCamera extends Component { private onMouseMove = (e: MouseEvent) => { if (this.active && this.mouseDown) { - this.azimuth += -(e.movementX * this.xSensitivity); - this.polar += e.movementY * this.ySensitivity; - this.polar = Math.min( - this.maxElevation, - Math.max(this.minElevation, this.polar) - ); - this.updateCamera(); + if (this.active && this.mouseDown) { + this.azimuthSpeed = -(e.movementX * this.xSensitivity); + this.polarSpeed = e.movementY * this.ySensitivity; + } + // this.azimuth += -(e.movementX * this.xSensitivity); + // this.polar += e.movementY * this.ySensitivity; + // this.polar = Math.min( + // this.maxElevation, + // Math.max(this.minElevation, this.polar) + // ); + // this.updateCamera(); } }; @@ -160,18 +194,11 @@ export class OrbitalCamera extends Component { private onTouchMove = (e: TouchEvent) => { if (this.active && this.mouseDown) { if (e.touches.length === 1) { - // Handle rotation const deltaX = e.touches[0].clientX - this.touchStartX; const deltaY = e.touches[0].clientY - this.touchStartY; - this.azimuth += -(deltaX * this.xSensitivity); - this.polar += deltaY * this.ySensitivity; - this.polar = Math.min( - this.maxElevation, - Math.max(this.minElevation, this.polar) - ); - - this.updateCamera(); + this.azimuthSpeed = -(deltaX * this.xSensitivity); + this.polarSpeed = deltaY * this.ySensitivity; this.touchStartX = e.touches[0].clientX; this.touchStartY = e.touches[0].clientY; From f901ef6c069919d3272efe5f338af730611708c7 Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Thu, 8 Feb 2024 17:47:49 +0100 Subject: [PATCH 05/12] Reset the speeds. --- orbital-camera.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index 9eaa2f3..5737be4 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -86,13 +86,17 @@ export class OrbitalCamera extends Component { canvas.removeEventListener('touchmove', this.onTouchMove); canvas.removeEventListener('touchend', this.onTouchEnd); + // Reset state to make sure nothing gets stuck this.mouseDown = false; this.initialPinchDistance = 0; - // Reset interaction states + this.mouseDown = false; this.initialPinchDistance = 0; this.touchStartX = 0; this.touchStartY = 0; + + this.azimuthSpeed = 0; + this.polarSpeed = 0; } update(): void { From 12fa18b7240349a6ed74974a8c2987d01f13876e Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Thu, 8 Feb 2024 17:52:17 +0100 Subject: [PATCH 06/12] Revert "Set spatial position when autoplay is set." This reverts commit e7dbb069dcf42dd971c02108aba62af004291fac. --- howler-audio-source.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/howler-audio-source.js b/howler-audio-source.js index c81c730..0131abe 100644 --- a/howler-audio-source.js +++ b/howler-audio-source.js @@ -28,30 +28,27 @@ export class HowlerAudioSource extends Component { }; start() { - this.lastPlayedAudioId = null; - this.origin = new Float32Array(3); - this.lastOrigin = new Float32Array(3); - this.audio = new Howl({ src: [this.src], loop: this.loop, volume: this.volume, - autoplay: false, - onload:()=>{ - if (this.spatial) { - this.object.getPositionWorld(this.origin); - } - if (this.autoplay) { - this.play(); - } - } + autoplay: this.autoplay, }); + + this.lastPlayedAudioId = null; + this.origin = new Float32Array(3); + this.lastOrigin = new Float32Array(3); + + if (this.spatial && this.autoplay) { + this.updatePosition(); + this.play(); + } } update() { if (!this.spatial || !this.lastPlayedAudioId) return; - this.object.getPositionWorld(this.origin); + this.object.getTranslationWorld(this.origin); /* Only call pos() if the position moved more than half a centimeter * otherwise this gets very performance heavy. * Smaller movement should only be perceivable if close to the From d32bb87dbf5785a06ffa2de321addf73111a60a2 Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Thu, 8 Feb 2024 17:54:20 +0100 Subject: [PATCH 07/12] Cleanup --- orbital-camera.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index 5737be4..fe07a22 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -44,6 +44,7 @@ export class OrbitalCamera extends Component { private azimuth = 0; private polar = 45; + private initialPinchDistance: number = 0; private touchStartX: number = 0; private touchStartY: number = 0; @@ -87,9 +88,6 @@ export class OrbitalCamera extends Component { canvas.removeEventListener('touchend', this.onTouchEnd); // Reset state to make sure nothing gets stuck - this.mouseDown = false; - this.initialPinchDistance = 0; - this.mouseDown = false; this.initialPinchDistance = 0; this.touchStartX = 0; @@ -159,13 +157,6 @@ export class OrbitalCamera extends Component { this.azimuthSpeed = -(e.movementX * this.xSensitivity); this.polarSpeed = e.movementY * this.ySensitivity; } - // this.azimuth += -(e.movementX * this.xSensitivity); - // this.polar += e.movementY * this.ySensitivity; - // this.polar = Math.min( - // this.maxElevation, - // Math.max(this.minElevation, this.polar) - // ); - // this.updateCamera(); } }; @@ -178,9 +169,6 @@ export class OrbitalCamera extends Component { // Touch event handlers - // Add a property to keep track of the initial pinch distance - private initialPinchDistance: number = 0; - private onTouchStart = (e: TouchEvent) => { if (e.touches.length === 1) { e.preventDefault(); // to prevent scrolling and allow us to track touch movement From ba6c577908a58b1c11d67145d83e7f852c4ca135 Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Fri, 9 Feb 2024 11:16:54 +0100 Subject: [PATCH 08/12] Change from review, slight improvements, docs and consistency. --- orbital-camera.ts | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index fe07a22..06f044c 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -6,6 +6,15 @@ const preventDefault = (e: Event) => { e.preventDefault(); }; +const tempVec = [0, 0, 0]; + +/** + * OrbitalCamera component allows the user to orbit around a target point, which + * is the position of the object itself. It rotates at the specified distance. + * + * @remarks + * The component works using mouse or touch. Therefor it does not work in VR. + */ export class OrbitalCamera extends Component { static TypeName = 'orbital-camera'; @@ -61,11 +70,11 @@ export class OrbitalCamera extends Component { canvas.addEventListener('mousemove', this.onMouseMove); if (this.mouseButtonIndex === 2) { - canvas.addEventListener('contextmenu', preventDefault, false); + canvas.addEventListener('contextmenu', preventDefault, {passive: false}); } canvas.addEventListener('mousedown', this.onMouseDown); canvas.addEventListener('mouseup', this.onMouseUp); - canvas.addEventListener('wheel', this.onMouseScroll); + canvas.addEventListener('wheel', this.onMouseScroll, {passive: false}); canvas.addEventListener('touchstart', this.onTouchStart, {passive: false}); canvas.addEventListener('touchmove', this.onTouchMove, {passive: false}); @@ -77,7 +86,7 @@ export class OrbitalCamera extends Component { canvas.removeEventListener('mousemove', this.onMouseMove); if (this.mouseButtonIndex === 2) { - canvas.removeEventListener('contextmenu', preventDefault, false); + canvas.removeEventListener('contextmenu', preventDefault); } canvas.removeEventListener('mousedown', this.onMouseDown); canvas.removeEventListener('mouseup', this.onMouseUp); @@ -121,17 +130,24 @@ export class OrbitalCamera extends Component { } } + /** + * Update the camera position based on the current azimuth, + * polar and radial values + */ private updateCamera() { - this.object.setPositionWorld([ - this.radial * Math.sin(deg2rad(this.azimuth)) * Math.cos(deg2rad(this.polar)), - this.radial * Math.sin(deg2rad(this.polar)), - this.radial * Math.cos(deg2rad(this.azimuth)) * Math.cos(deg2rad(this.polar)), - ]); + const azimuthInRadians = deg2rad(this.azimuth); + const polarInRadians = deg2rad(this.polar); + + tempVec[0] = this.radial * Math.sin(azimuthInRadians) * Math.cos(polarInRadians); + tempVec[1] = this.radial * Math.sin(polarInRadians); + tempVec[2] = this.radial * Math.cos(azimuthInRadians) * Math.cos(polarInRadians); + + this.object.setPositionWorld(tempVec); this.object.translateWorld(this.origin); this.object.lookAt(this.origin); } - // Mouse Event Handlers + /* Mouse Event Handlers */ private onMouseDown = (e: MouseEvent) => { if (e.button === this.mouseButtonIndex) { @@ -161,13 +177,15 @@ export class OrbitalCamera extends Component { }; private onMouseScroll = (e: WheelEvent) => { + e.preventDefault(); // to prevent scrolling + this.radial *= 1 - e.deltaY * this.zoomSensitivity * -0.001; this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); this.updateCamera(); }; - // Touch event handlers + /* Touch event handlers */ private onTouchStart = (e: TouchEvent) => { if (e.touches.length === 1) { @@ -185,6 +203,7 @@ export class OrbitalCamera extends Component { private onTouchMove = (e: TouchEvent) => { if (this.active && this.mouseDown) { + e.preventDefault(); // to prevent moving the page if (e.touches.length === 1) { const deltaX = e.touches[0].clientX - this.touchStartX; const deltaY = e.touches[0].clientY - this.touchStartY; @@ -221,7 +240,11 @@ export class OrbitalCamera extends Component { } }; - // Helper function to calculate the distance between two touch points + /** + * Helper function to calculate the distance between two touch points + * @param touches list of touch points + * @returns distance between the two touch points + */ private getDistanceBetweenTouches(touches: TouchList): number { const dx = touches[0].clientX - touches[1].clientX; const dy = touches[0].clientY - touches[1].clientY; From c8824349e56a5b9bbaedf93b666da888bef6e1cd Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Fri, 9 Feb 2024 12:01:43 +0100 Subject: [PATCH 09/12] Add `_` to mark properties and methods private --- orbital-camera.ts | 158 +++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index 06f044c..dce70b0 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -48,85 +48,85 @@ export class OrbitalCamera extends Component { @property.float(0.9) decelerationFactor = 0.9; - private mouseDown: boolean = false; - private origin = [0, 0, 0]; - private azimuth = 0; - private polar = 45; + private _mouseDown: boolean = false; + private _origin = [0, 0, 0]; + private _azimuth = 0; + private _polar = 45; - private initialPinchDistance: number = 0; - private touchStartX: number = 0; - private touchStartY: number = 0; + private _initialPinchDistance: number = 0; + private _touchStartX: number = 0; + private _touchStartY: number = 0; - private azimuthSpeed: number = 0; - private polarSpeed: number = 0; + private _azimuthSpeed: number = 0; + private _polarSpeed: number = 0; start(): void { - this.object.getPositionWorld(this.origin); - this.updateCamera(); + this.object.getPositionWorld(this._origin); + this._updateCamera(); } onActivate(): void { const canvas = this.engine.canvas; - canvas.addEventListener('mousemove', this.onMouseMove); + canvas.addEventListener('mousemove', this._onMouseMove); if (this.mouseButtonIndex === 2) { canvas.addEventListener('contextmenu', preventDefault, {passive: false}); } - canvas.addEventListener('mousedown', this.onMouseDown); - canvas.addEventListener('mouseup', this.onMouseUp); - canvas.addEventListener('wheel', this.onMouseScroll, {passive: false}); + canvas.addEventListener('mousedown', this._onMouseDown); + canvas.addEventListener('mouseup', this._onMouseUp); + canvas.addEventListener('wheel', this._onMouseScroll, {passive: false}); - canvas.addEventListener('touchstart', this.onTouchStart, {passive: false}); - canvas.addEventListener('touchmove', this.onTouchMove, {passive: false}); - canvas.addEventListener('touchend', this.onTouchEnd); + canvas.addEventListener('touchstart', this._onTouchStart, {passive: false}); + canvas.addEventListener('touchmove', this._onTouchMove, {passive: false}); + canvas.addEventListener('touchend', this._onTouchEnd); } onDeactivate(): void { const canvas = this.engine.canvas; - canvas.removeEventListener('mousemove', this.onMouseMove); + canvas.removeEventListener('mousemove', this._onMouseMove); if (this.mouseButtonIndex === 2) { canvas.removeEventListener('contextmenu', preventDefault); } - canvas.removeEventListener('mousedown', this.onMouseDown); - canvas.removeEventListener('mouseup', this.onMouseUp); - canvas.removeEventListener('wheel', this.onMouseScroll); + canvas.removeEventListener('mousedown', this._onMouseDown); + canvas.removeEventListener('mouseup', this._onMouseUp); + canvas.removeEventListener('wheel', this._onMouseScroll); - canvas.removeEventListener('touchstart', this.onTouchStart); - canvas.removeEventListener('touchmove', this.onTouchMove); - canvas.removeEventListener('touchend', this.onTouchEnd); + canvas.removeEventListener('touchstart', this._onTouchStart); + canvas.removeEventListener('touchmove', this._onTouchMove); + canvas.removeEventListener('touchend', this._onTouchEnd); // Reset state to make sure nothing gets stuck - this.mouseDown = false; - this.initialPinchDistance = 0; - this.touchStartX = 0; - this.touchStartY = 0; + this._mouseDown = false; + this._initialPinchDistance = 0; + this._touchStartX = 0; + this._touchStartY = 0; - this.azimuthSpeed = 0; - this.polarSpeed = 0; + this._azimuthSpeed = 0; + this._polarSpeed = 0; } update(): void { - if (!this.mouseDown) { + if (!this._mouseDown) { // Apply deceleration only when the user is not actively dragging - this.azimuthSpeed *= this.decelerationFactor; - this.polarSpeed *= this.decelerationFactor; + this._azimuthSpeed *= this.decelerationFactor; + this._polarSpeed *= this.decelerationFactor; // Stop completely if the speed is very low to avoid endless tiny movements - if (Math.abs(this.azimuthSpeed) < 0.01) this.azimuthSpeed = 0; - if (Math.abs(this.polarSpeed) < 0.01) this.polarSpeed = 0; + if (Math.abs(this._azimuthSpeed) < 0.01) this._azimuthSpeed = 0; + if (Math.abs(this._polarSpeed) < 0.01) this._polarSpeed = 0; } // Apply the speed to the camera angles - this.azimuth += this.azimuthSpeed; - this.polar += this.polarSpeed; + this._azimuth += this._azimuthSpeed; + this._polar += this._polarSpeed; // Clamp the polar angle - this.polar = Math.min(this.maxElevation, Math.max(this.minElevation, this.polar)); + this._polar = Math.min(this.maxElevation, Math.max(this.minElevation, this._polar)); // Update the camera if there's any speed - if (this.azimuthSpeed !== 0 || this.polarSpeed !== 0) { - this.updateCamera(); + if (this._azimuthSpeed !== 0 || this._polarSpeed !== 0) { + this._updateCamera(); } } @@ -134,24 +134,24 @@ export class OrbitalCamera extends Component { * Update the camera position based on the current azimuth, * polar and radial values */ - private updateCamera() { - const azimuthInRadians = deg2rad(this.azimuth); - const polarInRadians = deg2rad(this.polar); + private _updateCamera() { + const azimuthInRadians = deg2rad(this._azimuth); + const polarInRadians = deg2rad(this._polar); tempVec[0] = this.radial * Math.sin(azimuthInRadians) * Math.cos(polarInRadians); tempVec[1] = this.radial * Math.sin(polarInRadians); tempVec[2] = this.radial * Math.cos(azimuthInRadians) * Math.cos(polarInRadians); this.object.setPositionWorld(tempVec); - this.object.translateWorld(this.origin); - this.object.lookAt(this.origin); + this.object.translateWorld(this._origin); + this.object.lookAt(this._origin); } /* Mouse Event Handlers */ - private onMouseDown = (e: MouseEvent) => { + private _onMouseDown = (e: MouseEvent) => { if (e.button === this.mouseButtonIndex) { - this.mouseDown = true; + this._mouseDown = true; document.body.style.cursor = 'grabbing'; if (e.button === 1) { e.preventDefault(); // to prevent scrolling @@ -160,83 +160,83 @@ export class OrbitalCamera extends Component { } }; - private onMouseUp = (e: MouseEvent) => { + private _onMouseUp = (e: MouseEvent) => { if (e.button === this.mouseButtonIndex) { - this.mouseDown = false; + this._mouseDown = false; document.body.style.cursor = 'initial'; } }; - private onMouseMove = (e: MouseEvent) => { - if (this.active && this.mouseDown) { - if (this.active && this.mouseDown) { - this.azimuthSpeed = -(e.movementX * this.xSensitivity); - this.polarSpeed = e.movementY * this.ySensitivity; + private _onMouseMove = (e: MouseEvent) => { + if (this.active && this._mouseDown) { + if (this.active && this._mouseDown) { + this._azimuthSpeed = -(e.movementX * this.xSensitivity); + this._polarSpeed = e.movementY * this.ySensitivity; } } }; - private onMouseScroll = (e: WheelEvent) => { + private _onMouseScroll = (e: WheelEvent) => { e.preventDefault(); // to prevent scrolling this.radial *= 1 - e.deltaY * this.zoomSensitivity * -0.001; this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); - this.updateCamera(); + this._updateCamera(); }; /* Touch event handlers */ - private onTouchStart = (e: TouchEvent) => { + private _onTouchStart = (e: TouchEvent) => { if (e.touches.length === 1) { e.preventDefault(); // to prevent scrolling and allow us to track touch movement - this.touchStartX = e.touches[0].clientX; - this.touchStartY = e.touches[0].clientY; - this.mouseDown = true; // Treat touch like mouse down + this._touchStartX = e.touches[0].clientX; + this._touchStartY = e.touches[0].clientY; + this._mouseDown = true; // Treat touch like mouse down } else if (e.touches.length === 2) { // Calculate initial pinch distance - this.initialPinchDistance = this.getDistanceBetweenTouches(e.touches); + this._initialPinchDistance = this._getDistanceBetweenTouches(e.touches); e.preventDefault(); // Prevent default pinch actions } }; - private onTouchMove = (e: TouchEvent) => { - if (this.active && this.mouseDown) { + private _onTouchMove = (e: TouchEvent) => { + if (this.active && this._mouseDown) { e.preventDefault(); // to prevent moving the page if (e.touches.length === 1) { - const deltaX = e.touches[0].clientX - this.touchStartX; - const deltaY = e.touches[0].clientY - this.touchStartY; + const deltaX = e.touches[0].clientX - this._touchStartX; + const deltaY = e.touches[0].clientY - this._touchStartY; - this.azimuthSpeed = -(deltaX * this.xSensitivity); - this.polarSpeed = deltaY * this.ySensitivity; + this._azimuthSpeed = -(deltaX * this.xSensitivity); + this._polarSpeed = deltaY * this.ySensitivity; - this.touchStartX = e.touches[0].clientX; - this.touchStartY = e.touches[0].clientY; + this._touchStartX = e.touches[0].clientX; + this._touchStartY = e.touches[0].clientY; } else if (e.touches.length === 2) { // Handle pinch zoom - const currentPinchDistance = this.getDistanceBetweenTouches(e.touches); - const pinchScale = this.initialPinchDistance / currentPinchDistance; + const currentPinchDistance = this._getDistanceBetweenTouches(e.touches); + const pinchScale = this._initialPinchDistance / currentPinchDistance; this.radial *= pinchScale; this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); - this.updateCamera(); + this._updateCamera(); // Update initial pinch distance for next move - this.initialPinchDistance = currentPinchDistance; + this._initialPinchDistance = currentPinchDistance; } } }; - private onTouchEnd = (e: TouchEvent) => { + private _onTouchEnd = (e: TouchEvent) => { if (e.touches.length < 2) { - this.mouseDown = false; // Treat touch end like mouse up + this._mouseDown = false; // Treat touch end like mouse up } if (e.touches.length === 1) { // Prepare for possible single touch movement - this.touchStartX = e.touches[0].clientX; - this.touchStartY = e.touches[0].clientY; + this._touchStartX = e.touches[0].clientX; + this._touchStartY = e.touches[0].clientY; } }; @@ -245,7 +245,7 @@ export class OrbitalCamera extends Component { * @param touches list of touch points * @returns distance between the two touch points */ - private getDistanceBetweenTouches(touches: TouchList): number { + private _getDistanceBetweenTouches(touches: TouchList): number { const dx = touches[0].clientX - touches[1].clientX; const dy = touches[0].clientY - touches[1].clientY; return Math.sqrt(dx * dx + dy * dy); From 84eb81fbc7cf333170ea20e2af54684ff51029fd Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Fri, 9 Feb 2024 12:08:58 +0100 Subject: [PATCH 10/12] Rename deceleration to damping --- orbital-camera.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index dce70b0..71078f6 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -46,7 +46,7 @@ export class OrbitalCamera extends Component { zoomSensitivity = 0.02; @property.float(0.9) - decelerationFactor = 0.9; + damping = 0.9; private _mouseDown: boolean = false; private _origin = [0, 0, 0]; @@ -109,8 +109,8 @@ export class OrbitalCamera extends Component { update(): void { if (!this._mouseDown) { // Apply deceleration only when the user is not actively dragging - this._azimuthSpeed *= this.decelerationFactor; - this._polarSpeed *= this.decelerationFactor; + this._azimuthSpeed *= this.damping; + this._polarSpeed *= this.damping; // Stop completely if the speed is very low to avoid endless tiny movements if (Math.abs(this._azimuthSpeed) < 0.01) this._azimuthSpeed = 0; From 458d2537098bfe134d489788096a656045f3c5d0 Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Fri, 9 Feb 2024 12:17:03 +0100 Subject: [PATCH 11/12] Early return --- orbital-camera.ts | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index 71078f6..56be9ed 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -11,7 +11,7 @@ const tempVec = [0, 0, 0]; /** * OrbitalCamera component allows the user to orbit around a target point, which * is the position of the object itself. It rotates at the specified distance. - * + * * @remarks * The component works using mouse or touch. Therefor it does not work in VR. */ @@ -202,30 +202,31 @@ export class OrbitalCamera extends Component { }; private _onTouchMove = (e: TouchEvent) => { - if (this.active && this._mouseDown) { - e.preventDefault(); // to prevent moving the page - if (e.touches.length === 1) { - const deltaX = e.touches[0].clientX - this._touchStartX; - const deltaY = e.touches[0].clientY - this._touchStartY; + if (!this.active || !this._mouseDown) { + return; + } + e.preventDefault(); // to prevent moving the page + if (e.touches.length === 1) { + const deltaX = e.touches[0].clientX - this._touchStartX; + const deltaY = e.touches[0].clientY - this._touchStartY; - this._azimuthSpeed = -(deltaX * this.xSensitivity); - this._polarSpeed = deltaY * this.ySensitivity; + this._azimuthSpeed = -(deltaX * this.xSensitivity); + this._polarSpeed = deltaY * this.ySensitivity; - this._touchStartX = e.touches[0].clientX; - this._touchStartY = e.touches[0].clientY; - } else if (e.touches.length === 2) { - // Handle pinch zoom - const currentPinchDistance = this._getDistanceBetweenTouches(e.touches); - const pinchScale = this._initialPinchDistance / currentPinchDistance; + this._touchStartX = e.touches[0].clientX; + this._touchStartY = e.touches[0].clientY; + } else if (e.touches.length === 2) { + // Handle pinch zoom + const currentPinchDistance = this._getDistanceBetweenTouches(e.touches); + const pinchScale = this._initialPinchDistance / currentPinchDistance; - this.radial *= pinchScale; - this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); + this.radial *= pinchScale; + this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); - this._updateCamera(); + this._updateCamera(); - // Update initial pinch distance for next move - this._initialPinchDistance = currentPinchDistance; - } + // Update initial pinch distance for next move + this._initialPinchDistance = currentPinchDistance; } }; From da29f93578e41353e7f492b95523381e082ce7bc Mon Sep 17 00:00:00 2001 From: Timmy Kokke Date: Fri, 9 Feb 2024 12:22:19 +0100 Subject: [PATCH 12/12] Change comment style --- orbital-camera.ts | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/orbital-camera.ts b/orbital-camera.ts index 56be9ed..8ca4253 100644 --- a/orbital-camera.ts +++ b/orbital-camera.ts @@ -96,7 +96,7 @@ export class OrbitalCamera extends Component { canvas.removeEventListener('touchmove', this._onTouchMove); canvas.removeEventListener('touchend', this._onTouchEnd); - // Reset state to make sure nothing gets stuck + /** Reset state to make sure nothing gets stuck */ this._mouseDown = false; this._initialPinchDistance = 0; this._touchStartX = 0; @@ -108,23 +108,23 @@ export class OrbitalCamera extends Component { update(): void { if (!this._mouseDown) { - // Apply deceleration only when the user is not actively dragging + /** Apply deceleration only when the user is not actively dragging */ this._azimuthSpeed *= this.damping; this._polarSpeed *= this.damping; - // Stop completely if the speed is very low to avoid endless tiny movements + /** Stop completely if the speed is very low to avoid endless tiny movements */ if (Math.abs(this._azimuthSpeed) < 0.01) this._azimuthSpeed = 0; if (Math.abs(this._polarSpeed) < 0.01) this._polarSpeed = 0; } - // Apply the speed to the camera angles + /** Apply the speed to the camera angles */ this._azimuth += this._azimuthSpeed; this._polar += this._polarSpeed; - // Clamp the polar angle + /** Clamp the polar angle */ this._polar = Math.min(this.maxElevation, Math.max(this.minElevation, this._polar)); - // Update the camera if there's any speed + /** Update the camera if there's any speed */ if (this._azimuthSpeed !== 0 || this._polarSpeed !== 0) { this._updateCamera(); } @@ -154,7 +154,7 @@ export class OrbitalCamera extends Component { this._mouseDown = true; document.body.style.cursor = 'grabbing'; if (e.button === 1) { - e.preventDefault(); // to prevent scrolling + e.preventDefault(); /** to prevent scrolling */ return false; } } @@ -177,7 +177,7 @@ export class OrbitalCamera extends Component { }; private _onMouseScroll = (e: WheelEvent) => { - e.preventDefault(); // to prevent scrolling + e.preventDefault(); /** to prevent scrolling */ this.radial *= 1 - e.deltaY * this.zoomSensitivity * -0.001; this.radial = Math.min(this.maxZoom, Math.max(this.minZoom, this.radial)); @@ -189,15 +189,16 @@ export class OrbitalCamera extends Component { private _onTouchStart = (e: TouchEvent) => { if (e.touches.length === 1) { - e.preventDefault(); // to prevent scrolling and allow us to track touch movement + /** to prevent scrolling and allow us to track touch movement */ + e.preventDefault(); this._touchStartX = e.touches[0].clientX; this._touchStartY = e.touches[0].clientY; - this._mouseDown = true; // Treat touch like mouse down + this._mouseDown = true; /** Treat touch like mouse down */ } else if (e.touches.length === 2) { - // Calculate initial pinch distance + /** Calculate initial pinch distance */ this._initialPinchDistance = this._getDistanceBetweenTouches(e.touches); - e.preventDefault(); // Prevent default pinch actions + e.preventDefault(); /** Prevent default pinch actions */ } }; @@ -205,7 +206,7 @@ export class OrbitalCamera extends Component { if (!this.active || !this._mouseDown) { return; } - e.preventDefault(); // to prevent moving the page + e.preventDefault(); /** to prevent moving the page */ if (e.touches.length === 1) { const deltaX = e.touches[0].clientX - this._touchStartX; const deltaY = e.touches[0].clientY - this._touchStartY; @@ -216,7 +217,7 @@ export class OrbitalCamera extends Component { this._touchStartX = e.touches[0].clientX; this._touchStartY = e.touches[0].clientY; } else if (e.touches.length === 2) { - // Handle pinch zoom + /** Handle pinch zoom */ const currentPinchDistance = this._getDistanceBetweenTouches(e.touches); const pinchScale = this._initialPinchDistance / currentPinchDistance; @@ -225,17 +226,17 @@ export class OrbitalCamera extends Component { this._updateCamera(); - // Update initial pinch distance for next move + /** Update initial pinch distance for next move */ this._initialPinchDistance = currentPinchDistance; } }; private _onTouchEnd = (e: TouchEvent) => { if (e.touches.length < 2) { - this._mouseDown = false; // Treat touch end like mouse up + this._mouseDown = false; /** Treat touch end like mouse up */ } if (e.touches.length === 1) { - // Prepare for possible single touch movement + /** Prepare for possible single touch movement */ this._touchStartX = e.touches[0].clientX; this._touchStartY = e.touches[0].clientY; }