From cc29ec44afb70ea0d0d02167eaf8609cb772f5be Mon Sep 17 00:00:00 2001 From: agargaro Date: Wed, 13 Nov 2024 15:19:54 +0100 Subject: [PATCH 01/11] Fix test --- .github/codeql/codeql-config.yml | 4 +- eslint.config.js | 5 ++- src/core/Main.ts | 5 --- test/utils/Query.test.ts | 66 ++++++++++++++++---------------- 4 files changed, 38 insertions(+), 42 deletions(-) diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index 7136e6e..c40a03c 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -2,4 +2,6 @@ name: "CodeQL config" paths-ignore: - "node_modules" - - "examples" \ No newline at end of file + - "examples" + - "docs" + - "packages" diff --git a/eslint.config.js b/eslint.config.js index 92c88d8..47da483 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,7 +6,7 @@ export default [ files: ['**/*.{js,ts}'], }, { - ignores: ['dist', 'vite.config.js', 'examples', 'test', 'docs', 'packages'], + ignores: ['dist', 'vite.config.js', 'examples', 'docs', 'packages'], }, js.configs.recommended, ...tseslint.configs.strict, @@ -17,7 +17,8 @@ export default [ '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/no-wrapper-object-types': 'off' + '@typescript-eslint/no-wrapper-object-types': 'off', + '@typescript-eslint/no-dynamic-delete': 'off' }, }, ]; diff --git a/src/core/Main.ts b/src/core/Main.ts index f86325a..11c9b31 100644 --- a/src/core/Main.ts +++ b/src/core/Main.ts @@ -10,11 +10,6 @@ import { Stats } from "../utils/Stats.js"; import { RaycasterSortComparer } from "../events/RaycasterManager.js"; import { applyWebGLRendererPatch } from "../patch/WebGLRenderer.js"; -/** @internal */ -export function setup() { - // Script loaded for test. TODO remove this -} - /** * Configuration parameters for initializing the Main class. */ diff --git a/test/utils/Query.test.ts b/test/utils/Query.test.ts index fb8d562..78aa613 100644 --- a/test/utils/Query.test.ts +++ b/test/utils/Query.test.ts @@ -1,8 +1,6 @@ import { Group, Mesh, Scene } from 'three'; import { beforeAll, describe, expect, it } from 'vitest'; -import { setup } from '../../src'; - -setup(); +import { querySelector, querySelectorAll } from '../../src/index.js'; describe('Query standard cases', () => { let scene: Scene; @@ -29,88 +27,88 @@ describe('Query standard cases', () => { }); it('querySelector: no matching name', () => { - const result = scene.querySelector('[name=group_10]'); + const result = querySelector(scene, '[name=group_10]'); expect(result).toBeUndefined(); }); it('querySelector: matching name', () => { - const result = scene.querySelector('[name=group_3]'); + const result = querySelector(scene, '[name=group_3]'); expect(result).toEqual(scene.children[3]); }); it('querySelector: matching contains name', () => { - const result = scene.querySelector('[name*=_2]'); + const result = querySelector(scene, '[name*=_2]'); expect(result).toEqual(scene.children[0].children[2]); }); it('querySelector: matching ends with name', () => { - const result = scene.querySelector('[name$=_3]'); + const result = querySelector(scene, '[name$=_3]'); expect(result).toEqual(scene.children[0].children[3]); }); it('querySelector: matching start with name', () => { - const result = scene.querySelector('[name^=group]'); + const result = querySelector(scene, '[name^=group]'); expect(result).toEqual(scene.children[0]); }); it('querySelector: matching type', () => { - const result = scene.querySelector('Mesh'); + const result = querySelector(scene, 'Mesh'); expect(result).toEqual(scene.children[0].children[0]); }); it('querySelector: matching tags', () => { - const result = scene.querySelector('.first'); + const result = querySelector(scene, '.first'); expect(result).toEqual(scene.children[0]); }); it('querySelector: matching multiple tags', () => { - const result = scene.querySelector('.last.odd'); + const result = querySelector(scene, '.last.odd'); expect(result).toEqual(scene.children[0].children[3]); }); it('querySelector: matching type and tags', () => { - const result = scene.querySelector('Mesh.first'); + const result = querySelector(scene, 'Mesh.first'); expect(result).toEqual(scene.children[0].children[0]); }); it('querySelector: matching type, name and tags', () => { - const result = scene.querySelector('Mesh.even[name*=_2]'); + const result = querySelector(scene, 'Mesh.even[name*=_2]'); expect(result).toEqual(scene.children[0].children[2]); }); it('querySelector: matching type parent and recursive type children', () => { - const result = scene.querySelector('Scene Mesh.odd'); + const result = querySelector(scene, 'Scene Mesh.odd'); expect(result).toEqual(scene.children[0].children[1]); }); it('querySelector: no matching type parent and type children', () => { - const result = scene.querySelector('Scene > Mesh'); + const result = querySelector(scene, 'Scene > Mesh'); expect(result).toBeUndefined(); }); it('querySelector: matching type parent and type children', () => { - const result = scene.querySelector('Group > Mesh'); + const result = querySelector(scene, 'Group > Mesh'); expect(result).toEqual(scene.children[0].children[0]); }); it('querySelector: matching type 1 or type 2', () => { - const result = scene.querySelector('Mesh, Group'); + const result = querySelector(scene, 'Mesh, Group'); expect(result).toEqual(scene.children[0]); }); it('querySelectorAll: no matching name', () => { - const result = scene.querySelectorAll('[name=group_10]'); + const result = querySelectorAll(scene, '[name=group_10]'); expect(result.length).toEqual(0); }); it('querySelectorAll: matching name', () => { - const result = scene.querySelectorAll('[name=group_3]'); + const result = querySelectorAll(scene, '[name=group_3]'); expect(result.length).toEqual(1); expect(result[0]).toEqual(scene.children[3]); }); it('querySelectorAll: matching contains name', () => { - const result = scene.querySelectorAll('[name*=_2]'); + const result = querySelectorAll(scene, '[name*=_2]'); expect(result.length).toEqual(8); expect(result[0]).toEqual(scene.children[0].children[2]); expect(result[1]).toEqual(scene.children[1].children[2]); @@ -121,9 +119,9 @@ describe('Query standard cases', () => { expect(result[6]).toEqual(scene.children[2].children[3]); expect(result[7]).toEqual(scene.children[3].children[2]); }); - + it('querySelectorAll: matching ends with name', () => { - const result = scene.querySelectorAll('[name$=_3]'); + const result = querySelectorAll(scene, '[name$=_3]'); expect(result.length).toEqual(5); expect(result[0]).toEqual(scene.children[0].children[3]); expect(result[1]).toEqual(scene.children[1].children[3]); @@ -133,7 +131,7 @@ describe('Query standard cases', () => { }); it('querySelectorAll: matching start with name', () => { - const result = scene.querySelectorAll('[name^=group]'); + const result = querySelectorAll(scene, '[name^=group]'); expect(result.length).toEqual(4); expect(result[0]).toEqual(scene.children[0]); expect(result[1]).toEqual(scene.children[1]); @@ -142,7 +140,7 @@ describe('Query standard cases', () => { }); it('querySelectorAll: matching type', () => { - const result = scene.querySelectorAll('Mesh'); + const result = querySelectorAll(scene, 'Mesh'); expect(result.length).toEqual(16); expect(result[0]).toEqual(scene.children[0].children[0]); expect(result[1]).toEqual(scene.children[0].children[1]); @@ -163,7 +161,7 @@ describe('Query standard cases', () => { }); it('querySelectorAll: matching tags', () => { - const result = scene.querySelectorAll('.first'); + const result = querySelectorAll(scene, '.first'); expect(result.length).toEqual(5); expect(result[0]).toEqual(scene.children[0]); expect(result[1]).toEqual(scene.children[0].children[0]); @@ -173,7 +171,7 @@ describe('Query standard cases', () => { }); it('querySelectorAll: matching multiple tags', () => { - const result = scene.querySelectorAll('.last.odd'); + const result = querySelectorAll(scene, '.last.odd'); expect(result.length).toEqual(4); expect(result[0]).toEqual(scene.children[0].children[3]); expect(result[1]).toEqual(scene.children[1].children[3]); @@ -182,13 +180,13 @@ describe('Query standard cases', () => { }); it('querySelectorAll: matching type and tags', () => { - const result = scene.querySelectorAll('Mesh.last[name*=_2]'); + const result = querySelectorAll(scene, 'Mesh.last[name*=_2]'); expect(result.length).toEqual(1); expect(result[0]).toEqual(scene.children[2].children[3]); }); it('querySelectorAll: matching type, name and tags', () => { - const result = scene.querySelectorAll('Mesh.odd[name*=_3]'); + const result = querySelectorAll(scene, 'Mesh.odd[name*=_3]'); expect(result.length).toEqual(5); expect(result[0]).toEqual(scene.children[0].children[3]); expect(result[1]).toEqual(scene.children[1].children[3]); @@ -198,7 +196,7 @@ describe('Query standard cases', () => { }); it('querySelectorAll: matching type parent and recursive type children', () => { - const result = scene.querySelectorAll('Scene Mesh.even'); + const result = querySelectorAll(scene, 'Scene Mesh.even'); expect(result.length).toEqual(8); expect(result[0]).toEqual(scene.children[0].children[0]); expect(result[1]).toEqual(scene.children[0].children[2]); @@ -211,12 +209,12 @@ describe('Query standard cases', () => { }); it('querySelectorAll: no matching type parent and type children', () => { - const result = scene.querySelectorAll('Scene > Mesh'); + const result = querySelectorAll(scene, 'Scene > Mesh'); expect(result.length).toBe(0); }); it('querySelectorAll: matching type parent and type children', () => { - const result = scene.querySelectorAll('Group > Mesh.odd'); + const result = querySelectorAll(scene, 'Group > Mesh.odd'); expect(result.length).toEqual(8); expect(result[0]).toEqual(scene.children[0].children[1]); expect(result[1]).toEqual(scene.children[0].children[3]); @@ -229,7 +227,7 @@ describe('Query standard cases', () => { }); it('querySelectorAll: matching type 1 or type 2', () => { - const result = scene.querySelectorAll('Mesh, Group'); + const result = querySelectorAll(scene, 'Mesh, Group'); expect(result.length).toEqual(20); expect(result[0]).toEqual(scene.children[0]); expect(result[1]).toEqual(scene.children[0].children[0]); @@ -276,12 +274,12 @@ describe('Query special cases', () => { }); it('querySelector: special case', () => { - const result = scene.querySelector('.x > .y > .z'); + const result = querySelector(scene, '.x > .y > .z'); expect(result).toEqual(mesh); }); it('querySelectorAll: special case', () => { - const result = scene.querySelectorAll('.x > .y > .z, Group > Group'); + const result = querySelectorAll(scene, '.x > .y > .z, Group > Group'); expect(result.length).toEqual(2); expect(result[0]).toEqual(group2); expect(result[1]).toEqual(mesh); From 709b5f22b421589ab98fdb0feacd1ee767dde9de Mon Sep 17 00:00:00 2001 From: agargaro Date: Thu, 14 Nov 2024 13:51:39 +0100 Subject: [PATCH 02/11] wip --- src/patch/Object3D.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/patch/Object3D.ts b/src/patch/Object3D.ts index 6cc7b96..805be8f 100644 --- a/src/patch/Object3D.ts +++ b/src/patch/Object3D.ts @@ -214,10 +214,13 @@ Object.defineProperty(Object3D.prototype, "firstFocusable", { Object.defineProperty(Object3D.prototype, "enabledState", { get: function (this: Object3D) { - let obj = this; + if (!this.__enabled) return false; + + let obj = this.parent; do { - if (!obj.enabled) return false; - } while (obj = obj.parent); + if (!obj.__enabled) return false; + } while ((obj = obj.parent)); + return true; } }); From 73870151e81ded906db9fa0f57a6e7efea010db8 Mon Sep 17 00:00:00 2001 From: agargaro Date: Thu, 14 Nov 2024 18:59:03 +0100 Subject: [PATCH 03/11] wip --- eslint.config.js | 20 ++++++++++++++------ package-lock.json | 20 ++++++++++++++++++++ package.json | 3 ++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 47da483..8a0e1c4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,17 +1,25 @@ import js from '@eslint/js'; import tseslint from 'typescript-eslint'; +import stylistic from '@stylistic/eslint-plugin'; export default [ + js.configs.recommended, + ...tseslint.configs.strict, + stylistic.configs.customize({ + jsx: false, + semi: true, + commaDangle: 'never', + arrowParens: true + }), { - files: ['**/*.{js,ts}'], + files: ['**/*.{js,ts}'] }, { - ignores: ['dist', 'vite.config.js', 'examples', 'docs', 'packages'], + ignores: ['dist', 'vite.config.js', 'examples', 'docs', 'packages'] }, - js.configs.recommended, - ...tseslint.configs.strict, { rules: { + 'camelcase': 'warn', 'no-unused-vars': 'off', 'no-undef': 'off', '@typescript-eslint/no-explicit-any': 'off', @@ -19,6 +27,6 @@ export default [ '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-wrapper-object-types': 'off', '@typescript-eslint/no-dynamic-delete': 'off' - }, - }, + } + } ]; diff --git a/package-lock.json b/package-lock.json index 1b657de..1224d67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "devDependencies": { "@eslint/js": "^9.10.0", + "@stylistic/eslint-plugin": "^2.10.1", "@three.ez/main": "^0.5.8", "@types/three": "^0.170.0", "eslint": "^9.14.0", @@ -1122,6 +1123,25 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.10.1.tgz", + "integrity": "sha512-U+4yzNXElTf9q0kEfnloI9XbOyD4cnEQCxjUI94q0+W++0GAEQvJ/slwEj9lwjDHfGADRSr+Tco/z0XJvmDfCQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^8.12.2", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, "node_modules/@three.ez/main": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@three.ez/main/-/main-0.5.8.tgz", diff --git a/package.json b/package.json index b7797a1..4cdc91f 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "vite-plugin-dts": "^4.3.0", "vite-plugin-externalize-deps": "^0.8.0", "vite-plugin-static-copy": "^1.0.6", - "vitest": "^0.34.6" + "vitest": "^0.34.6", + "@stylistic/eslint-plugin": "^2.10.1" }, "peerDependencies": { "three": ">=0.151.0" From ece00f40cf8cc6fe11ccfa572cba441761ec1f06 Mon Sep 17 00:00:00 2001 From: agargaro Date: Thu, 14 Nov 2024 22:20:37 +0100 Subject: [PATCH 04/11] es lint fix --- eslint.config.js | 8 +- examples/example.ts | 4 +- src/binding/Binding.ts | 8 +- src/cameras/OrthographicCameraAuto.ts | 90 +- src/cameras/PerspectiveCameraAuto.ts | 21 +- src/core/Main.ts | 374 +++--- src/events/CursorManager.ts | 79 +- src/events/Default.ts | 12 +- src/events/DragAndDropManager.ts | 302 ++--- src/events/Events.ts | 92 +- src/events/EventsDispatcher.ts | 179 ++- src/events/Hitbox.ts | 8 +- src/events/InteractionEventsQueue.ts | 56 +- src/events/InteractionManager.ts | 553 ++++---- src/events/MiscEventsManager.ts | 113 +- src/events/RaycasterManager.ts | 122 +- src/index.ts | 6 +- .../EventsDispatcherInstanced.ts | 100 +- src/instancedMesh/InstancedMesh2.ts | 450 +++---- src/instancedMesh/InstancedMeshEntity.ts | 292 ++--- src/patch/Euler.ts | 32 +- src/patch/Material.ts | 10 +- src/patch/Matrix4.ts | 68 +- src/patch/Object3D.ts | 104 +- src/patch/Quaternion.ts | 32 +- src/patch/Scene.ts | 203 ++- src/patch/SmartRendering.ts | 102 +- src/patch/Vector3.ts | 1121 ++++++++--------- src/patch/WebGLRenderer.ts | 74 +- src/rendering/RenderManager.ts | 18 +- src/rendering/RenderView.ts | 8 +- src/tweening/Actions.ts | 266 ++-- src/tweening/Easings.ts | 260 ++-- src/tweening/RunningTween.ts | 514 ++++---- src/tweening/Tween.ts | 254 ++-- src/tweening/TweenManager.ts | 208 +-- src/utils/Asset.ts | 3 +- src/utils/IntersectionUtils.ts | 289 +++-- src/utils/Query.ts | 7 +- src/utils/Stats.ts | 20 +- src/utils/Utils.ts | 92 +- test/utils/Query.test.ts | 13 +- vite.config.ts | 6 +- 43 files changed, 3277 insertions(+), 3296 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 8a0e1c4..88f3a37 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,13 +9,11 @@ export default [ jsx: false, semi: true, commaDangle: 'never', - arrowParens: true + arrowParens: true, + braceStyle: '1tbs' }), { - files: ['**/*.{js,ts}'] - }, - { - ignores: ['dist', 'vite.config.js', 'examples', 'docs', 'packages'] + ignores: ['dist', 'docs', 'packages'] }, { rules: { diff --git a/examples/example.ts b/examples/example.ts index a51b3e6..1ca68ee 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -5,10 +5,10 @@ const box = new Mesh(new BoxGeometry(0.1, 0.1, 0.1), new MeshNormalMaterial()); box.draggable = true; box.on('animate', (e) => box.rotateX(e.delta).rotateY(e.delta * 2)); box.on(['pointerover', 'pointerout'], function (e) { - this.tween('id').to(500, { scale: e.type === 'pointerover' ? 1.5 : 1 }, { easing: 'easeOutElastic' }).start(); + this.tween('id').to(500, { scale: e.type === 'pointerover' ? 1.5 : 1 }, { easing: 'easeOutElastic' }).start(); }); const scene = new Scene().add(box); const main = new Main({ fullscreen: false }); -main.createView({ scene, camera: new PerspectiveCameraAuto(70).translateZ(1) }); \ No newline at end of file +main.createView({ scene, camera: new PerspectiveCameraAuto(70).translateZ(1) }); diff --git a/src/binding/Binding.ts b/src/binding/Binding.ts index 311dd1b..cfc09fb 100644 --- a/src/binding/Binding.ts +++ b/src/binding/Binding.ts @@ -1,4 +1,4 @@ -import { Object3D, Scene } from "three"; +import { Object3D, Scene } from 'three'; /** @internal */ export interface BindingCallback { @@ -9,7 +9,6 @@ export interface BindingCallback { /** @internal */ export class Binding { - public static detectChanges(target: Object3D, resursive: boolean): void { this.executeAllCallbacks(target); if (resursive) { @@ -21,7 +20,7 @@ export class Binding { public static bindProperty(key: string, target: Object3D, getValue: () => T, renderOnChange?: boolean): void { if (this.getIndexByKey(target, key) > -1) { - console.error("Cannot override property already bound."); + console.error('Cannot override property already bound.'); return; } @@ -65,7 +64,7 @@ export class Binding { public static setManualDetectionMode(target: Object3D): void { if (target.__manualDetection) return; if (target.__boundCallbacks.length > 0) { - console.error("Cannot change detectChangesMode if a binding is already created."); + console.error('Cannot change detectChangesMode if a binding is already created.'); } else { target.__manualDetection = true; } @@ -108,5 +107,4 @@ export class Binding { this.executeAllCallbacks(target); } } - } diff --git a/src/cameras/OrthographicCameraAuto.ts b/src/cameras/OrthographicCameraAuto.ts index ee738a1..e4fcbe0 100644 --- a/src/cameras/OrthographicCameraAuto.ts +++ b/src/cameras/OrthographicCameraAuto.ts @@ -1,67 +1,67 @@ -import { OrthographicCamera } from "three"; +import { OrthographicCamera } from 'three'; /** * Extends the OrthographicCamera to automatically resize based on a fixed width or height dimension. */ export class OrthographicCameraAuto extends OrthographicCamera { - private _size: number; - private _fixedWidth: boolean; - private _width = -1; - private _height = -1; + private _size: number; + private _fixedWidth: boolean; + private _width = -1; + private _height = -1; - /** + /** * Gets or sets the fixed width or height dimension based on the 'fixedWidth' property. */ - public get size(): number { return this._size } - public set size(value: number) { - this._size = value; - this.update(); - } + public get size(): number { return this._size; } + public set size(value: number) { + this._size = value; + this.update(); + } - /** + /** * Determines whether the 'size' property refers to the width (true) or height (false). */ - public get fixedWidth(): boolean { return this._fixedWidth } - public set fixedWidth(value: boolean) { - this._fixedWidth = value; - this.update(); - } + public get fixedWidth(): boolean { return this._fixedWidth; } + public set fixedWidth(value: boolean) { + this._fixedWidth = value; + this.update(); + } - /** + /** * @param size Fixed width or height dimension based on the 'fixedWidth' property. Default `2`. * @param fixedWidth If true, the 'size' property will refer to the width. If not, to the height. Default `true`. * @param near Camera frustum near plane. Default `0.1`. * @param far Camera frustum far plane. Default `2000`. */ - constructor(size = 2, fixedWidth = true, near?: number, far?: number) { - super(-1, 1, 1, -1, near, far); - this._size = size; - this._fixedWidth = fixedWidth; + constructor(size = 2, fixedWidth = true, near?: number, far?: number) { + super(-1, 1, 1, -1, near, far); + this._size = size; + this._fixedWidth = fixedWidth; - this.on("viewportresize", (e) => { - this._width = e.width; - this._height = e.height; - this.update(); - }); - } + this.on('viewportresize', (e) => { + this._width = e.width; + this._height = e.height; + this.update(); + }); + } - private update(): void { - const halfSize = 0.5 * this._size; + private update(): void { + const halfSize = 0.5 * this._size; - if (this._fixedWidth) { - const aspect = this._height / this._width; - this.left = -halfSize; - this.right = halfSize; - this.top = halfSize * aspect; - this.bottom = -halfSize * aspect; - } else { - const aspect = this._width / this._height; - this.left = -halfSize * aspect; - this.right = halfSize * aspect; - this.top = halfSize; - this.bottom = -halfSize; - } - - this.updateProjectionMatrix(); + if (this._fixedWidth) { + const aspect = this._height / this._width; + this.left = -halfSize; + this.right = halfSize; + this.top = halfSize * aspect; + this.bottom = -halfSize * aspect; + } else { + const aspect = this._width / this._height; + this.left = -halfSize * aspect; + this.right = halfSize * aspect; + this.top = halfSize; + this.bottom = -halfSize; } + + this.updateProjectionMatrix(); + } } diff --git a/src/cameras/PerspectiveCameraAuto.ts b/src/cameras/PerspectiveCameraAuto.ts index e2b92b9..c680c18 100644 --- a/src/cameras/PerspectiveCameraAuto.ts +++ b/src/cameras/PerspectiveCameraAuto.ts @@ -1,21 +1,20 @@ -import { PerspectiveCamera } from "three"; +import { PerspectiveCamera } from 'three'; /** * Extends the PerspectiveCamera to automatically adjust its aspect ratio on renderer resize. */ export class PerspectiveCameraAuto extends PerspectiveCamera { - - /** + /** * @param fov Camera frustum vertical field of view in degrees. Default `50`. * @param near Camera frustum near plane distance. Default `0.1`. * @param far Camera frustum far plane distance. Default `2000`. */ - constructor(fov?: number, near?: number, far?: number) { - super(fov, undefined, near, far); - - this.on("viewportresize", (e) => { - this.aspect = e.width / e.height; - this.updateProjectionMatrix(); - }); - } + constructor(fov?: number, near?: number, far?: number) { + super(fov, undefined, near, far); + + this.on('viewportresize', (e) => { + this.aspect = e.width / e.height; + this.updateProjectionMatrix(); + }); + } } diff --git a/src/core/Main.ts b/src/core/Main.ts index 11c9b31..295aec3 100644 --- a/src/core/Main.ts +++ b/src/core/Main.ts @@ -1,39 +1,39 @@ -import { Camera, Clock, ColorRepresentation, Raycaster, Scene, Vector2, WebGLRenderer, WebGLRendererParameters } from "three"; -import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"; -import { Binding } from "../binding/Binding.js"; -import { InteractionManager } from "../events/InteractionManager.js"; -import { EventsCache } from "../events/MiscEventsManager.js"; -import { RenderManager } from "../rendering/RenderManager.js"; -import { RenderView, ViewParameters } from "../rendering/RenderView.js"; -import { TweenManager } from "../tweening/TweenManager.js"; -import { Stats } from "../utils/Stats.js"; -import { RaycasterSortComparer } from "../events/RaycasterManager.js"; -import { applyWebGLRendererPatch } from "../patch/WebGLRenderer.js"; +import { Camera, Clock, ColorRepresentation, Raycaster, Scene, Vector2, WebGLRenderer, WebGLRendererParameters } from 'three'; +import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; +import { Binding } from '../binding/Binding.js'; +import { InteractionManager } from '../events/InteractionManager.js'; +import { EventsCache } from '../events/MiscEventsManager.js'; +import { RenderManager } from '../rendering/RenderManager.js'; +import { RenderView, ViewParameters } from '../rendering/RenderView.js'; +import { TweenManager } from '../tweening/TweenManager.js'; +import { Stats } from '../utils/Stats.js'; +import { RaycasterSortComparer } from '../events/RaycasterManager.js'; +import { applyWebGLRendererPatch } from '../patch/WebGLRenderer.js'; /** * Configuration parameters for initializing the Main class. */ export interface MainParameters { - /** Enable full-screen mode and automatic canvas resizing (default: true). */ - fullscreen?: boolean; - /** Display performance statistics (default: true). */ - showStats?: boolean; - /** Disable the context menu on right-click (default: true). */ - disableContextMenu?: boolean; - /** Default background color (default: 'black'). */ - backgroundColor?: ColorRepresentation; - /** Default background alpha (transparency) value (default: 1). */ - backgroundAlpha?: number; - /** Callback function executed for each frame. */ - animate?: (delta: number, total: number) => void; - /** Configuration parameters for the WebGLRenderer. Ignored if renderer is specified. */ - rendererParameters?: WebGLRendererParameters; - /** Enable cursor handling in the application (default: true). */ - enableCursor?: boolean; - /** Enable multitouch interactions (default: false). */ - multitouch?: boolean; - /** */ - renderer?: WebGLRenderer; + /** Enable full-screen mode and automatic canvas resizing (default: true). */ + fullscreen?: boolean; + /** Display performance statistics (default: true). */ + showStats?: boolean; + /** Disable the context menu on right-click (default: true). */ + disableContextMenu?: boolean; + /** Default background color (default: 'black'). */ + backgroundColor?: ColorRepresentation; + /** Default background alpha (transparency) value (default: 1). */ + backgroundAlpha?: number; + /** Callback function executed for each frame. */ + animate?: (delta: number, total: number) => void; + /** Configuration parameters for the WebGLRenderer. Ignored if renderer is specified. */ + rendererParameters?: WebGLRendererParameters; + /** Enable cursor handling in the application (default: true). */ + enableCursor?: boolean; + /** Enable multitouch interactions (default: false). */ + multitouch?: boolean; + /** */ + renderer?: WebGLRenderer; } /** @@ -41,259 +41,259 @@ export interface MainParameters { * It provides configuration options and methods for setting up and controlling the application's behavior. */ export class Main { - /** A static counter representing the number of animation frames elapsed. */ - public ticks = 0; - private _renderManager: RenderManager; - private _interactionManager: InteractionManager; - /** @internal **/ public _stats: Stats; - /** @internal **/ public _showStats: boolean; - private _animate: (delta: number, total: number) => void; - private _clock = new Clock(); - - /** + /** A static counter representing the number of animation frames elapsed. */ + public ticks = 0; + private _renderManager: RenderManager; + private _interactionManager: InteractionManager; + /** @internal **/ public _stats: Stats; + /** @internal **/ public _showStats: boolean; + private _animate: (delta: number, total: number) => void; + private _clock = new Clock(); + + /** * The WebGLRenderer instance used for rendering the 3D scene. */ - public get renderer(): WebGLRenderer { return this._renderManager.renderer } + public get renderer(): WebGLRenderer { return this._renderManager.renderer; } - /** + /** * An array of all RenderView instances managed by the application. * Lists all views created and managed by the application, each representing a separate viewport or scene. */ - public get views(): RenderView[] { return this._renderManager.views } + public get views(): RenderView[] { return this._renderManager.views; } - /** + /** * The currently active RenderView (activated by mouse position). */ - public get activeView(): RenderView { return this._renderManager.activeView } + public get activeView(): RenderView { return this._renderManager.activeView; } - /** + /** * The Scene associated with the currently active RenderView. */ - public get activeScene(): Scene { return this._renderManager.activeView?.scene } + public get activeScene(): Scene { return this._renderManager.activeView?.scene; } - /** + /** * The Camera associated with the currently active RenderView. */ - public get activeCamera(): Camera { return this._renderManager.activeView?.camera } + public get activeCamera(): Camera { return this._renderManager.activeView?.camera; } - /** + /** * The EffectComposer (used for post-processing) associated with the currently active RenderView. */ - public get activeComposer(): EffectComposer { return this._renderManager.activeView?.composer } + public get activeComposer(): EffectComposer { return this._renderManager.activeView?.composer; } - /** + /** * Indicates whether to display performance statistics. * If set to true, statistics will be shown; otherwise, they will be hidden. */ - public get showStats(): boolean { return this._showStats } - public set showStats(value: boolean) { - if (value) { - if (!this._stats) this._stats = new Stats(this.renderer); - document.body.appendChild(this._stats.dom); - } else if (this._stats) { - document.body.removeChild(this._stats.dom); - } - this._showStats = value; + public get showStats(): boolean { return this._showStats; } + public set showStats(value: boolean) { + if (value) { + if (!this._stats) this._stats = new Stats(this.renderer); + document.body.appendChild(this._stats.dom); + } else if (this._stats) { + document.body.removeChild(this._stats.dom); } + this._showStats = value; + } - /** + /** * Indicates whether to enable multitouch interactions. */ - public get multitouch(): boolean { return this._interactionManager.queue.multitouch } - public set multitouch(value: boolean) { this._interactionManager.queue.multitouch = value } + public get multitouch(): boolean { return this._interactionManager.queue.multitouch; } + public set multitouch(value: boolean) { this._interactionManager.queue.multitouch = value; } - /** + /** * Defines the mouse buttons that can be used for dragging objects. * Specify the button values as an array of PointerEvent button values. */ - public get dragButtons(): number[] { return this._interactionManager.dragManager.dragButtons } - public set dragButtons(value: number[]) { this._interactionManager.dragManager.dragButtons = value } + public get dragButtons(): number[] { return this._interactionManager.dragManager.dragButtons; } + public set dragButtons(value: number[]) { this._interactionManager.dragManager.dragButtons = value; } - /** + /** * Indicates whether to enable cursor handling in the application. */ - public get enableCursor(): boolean { return this._interactionManager.cursorManager.enabled } - public set enableCursor(value: boolean) { this._interactionManager.cursorManager.enabled = value } + public get enableCursor(): boolean { return this._interactionManager.cursorManager.enabled; } + public set enableCursor(value: boolean) { this._interactionManager.cursorManager.enabled = value; } - /** + /** * A custom sorting comparer function used to order intersections when performing raycasting. */ - public get raycasterSortComparer(): RaycasterSortComparer { return this._interactionManager.raycasterManager.raycasterSortComparer } - public set raycasterSortComparer(value: RaycasterSortComparer) { this._interactionManager.raycasterManager.raycasterSortComparer = value } + public get raycasterSortComparer(): RaycasterSortComparer { return this._interactionManager.raycasterManager.raycasterSortComparer; } + public set raycasterSortComparer(value: RaycasterSortComparer) { this._interactionManager.raycasterManager.raycasterSortComparer = value; } - /** + /** * A Raycaster instance responsible for handling raycasting operations in the application. */ - public get raycaster(): Raycaster { return this._interactionManager.raycasterManager.raycaster } + public get raycaster(): Raycaster { return this._interactionManager.raycasterManager.raycaster; } - /** + /** * The default background color used in the application. */ - public get backgroundColor(): ColorRepresentation { return this._renderManager.backgroundColor } - public set backgroundColor(value: ColorRepresentation) { this._renderManager.backgroundColor = value } + public get backgroundColor(): ColorRepresentation { return this._renderManager.backgroundColor; } + public set backgroundColor(value: ColorRepresentation) { this._renderManager.backgroundColor = value; } - /** + /** * The default alpha (transparency) value for the background. */ - public get backgroundAlpha(): number { return this._renderManager.backgroundAlpha } - public set backgroundAlpha(value: number) { this._renderManager.backgroundAlpha = value } + public get backgroundAlpha(): number { return this._renderManager.backgroundAlpha; } + public set backgroundAlpha(value: number) { this._renderManager.backgroundAlpha = value; } - /** + /** * The current mouse position represented as a Vector2. * Provides the x and y coordinates of the mouse pointer within the application. */ - public get mousePosition(): Vector2 { return this._interactionManager.raycasterManager.pointer } + public get mousePosition(): Vector2 { return this._interactionManager.raycasterManager.pointer; } - /** + /** * Indicates if the pointer is over the canvas. */ - public get pointerOnCanvas(): boolean { return this._interactionManager.raycasterManager.pointerOnCanvas } + public get pointerOnCanvas(): boolean { return this._interactionManager.raycasterManager.pointerOnCanvas; } - /** + /** * @param params Configuration parameters for initializing the Main class. */ - constructor(params: MainParameters = {}) { - if (!params.renderer) this.setDefaultRendererParameters(params); - const renderer = params.renderer ?? new WebGLRenderer(params.rendererParameters); - const appendCanvas = !params.rendererParameters.canvas; - - this._renderManager = new RenderManager(renderer, appendCanvas, params.fullscreen, params.backgroundColor, params.backgroundAlpha); - this._interactionManager = new InteractionManager(this._renderManager); - this.multitouch = params.multitouch ?? false; - this.handleContextMenu(params.disableContextMenu); - this.showStats = params.showStats ?? true; - this.enableCursor = params.enableCursor ?? true; - this._animate = params.animate || null; - - applyWebGLRendererPatch(this); - } - - private setDefaultRendererParameters(parameters: MainParameters): void { - if (!parameters.rendererParameters) parameters.rendererParameters = {}; - parameters.rendererParameters.antialias ??= true; - } - - private handleContextMenu(disableContextMenu = true): void { - if (disableContextMenu) { - this.renderer.domElement.addEventListener("contextmenu", (e) => e.preventDefault()); - } + constructor(params: MainParameters = {}) { + if (!params.renderer) this.setDefaultRendererParameters(params); + const renderer = params.renderer ?? new WebGLRenderer(params.rendererParameters); + const appendCanvas = !params.rendererParameters.canvas; + + this._renderManager = new RenderManager(renderer, appendCanvas, params.fullscreen, params.backgroundColor, params.backgroundAlpha); + this._interactionManager = new InteractionManager(this._renderManager); + this.multitouch = params.multitouch ?? false; + this.handleContextMenu(params.disableContextMenu); + this.showStats = params.showStats ?? true; + this.enableCursor = params.enableCursor ?? true; + this._animate = params.animate || null; + + applyWebGLRendererPatch(this); + } + + private setDefaultRendererParameters(parameters: MainParameters): void { + if (!parameters.rendererParameters) parameters.rendererParameters = {}; + parameters.rendererParameters.antialias ??= true; + } + + private handleContextMenu(disableContextMenu = true): void { + if (disableContextMenu) { + this.renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault()); } + } - private setAnimationLoop(): void { - this.renderer.setAnimationLoop((time, frame) => { - if (this._showStats) this._stats.begin(); - - this.ticks++; - const currentDelta = this._clock.getDelta(); - - this._interactionManager.update(); - TweenManager.update(currentDelta * 1000); + private setAnimationLoop(): void { + this.renderer.setAnimationLoop((time, frame) => { + if (this._showStats) this._stats.begin(); - this.animate(currentDelta, this._clock.elapsedTime); + this.ticks++; + const currentDelta = this._clock.getDelta(); - const visibleScenes = this._renderManager.getVisibleScenes(); + this._interactionManager.update(); + TweenManager.update(currentDelta * 1000); - if (visibleScenes) { - for (const scene of visibleScenes) { - const delta = currentDelta * scene.timeScale; - const total = scene.totalTime += delta; - EventsCache.dispatchEvent(scene, "beforeanimate", { delta, total }); - EventsCache.dispatchEvent(scene, "animate", { delta, total }); - EventsCache.dispatchEvent(scene, "afteranimate", { delta, total }); - Binding.compute(scene); - } + this.animate(currentDelta, this._clock.elapsedTime); - this._renderManager.render(); + const visibleScenes = this._renderManager.getVisibleScenes(); - for (const scene of visibleScenes) { - scene.needsRender = !scene.__smartRendering; - } - } - - if (this._showStats) { - this._stats.end(); - this._stats.update(); - } - }); - } + if (visibleScenes) { + for (const scene of visibleScenes) { + const delta = currentDelta * scene.timeScale; + const total = scene.totalTime += delta; + EventsCache.dispatchEvent(scene, 'beforeanimate', { delta, total }); + EventsCache.dispatchEvent(scene, 'animate', { delta, total }); + EventsCache.dispatchEvent(scene, 'afteranimate', { delta, total }); + Binding.compute(scene); + } - private clearAnimationLoop(): void { - this.renderer.setAnimationLoop(null); - } + this._renderManager.render(); - public animate(delta: number, total: number): void { - if (this._animate) { - this._animate(delta, total); + for (const scene of visibleScenes) { + scene.needsRender = !scene.__smartRendering; } + } + + if (this._showStats) { + this._stats.end(); + this._stats.update(); + } + }); + } + + private clearAnimationLoop(): void { + this.renderer.setAnimationLoop(null); + } + + public animate(delta: number, total: number): void { + if (this._animate) { + this._animate(delta, total); } + } - /** + /** * Creates a new RenderView and adds it to the RenderManager. * @param view The parameters for the new RenderView. * @returns The created RenderView instance. */ - public createView(view: ViewParameters): RenderView { - if (this._renderManager.views.length === 0) this.setAnimationLoop(); - return this._renderManager.create(view); - } + public createView(view: ViewParameters): RenderView { + if (this._renderManager.views.length === 0) this.setAnimationLoop(); + return this._renderManager.create(view); + } - /** + /** * Adds a RenderView to the RenderManager. * @param view The RenderView instance to add. */ - public addView(view: RenderView): void { - if (this._renderManager.views.length === 0) this.setAnimationLoop(); - this._renderManager.add(view); - } + public addView(view: RenderView): void { + if (this._renderManager.views.length === 0) this.setAnimationLoop(); + this._renderManager.add(view); + } - /** + /** * Removes a RenderView from the RenderManager. * @param view The RenderView instance to remove. */ - public removeView(view: RenderView): void { - this._renderManager.remove(view); - if (this._renderManager.views.length === 0) this.clearAnimationLoop(); // TODO consider if we want to move it to renderManager - } + public removeView(view: RenderView): void { + this._renderManager.remove(view); + if (this._renderManager.views.length === 0) this.clearAnimationLoop(); // TODO consider if we want to move it to renderManager + } - /** + /** * Removes a RenderView from the RenderManager by its tag. * @param tag The tag of the RenderView to remove. */ - public removeViewByTag(tag: string): void { - this._renderManager.removeByTag(tag); - if (this._renderManager.views.length === 0) this.clearAnimationLoop(); - } + public removeViewByTag(tag: string): void { + this._renderManager.removeByTag(tag); + if (this._renderManager.views.length === 0) this.clearAnimationLoop(); + } - /** + /** * Clears all RenderViews from the RenderManager. */ - public clearViews(): void { - this._renderManager.clear(); - this.clearAnimationLoop(); - } + public clearViews(): void { + this._renderManager.clear(); + this.clearAnimationLoop(); + } - /** + /** * Retrieves a RenderView by its tag. * @param tag The tag to search for. * @returns The RenderView with the specified tag, if found, otherwise, undefined. */ - public getViewByTag(tag: string): RenderView { - return this._renderManager.getByTag(tag); - } + public getViewByTag(tag: string): RenderView { + return this._renderManager.getByTag(tag); + } - /** + /** * Retrieves a RenderView by mouse position. * @param mouse The mouse position as a Vector2. */ - public getViewByMouse(mouse: Vector2): void { - this._renderManager.getViewByMouse(mouse); - } + public getViewByMouse(mouse: Vector2): void { + this._renderManager.getViewByMouse(mouse); + } - /** + /** * Sets active RenderViews by tag. * @param tag The tag of the RenderViews to set as active. */ - public setActiveViewsByTag(tag: string): void { - this._renderManager.setActiveViewsByTag(tag); - } + public setActiveViewsByTag(tag: string): void { + this._renderManager.setActiveViewsByTag(tag); + } } diff --git a/src/events/CursorManager.ts b/src/events/CursorManager.ts index 467345a..4d8399e 100644 --- a/src/events/CursorManager.ts +++ b/src/events/CursorManager.ts @@ -1,17 +1,17 @@ -import { Object3D } from "three"; -import { InstancedMesh2 } from "../instancedMesh/InstancedMesh2.js"; +import { Object3D } from 'three'; +import { InstancedMesh2 } from '../instancedMesh/InstancedMesh2.js'; /** Valid cursor values based on the CSS cursor property. */ -export type CursorsKeys = "auto" | "default" | "none" | "context-menu" | "help" | "pointer" | "progress" | "wait" | - "cell" | "crosshair" | "text" | "vertical-text" | "alias" | "copy" | "move" | "no-drop" | "not-allowed" | "grab" | "grabbing" | - "all-scroll" | "col-resize" | "row-resize" | "n-resize" | "e-resize" | "s-resize" | "w-resize" | - "ne-resize" | "nw-resize" | "se-resize" | "sw-resize" | "ew-resize" | "ns-resize" | "nesw-resize" | "nwse-resize" | "zoom-in" | "zoom-out"; +export type CursorsKeys = 'auto' | 'default' | 'none' | 'context-menu' | 'help' | 'pointer' | 'progress' | 'wait' | + 'cell' | 'crosshair' | 'text' | 'vertical-text' | 'alias' | 'copy' | 'move' | 'no-drop' | 'not-allowed' | 'grab' | 'grabbing' | + 'all-scroll' | 'col-resize' | 'row-resize' | 'n-resize' | 'e-resize' | 's-resize' | 'w-resize' | + 'ne-resize' | 'nw-resize' | 'se-resize' | 'sw-resize' | 'ew-resize' | 'ns-resize' | 'nesw-resize' | 'nwse-resize' | 'zoom-in' | 'zoom-out'; const cursorSet = new Set([ - "auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", - "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "grab", "grabbing", - "all-scroll", "col-resize", "row-resize", "n-resize", "e-resize", "s-resize", "w-resize", - "ne-resize", "nw-resize", "se-resize", "sw-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "zoom-in", "zoom-out" + 'auto', 'default', 'none', 'context-menu', 'help', 'pointer', 'progress', 'wait', + 'cell', 'crosshair', 'text', 'vertical-text', 'alias', 'copy', 'move', 'no-drop', 'not-allowed', 'grab', 'grabbing', + 'all-scroll', 'col-resize', 'row-resize', 'n-resize', 'e-resize', 's-resize', 'w-resize', + 'ne-resize', 'nw-resize', 'se-resize', 'sw-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out' ]); /** Represents a cursor, either by a CSS cursor key or a URL. */ @@ -19,35 +19,34 @@ export type Cursor = CursorsKeys | String; /** @internal */ export class CursorHandler { - public enabled = true; - private _cursor: Cursor; - private _domElement: HTMLCanvasElement; - - constructor(domElement: HTMLCanvasElement) { - this._domElement = domElement; - } - - public update(objDragged: Object3D, objHovered: Object3D, objDropTarget: Object3D): void { - if (!this.enabled || !objHovered) return; - const cursor = this.getCursor(objDragged, objHovered, objDropTarget); - if (cursor !== this._cursor) { - this._cursor = cursor; - if (cursorSet.has(cursor as string)) { - this._domElement.style.cursor = cursor as string; - } else { - this._domElement.style.cursor = `url(${cursor}), default`; - } - } + public enabled = true; + private _cursor: Cursor; + private _domElement: HTMLCanvasElement; + + constructor(domElement: HTMLCanvasElement) { + this._domElement = domElement; + } + + public update(objDragged: Object3D, objHovered: Object3D, objDropTarget: Object3D): void { + if (!this.enabled || !objHovered) return; + const cursor = this.getCursor(objDragged, objHovered, objDropTarget); + if (cursor !== this._cursor) { + this._cursor = cursor; + if (cursorSet.has(cursor as string)) { + this._domElement.style.cursor = cursor as string; + } else { + this._domElement.style.cursor = `url(${cursor}), default`; + } } - - private getCursor(objDragged: Object3D, objHovered: Object3D, objDropTarget: Object3D): Cursor { - if (objDropTarget) return objDropTarget.cursorDrop ?? "alias"; - if (objDragged) return objDragged.cursorDrag ?? "grabbing"; - if (objHovered.cursor) return objHovered.cursor; - if ((objHovered as InstancedMesh2).isInstancedMesh2) { - if (!(objHovered as InstancedMesh2).__enabledStateHovered) return "default"; - } else if (!objHovered.enabledState) return "default"; - return objHovered.draggable ? "grab" : "pointer"; - } - + } + + private getCursor(objDragged: Object3D, objHovered: Object3D, objDropTarget: Object3D): Cursor { + if (objDropTarget) return objDropTarget.cursorDrop ?? 'alias'; + if (objDragged) return objDragged.cursorDrag ?? 'grabbing'; + if (objHovered.cursor) return objHovered.cursor; + if ((objHovered as InstancedMesh2).isInstancedMesh2) { + if (!(objHovered as InstancedMesh2).__enabledStateHovered) return 'default'; + } else if (!objHovered.enabledState) return 'default'; + return objHovered.draggable ? 'grab' : 'pointer'; + } } diff --git a/src/events/Default.ts b/src/events/Default.ts index 3a51428..03fbf24 100644 --- a/src/events/Default.ts +++ b/src/events/Default.ts @@ -2,10 +2,10 @@ * This class defines default settings for newly created Object3D instances. */ export class Default { - /** The default setting for 'focusable' for newly created Object3Ds. */ - public static focusable = true; - /** The default setting for 'draggable' for newly created Object3Ds. */ - public static draggable = false; - /** The default setting for 'interceptByRaycaster' for newly created Object3Ds. */ - public static interceptByRaycaster = true; + /** The default setting for 'focusable' for newly created Object3Ds. */ + public static focusable = true; + /** The default setting for 'draggable' for newly created Object3Ds. */ + public static draggable = false; + /** The default setting for 'interceptByRaycaster' for newly created Object3Ds. */ + public static interceptByRaycaster = true; } diff --git a/src/events/DragAndDropManager.ts b/src/events/DragAndDropManager.ts index c642b03..eb2353c 100644 --- a/src/events/DragAndDropManager.ts +++ b/src/events/DragAndDropManager.ts @@ -1,176 +1,176 @@ -import { Plane, Matrix4, Vector3, Raycaster, Camera, Object3D } from "three"; -import { DragEventExt, InteractionEvents, IntersectionExt } from "./Events.js"; -import { InstancedMesh2 } from "../instancedMesh/InstancedMesh2.js"; -import { InstancedMeshEntity } from "../instancedMesh/InstancedMeshEntity.js"; +import { Plane, Matrix4, Vector3, Raycaster, Camera, Object3D } from 'three'; +import { DragEventExt, InteractionEvents, IntersectionExt } from './Events.js'; +import { InstancedMesh2 } from '../instancedMesh/InstancedMesh2.js'; +import { InstancedMeshEntity } from '../instancedMesh/InstancedMeshEntity.js'; /** @internal */ export class DragAndDropManager { - public isDragging = false; - public dragButtons = [0]; - private _plane = new Plane(); - private _offset = new Vector3(); - private _intersection = new Vector3(); - private _worldPosition = new Vector3(); - private _inverseMatrix = new Matrix4(); - private _startPosition = new Vector3(); - private _originalIntersection = new Vector3(); - private _target: Object3D; - private _targetInstanced: InstancedMeshEntity; - private _targetMatrixWorld = new Matrix4(); - private _dataTransfer: { [x: string]: any }; - private _lastDropTarget: Object3D; - private _raycaster: Raycaster; - private _startIntersection: IntersectionExt; - - public get target(): Object3D { return this._target } - public get findDropTarget(): boolean { return this._target.findDropTarget } - - constructor(raycaster: Raycaster) { - this._raycaster = raycaster; + public isDragging = false; + public dragButtons = [0]; + private _plane = new Plane(); + private _offset = new Vector3(); + private _intersection = new Vector3(); + private _worldPosition = new Vector3(); + private _inverseMatrix = new Matrix4(); + private _startPosition = new Vector3(); + private _originalIntersection = new Vector3(); + private _target: Object3D; + private _targetInstanced: InstancedMeshEntity; + private _targetMatrixWorld = new Matrix4(); + private _dataTransfer: { [x: string]: any }; + private _lastDropTarget: Object3D; + private _raycaster: Raycaster; + private _startIntersection: IntersectionExt; + + public get target(): Object3D { return this._target; } + public get findDropTarget(): boolean { return this._target.findDropTarget; } + + constructor(raycaster: Raycaster) { + this._raycaster = raycaster; + } + + public needsDrag(event: PointerEvent, camera: Camera): boolean { + if (this.isDragging) return true; + if (this._target) { + this.startDragging(event, camera); + return true; } - - public needsDrag(event: PointerEvent, camera: Camera): boolean { - if (this.isDragging) return true; - if (this._target) { - this.startDragging(event, camera); - return true; - } - return false; + return false; + } + + public performDrag(event: PointerEvent, camera: Camera, dropTargetIntersection: IntersectionExt): void { + if (!event.isPrimary) return; + + this._plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(this._plane.normal), this._worldPosition.setFromMatrixPosition(this._targetMatrixWorld)); + this._raycaster.ray.intersectPlane(this._plane, this._intersection); + this._intersection.sub(this._offset).applyMatrix4(this._inverseMatrix); + this._originalIntersection.copy(this._intersection); + + const dragEvent = this.trigger('drag', event, this._target, true, this._intersection, dropTargetIntersection?.object, dropTargetIntersection); + + if (this._targetInstanced) { + if (!dragEvent._defaultPrevented && !this._targetInstanced.position.equals(this._intersection)) { + this._targetInstanced.position.copy(this._intersection); + this._targetInstanced.updateMatrix(); + this._offset.add(this._originalIntersection.sub(this._targetInstanced.position)); + } + } else if (!dragEvent._defaultPrevented && !this._target.position.equals(this._intersection)) { + this._target.position.copy(this._intersection); + this._offset.add(this._originalIntersection.sub(this._target.position)); } - public performDrag(event: PointerEvent, camera: Camera, dropTargetIntersection: IntersectionExt): void { - if (!event.isPrimary) return; - - this._plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(this._plane.normal), this._worldPosition.setFromMatrixPosition(this._targetMatrixWorld)); - this._raycaster.ray.intersectPlane(this._plane, this._intersection); - this._intersection.sub(this._offset).applyMatrix4(this._inverseMatrix); - this._originalIntersection.copy(this._intersection); - - const dragEvent = this.trigger("drag", event, this._target, true, this._intersection, dropTargetIntersection?.object, dropTargetIntersection); - - if (this._targetInstanced) { - if (!dragEvent._defaultPrevented && !this._targetInstanced.position.equals(this._intersection)) { - this._targetInstanced.position.copy(this._intersection); - this._targetInstanced.updateMatrix(); - this._offset.add(this._originalIntersection.sub(this._targetInstanced.position)); - } - } else if (!dragEvent._defaultPrevented && !this._target.position.equals(this._intersection)) { - this._target.position.copy(this._intersection); - this._offset.add(this._originalIntersection.sub(this._target.position)); - } - - this.dropTargetEvent(event, dropTargetIntersection); - } + this.dropTargetEvent(event, dropTargetIntersection); + } - public initDrag(event: PointerEvent, target: Object3D, instanceId: number, intersection: IntersectionExt): void { - if (this.isDragButton(event) && target?.draggable) { - if (instanceId >= 0) { - if ((target as InstancedMesh2).isInstancedMesh2 && (target as InstancedMesh2).__enabledStateHovered) { - this._targetInstanced = (target as InstancedMesh2).instances[instanceId]; - this._target = target; - this._startIntersection = intersection; - } - } else if (target.enabledState) { - this._target = target.dragTarget ?? target; - this._startIntersection = intersection; - } + public initDrag(event: PointerEvent, target: Object3D, instanceId: number, intersection: IntersectionExt): void { + if (this.isDragButton(event) && target?.draggable) { + if (instanceId >= 0) { + if ((target as InstancedMesh2).isInstancedMesh2 && (target as InstancedMesh2).__enabledStateHovered) { + this._targetInstanced = (target as InstancedMesh2).instances[instanceId]; + this._target = target; + this._startIntersection = intersection; } + } else if (target.enabledState) { + this._target = target.dragTarget ?? target; + this._startIntersection = intersection; + } } - - public startDragging(event: PointerEvent, camera: Camera): void { - const currentTarget = this._targetInstanced ?? this._target; - this._target.__dragging = true; - this.isDragging = true; - this._startPosition.copy(currentTarget.position); - this.trigger("dragstart", event, this._target, false, undefined, undefined, this._startIntersection); - - const matrixWorld = currentTarget.matrixWorld; - this._plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(this._plane.normal), this._worldPosition.setFromMatrixPosition(matrixWorld)); - this._raycaster.ray.intersectPlane(this._plane, this._intersection); - this._targetMatrixWorld.copy(matrixWorld); - this._inverseMatrix.copy(currentTarget.parent.matrixWorld).invert(); - this._offset.copy(this._intersection).sub(this._worldPosition.setFromMatrixPosition(matrixWorld)); - - if (currentTarget.findDropTarget) { - this._dataTransfer = {}; - } + } + + public startDragging(event: PointerEvent, camera: Camera): void { + const currentTarget = this._targetInstanced ?? this._target; + this._target.__dragging = true; + this.isDragging = true; + this._startPosition.copy(currentTarget.position); + this.trigger('dragstart', event, this._target, false, undefined, undefined, this._startIntersection); + + const matrixWorld = currentTarget.matrixWorld; + this._plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(this._plane.normal), this._worldPosition.setFromMatrixPosition(matrixWorld)); + this._raycaster.ray.intersectPlane(this._plane, this._intersection); + this._targetMatrixWorld.copy(matrixWorld); + this._inverseMatrix.copy(currentTarget.parent.matrixWorld).invert(); + this._offset.copy(this._intersection).sub(this._worldPosition.setFromMatrixPosition(matrixWorld)); + + if (currentTarget.findDropTarget) { + this._dataTransfer = {}; } + } - public cancelDragging(event: PointerEvent): boolean { - if (this._target) { - const cancelEvent = this.trigger("dragcancel", event, this._target, true, undefined, this._lastDropTarget); - if (cancelEvent._defaultPrevented) return false; - - if (this._targetInstanced) { - if (!this._targetInstanced.position.equals(this._startPosition)) { - this._targetInstanced.position.copy(this._startPosition); - this._targetInstanced.updateMatrix(); - } - } else if (!this._target.position.equals(this._startPosition)) { - this._target.position.copy(this._startPosition); - } - - this.trigger("dragcancel", event, this._lastDropTarget, false, undefined, this._target); + public cancelDragging(event: PointerEvent): boolean { + if (this._target) { + const cancelEvent = this.trigger('dragcancel', event, this._target, true, undefined, this._lastDropTarget); + if (cancelEvent._defaultPrevented) return false; - this.dragEnd(event); - this.clear(); - } - return true; - } - - public stopDragging(event: PointerEvent): boolean { - if (!event.isPrimary) return false; - if (!this.isDragging) { - this._target = undefined; - this._targetInstanced = undefined; - return false; + if (this._targetInstanced) { + if (!this._targetInstanced.position.equals(this._startPosition)) { + this._targetInstanced.position.copy(this._startPosition); + this._targetInstanced.updateMatrix(); } + } else if (!this._target.position.equals(this._startPosition)) { + this._target.position.copy(this._startPosition); + } - this.trigger("drop", event, this._lastDropTarget, false, undefined, this._target); + this.trigger('dragcancel', event, this._lastDropTarget, false, undefined, this._target); - this.dragEnd(event); - this.clear(); - return true; + this.dragEnd(event); + this.clear(); } - - private dragEnd(event: PointerEvent): void { - this.trigger("dragend", event, this._target, false, undefined, this._lastDropTarget); + return true; + } + + public stopDragging(event: PointerEvent): boolean { + if (!event.isPrimary) return false; + if (!this.isDragging) { + this._target = undefined; + this._targetInstanced = undefined; + return false; } - private clear(): void { - this.isDragging = false; - this._target.__dragging = false; - this._target = undefined; - this._targetInstanced = undefined; - this._dataTransfer = undefined; - this._lastDropTarget = undefined; + this.trigger('drop', event, this._lastDropTarget, false, undefined, this._target); + + this.dragEnd(event); + this.clear(); + return true; + } + + private dragEnd(event: PointerEvent): void { + this.trigger('dragend', event, this._target, false, undefined, this._lastDropTarget); + } + + private clear(): void { + this.isDragging = false; + this._target.__dragging = false; + this._target = undefined; + this._targetInstanced = undefined; + this._dataTransfer = undefined; + this._lastDropTarget = undefined; + } + + private trigger(type: keyof InteractionEvents, event: PointerEvent, target: Object3D, cancelable: boolean, position?: Vector3, relatedTarget?: Object3D, intersection?: IntersectionExt): DragEventExt { + if (target) { + const dragEvent = new DragEventExt(event, cancelable, this._dataTransfer, position, relatedTarget, intersection); + target.__eventsDispatcher.dispatchDOMAncestor(type, dragEvent); + return dragEvent; } + } - private trigger(type: keyof InteractionEvents, event: PointerEvent, target: Object3D, cancelable: boolean, position?: Vector3, relatedTarget?: Object3D, intersection?: IntersectionExt): DragEventExt { - if (target) { - const dragEvent = new DragEventExt(event, cancelable, this._dataTransfer, position, relatedTarget, intersection); - target.__eventsDispatcher.dispatchDOMAncestor(type, dragEvent); - return dragEvent; - } - } - - public dropTargetEvent(event: PointerEvent, dropTargetIntersection: IntersectionExt): void { - if (this.findDropTarget) { - const dropTarget = dropTargetIntersection?.object; - const lastDropTarget = this._lastDropTarget; - const dropTargetPoint = dropTargetIntersection?.point; - this._lastDropTarget = dropTarget; + public dropTargetEvent(event: PointerEvent, dropTargetIntersection: IntersectionExt): void { + if (this.findDropTarget) { + const dropTarget = dropTargetIntersection?.object; + const lastDropTarget = this._lastDropTarget; + const dropTargetPoint = dropTargetIntersection?.point; + this._lastDropTarget = dropTarget; - if (dropTarget !== lastDropTarget) { - this.trigger("dragleave", event, lastDropTarget, false, dropTargetPoint, this._target, dropTargetIntersection); - this.trigger("dragenter", event, dropTarget, false, dropTargetPoint, this._target, dropTargetIntersection); - } + if (dropTarget !== lastDropTarget) { + this.trigger('dragleave', event, lastDropTarget, false, dropTargetPoint, this._target, dropTargetIntersection); + this.trigger('dragenter', event, dropTarget, false, dropTargetPoint, this._target, dropTargetIntersection); + } - this.trigger("dragover", event, dropTarget, false, dropTargetPoint, this._target, dropTargetIntersection); - } + this.trigger('dragover', event, dropTarget, false, dropTargetPoint, this._target, dropTargetIntersection); } + } - private isDragButton(event: PointerEvent): boolean { - return event.isPrimary && ((event.pointerType === "mouse" && this.dragButtons.some(x => x === event.button)) || event.pointerType !== "mouse"); - } + private isDragButton(event: PointerEvent): boolean { + return event.isPrimary && ((event.pointerType === 'mouse' && this.dragButtons.some((x) => x === event.button)) || event.pointerType !== 'mouse'); + } } diff --git a/src/events/Events.ts b/src/events/Events.ts index a1e0aa4..bfaf96d 100644 --- a/src/events/Events.ts +++ b/src/events/Events.ts @@ -1,6 +1,6 @@ -import { Camera, Intersection, Object3D, Vector3, WebGLRenderer } from "three"; -import { InstancedMeshEntity } from "../instancedMesh/InstancedMeshEntity.js"; -import { Hitbox } from "./Hitbox.js"; +import { Camera, Intersection, Object3D, Vector3, WebGLRenderer } from 'three'; +import { InstancedMeshEntity } from '../instancedMesh/InstancedMeshEntity.js'; +import { Hitbox } from './Hitbox.js'; export type MiscUpdateEvents = MiscEvents & UpdateEvents; export type Events = InteractionEvents & MiscUpdateEvents; @@ -106,24 +106,24 @@ export interface IntersectionExt extends Intersection { hitbox: Hitbox; } -/** +/** * Represents a custom extended event. */ export class EventExt { /** A boolean value indicating whether or not the event bubbles up through the DOM. */ - public get bubbles() { return this._bubbles } + public get bubbles() { return this._bubbles; } /** A boolean value indicating whether the event is cancelable. */ public readonly cancelable; /** A reference to the currently registered target for the event. This is the object to which the event is currently slated to be sent. It's possible this has been changed along the way through retargeting. */ public currentTarget: T; /** Indicates whether or not the call to event.preventDefault() canceled the event. */ - public get defaultPrevented() { return this._defaultPrevented } + public get defaultPrevented() { return this._defaultPrevented; } /** A reference to the object to which the event was originally dispatched. */ - public get target() { return this._target } + public get target() { return this._target; } /** The time at which the event was created (in milliseconds). By specification, this value is time since epoch—but in reality, browsers' definitions vary. In addition, work is underway to change this to be a DOMHighResTimeStamp instead. */ public readonly timeStamp = performance.now(); /** The case-insensitive name identifying the type of the event. */ - public get type() { return this._type } + public get type() { return this._type; } /** @internal */ public _defaultPrevented: boolean; /** @internal */ public _stoppedImmediatePropagation: boolean; @@ -163,39 +163,39 @@ export class MouseEventExt extends EventExt { /** Original dom event. */ public readonly domEvent: MouseEvent; /** Returns true if the alt key was down when the mouse event was fired. */ - public get altKey() { return this.domEvent.altKey } + public get altKey() { return this.domEvent.altKey; } /** The button number that was pressed (if applicable) when the mouse event was fired. */ - public get button() { return this.domEvent.button } + public get button() { return this.domEvent.button; } /** The buttons being pressed (if any) when the mouse event was fired. */ - public get buttons() { return this.domEvent.buttons } + public get buttons() { return this.domEvent.buttons; } /** The X coordinate of the mouse pointer in local (DOM content) coordinates. */ - public get clientX() { return this.domEvent.clientX } + public get clientX() { return this.domEvent.clientX; } /** The Y coordinate of the mouse pointer in local (DOM content) coordinates. */ - public get clientY() { return this.domEvent.clientY } + public get clientY() { return this.domEvent.clientY; } /** Returns true if the control key was down when the mouse event was fired. */ - public get ctrlKey() { return this.domEvent.ctrlKey } + public get ctrlKey() { return this.domEvent.ctrlKey; } /** Returns true if the meta key was down when the mouse event was fired. */ - public get metaKey() { return this.domEvent.metaKey } + public get metaKey() { return this.domEvent.metaKey; } /** The X coordinate of the pointer relative to the position of the last event. */ - public get movementX() { return this.domEvent.movementX } + public get movementX() { return this.domEvent.movementX; } /** The Y coordinate of the pointer relative to the position of the last event. */ - public get movementY() { return this.domEvent.movementY } + public get movementY() { return this.domEvent.movementY; } /** The X coordinate of the mouse pointer relative to the position of the padding edge of the target node. */ - public get offsetX() { return this.domEvent.offsetX } + public get offsetX() { return this.domEvent.offsetX; } /** The Y coordinate of the mouse pointer relative to the position of the padding edge of the target node. */ - public get offsetY() { return this.domEvent.offsetY } + public get offsetY() { return this.domEvent.offsetY; } /** The X coordinate of the mouse pointer relative to the whole document. */ - public get pageX() { return this.domEvent.pageX } + public get pageX() { return this.domEvent.pageX; } /** The Y coordinate of the mouse pointer relative to the whole document. */ - public get pageY() { return this.domEvent.pageY } + public get pageY() { return this.domEvent.pageY; } /** The secondary target for the event, if there is one. */ public readonly relatedTarget: R; /** The X coordinate of the mouse pointer in global (screen) coordinates. */ - public get screenX() { return this.domEvent.screenX } + public get screenX() { return this.domEvent.screenX; } /** The Y coordinate of the mouse pointer in global (screen) coordinates. */ - public get screenY() { return this.domEvent.screenY } + public get screenY() { return this.domEvent.screenY; } /** Returns true if the shift key was down when the mouse event was fired. */ - public get shiftKey() { return this.domEvent.shiftKey } + public get shiftKey() { return this.domEvent.shiftKey; } /** Returns the intersection information between the mouse event and 3D objects in the scene. */ public readonly intersection: IntersectionExt; @@ -226,25 +226,25 @@ export class MouseEventExt extends EventExt { export class PointerEventExt extends MouseEventExt { declare public readonly domEvent: PointerEvent; /** A unique identifier for the pointer causing the event. */ - public get pointerId() { return this.domEvent.pointerId } + public get pointerId() { return this.domEvent.pointerId; } /** The width (magnitude on the X axis), in CSS pixels, of the contact geometry of the pointer. */ - public get width() { return this.domEvent.width } + public get width() { return this.domEvent.width; } /** The height (magnitude on the Y axis), in CSS pixels, of the contact geometry of the pointer. */ - public get height() { return this.domEvent.height } + public get height() { return this.domEvent.height; } /** The normalized pressure of the pointer input in the range 0 to 1, where 0 and 1 represent the minimum and maximum pressure the hardware is capable of detecting, respectively. */ - public get pressure() { return this.domEvent.pressure } + public get pressure() { return this.domEvent.pressure; } /** The normalized tangential pressure of the pointer input (also known as barrel pressure or cylinder stress) in the range -1 to 1, where 0 is the neutral position of the control. */ - public get tangentialPressure() { return this.domEvent.tangentialPressure } + public get tangentialPressure() { return this.domEvent.tangentialPressure; } /** The plane angle (in degrees, in the range of -90 to 90) between the Y–Z plane and the plane containing both the pointer (e.g. pen stylus) axis and the Y axis. */ - public get tiltX() { return this.domEvent.tiltX } + public get tiltX() { return this.domEvent.tiltX; } /** The plane angle (in degrees, in the range of -90 to 90) between the X–Z plane and the plane containing both the pointer (e.g. pen stylus) axis and the X axis. */ - public get tiltY() { return this.domEvent.tiltY } + public get tiltY() { return this.domEvent.tiltY; } /** The clockwise rotation of the pointer (e.g. pen stylus) around its major axis in degrees, with a value in the range 0 to 359. */ - public get twist() { return this.domEvent.twist } + public get twist() { return this.domEvent.twist; } /** Indicates the device type that caused the event (mouse, pen, touch, etc.). */ - public get pointerType() { return this.domEvent.pointerType } + public get pointerType() { return this.domEvent.pointerType; } /** Indicates if the pointer represents the primary pointer of this pointer type. */ - public get isPrimary() { return this.domEvent.isPrimary } + public get isPrimary() { return this.domEvent.isPrimary; } } /** @@ -281,13 +281,13 @@ export class DragEventExt exte export class WheelEventExt extends MouseEventExt { declare public readonly domEvent: WheelEvent; /* Returns an unsigned long representing the unit of the delta* values' scroll amount. Permitted values are: 0 = pixels, 1 = lines, 2 = pages. */ - public get deltaMode() { return this.domEvent.deltaMode } + public get deltaMode() { return this.domEvent.deltaMode; } /** Returns a double representing the horizontal scroll amount. */ - public get deltaX() { return this.domEvent.deltaX } + public get deltaX() { return this.domEvent.deltaX; } /** Returns a double representing the vertical scroll amount. */ - public get deltaY() { return this.domEvent.deltaY } + public get deltaY() { return this.domEvent.deltaY; } /** Returns a double representing the scroll amount for the z-axis. */ - public get deltaZ() { return this.domEvent.deltaZ } + public get deltaZ() { return this.domEvent.deltaZ; } } /** @@ -315,21 +315,21 @@ export class KeyboardEventExt extends EventExt { /** Original dom event. */ public readonly domEvent: KeyboardEvent; /** Returns a boolean value that is true if the Alt (Option or ⌥ on macOS) key was active when the key event was generated. */ - public get altKey() { return this.domEvent.altKey } + public get altKey() { return this.domEvent.altKey; } /** Returns a string with the code value of the physical key represented by the event. */ - public get code() { return this.domEvent.code } + public get code() { return this.domEvent.code; } /** Returns a boolean value that is true if the Ctrl key was active when the key event was generated. */ - public get ctrlKey() { return this.domEvent.ctrlKey } + public get ctrlKey() { return this.domEvent.ctrlKey; } /** Returns a string representing the key value of the key represented by the event. */ - public get key() { return this.domEvent.key } + public get key() { return this.domEvent.key; } /** Returns a number representing the location of the key on the keyboard or other input device. Visit https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/location for more info. */ - public get location() { return this.domEvent.location } + public get location() { return this.domEvent.location; } /** Returns a boolean value that is true if the Meta key (on Mac keyboards, the ⌘ Command key; on Windows keyboards, the Windows key (⊞)) was active when the key event was generated. */ - public get metaKey() { return this.domEvent.metaKey } + public get metaKey() { return this.domEvent.metaKey; } /** Returns a boolean value that is true if the key is being held down such that it is automatically repeating. */ - public get repeat() { return this.domEvent.repeat } + public get repeat() { return this.domEvent.repeat; } /** Returns a boolean value that is true if the Shift key was active when the key event was generated. */ - public get shiftKey() { return this.domEvent.shiftKey } + public get shiftKey() { return this.domEvent.shiftKey; } /** * @param event Original dom event. diff --git a/src/events/EventsDispatcher.ts b/src/events/EventsDispatcher.ts index 7846a7a..8581409 100644 --- a/src/events/EventsDispatcher.ts +++ b/src/events/EventsDispatcher.ts @@ -1,114 +1,113 @@ -import { InstancedMesh, Object3D } from "three"; -import { applyObject3DRotationPatch, applyObject3DVector3Patch } from "../patch/Object3D.js"; -import { EventExt, Events, InteractionEvents, MiscUpdateEvents, UpdateEvents } from "./Events.js"; -import { EventsCache } from "./MiscEventsManager.js"; +import { InstancedMesh, Object3D } from 'three'; +import { applyObject3DRotationPatch, applyObject3DVector3Patch } from '../patch/Object3D.js'; +import { EventExt, Events, InteractionEvents, MiscUpdateEvents, UpdateEvents } from './Events.js'; +import { EventsCache } from './MiscEventsManager.js'; /** @internal */ export class EventsDispatcher { - public parent: Object3D; - public listeners: { [K in keyof Events]?: ((event?: Events[K]) => void)[] } = {}; + public parent: Object3D; + public listeners: { [K in keyof Events]?: ((event?: Events[K]) => void)[] } = {}; - constructor(parent: Object3D) { - this.parent = parent; - } + constructor(parent: Object3D) { + this.parent = parent; + } - public add(type: K, listener: (event: Events[K]) => void): (event: Events[K]) => void { - if (!this.listeners[type]) { - this.listeners[type] = []; - if (type === "positionchange" || type === "scalechange") { - applyObject3DVector3Patch(this.parent); - } else if (type === "rotationchange") { - applyObject3DRotationPatch(this.parent); - } else if (type === "drop" || type === "dragenter" || type === "dragleave" || type === "dragover") { - this.parent.__isDropTarget = true; - } - } - if (this.listeners[type].indexOf(listener) < 0) { - this.listeners[type].push(listener); - } - EventsCache.push(type, this.parent); - return listener; + public add(type: K, listener: (event: Events[K]) => void): (event: Events[K]) => void { + if (!this.listeners[type]) { + this.listeners[type] = []; + if (type === 'positionchange' || type === 'scalechange') { + applyObject3DVector3Patch(this.parent); + } else if (type === 'rotationchange') { + applyObject3DRotationPatch(this.parent); + } else if (type === 'drop' || type === 'dragenter' || type === 'dragleave' || type === 'dragover') { + this.parent.__isDropTarget = true; + } } - - public has(type: K, listener: (event: Events[K]) => void): boolean { - return this.listeners[type]?.indexOf(listener) > -1; + if (this.listeners[type].indexOf(listener) < 0) { + this.listeners[type].push(listener); } + EventsCache.push(type, this.parent); + return listener; + } - public remove(type: K, listener: (event: Events[K]) => void): void { - const index = this.listeners[type]?.indexOf(listener) ?? -1; - if (index > -1) { - this.listeners[type].splice(index, 1); - if (this.listeners[type].length === 0) { - EventsCache.remove(type, this.parent); - this.parent.__isDropTarget = this.isDropTarget(); - } - } - } + public has(type: K, listener: (event: Events[K]) => void): boolean { + return this.listeners[type]?.indexOf(listener) > -1; + } - private isDropTarget(): boolean { - const l = this.listeners; - return !(this.parent as InstancedMesh).isInstancedMesh && - (l["drop"]?.length > 0 || l["dragenter"]?.length > 0 || l["dragleave"]?.length > 0 || l["dragover"]?.length > 0); + public remove(type: K, listener: (event: Events[K]) => void): void { + const index = this.listeners[type]?.indexOf(listener) ?? -1; + if (index > -1) { + this.listeners[type].splice(index, 1); + if (this.listeners[type].length === 0) { + EventsCache.remove(type, this.parent); + this.parent.__isDropTarget = this.isDropTarget(); + } } + } - public dispatchDOM(type: K, event: InteractionEvents[K]): void { - event._bubbles = false; - event._stoppedImmediatePropagation = false; - event._defaultPrevented = false; - event._type = type; - event._target = this.parent; - this.executeDOM(type, event); - } + private isDropTarget(): boolean { + const l = this.listeners; + return !(this.parent as InstancedMesh).isInstancedMesh + && (l['drop']?.length > 0 || l['dragenter']?.length > 0 || l['dragleave']?.length > 0 || l['dragover']?.length > 0); + } - public dispatchDOMAncestor(type: K, event: InteractionEvents[K]): void { - let target = this.parent; - event._bubbles = true; - event._stoppedImmediatePropagation = false; - event._defaultPrevented = false; - event._type = type; - event._target = target; - while (target && event._bubbles) { - target.__eventsDispatcher.executeDOM(type, event); - target = target.parent; - } - } + public dispatchDOM(type: K, event: InteractionEvents[K]): void { + event._bubbles = false; + event._stoppedImmediatePropagation = false; + event._defaultPrevented = false; + event._type = type; + event._target = this.parent; + this.executeDOM(type, event); + } - private executeDOM(type: K, event: InteractionEvents[K]): void { - if (!this.listeners[type]) return; - const target = event.currentTarget = this.parent; - for (const callback of this.listeners[type]) { - if (event._stoppedImmediatePropagation) break; - callback.call(target, event as any); - } + public dispatchDOMAncestor(type: K, event: InteractionEvents[K]): void { + let target = this.parent; + event._bubbles = true; + event._stoppedImmediatePropagation = false; + event._defaultPrevented = false; + event._type = type; + event._target = target; + while (target && event._bubbles) { + target.__eventsDispatcher.executeDOM(type, event); + target = target.parent; } + } - public dispatch(type: T, event?: MiscUpdateEvents[T]): void { - if (!this.listeners[type]) return; - for (const callback of this.listeners[type]) { - callback.call(this.parent, event as any); - } + private executeDOM(type: K, event: InteractionEvents[K]): void { + if (!this.listeners[type]) return; + const target = event.currentTarget = this.parent; + for (const callback of this.listeners[type]) { + if (event._stoppedImmediatePropagation) break; + callback.call(target, event as any); } + } - public dispatchDescendant(type: T, event?: UpdateEvents[T]): void { - const target = this.parent; - target.__eventsDispatcher.dispatch(type, event as any); - if (!target.children) return; - for (const child of target.children) { - child.__eventsDispatcher.dispatchDescendant(type, event); - } + public dispatch(type: T, event?: MiscUpdateEvents[T]): void { + if (!this.listeners[type]) return; + for (const callback of this.listeners[type]) { + callback.call(this.parent, event as any); } + } - public dispatchManual(type: T, event?: Events[T]): void { - if ((event as EventExt)?.cancelable !== undefined) { - return this.dispatchDOM(type as keyof InteractionEvents, event as any); - } - this.dispatch(type as keyof MiscUpdateEvents, event as any); + public dispatchDescendant(type: T, event?: UpdateEvents[T]): void { + const target = this.parent; + target.__eventsDispatcher.dispatch(type, event as any); + if (!target.children) return; + for (const child of target.children) { + child.__eventsDispatcher.dispatchDescendant(type, event); } + } - public dispatchAncestorManual(type: T, event?: Events[T]): void { - if ((event as EventExt)?.cancelable !== undefined) { - this.dispatchDOMAncestor(type as keyof InteractionEvents, event as any); - } + public dispatchManual(type: T, event?: Events[T]): void { + if ((event as EventExt)?.cancelable !== undefined) { + return this.dispatchDOM(type as keyof InteractionEvents, event as any); } + this.dispatch(type as keyof MiscUpdateEvents, event as any); + } + public dispatchAncestorManual(type: T, event?: Events[T]): void { + if ((event as EventExt)?.cancelable !== undefined) { + this.dispatchDOMAncestor(type as keyof InteractionEvents, event as any); + } + } } diff --git a/src/events/Hitbox.ts b/src/events/Hitbox.ts index 9fb0b00..535a0e4 100644 --- a/src/events/Hitbox.ts +++ b/src/events/Hitbox.ts @@ -1,4 +1,4 @@ -import { BufferGeometry, Mesh, MeshBasicMaterial } from "three"; +import { BufferGeometry, Mesh, MeshBasicMaterial } from 'three'; const sharedMaterial = new MeshBasicMaterial(); @@ -6,7 +6,7 @@ const sharedMaterial = new MeshBasicMaterial(); * Hitbox for collision detection. */ export class Hitbox extends Mesh { - constructor(geometry: BufferGeometry) { - super(geometry, sharedMaterial); - } + constructor(geometry: BufferGeometry) { + super(geometry, sharedMaterial); + } } diff --git a/src/events/InteractionEventsQueue.ts b/src/events/InteractionEventsQueue.ts index 797b4ed..4a5d650 100644 --- a/src/events/InteractionEventsQueue.ts +++ b/src/events/InteractionEventsQueue.ts @@ -3,36 +3,36 @@ * Syncronize DOM events with the frame generation, discarding ripetitive pointermove event. */ export class InteractionEventsQueue { - public multitouch: boolean; - private _items: Event[] = []; + public multitouch: boolean; + private _items: Event[] = []; - public enqueue(event: Event): void { - if (!this.multitouch && (event as PointerEvent).isPrimary === false) return; - if (this.isValidEvent(event)) { - this._items.push(event); - } - } + public enqueue(event: Event): void { + if (!this.multitouch && (event as PointerEvent).isPrimary === false) return; + if (this.isValidEvent(event)) { + this._items.push(event); + } + } - private isValidEvent(event: Event): boolean { - if (event.type === "pointermove") { - for (let i = this._items.length - 1; i >= 0; i--) { - const item = this._items[i] as PointerEvent; - if (item.pointerId === (event as PointerEvent).pointerId) { - const type = item.type; - if (type === "pointermove") { - this._items[i] = event; - return false; - } - if (type === "pointerdown" || type === "pointerout" || type === "pointerover" || type === "pointerup") break; - } - } + private isValidEvent(event: Event): boolean { + if (event.type === 'pointermove') { + for (let i = this._items.length - 1; i >= 0; i--) { + const item = this._items[i] as PointerEvent; + if (item.pointerId === (event as PointerEvent).pointerId) { + const type = item.type; + if (type === 'pointermove') { + this._items[i] = event; + return false; + } + if (type === 'pointerdown' || type === 'pointerout' || type === 'pointerover' || type === 'pointerup') break; + } } - return true; - } + } + return true; + } - public dequeue(): Event[] { - const items = this._items; - this._items = []; - return items; - } + public dequeue(): Event[] { + const items = this._items; + this._items = []; + return items; + } } diff --git a/src/events/InteractionManager.ts b/src/events/InteractionManager.ts index d9bf8f6..e6c5b49 100644 --- a/src/events/InteractionManager.ts +++ b/src/events/InteractionManager.ts @@ -1,325 +1,324 @@ -import { Object3D, WebGLRenderer } from "three"; -import { RenderManager } from "../rendering/RenderManager.js"; -import { CursorHandler } from "./CursorManager.js"; -import { DragAndDropManager } from "./DragAndDropManager.js"; -import { InteractionEvents, IntersectionExt, KeyboardEventExt, PointerEventExt, PointerIntersectionEvent, WheelEventExt } from "./Events.js"; -import { InteractionEventsQueue } from "./InteractionEventsQueue.js"; -import { RaycasterManager } from "./RaycasterManager.js"; +import { Object3D, WebGLRenderer } from 'three'; +import { RenderManager } from '../rendering/RenderManager.js'; +import { CursorHandler } from './CursorManager.js'; +import { DragAndDropManager } from './DragAndDropManager.js'; +import { InteractionEvents, IntersectionExt, KeyboardEventExt, PointerEventExt, PointerIntersectionEvent, WheelEventExt } from './Events.js'; +import { InteractionEventsQueue } from './InteractionEventsQueue.js'; +import { RaycasterManager } from './RaycasterManager.js'; /** @internal */ export class InteractionManager { - public raycasterManager: RaycasterManager; - public cursorManager: CursorHandler; - public dragManager: DragAndDropManager; - public queue = new InteractionEventsQueue(); - private _intersection: { [x: string]: IntersectionExt } = {}; - private _intersectionDropTarget: IntersectionExt; - private _renderManager: RenderManager; - private _primaryIdentifier: number; - private _pointerDownTarget: { [x: string]: Object3D } = {}; - private _lastPointerDown: { [x: string]: PointerEvent } = {}; - private _lastPointerMove: { [x: string]: PointerEvent } = {}; - private _lastClickTarget: Object3D; - private _lastClickTimeStamp: DOMHighResTimeStamp; - private _lastHoveredTarget: { [x: string]: Object3D } = {}; - private _primaryRaycasted: boolean; - private _mouseDetected = false; - private _isTapping = false; - - constructor(renderManager: RenderManager) { - this._renderManager = renderManager; - const renderer = renderManager.renderer; - this.registerRenderer(renderer); - this.cursorManager = new CursorHandler(renderer.domElement); - this.raycasterManager = new RaycasterManager(renderManager); - this.dragManager = new DragAndDropManager(this.raycasterManager.raycaster); + public raycasterManager: RaycasterManager; + public cursorManager: CursorHandler; + public dragManager: DragAndDropManager; + public queue = new InteractionEventsQueue(); + private _intersection: { [x: string]: IntersectionExt } = {}; + private _intersectionDropTarget: IntersectionExt; + private _renderManager: RenderManager; + private _primaryIdentifier: number; + private _pointerDownTarget: { [x: string]: Object3D } = {}; + private _lastPointerDown: { [x: string]: PointerEvent } = {}; + private _lastPointerMove: { [x: string]: PointerEvent } = {}; + private _lastClickTarget: Object3D; + private _lastClickTimeStamp: DOMHighResTimeStamp; + private _lastHoveredTarget: { [x: string]: Object3D } = {}; + private _primaryRaycasted: boolean; + private _mouseDetected = false; + private _isTapping = false; + + constructor(renderManager: RenderManager) { + this._renderManager = renderManager; + const renderer = renderManager.renderer; + this.registerRenderer(renderer); + this.cursorManager = new CursorHandler(renderer.domElement); + this.raycasterManager = new RaycasterManager(renderManager); + this.dragManager = new DragAndDropManager(this.raycasterManager.raycaster); + } + + public registerRenderer(renderer: WebGLRenderer): void { + renderer.domElement.addEventListener('pointermove', (e) => this._mouseDetected = e.pointerType === 'mouse'); + renderer.domElement.addEventListener('pointerdown', (e) => this._isTapping = e.pointerType !== 'mouse' && e.isPrimary); + renderer.domElement.addEventListener('pointerup', (e) => this._isTapping &&= !e.isPrimary); + renderer.domElement.addEventListener('pointercancel', (e) => this._isTapping &&= !e.isPrimary); + this.bindEvents(renderer); + } + + private bindEvents(renderer: WebGLRenderer): void { + const domElement = renderer.domElement; + domElement.addEventListener('pointerenter', this.enqueue.bind(this)); + domElement.addEventListener('pointerleave', this.enqueue.bind(this)); + domElement.addEventListener('pointerdown', this.enqueue.bind(this)); + domElement.addEventListener('pointermove', this.enqueue.bind(this)); + document.addEventListener('pointerup', this.enqueue.bind(this)); + document.addEventListener('pointercancel', this.enqueue.bind(this)); + domElement.addEventListener('wheel', this.enqueue.bind(this), { passive: true }); + domElement.tabIndex = -1; + domElement.addEventListener('keydown', this.enqueue.bind(this)); + domElement.addEventListener('keyup', this.enqueue.bind(this)); + } + + private enqueue(event: Event): void { + this.queue.enqueue(event); + } + + public update(): void { + this._renderManager.update(); + this._primaryRaycasted = false; + for (const event of this.queue.dequeue()) { + this.computeQueuedEvent(event); } - - public registerRenderer(renderer: WebGLRenderer): void { - renderer.domElement.addEventListener("pointermove", (e) => this._mouseDetected = e.pointerType === "mouse"); - renderer.domElement.addEventListener("pointerdown", (e) => this._isTapping = e.pointerType !== "mouse" && e.isPrimary); - renderer.domElement.addEventListener("pointerup", (e) => this._isTapping &&= !e.isPrimary); - renderer.domElement.addEventListener("pointercancel", (e) => this._isTapping &&= !e.isPrimary); - this.bindEvents(renderer); - } - - private bindEvents(renderer: WebGLRenderer): void { - const domElement = renderer.domElement; - domElement.addEventListener("pointerenter", this.enqueue.bind(this)); - domElement.addEventListener("pointerleave", this.enqueue.bind(this)); - domElement.addEventListener("pointerdown", this.enqueue.bind(this)); - domElement.addEventListener("pointermove", this.enqueue.bind(this)); - document.addEventListener("pointerup", this.enqueue.bind(this)); - document.addEventListener("pointercancel", this.enqueue.bind(this)); - domElement.addEventListener("wheel", this.enqueue.bind(this), { passive: true }); - domElement.tabIndex = -1; - domElement.addEventListener("keydown", this.enqueue.bind(this)); - domElement.addEventListener("keyup", this.enqueue.bind(this)); + this.pointerIntersection(); + const hoveredObj = this._intersection[this._primaryIdentifier]?.object ?? this._renderManager.activeScene; + this.cursorManager.update(this.dragManager.target, hoveredObj, this._intersectionDropTarget?.object); + } + + private raycastScene(event: PointerEvent): void { + this.handlePrimaryIdentifier(event); + if (this.dragManager.isDragging) { + if (!event.isPrimary) return; + const intersections = this.raycasterManager.getIntersections(event, true, this.dragManager.findDropTarget ? this.dragManager.target : undefined); + this.setDropTarget(intersections); + } else { + const intersections = this.raycasterManager.getIntersections(event, false); + this._intersection[event.pointerId] = intersections[0]; + const scene = this._renderManager.activeScene; + if (scene && event.isPrimary) { + scene.intersections = intersections; + } } - - private enqueue(event: Event): void { - this.queue.enqueue(event); + } + + private handlePrimaryIdentifier(event: PointerEvent): void { + if (event.isPrimary) { + this._primaryRaycasted = true; + if (this._primaryIdentifier !== event.pointerId) { + this.clearPointerId(this._primaryIdentifier); + this._primaryIdentifier = event.pointerId; + } } + } - public update(): void { - this._renderManager.update(); - this._primaryRaycasted = false; - for (const event of this.queue.dequeue()) { - this.computeQueuedEvent(event); - } - this.pointerIntersection(); - const hoveredObj = this._intersection[this._primaryIdentifier]?.object ?? this._renderManager.activeScene; - this.cursorManager.update(this.dragManager.target, hoveredObj, this._intersectionDropTarget?.object); - } - - private raycastScene(event: PointerEvent): void { - this.handlePrimaryIdentifier(event); - if (this.dragManager.isDragging) { - if (!event.isPrimary) return; - const intersections = this.raycasterManager.getIntersections(event, true, this.dragManager.findDropTarget ? this.dragManager.target : undefined); - this.setDropTarget(intersections); - } else { - const intersections = this.raycasterManager.getIntersections(event, false); - this._intersection[event.pointerId] = intersections[0]; - const scene = this._renderManager.activeScene; - if (scene && event.isPrimary) { - scene.intersections = intersections; - } - } - } - - private handlePrimaryIdentifier(event: PointerEvent): void { - if (event.isPrimary) { - this._primaryRaycasted = true; - if (this._primaryIdentifier !== event.pointerId) { - this.clearPointerId(this._primaryIdentifier); - this._primaryIdentifier = event.pointerId; - } - } + private triggerPointer(type: keyof InteractionEvents, event: PointerEvent, target: Object3D, relatedTarget?: Object3D): void { + if (target?.enabledState) { + const pointerEvent = new PointerEventExt(event, this._intersection[event.pointerId], relatedTarget); + target.__eventsDispatcher.dispatchDOM(type, pointerEvent); } + } - private triggerPointer(type: keyof InteractionEvents, event: PointerEvent, target: Object3D, relatedTarget?: Object3D): void { - if (target?.enabledState) { - const pointerEvent = new PointerEventExt(event, this._intersection[event.pointerId], relatedTarget); - target.__eventsDispatcher.dispatchDOM(type, pointerEvent); - } + private triggerAncestorPointer(type: keyof InteractionEvents, event: PointerEvent, target: Object3D, relatedTarget?: Object3D, cancelable?: boolean): PointerEventExt { + if (target?.enabledState) { + const pointerEvent = new PointerEventExt(event, this._intersection[event.pointerId], relatedTarget, cancelable); + target.__eventsDispatcher.dispatchDOMAncestor(type, pointerEvent); + return pointerEvent; } + } - private triggerAncestorPointer(type: keyof InteractionEvents, event: PointerEvent, target: Object3D, relatedTarget?: Object3D, cancelable?: boolean): PointerEventExt { - if (target?.enabledState) { - const pointerEvent = new PointerEventExt(event, this._intersection[event.pointerId], relatedTarget, cancelable); - target.__eventsDispatcher.dispatchDOMAncestor(type, pointerEvent); - return pointerEvent; - } + private triggerAncestorWheel(event: WheelEvent, intersection: IntersectionExt): void { + const target = intersection?.object ?? this._renderManager.activeScene; + if (target?.enabledState) { + const wheelEvent = new WheelEventExt(event, intersection); + target.__eventsDispatcher.dispatchDOMAncestor('wheel', wheelEvent); } - - private triggerAncestorWheel(event: WheelEvent, intersection: IntersectionExt): void { - const target = intersection?.object ?? this._renderManager.activeScene; - if (target?.enabledState) { - const wheelEvent = new WheelEventExt(event, intersection); - target.__eventsDispatcher.dispatchDOMAncestor("wheel", wheelEvent); - } + } + + private triggerAncestorKeyboard(type: keyof InteractionEvents, event: KeyboardEvent, cancelable: boolean): KeyboardEventExt { + const scene = this._renderManager.activeScene; + const target = scene.focusedObject ?? scene; + if (target.enabledState) { + const keyboardEvent = new KeyboardEventExt(event, cancelable); + target.__eventsDispatcher.dispatchDOMAncestor(type, keyboardEvent); + return keyboardEvent; } - - private triggerAncestorKeyboard(type: keyof InteractionEvents, event: KeyboardEvent, cancelable: boolean): KeyboardEventExt { - const scene = this._renderManager.activeScene; - const target = scene.focusedObject ?? scene; - if (target.enabledState) { - const keyboardEvent = new KeyboardEventExt(event, cancelable); - target.__eventsDispatcher.dispatchDOMAncestor(type, keyboardEvent); - return keyboardEvent; - } + } + + private computeQueuedEvent(event: Event): void { + switch (event.type) { + case 'pointerenter': return this.pointerEnter(event as PointerEvent); + case 'pointerleave': return this.pointerLeave(event as PointerEvent); + case 'pointermove': return this.pointerMove(event as PointerEvent); + case 'pointerdown': return this.pointerDown(event as PointerEvent); + case 'pointerup': + case 'pointercancel': return this.pointerUp(event as PointerEvent); + case 'wheel': return this.wheel(event as WheelEvent); + case 'keydown': return this.keyDown(event as KeyboardEvent); + case 'keyup': return this.keyUp(event as KeyboardEvent); + default: console.error('Error: computeEvent failed.'); } + } - private computeQueuedEvent(event: Event): void { - switch (event.type) { - case "pointerenter": return this.pointerEnter(event as PointerEvent); - case "pointerleave": return this.pointerLeave(event as PointerEvent); - case "pointermove": return this.pointerMove(event as PointerEvent); - case "pointerdown": return this.pointerDown(event as PointerEvent); - case "pointerup": - case "pointercancel": return this.pointerUp(event as PointerEvent); - case "wheel": return this.wheel(event as WheelEvent); - case "keydown": return this.keyDown(event as KeyboardEvent); - case "keyup": return this.keyUp(event as KeyboardEvent); - default: console.error("Error: computeEvent failed."); - } - } + public isMainClick(event: PointerEvent): boolean { + return event.isPrimary && ((event.pointerType === 'mouse' && event.button === 0) || event.pointerType !== 'mouse'); + } - public isMainClick(event: PointerEvent): boolean { - return event.isPrimary && ((event.pointerType === "mouse" && event.button === 0) || event.pointerType !== "mouse"); + private pointerDown(event: PointerEvent): void { + if (event.pointerType !== 'mouse') { + this.pointerMove(event); } - private pointerDown(event: PointerEvent): void { - if (event.pointerType !== "mouse") { - this.pointerMove(event); - } - - const intersection = this._intersection[event.pointerId]; - const target = intersection?.object ?? this._renderManager.activeScene; - - if (target === undefined) return; - - const pointerDownEvent = this.triggerAncestorPointer("pointerdown", event, target, undefined, true); - this._lastPointerDown[event.pointerId] = event; - this._pointerDownTarget[event.pointerId] = target; + const intersection = this._intersection[event.pointerId]; + const target = intersection?.object ?? this._renderManager.activeScene; - if (this.isMainClick(event)) { - target.__clicking = true; - } + if (target === undefined) return; - if (!pointerDownEvent?._defaultPrevented && event.isPrimary) { - const scene = this._renderManager.activeScene; - const firstFocusable = target?.firstFocusable; - if (firstFocusable || scene?.blurOnClickOut) { - scene.focus(firstFocusable); - } - } + const pointerDownEvent = this.triggerAncestorPointer('pointerdown', event, target, undefined, true); + this._lastPointerDown[event.pointerId] = event; + this._pointerDownTarget[event.pointerId] = target; - this.dragManager.initDrag(event, target, intersection?.instanceId, intersection); + if (this.isMainClick(event)) { + target.__clicking = true; } - private pointerEnter(event: PointerEvent): void { - this.raycasterManager.pointerOnCanvas = true; + if (!pointerDownEvent?._defaultPrevented && event.isPrimary) { + const scene = this._renderManager.activeScene; + const firstFocusable = target?.firstFocusable; + if (firstFocusable || scene?.blurOnClickOut) { + scene.focus(firstFocusable); + } } - private pointerLeave(event: PointerEvent): void { - this.raycasterManager.pointerOnCanvas = false; - this._lastPointerMove[event.pointerId] = event; + this.dragManager.initDrag(event, target, intersection?.instanceId, intersection); + } + + private pointerEnter(event: PointerEvent): void { + this.raycasterManager.pointerOnCanvas = true; + } + + private pointerLeave(event: PointerEvent): void { + this.raycasterManager.pointerOnCanvas = false; + this._lastPointerMove[event.pointerId] = event; + } + + private pointerMove(event: PointerEvent): void { + this._lastPointerMove[event.pointerId] = event; + this.raycastScene(event); + const camera = this._renderManager.activeView?.camera; + if (this.dragManager.needsDrag(event, camera)) { + this.dragManager.performDrag(event, camera, this._intersectionDropTarget); + } else { + this.pointerOutOver(event); + const target = this._intersection[event.pointerId]?.object ?? this._renderManager.activeScene; + this.triggerAncestorPointer('pointermove', event, target); } + } - private pointerMove(event: PointerEvent): void { - this._lastPointerMove[event.pointerId] = event; + private pointerIntersection(): void { + if (this.dragManager.isDragging) { + if (!this._primaryRaycasted && this.dragManager.findDropTarget && this._renderManager.activeScene?.continuousRaycastingDropTarget) { + const event = this._lastPointerMove[this._primaryIdentifier] || this._lastPointerDown[this._primaryIdentifier]; this.raycastScene(event); - const camera = this._renderManager.activeView?.camera; - if (this.dragManager.needsDrag(event, camera)) { - this.dragManager.performDrag(event, camera, this._intersectionDropTarget); - } else { - this.pointerOutOver(event); - const target = this._intersection[event.pointerId]?.object ?? this._renderManager.activeScene; - this.triggerAncestorPointer("pointermove", event, target); - } + this.dragManager.dropTargetEvent(event, this._intersectionDropTarget); + } + } else if (this._renderManager.hoveredScene?.continuousRaycasting && (this._mouseDetected || this._isTapping)) { + if (!this._primaryRaycasted) { + const event = this._lastPointerMove[this._primaryIdentifier] || this._lastPointerDown[this._primaryIdentifier]; + this.raycastScene(event); + this.pointerOutOver(event); + } + const intersection = this._intersection[this._primaryIdentifier]; + const target = intersection?.object ?? this._renderManager.hoveredScene; + if (target?.enabledState) { + target.__eventsDispatcher.dispatchDOMAncestor('pointerintersection', new PointerIntersectionEvent(intersection)); + } } + } - private pointerIntersection(): void { - if (this.dragManager.isDragging) { - if (!this._primaryRaycasted && this.dragManager.findDropTarget && this._renderManager.activeScene?.continuousRaycastingDropTarget) { - const event = this._lastPointerMove[this._primaryIdentifier] || this._lastPointerDown[this._primaryIdentifier]; - this.raycastScene(event); - this.dragManager.dropTargetEvent(event, this._intersectionDropTarget); - } - } else if (this._renderManager.hoveredScene?.continuousRaycasting && (this._mouseDetected || this._isTapping)) { - if (!this._primaryRaycasted) { - const event = this._lastPointerMove[this._primaryIdentifier] || this._lastPointerDown[this._primaryIdentifier]; - this.raycastScene(event); - this.pointerOutOver(event); - } - const intersection = this._intersection[this._primaryIdentifier]; - const target = intersection?.object ?? this._renderManager.hoveredScene; - if (target?.enabledState) { - target.__eventsDispatcher.dispatchDOMAncestor("pointerintersection", new PointerIntersectionEvent(intersection)); - } - } - } + private wheel(event: WheelEvent): void { + this.triggerAncestorWheel(event, this._intersection[this._primaryIdentifier]); + } - private wheel(event: WheelEvent): void { - this.triggerAncestorWheel(event, this._intersection[this._primaryIdentifier]); - } + private pointerOutOver(event: PointerEvent): void { + const target = this._intersection[event.pointerId]?.object ?? this._renderManager.activeScene; + const lastHoveredTarget = this._lastHoveredTarget[event.pointerId]; - private pointerOutOver(event: PointerEvent): void { - const target = this._intersection[event.pointerId]?.object ?? this._renderManager.activeScene; - const lastHoveredTarget = this._lastHoveredTarget[event.pointerId]; - - if (target !== lastHoveredTarget) { - if (event.isPrimary) { - if (lastHoveredTarget) { - lastHoveredTarget.__hovered = false; - } - target.__hovered = true; - } - this._lastHoveredTarget[event.pointerId] = target; - this.triggerAncestorPointer("pointerout", event, lastHoveredTarget, target); - this.triggerPointer("pointerleave", event, lastHoveredTarget, target); - this.triggerAncestorPointer("pointerover", event, target, lastHoveredTarget); - this.triggerPointer("pointerenter", event, target, lastHoveredTarget); + if (target !== lastHoveredTarget) { + if (event.isPrimary) { + if (lastHoveredTarget) { + lastHoveredTarget.__hovered = false; } + target.__hovered = true; + } + this._lastHoveredTarget[event.pointerId] = target; + this.triggerAncestorPointer('pointerout', event, lastHoveredTarget, target); + this.triggerPointer('pointerleave', event, lastHoveredTarget, target); + this.triggerAncestorPointer('pointerover', event, target, lastHoveredTarget); + this.triggerPointer('pointerenter', event, target, lastHoveredTarget); } + } - private pointerUp(event: PointerEvent): void { - const startTarget = this._pointerDownTarget[event.pointerId]; - if (!startTarget && !this.raycasterManager.pointerOnCanvas) return; - const target = this._intersection[event.pointerId]?.object ?? this._renderManager.activeScene; - - if (event.pointerType !== "mouse") { - target.__hovered = false; - this.triggerAncestorPointer("pointerout", event, target); - this.triggerPointer("pointerleave", event, target); - } - - if (this.dragManager.stopDragging(event)) { - this.setDropTarget([]); - } else { - this.triggerAncestorPointer("pointerup", event, target, startTarget); - if (target === startTarget) { - this.triggerAncestorPointer("click", event, target); - } - } - - if (event.type === "pointerup") { - this.dblClick(event, target); - } - - if (startTarget && this.isMainClick(event)) { - startTarget.__clicking = false; - } + private pointerUp(event: PointerEvent): void { + const startTarget = this._pointerDownTarget[event.pointerId]; + if (!startTarget && !this.raycasterManager.pointerOnCanvas) return; + const target = this._intersection[event.pointerId]?.object ?? this._renderManager.activeScene; - if (event.pointerId !== this._primaryIdentifier) { - this.clearPointerId(event.pointerId); - } + if (event.pointerType !== 'mouse') { + target.__hovered = false; + this.triggerAncestorPointer('pointerout', event, target); + this.triggerPointer('pointerleave', event, target); } - private clearPointerId(pointerId: number): void { - delete this._intersection[pointerId]; - delete this._pointerDownTarget[pointerId]; - delete this._lastPointerDown[pointerId]; - delete this._lastPointerMove[pointerId]; - delete this._lastHoveredTarget[pointerId]; + if (this.dragManager.stopDragging(event)) { + this.setDropTarget([]); + } else { + this.triggerAncestorPointer('pointerup', event, target, startTarget); + if (target === startTarget) { + this.triggerAncestorPointer('click', event, target); + } } - private dblClick(event: PointerEvent, target: Object3D): void { - if (this.isMainClick(event)) { - if (target === this._lastClickTarget && event.timeStamp - this._lastClickTimeStamp <= 300) { - this.triggerAncestorPointer("dblclick", event, target); - this._lastClickTimeStamp = undefined; - } else { - this._lastClickTarget = target; - this._lastClickTimeStamp = event.timeStamp; - } - } + if (event.type === 'pointerup') { + this.dblClick(event, target); } - private keyDown(event: KeyboardEvent): void { - const keyDownEvent = this.triggerAncestorKeyboard("keydown", event, true); - if (!keyDownEvent?._defaultPrevented) { - if (event.key === "Escape" || event.key === "Esc") { - if (this.dragManager.cancelDragging(this._lastPointerMove[this._primaryIdentifier])) { - this.setDropTarget([]); - } - } - } + if (startTarget && this.isMainClick(event)) { + startTarget.__clicking = false; } - private keyUp(event: KeyboardEvent): void { - this.triggerAncestorKeyboard("keyup", event, false); + if (event.pointerId !== this._primaryIdentifier) { + this.clearPointerId(event.pointerId); } - - private setDropTarget(intersections: IntersectionExt[]): void { - const int = intersections[0]; - this._intersectionDropTarget = (int?.object.__isDropTarget && int.object.enabledState) ? int : undefined; - const scene = this._renderManager.activeScene; - if (scene) { - scene.intersectionsDropTarget = intersections; + } + + private clearPointerId(pointerId: number): void { + delete this._intersection[pointerId]; + delete this._pointerDownTarget[pointerId]; + delete this._lastPointerDown[pointerId]; + delete this._lastPointerMove[pointerId]; + delete this._lastHoveredTarget[pointerId]; + } + + private dblClick(event: PointerEvent, target: Object3D): void { + if (this.isMainClick(event)) { + if (target === this._lastClickTarget && event.timeStamp - this._lastClickTimeStamp <= 300) { + this.triggerAncestorPointer('dblclick', event, target); + this._lastClickTimeStamp = undefined; + } else { + this._lastClickTarget = target; + this._lastClickTimeStamp = event.timeStamp; + } + } + } + + private keyDown(event: KeyboardEvent): void { + const keyDownEvent = this.triggerAncestorKeyboard('keydown', event, true); + if (!keyDownEvent?._defaultPrevented) { + if (event.key === 'Escape' || event.key === 'Esc') { + if (this.dragManager.cancelDragging(this._lastPointerMove[this._primaryIdentifier])) { + this.setDropTarget([]); } + } } - + } + + private keyUp(event: KeyboardEvent): void { + this.triggerAncestorKeyboard('keyup', event, false); + } + + private setDropTarget(intersections: IntersectionExt[]): void { + const int = intersections[0]; + this._intersectionDropTarget = (int?.object.__isDropTarget && int.object.enabledState) ? int : undefined; + const scene = this._renderManager.activeScene; + if (scene) { + scene.intersectionsDropTarget = intersections; + } + } } diff --git a/src/events/MiscEventsManager.ts b/src/events/MiscEventsManager.ts index bf776e9..6388f9b 100644 --- a/src/events/MiscEventsManager.ts +++ b/src/events/MiscEventsManager.ts @@ -1,74 +1,73 @@ -import { Camera, Object3D, Scene } from "three"; -import { Events, MiscEvents } from "./Events.js"; +import { Camera, Object3D, Scene } from 'three'; +import { Events, MiscEvents } from './Events.js'; type SceneEventsCache = { [x: string]: Set }; /** @internal */ export class EventsCache { - private static _allowedEventsSet = new Set(["viewportresize", "beforeanimate", "animate", "afteranimate"] as (keyof MiscEvents)[]); - private static _events: { [x: number]: SceneEventsCache } = {}; + private static _allowedEventsSet = new Set(['viewportresize', 'beforeanimate', 'animate', 'afteranimate'] as (keyof MiscEvents)[]); + private static _events: { [x: number]: SceneEventsCache } = {}; - public static push(type: keyof Events, target: Object3D): void { - const scene = target.scene; - if (scene && this._allowedEventsSet.has(type)) { - this.pushScene(scene, type, target); - } - } + public static push(type: keyof Events, target: Object3D): void { + const scene = target.scene; + if (scene && this._allowedEventsSet.has(type)) { + this.pushScene(scene, type, target); + } + } - public static update(target: Object3D): void { - this.updateEvent(target, "viewportresize"); - this.updateEvent(target, "beforeanimate"); - this.updateEvent(target, "animate"); - this.updateEvent(target, "afteranimate"); - } + public static update(target: Object3D): void { + this.updateEvent(target, 'viewportresize'); + this.updateEvent(target, 'beforeanimate'); + this.updateEvent(target, 'animate'); + this.updateEvent(target, 'afteranimate'); + } - private static updateEvent(target: Object3D, name: keyof Events): void { - if (target.__eventsDispatcher.listeners[name]?.length > 0) { - this.pushScene(target.scene, name, target); - } - } + private static updateEvent(target: Object3D, name: keyof Events): void { + if (target.__eventsDispatcher.listeners[name]?.length > 0) { + this.pushScene(target.scene, name, target); + } + } - private static pushScene(scene: Scene, type: keyof Events, target: Object3D): void { - const sceneCache = this._events[scene.id] ?? (this._events[scene.id] = {}); - const eventCache = sceneCache[type] ?? (sceneCache[type] = new Set()); - eventCache.add(target); - } + private static pushScene(scene: Scene, type: keyof Events, target: Object3D): void { + const sceneCache = this._events[scene.id] ?? (this._events[scene.id] = {}); + const eventCache = sceneCache[type] ?? (sceneCache[type] = new Set()); + eventCache.add(target); + } - public static removeAll(target: Object3D): void { - const sceneCache = this._events[target.scene?.id]; - if (sceneCache) { - for (const key in sceneCache) { - const eventCache = sceneCache[key]; - eventCache.delete(target); - } + public static removeAll(target: Object3D): void { + const sceneCache = this._events[target.scene?.id]; + if (sceneCache) { + for (const key in sceneCache) { + const eventCache = sceneCache[key]; + eventCache.delete(target); } - } + } + } - public static remove(type: keyof Events, target: Object3D): void { - const sceneCache = this._events[target.scene?.id]; - if (sceneCache) { - sceneCache[type]?.delete(target); - } - } + public static remove(type: keyof Events, target: Object3D): void { + const sceneCache = this._events[target.scene?.id]; + if (sceneCache) { + sceneCache[type]?.delete(target); + } + } - public static dispatchEvent(scene: Scene, type: K, event?: Events[K]): void { - const sceneCache = this._events[scene?.id]; - if (sceneCache?.[type]) { - for (const target of sceneCache[type]) { - target.__eventsDispatcher.dispatch(type, event); - } + public static dispatchEvent(scene: Scene, type: K, event?: Events[K]): void { + const sceneCache = this._events[scene?.id]; + if (sceneCache?.[type]) { + for (const target of sceneCache[type]) { + target.__eventsDispatcher.dispatch(type, event); } - } + } + } - public static dispatchEventExcludeCameras(scene: Scene, type: K, event?: Events[K]): void { - const sceneCache = this._events[scene?.id]; - if (sceneCache?.[type]) { - for (const target of sceneCache[type]) { - if (!(target as Camera).isCamera) { - target.__eventsDispatcher.dispatch(type, event); - } - } + public static dispatchEventExcludeCameras(scene: Scene, type: K, event?: Events[K]): void { + const sceneCache = this._events[scene?.id]; + if (sceneCache?.[type]) { + for (const target of sceneCache[type]) { + if (!(target as Camera).isCamera) { + target.__eventsDispatcher.dispatch(type, event); + } } - } - + } + } } diff --git a/src/events/RaycasterManager.ts b/src/events/RaycasterManager.ts index c78bc1f..75323d1 100644 --- a/src/events/RaycasterManager.ts +++ b/src/events/RaycasterManager.ts @@ -1,7 +1,7 @@ -import { Object3D, PerspectiveCamera, Raycaster, Vector2 } from "three"; -import { RenderManager } from "../rendering/RenderManager.js"; -import { IntersectionExt } from "./Events.js"; -import { Hitbox } from "./Hitbox.js"; +import { Object3D, PerspectiveCamera, Raycaster, Vector2 } from 'three'; +import { RenderManager } from '../rendering/RenderManager.js'; +import { IntersectionExt } from './Events.js'; +import { Hitbox } from './Hitbox.js'; /** * A custom sorting comparison function used for ordering intersections during raycasting. @@ -13,75 +13,73 @@ export type RaycasterSortComparer = (a: IntersectionExt, b: IntersectionExt) => /** @internal */ export class RaycasterManager { - public raycaster = new Raycaster(); - public raycasterSortComparer: RaycasterSortComparer = (a: IntersectionExt, b: IntersectionExt) => a.distance - b.distance; - public pointer = new Vector2(); - public pointerOnCanvas = false; - private _computedPointer = new Vector2(); - private _renderManager: RenderManager; + public raycaster = new Raycaster(); + public raycasterSortComparer: RaycasterSortComparer = (a: IntersectionExt, b: IntersectionExt) => a.distance - b.distance; + public pointer = new Vector2(); + public pointerOnCanvas = false; + private _computedPointer = new Vector2(); + private _renderManager: RenderManager; - constructor(renderManager: RenderManager) { - this._renderManager = renderManager; - } + constructor(renderManager: RenderManager) { + this._renderManager = renderManager; + } - public getIntersections(event: PointerEvent, isDragging: boolean, excluded?: Object3D): IntersectionExt[] { - const intersections: IntersectionExt[] = []; - const canvasBounds = this._renderManager.renderer.domElement.getBoundingClientRect(); - this.pointer.set(event.clientX - canvasBounds.left, event.clientY - canvasBounds.top); - if (this.getComputedMousePosition(this.pointer, this._computedPointer, isDragging, event.isPrimary)) { - const scene = this._renderManager.activeScene; - const camera = this._renderManager.activeView.camera; - this.raycaster.setFromCamera(this._computedPointer, camera); - this.raycaster.far = (camera as PerspectiveCamera).far ?? Infinity; - if (!isDragging || excluded) { - this.raycastObjects(scene, intersections, excluded); - } - intersections.sort(this.raycasterSortComparer); - } - return intersections; + public getIntersections(event: PointerEvent, isDragging: boolean, excluded?: Object3D): IntersectionExt[] { + const intersections: IntersectionExt[] = []; + const canvasBounds = this._renderManager.renderer.domElement.getBoundingClientRect(); + this.pointer.set(event.clientX - canvasBounds.left, event.clientY - canvasBounds.top); + if (this.getComputedMousePosition(this.pointer, this._computedPointer, isDragging, event.isPrimary)) { + const scene = this._renderManager.activeScene; + const camera = this._renderManager.activeView.camera; + this.raycaster.setFromCamera(this._computedPointer, camera); + this.raycaster.far = (camera as PerspectiveCamera).far ?? Infinity; + if (!isDragging || excluded) { + this.raycastObjects(scene, intersections, excluded); + } + intersections.sort(this.raycasterSortComparer); } + return intersections; + } - private getComputedMousePosition(mouse: Vector2, target: Vector2, isDragging: boolean, isPrimary: boolean): boolean { - if (!isDragging && isPrimary) { - this._renderManager.updateActiveView(mouse, this.pointerOnCanvas); - } - const activeView = this._renderManager.activeView; - if (!activeView?.enabled || activeView !== this._renderManager.hoveredView) return false; - const viewport = activeView.computedViewport; - target.set((mouse.x - viewport.left) / viewport.width * 2 - 1, (mouse.y - viewport.top) / viewport.height * -2 + 1); - return true; + private getComputedMousePosition(mouse: Vector2, target: Vector2, isDragging: boolean, isPrimary: boolean): boolean { + if (!isDragging && isPrimary) { + this._renderManager.updateActiveView(mouse, this.pointerOnCanvas); } + const activeView = this._renderManager.activeView; + if (!activeView?.enabled || activeView !== this._renderManager.hoveredView) return false; + const viewport = activeView.computedViewport; + target.set((mouse.x - viewport.left) / viewport.width * 2 - 1, (mouse.y - viewport.top) / viewport.height * -2 + 1); + return true; + } - private raycastObjects(object: Object3D, target: IntersectionExt[], excluded?: Object3D): IntersectionExt[] { - if (object === excluded) return; - - if (object.interceptByRaycaster && object.visible) { - - for (const obj of object.children) { - this.raycastObjects(obj, target, excluded); - } + private raycastObjects(object: Object3D, target: IntersectionExt[], excluded?: Object3D): IntersectionExt[] { + if (object === excluded) return; - let previousCount = target.length; + if (object.interceptByRaycaster && object.visible) { + for (const obj of object.children) { + this.raycastObjects(obj, target, excluded); + } - if (object.hitboxes) { - for (const hitbox of object.hitboxes) { - hitbox.updateMatrix(); - hitbox.matrixWorld.multiplyMatrices(object.matrixWorld, hitbox.matrix); - this.raycaster.intersectObject(hitbox, false, target); - } - } else { - this.raycaster.intersectObject(object, false, target); - } + let previousCount = target.length; - while (target.length > previousCount) { - const intersection = target[previousCount]; - intersection.hitbox = intersection.object as Hitbox; - intersection.object = object; - previousCount++; - } + if (object.hitboxes) { + for (const hitbox of object.hitboxes) { + hitbox.updateMatrix(); + hitbox.matrixWorld.multiplyMatrices(object.matrixWorld, hitbox.matrix); + this.raycaster.intersectObject(hitbox, false, target); } + } else { + this.raycaster.intersectObject(object, false, target); + } - return target; + while (target.length > previousCount) { + const intersection = target[previousCount]; + intersection.hitbox = intersection.object as Hitbox; + intersection.object = object; + previousCount++; + } } + return target; + } } diff --git a/src/index.ts b/src/index.ts index be79c44..80fe05e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,7 +43,7 @@ export * from './utils/Utils.js'; export * from './utils/VectorUtils.js'; declare module 'three' { - export interface Object3D extends Object3DExtPrototype { } - export interface Material extends MaterialExtPrototype { } - export interface Scene extends SceneExtPrototype { } + export interface Object3D extends Object3DExtPrototype { } + export interface Material extends MaterialExtPrototype { } + export interface Scene extends SceneExtPrototype { } } diff --git a/src/instancedMesh/EventsDispatcherInstanced.ts b/src/instancedMesh/EventsDispatcherInstanced.ts index 629746b..15f0e44 100644 --- a/src/instancedMesh/EventsDispatcherInstanced.ts +++ b/src/instancedMesh/EventsDispatcherInstanced.ts @@ -1,70 +1,70 @@ -import { EventExt, InteractionEvents, MiscEvents } from "../events/Events.js"; -import { InstancedMeshEntity } from "./InstancedMeshEntity.js"; +import { EventExt, InteractionEvents, MiscEvents } from '../events/Events.js'; +import { InstancedMeshEntity } from './InstancedMeshEntity.js'; -export type InstancedMiscUpdateEvents = Omit; +export type InstancedMiscUpdateEvents = Omit; export type InstancedInteractionEvents = Omit, - "focusout" | "focusin" | "pointerleave" | "pointerenter" | "dragenter" | "dragover" | "dragleave" | "drop">; + 'focusout' | 'focusin' | 'pointerleave' | 'pointerenter' | 'dragenter' | 'dragover' | 'dragleave' | 'drop'>; export type InstancedEvents = InstancedMiscUpdateEvents & InstancedInteractionEvents; /** @internal */ export class EventsDispatcherInstanced { - public parent: InstancedMeshEntity; - public listeners: { [K in keyof InstancedEvents]?: ((event?: InstancedEvents[K]) => void)[] } = {}; + public parent: InstancedMeshEntity; + public listeners: { [K in keyof InstancedEvents]?: ((event?: InstancedEvents[K]) => void)[] } = {}; - constructor(parent: InstancedMeshEntity) { - this.parent = parent; - } + constructor(parent: InstancedMeshEntity) { + this.parent = parent; + } - public add(type: K, listener: (event: InstancedEvents[K]) => void): (event: InstancedEvents[K]) => void { - if (!this.listeners[type]) { - this.listeners[type] = []; - } - if (this.listeners[type].indexOf(listener) < 0) { - this.listeners[type].push(listener); - } - return listener; + public add(type: K, listener: (event: InstancedEvents[K]) => void): (event: InstancedEvents[K]) => void { + if (!this.listeners[type]) { + this.listeners[type] = []; } - - public has(type: K, listener: (event: InstancedEvents[K]) => void): boolean { - return this.listeners[type]?.indexOf(listener) > -1; + if (this.listeners[type].indexOf(listener) < 0) { + this.listeners[type].push(listener); } + return listener; + } - public remove(type: K, listener: (event: InstancedEvents[K]) => void): void { - const index = this.listeners[type]?.indexOf(listener) ?? -1; - if (index > -1) { - this.listeners[type].splice(index, 1); - } - } + public has(type: K, listener: (event: InstancedEvents[K]) => void): boolean { + return this.listeners[type]?.indexOf(listener) > -1; + } - public dispatchDOM(type: K, event: InstancedInteractionEvents[K]): void { - event._bubbles = false; - event._stoppedImmediatePropagation = false; - event._defaultPrevented = false; - event._type = type; - event._target = this.parent; - this.executeDOM(type, event); + public remove(type: K, listener: (event: InstancedEvents[K]) => void): void { + const index = this.listeners[type]?.indexOf(listener) ?? -1; + if (index > -1) { + this.listeners[type].splice(index, 1); } + } + + public dispatchDOM(type: K, event: InstancedInteractionEvents[K]): void { + event._bubbles = false; + event._stoppedImmediatePropagation = false; + event._defaultPrevented = false; + event._type = type; + event._target = this.parent; + this.executeDOM(type, event); + } - private executeDOM(type: K, event: InstancedInteractionEvents[K]): void { - if (!this.listeners[type]) return; - const target = event.currentTarget = this.parent; - for (const callback of this.listeners[type]) { - if (event._stoppedImmediatePropagation) break; - callback.call(target, event as any); - } + private executeDOM(type: K, event: InstancedInteractionEvents[K]): void { + if (!this.listeners[type]) return; + const target = event.currentTarget = this.parent; + for (const callback of this.listeners[type]) { + if (event._stoppedImmediatePropagation) break; + callback.call(target, event as any); } + } - public dispatch(type: T, event?: InstancedMiscUpdateEvents[T]): void { - if (!this.listeners[type]) return; - for (const callback of this.listeners[type]) { - callback.call(this.parent, event as any); - } + public dispatch(type: T, event?: InstancedMiscUpdateEvents[T]): void { + if (!this.listeners[type]) return; + for (const callback of this.listeners[type]) { + callback.call(this.parent, event as any); } + } - public dispatchManual(type: T, event?: InstancedEvents[T]): void { - if ((event as EventExt)?.cancelable !== undefined) { - return this.dispatchDOM(type as keyof InstancedInteractionEvents, event as any); - } - this.dispatch(type as keyof InstancedMiscUpdateEvents, event as any); + public dispatchManual(type: T, event?: InstancedEvents[T]): void { + if ((event as EventExt)?.cancelable !== undefined) { + return this.dispatchDOM(type as keyof InstancedInteractionEvents, event as any); } + this.dispatch(type as keyof InstancedMiscUpdateEvents, event as any); + } } diff --git a/src/instancedMesh/InstancedMesh2.ts b/src/instancedMesh/InstancedMesh2.ts index dba8818..8746e23 100644 --- a/src/instancedMesh/InstancedMesh2.ts +++ b/src/instancedMesh/InstancedMesh2.ts @@ -1,15 +1,15 @@ -import { BufferGeometry, Color, ColorRepresentation, DynamicDrawUsage, InstancedMesh, Material, Matrix4 } from "three"; -import { AnimateEvent, DragEventExt, FocusEventExt, IntersectionExt, KeyboardEventExt, PointerEventExt, PointerIntersectionEvent, WheelEventExt } from "../events/Events.js"; -import { InstancedMeshEntity } from "./InstancedMeshEntity.js"; +import { BufferGeometry, Color, ColorRepresentation, DynamicDrawUsage, InstancedMesh, Material, Matrix4 } from 'three'; +import { AnimateEvent, DragEventExt, FocusEventExt, IntersectionExt, KeyboardEventExt, PointerEventExt, PointerIntersectionEvent, WheelEventExt } from '../events/Events.js'; +import { InstancedMeshEntity } from './InstancedMeshEntity.js'; function overrideProperty(...names: (keyof InstancedMesh2)[]): void { - for (const name of names) { - Object.defineProperty(InstancedMesh2.prototype, name, { - get: function (this: InstancedMesh2) { return this._hoveredInstance[name] }, - set: function () { /* console.warn(`Cannot set ${name} in InstancedMesh2. Set it in InstancedMeshEntity instead.`) */ }, - configurable: true - }); - } + for (const name of names) { + Object.defineProperty(InstancedMesh2.prototype, name, { + get: function (this: InstancedMesh2) { return this._hoveredInstance[name]; }, + set: function () { /* console.warn(`Cannot set ${name} in InstancedMesh2. Set it in InstancedMeshEntity instead.`) */ }, + configurable: true + }); + } } /** @@ -17,45 +17,45 @@ function overrideProperty(...names: (keyof InstancedMesh2)[]): void { * @deprecated */ export class InstancedMesh2 extends InstancedMesh { - /** A flag indicating that this is an instance of InstancedMesh2. */ - public isInstancedMesh2 = true; - /** + /** A flag indicating that this is an instance of InstancedMesh2. */ + public isInstancedMesh2 = true; + /** * An array storing individual InstancedMeshEntity instances associated with this InstancedMesh2. * Each element represents a separate instance that can be managed individually. */ - public instances: InstancedMeshEntity[] = []; - /** @internal */ public _hoveredInstance: InstancedMeshEntity; - /** @internal */ public _focusedInstance: InstancedMeshEntity; - /** @internal */ public _clickingInstance: InstancedMeshEntity; - /** @internal */ public _draggingInstance: InstancedMeshEntity; - /** @internal */ public _tempMatrix = new Matrix4(); - /** @internal */ public _tempColor = new Color(); - /** @internal */ public _animate: boolean; - /** @internal */ public get __enabledStateHovered(): boolean { return this._hoveredInstance.enabled && super.enabledState } - private _lastPointerMove: PointerEventExt; - private _lastClick: PointerEventExt; - - /** + public instances: InstancedMeshEntity[] = []; + /** @internal */ public _hoveredInstance: InstancedMeshEntity; + /** @internal */ public _focusedInstance: InstancedMeshEntity; + /** @internal */ public _clickingInstance: InstancedMeshEntity; + /** @internal */ public _draggingInstance: InstancedMeshEntity; + /** @internal */ public _tempMatrix = new Matrix4(); + /** @internal */ public _tempColor = new Color(); + /** @internal */ public _animate: boolean; + /** @internal */ public get __enabledStateHovered(): boolean { return this._hoveredInstance.enabled && super.enabledState; } + private _lastPointerMove: PointerEventExt; + private _lastClick: PointerEventExt; + + /** * Gets the currently hovered instance. */ - public get hoveredInstance(): InstancedMeshEntity { return this._hoveredInstance } + public get hoveredInstance(): InstancedMeshEntity { return this._hoveredInstance; } - /** + /** * Gets the currently focused instance. */ - public get focusedInstance(): InstancedMeshEntity { return this._focusedInstance } + public get focusedInstance(): InstancedMeshEntity { return this._focusedInstance; } - /** + /** * Gets the currently clicking instance. */ - public get clickingInstance(): InstancedMeshEntity { return this._clickingInstance } + public get clickingInstance(): InstancedMeshEntity { return this._clickingInstance; } - /** + /** * Gets the currently dragging instance. */ - public get draggingInstance(): InstancedMeshEntity { return this._draggingInstance } + public get draggingInstance(): InstancedMeshEntity { return this._draggingInstance; } - /** + /** * @param geometry The geometry for the instanced mesh. * @param material The material to apply to the instanced mesh. * @param count The number of instances to create. @@ -63,230 +63,230 @@ export class InstancedMesh2 extends InstancedMesh { * @param animate A flag indicating whether the 'animate' event will be triggered for each instance (optional, default: false). * @param color The default color to apply to each instance (optional). */ - constructor(geometry: BufferGeometry, material: Material, count: number, singleInstanceType: typeof InstancedMeshEntity, animate = false, color?: ColorRepresentation) { - super(geometry, material, count); - color = this._tempColor.set(color); - - this._animate = animate; - if (animate) { - this.instanceMatrix.setUsage(DynamicDrawUsage); - } + constructor(geometry: BufferGeometry, material: Material, count: number, singleInstanceType: typeof InstancedMeshEntity, animate = false, color?: ColorRepresentation) { + super(geometry, material, count); + color = this._tempColor.set(color); - for (let i = 0; i < count; i++) { - this.instances.push(new singleInstanceType(this, i, color)); - } + this._animate = animate; + if (animate) { + this.instanceMatrix.setUsage(DynamicDrawUsage); + } - this.on("animate", this.animate.bind(this)); - this.on("pointerintersection", this.pointerIntersection.bind(this)); - this.on("pointermove", this.pointerMove.bind(this)); - this.on("pointerleave", this.pointerLeave.bind(this)); - this.on("focusin", this.focusIn.bind(this)); - this.on("focusout", this.focusOut.bind(this)); - this.on("click", this.click.bind(this)); - this.on("pointerdown", this.pointerDown.bind(this)); - this.on("pointerup", this.pointerUp.bind(this)); - this.on("keydown", this.keyDown.bind(this)); - this.on("keyup", this.keyUp.bind(this)); - this.on("wheel", this.wheel.bind(this)); - this.on("drag", this.drag.bind(this)); - this.on("dragstart", this.dragStart.bind(this)); - this.on("dragend", this.dragEnd.bind(this)); - this.on("dragcancel", this.dragCancel.bind(this)); + for (let i = 0; i < count; i++) { + this.instances.push(new singleInstanceType(this, i, color)); } - /** + this.on('animate', this.animate.bind(this)); + this.on('pointerintersection', this.pointerIntersection.bind(this)); + this.on('pointermove', this.pointerMove.bind(this)); + this.on('pointerleave', this.pointerLeave.bind(this)); + this.on('focusin', this.focusIn.bind(this)); + this.on('focusout', this.focusOut.bind(this)); + this.on('click', this.click.bind(this)); + this.on('pointerdown', this.pointerDown.bind(this)); + this.on('pointerup', this.pointerUp.bind(this)); + this.on('keydown', this.keyDown.bind(this)); + this.on('keyup', this.keyUp.bind(this)); + this.on('wheel', this.wheel.bind(this)); + this.on('drag', this.drag.bind(this)); + this.on('dragstart', this.dragStart.bind(this)); + this.on('dragend', this.dragEnd.bind(this)); + this.on('dragcancel', this.dragCancel.bind(this)); + } + + /** * Set the focus to the specified instance, if focus is enabled for the InstancedMesh2, or clears the focus if no target is provided. * @param target Optional. The instance to focus on. If not provided, the focus is cleared. */ - public focus(target?: InstancedMeshEntity): void { - if (!this.__focused) return; + public focus(target?: InstancedMeshEntity): void { + if (!this.__focused) return; - const focusableObj = target?.focusable ? target : undefined; - if ((!target || focusableObj?.enabled) && this._focusedInstance !== focusableObj) { - const oldFocusedObj = this._focusedInstance; - this._focusedInstance = focusableObj; + const focusableObj = target?.focusable ? target : undefined; + if ((!target || focusableObj?.enabled) && this._focusedInstance !== focusableObj) { + const oldFocusedObj = this._focusedInstance; + this._focusedInstance = focusableObj; - if (oldFocusedObj?.enabled) { - oldFocusedObj.__focused = false; - oldFocusedObj.__eventsDispatcher.dispatchDOM("blur", new FocusEventExt(focusableObj)); - } + if (oldFocusedObj?.enabled) { + oldFocusedObj.__focused = false; + oldFocusedObj.__eventsDispatcher.dispatchDOM('blur', new FocusEventExt(focusableObj)); + } - if (focusableObj) { - focusableObj.__focused = true - focusableObj.__eventsDispatcher.dispatchDOM("focus", new FocusEventExt(oldFocusedObj)); - } + if (focusableObj) { + focusableObj.__focused = true; + focusableObj.__eventsDispatcher.dispatchDOM('focus', new FocusEventExt(oldFocusedObj)); + } - this.needsRender = true; - } + this.needsRender = true; } - - private pointerOverOut(intersection: IntersectionExt, domEvent: PointerEvent): void { - const hoveredInstance = this.instances[intersection.instanceId]; - if (this._hoveredInstance !== hoveredInstance) { - const oldHoveredInstance = this._hoveredInstance; - - this._hoveredInstance = hoveredInstance; - if (hoveredInstance.enabled) { - hoveredInstance.__hovered = true; - } - - if (oldHoveredInstance) { - oldHoveredInstance.__hovered = false; - if (oldHoveredInstance.enabled) { - const event = new PointerEventExt(domEvent, intersection, hoveredInstance); - oldHoveredInstance.__eventsDispatcher.dispatchDOM("pointerout", event); - } - } - - if (hoveredInstance.enabled) { - const event = new PointerEventExt(domEvent, intersection, oldHoveredInstance); - hoveredInstance.__eventsDispatcher.dispatchDOM("pointerover", event); - } + } + + private pointerOverOut(intersection: IntersectionExt, domEvent: PointerEvent): void { + const hoveredInstance = this.instances[intersection.instanceId]; + if (this._hoveredInstance !== hoveredInstance) { + const oldHoveredInstance = this._hoveredInstance; + + this._hoveredInstance = hoveredInstance; + if (hoveredInstance.enabled) { + hoveredInstance.__hovered = true; + } + + if (oldHoveredInstance) { + oldHoveredInstance.__hovered = false; + if (oldHoveredInstance.enabled) { + const event = new PointerEventExt(domEvent, intersection, hoveredInstance); + oldHoveredInstance.__eventsDispatcher.dispatchDOM('pointerout', event); } - } + } - private animate(e: AnimateEvent): void { - if (this._animate) { - for (let i = 0; i < this.count; i++) { - this.instances[i].__eventsDispatcher.dispatch("animate", e); - } - } + if (hoveredInstance.enabled) { + const event = new PointerEventExt(domEvent, intersection, oldHoveredInstance); + hoveredInstance.__eventsDispatcher.dispatchDOM('pointerover', event); + } } + } - private pointerIntersection(e: PointerIntersectionEvent): void { - this.pointerOverOut(e.intersection, this._lastPointerMove?.domEvent); - if (this._hoveredInstance.enabled) { - const event = new PointerIntersectionEvent(e.intersection); - this._hoveredInstance.__eventsDispatcher.dispatchDOM("pointerintersection", event); - } + private animate(e: AnimateEvent): void { + if (this._animate) { + for (let i = 0; i < this.count; i++) { + this.instances[i].__eventsDispatcher.dispatch('animate', e); + } } + } - private pointerMove(e: PointerEventExt): void { - this._lastPointerMove = e; - this.pointerOverOut(e.intersection, e.domEvent); - if (this._hoveredInstance.enabled) { - const event = new PointerEventExt(e.domEvent, e.intersection); - this._hoveredInstance.__eventsDispatcher.dispatchDOM("pointermove", event); - } + private pointerIntersection(e: PointerIntersectionEvent): void { + this.pointerOverOut(e.intersection, this._lastPointerMove?.domEvent); + if (this._hoveredInstance.enabled) { + const event = new PointerIntersectionEvent(e.intersection); + this._hoveredInstance.__eventsDispatcher.dispatchDOM('pointerintersection', event); } - - private pointerLeave(e: PointerEventExt): void { - const instance = this._hoveredInstance; - instance.__hovered = false; - this._hoveredInstance = undefined; - if (instance.enabled) { - const event = new PointerEventExt(e.domEvent, e.intersection); - instance.__eventsDispatcher.dispatchDOM("pointerout", event); - } + } + + private pointerMove(e: PointerEventExt): void { + this._lastPointerMove = e; + this.pointerOverOut(e.intersection, e.domEvent); + if (this._hoveredInstance.enabled) { + const event = new PointerEventExt(e.domEvent, e.intersection); + this._hoveredInstance.__eventsDispatcher.dispatchDOM('pointermove', event); } - - private focusIn(): void { - this.focus(this._hoveredInstance); + } + + private pointerLeave(e: PointerEventExt): void { + const instance = this._hoveredInstance; + instance.__hovered = false; + this._hoveredInstance = undefined; + if (instance.enabled) { + const event = new PointerEventExt(e.domEvent, e.intersection); + instance.__eventsDispatcher.dispatchDOM('pointerout', event); } - - private focusOut(): void { - this.focus(); + } + + private focusIn(): void { + this.focus(this._hoveredInstance); + } + + private focusOut(): void { + this.focus(); + } + + private click(e: PointerEventExt): void { + const target = this.instances[e.intersection.instanceId]; + if (target.enabled) { + const event = new PointerEventExt(e.domEvent, e.intersection); + target.__eventsDispatcher.dispatchDOM('click', event); + if (e.intersection.instanceId === this._lastClick?.intersection.instanceId && e.timeStamp - this._lastClick.timeStamp <= 300) { + const event = new PointerEventExt(e.domEvent, e.intersection); + target.__eventsDispatcher.dispatchDOM('dblclick', event); + this._lastClick = undefined; + } else { + this._lastClick = e; + } } - - private click(e: PointerEventExt): void { - const target = this.instances[e.intersection.instanceId]; - if (target.enabled) { - const event = new PointerEventExt(e.domEvent, e.intersection); - target.__eventsDispatcher.dispatchDOM("click", event); - if (e.intersection.instanceId === this._lastClick?.intersection.instanceId && e.timeStamp - this._lastClick.timeStamp <= 300) { - const event = new PointerEventExt(e.domEvent, e.intersection); - target.__eventsDispatcher.dispatchDOM("dblclick", event); - this._lastClick = undefined; - } else { - this._lastClick = e; - } - } + } + + private pointerDown(e: PointerEventExt): void { + const target = this.instances[e.intersection.instanceId]; + if (target.enabled) { + this._clickingInstance = target; + target.__clicking = true; + const event = new PointerEventExt(e.domEvent, e.intersection, undefined, true); + target.__eventsDispatcher.dispatchDOM('pointerdown', event); + if (!event._defaultPrevented) { + this.focus(target); + } else { + e.preventDefault(); + } } - - private pointerDown(e: PointerEventExt): void { - const target = this.instances[e.intersection.instanceId]; - if (target.enabled) { - this._clickingInstance = target; - target.__clicking = true; - const event = new PointerEventExt(e.domEvent, e.intersection, undefined, true); - target.__eventsDispatcher.dispatchDOM("pointerdown", event); - if (!event._defaultPrevented) { - this.focus(target); - } else { - e.preventDefault(); - } - } - } - - private pointerUp(e: PointerEventExt): void { - const instance = this._clickingInstance; - if (instance) { - instance.__clicking = false; - if (this._clickingInstance.enabled) { - const event = new PointerEventExt(e.domEvent, e.intersection); - instance.__eventsDispatcher.dispatchDOM("pointerup", event); - } - this._clickingInstance = undefined; - } - } - - private keyDown(e: KeyboardEventExt): void { - if (this._focusedInstance.enabled) { - const event = new KeyboardEventExt(e.domEvent, true); - this._focusedInstance.__eventsDispatcher.dispatchDOM("keydown", event); - if (event._defaultPrevented) { - e.preventDefault(); - } - } + } + + private pointerUp(e: PointerEventExt): void { + const instance = this._clickingInstance; + if (instance) { + instance.__clicking = false; + if (this._clickingInstance.enabled) { + const event = new PointerEventExt(e.domEvent, e.intersection); + instance.__eventsDispatcher.dispatchDOM('pointerup', event); + } + this._clickingInstance = undefined; } - - private keyUp(e: KeyboardEventExt): void { - if (this._focusedInstance.enabled) { - const event = new KeyboardEventExt(e.domEvent, false); - this._focusedInstance.__eventsDispatcher.dispatchDOM("keyup", event); - } - } - - private wheel(e: WheelEventExt): void { - if (this._hoveredInstance.enabled) { - const event = new WheelEventExt(e.domEvent, e.intersection); - this._hoveredInstance.__eventsDispatcher.dispatchDOM("wheel", event); - } + } + + private keyDown(e: KeyboardEventExt): void { + if (this._focusedInstance.enabled) { + const event = new KeyboardEventExt(e.domEvent, true); + this._focusedInstance.__eventsDispatcher.dispatchDOM('keydown', event); + if (event._defaultPrevented) { + e.preventDefault(); + } } + } - private drag(e: DragEventExt): void { - const event = new DragEventExt(e.domEvent, true, e.dataTransfer, e.position, e.relatedTarget, e.intersection); - this._draggingInstance.__eventsDispatcher.dispatchDOM("drag", event); - if (event._defaultPrevented) { - e.preventDefault(); - } + private keyUp(e: KeyboardEventExt): void { + if (this._focusedInstance.enabled) { + const event = new KeyboardEventExt(e.domEvent, false); + this._focusedInstance.__eventsDispatcher.dispatchDOM('keyup', event); } + } - private dragStart(e: DragEventExt): void { - this._draggingInstance = this.instances[e.intersection.instanceId]; - this._draggingInstance.__dragging = true; - const event = new DragEventExt(e.domEvent, false, e.dataTransfer, e.position, e.relatedTarget, e.intersection); - this._draggingInstance.__eventsDispatcher.dispatchDOM("dragstart", event); + private wheel(e: WheelEventExt): void { + if (this._hoveredInstance.enabled) { + const event = new WheelEventExt(e.domEvent, e.intersection); + this._hoveredInstance.__eventsDispatcher.dispatchDOM('wheel', event); } + } - private dragEnd(e: DragEventExt): void { - const instance = this._draggingInstance; - instance.__dragging = false; - this._draggingInstance = undefined; - const event = new DragEventExt(e.domEvent, false, e.dataTransfer, e.position, e.relatedTarget, e.intersection); - instance.__eventsDispatcher.dispatchDOM("dragend", event); - this.computeBoundingSphere(); + private drag(e: DragEventExt): void { + const event = new DragEventExt(e.domEvent, true, e.dataTransfer, e.position, e.relatedTarget, e.intersection); + this._draggingInstance.__eventsDispatcher.dispatchDOM('drag', event); + if (event._defaultPrevented) { + e.preventDefault(); } - - private dragCancel(e: DragEventExt): void { - const event = new DragEventExt(e.domEvent, e.cancelable, e.dataTransfer, e.position, e.relatedTarget, e.intersection); - this._draggingInstance.__eventsDispatcher.dispatchDOM("dragcancel", event); - if (event._defaultPrevented) { - e.preventDefault(); - } + } + + private dragStart(e: DragEventExt): void { + this._draggingInstance = this.instances[e.intersection.instanceId]; + this._draggingInstance.__dragging = true; + const event = new DragEventExt(e.domEvent, false, e.dataTransfer, e.position, e.relatedTarget, e.intersection); + this._draggingInstance.__eventsDispatcher.dispatchDOM('dragstart', event); + } + + private dragEnd(e: DragEventExt): void { + const instance = this._draggingInstance; + instance.__dragging = false; + this._draggingInstance = undefined; + const event = new DragEventExt(e.domEvent, false, e.dataTransfer, e.position, e.relatedTarget, e.intersection); + instance.__eventsDispatcher.dispatchDOM('dragend', event); + this.computeBoundingSphere(); + } + + private dragCancel(e: DragEventExt): void { + const event = new DragEventExt(e.domEvent, e.cancelable, e.dataTransfer, e.position, e.relatedTarget, e.intersection); + this._draggingInstance.__eventsDispatcher.dispatchDOM('dragcancel', event); + if (event._defaultPrevented) { + e.preventDefault(); } + } } -overrideProperty("cursor", "cursorDrag", "cursorDrop", "draggable", "findDropTarget"); +overrideProperty('cursor', 'cursorDrag', 'cursorDrop', 'draggable', 'findDropTarget'); // TODO pointeridPrimary on focus and assign hovered, clicking, ecc. diff --git a/src/instancedMesh/InstancedMeshEntity.ts b/src/instancedMesh/InstancedMeshEntity.ts index c629956..363813e 100644 --- a/src/instancedMesh/InstancedMeshEntity.ts +++ b/src/instancedMesh/InstancedMeshEntity.ts @@ -1,8 +1,8 @@ -import { Color, ColorRepresentation, EventDispatcher, Matrix4, Quaternion, Vector3 } from "three"; -import { Cursor } from "../events/CursorManager.js"; -import { EventsDispatcherInstanced, InstancedEvents } from "./EventsDispatcherInstanced.js"; -import { Tween } from "../tweening/Tween.js"; -import { InstancedMesh2 } from "./InstancedMesh2.js"; +import { Color, ColorRepresentation, EventDispatcher, Matrix4, Quaternion, Vector3 } from 'three'; +import { Cursor } from '../events/CursorManager.js'; +import { EventsDispatcherInstanced, InstancedEvents } from './EventsDispatcherInstanced.js'; +import { Tween } from '../tweening/Tween.js'; +import { InstancedMesh2 } from './InstancedMesh2.js'; const tempQuaternion = new Quaternion(); @@ -10,223 +10,223 @@ const tempQuaternion = new Quaternion(); * Represents an individual instance within an InstancedMesh2, providing properties and methods for interaction and transformation. */ export class InstancedMeshEntity extends EventDispatcher { - /** A flag indicating that this is an instance of InstancedMeshEntity. */ - public isInstancedMeshEntity = true; - /** The parent InstancedMesh2 that contains this instance. */ - public parent: InstancedMesh2; - /** An identifier for this individual instance within an InstancedMesh2. */ - public instanceId: number; - /** A Vector3 representing the object's local position. Default is (0, 0, 0). */ - public readonly position = new Vector3(); - /** The object's local scale. Default is Vector3(1, 1, 1). */ - public readonly scale = new Vector3(1, 1, 1); - /** Object's local rotation as a Quaternion. */ - public readonly quaternion = new Quaternion(); - /** + /** A flag indicating that this is an instance of InstancedMeshEntity. */ + public isInstancedMeshEntity = true; + /** The parent InstancedMesh2 that contains this instance. */ + public parent: InstancedMesh2; + /** An identifier for this individual instance within an InstancedMesh2. */ + public instanceId: number; + /** A Vector3 representing the object's local position. Default is (0, 0, 0). */ + public readonly position = new Vector3(); + /** The object's local scale. Default is Vector3(1, 1, 1). */ + public readonly scale = new Vector3(1, 1, 1); + /** Object's local rotation as a Quaternion. */ + public readonly quaternion = new Quaternion(); + /** * Determines if the object is enabled. (default: true). * If set to true, it allows triggering all InteractionEvents; otherwise, events are disabled. */ - public enabled = true; - /** Indicates whether the object can receive focus (default: true). */ - public focusable = true; - /** Indicates whether the object is draggable (default: false). */ - public draggable = false; - /** Determines when the object is dragged, whether it will have to search for any drop targets (default: false). */ - public findDropTarget = false; - /** Cursor style when interacting with the object. */ - public cursor: Cursor; - /** Cursor style when dragging the object. */ - public cursorDrag: Cursor; - /** Cursor style when dropping an object onto this one. */ - public cursorDrop: Cursor; - /** @internal */ public __eventsDispatcher: EventsDispatcherInstanced; - /** @internal */ public __hovered = false; - /** @internal */ public __focused = false; - /** @internal */ public __clicking = false; - /** @internal */ public __dragging = false; - - /** Indicates if the primary pointer is over this object. */ - public get hovered(): boolean { return this.__hovered } - /** Indicates if the object is currently focused. */ - public get focused(): boolean { return this.__focused } - /** Indicates if the object is currently being clicked. */ - public get clicking(): boolean { return this.__clicking } - /** Indicates if the object is currently being dragged. */ - public get dragging(): boolean { return this.__dragging } - /** Retrieves the combined enabled state considering parent objects. */ - public get enabledState(): boolean { return this.enabled && this.parent.enabledState } - - /** + public enabled = true; + /** Indicates whether the object can receive focus (default: true). */ + public focusable = true; + /** Indicates whether the object is draggable (default: false). */ + public draggable = false; + /** Determines when the object is dragged, whether it will have to search for any drop targets (default: false). */ + public findDropTarget = false; + /** Cursor style when interacting with the object. */ + public cursor: Cursor; + /** Cursor style when dragging the object. */ + public cursorDrag: Cursor; + /** Cursor style when dropping an object onto this one. */ + public cursorDrop: Cursor; + /** @internal */ public __eventsDispatcher: EventsDispatcherInstanced; + /** @internal */ public __hovered = false; + /** @internal */ public __focused = false; + /** @internal */ public __clicking = false; + /** @internal */ public __dragging = false; + + /** Indicates if the primary pointer is over this object. */ + public get hovered(): boolean { return this.__hovered; } + /** Indicates if the object is currently focused. */ + public get focused(): boolean { return this.__focused; } + /** Indicates if the object is currently being clicked. */ + public get clicking(): boolean { return this.__clicking; } + /** Indicates if the object is currently being dragged. */ + public get dragging(): boolean { return this.__dragging; } + /** Retrieves the combined enabled state considering parent objects. */ + public get enabledState(): boolean { return this.enabled && this.parent.enabledState; } + + /** * The global transform of the object. */ - public get matrixWorld(): Matrix4 { - const matrix = this.parent._tempMatrix; - matrix.compose(this.position, this.quaternion, this.scale); - this.parent.updateWorldMatrix(true, false); - return matrix.premultiply(this.parent.matrixWorld); - } + public get matrixWorld(): Matrix4 { + const matrix = this.parent._tempMatrix; + matrix.compose(this.position, this.quaternion, this.scale); + this.parent.updateWorldMatrix(true, false); + return matrix.premultiply(this.parent.matrixWorld); + } - /** + /** * @param parent - The parent InstancedMesh2 that contains this instance. * @param index - The index of this instance within the parent InstancedMesh2. * @param color - The initial color representation for this instance (optional). */ - constructor(parent: InstancedMesh2, index: number, color?: ColorRepresentation) { - super(); - this.parent = parent; - this.instanceId = index; - this.__eventsDispatcher = new EventsDispatcherInstanced(this); - if (color !== undefined) { - this.setColor(color); - } + constructor(parent: InstancedMesh2, index: number, color?: ColorRepresentation) { + super(); + this.parent = parent; + this.instanceId = index; + this.__eventsDispatcher = new EventsDispatcherInstanced(this); + if (color !== undefined) { + this.setColor(color); } + } - /** + /** * Sets the color of this instance. * @param color - The color representation to set. */ - public setColor(color: ColorRepresentation): void { - const parent = this.parent; - parent.setColorAt(this.instanceId, parent._tempColor.set(color)); - parent.instanceColor.needsUpdate = true; - } + public setColor(color: ColorRepresentation): void { + const parent = this.parent; + parent.setColorAt(this.instanceId, parent._tempColor.set(color)); + parent.instanceColor.needsUpdate = true; + } - /** + /** * Gets the color of this instance. * @param color - An optional target Color object to store the result (optional). * @returns The color representation of this instance. */ - public getColor(color = this.parent._tempColor): Color { - this.parent.getColorAt(this.instanceId, color); - return color; - } + public getColor(color = this.parent._tempColor): Color { + this.parent.getColorAt(this.instanceId, color); + return color; + } - /** + /** * Updates the local transform. */ - public updateMatrix(): void { - const parent = this.parent; - const matrix = parent._tempMatrix; - matrix.compose(this.position, this.quaternion, this.scale); - parent.setMatrixAt(this.instanceId, matrix); - parent.instanceMatrix.needsUpdate = true; - } + public updateMatrix(): void { + const parent = this.parent; + const matrix = parent._tempMatrix; + matrix.compose(this.position, this.quaternion, this.scale); + parent.setMatrixAt(this.instanceId, matrix); + parent.instanceMatrix.needsUpdate = true; + } - /** + /** * Applies the matrix transform to the object and updates the object's position, rotation, and scale. * @param m Matrix to apply. * @returns The instance of the object. */ - public applyMatrix4(m: Matrix4): this { - const parent = this.parent; - const matrix = parent._tempMatrix; - matrix.compose(this.position, this.quaternion, this.scale); - matrix.premultiply(m); - matrix.decompose(this.position, this.quaternion, this.scale); - parent.setMatrixAt(this.instanceId, matrix); - parent.instanceMatrix.needsUpdate = true; - return this; - } - - /** + public applyMatrix4(m: Matrix4): this { + const parent = this.parent; + const matrix = parent._tempMatrix; + matrix.compose(this.position, this.quaternion, this.scale); + matrix.premultiply(m); + matrix.decompose(this.position, this.quaternion, this.scale); + parent.setMatrixAt(this.instanceId, matrix); + parent.instanceMatrix.needsUpdate = true; + return this; + } + + /** * Applies the rotation represented by the quaternion to the object. * @param q Quaternion to apply. * @returns The instance of the object. */ - public applyQuaternion(q: Quaternion): this { - this.quaternion.premultiply(q); - return this; - } + public applyQuaternion(q: Quaternion): this { + this.quaternion.premultiply(q); + return this; + } - /** + /** * Rotate an object along an axis in object space. The axis is assumed to be normalized. * @param axis A normalized vector in object space. * @param angle The angle in radians. * @returns The instance of the object. */ - public rotateOnAxis(axis: Vector3, angle: number): this { - tempQuaternion.setFromAxisAngle(axis, angle); - this.quaternion.multiply(tempQuaternion); - return this; - } + public rotateOnAxis(axis: Vector3, angle: number): this { + tempQuaternion.setFromAxisAngle(axis, angle); + this.quaternion.multiply(tempQuaternion); + return this; + } - /** + /** * Rotate an object along an axis in world space. The axis is assumed to be normalized. Method Assumes no rotated parent. * @param axis A normalized vector in world space. * @param angle The angle in radians. * @returns The instance of the object. */ - public rotateOnWorldAxis(axis: Vector3, angle: number): this { - tempQuaternion.setFromAxisAngle(axis, angle); - this.quaternion.premultiply(tempQuaternion); - return this; - } + public rotateOnWorldAxis(axis: Vector3, angle: number): this { + tempQuaternion.setFromAxisAngle(axis, angle); + this.quaternion.premultiply(tempQuaternion); + return this; + } - /** + /** * Applies focus to the object. */ - public applyFocus(): void { - this.parent.focus(this); - } + public applyFocus(): void { + this.parent.focus(this); + } - /** + /** * Applies blur (removes focus) from the object. */ - public applyBlur(): void { - if (this.parent.focusedInstance === this) { - this.parent.focus(); - } + public applyBlur(): void { + if (this.parent.focusedInstance === this) { + this.parent.focus(); } + } - /** + /** * Attaches an event listener to the object. * @param type - The type of event to listen for. * @param listener - The callback function to execute when the event occurs. * @returns A function to remove the event listener. */ - public on(types: K | K[], listener: (event?: InstancedEvents[K]) => void): (event?: InstancedEvents[K]) => void { - if (typeof types === "string") { - return this.__eventsDispatcher.add(types, listener) as (event?: InstancedEvents[K]) => void; - } - for (const type of types as any) { - this.__eventsDispatcher.add(type, listener); - } - return listener; + public on(types: K | K[], listener: (event?: InstancedEvents[K]) => void): (event?: InstancedEvents[K]) => void { + if (typeof types === 'string') { + return this.__eventsDispatcher.add(types, listener) as (event?: InstancedEvents[K]) => void; } + for (const type of types as any) { + this.__eventsDispatcher.add(type, listener); + } + return listener; + } - /** + /** * Checks if the object has a specific event listener. * @param type - The type of event to check for. * @param listener - The callback function to check. * @returns `true` if the event listener is attached; otherwise, `false`. */ - public hasEvent(type: K, listener: (event?: InstancedEvents[K]) => void): boolean { - return this.__eventsDispatcher.has(type, listener); - } + public hasEvent(type: K, listener: (event?: InstancedEvents[K]) => void): boolean { + return this.__eventsDispatcher.has(type, listener); + } - /** + /** * Removes an event listener from the object. * @param type - The type of event to remove the listener from. * @param listener - The callback function to remove. */ - public off(type: K, listener: (event?: InstancedEvents[K]) => void): void { - this.__eventsDispatcher.remove(type, listener); - } + public off(type: K, listener: (event?: InstancedEvents[K]) => void): void { + this.__eventsDispatcher.remove(type, listener); + } - /** + /** * Triggers a specific event on the object. * @param type - The type of event to trigger. * @param event - Optional event data to pass to the listeners. */ - public trigger(type: K, event?: InstancedEvents[K]): void { - this.__eventsDispatcher.dispatchManual(type, event); - } + public trigger(type: K, event?: InstancedEvents[K]): void { + this.__eventsDispatcher.dispatchManual(type, event); + } - /** + /** * Initiates a Tween animation for the object. * @returns A Tween instance for further configuration. */ - public tween(): Tween { - return new Tween(this); - } + public tween(): Tween { + return new Tween(this); + } } diff --git a/src/patch/Euler.ts b/src/patch/Euler.ts index 06dd875..8debd14 100644 --- a/src/patch/Euler.ts +++ b/src/patch/Euler.ts @@ -1,28 +1,28 @@ -import { Object3D } from "three"; +import { Object3D } from 'three'; /** @internal */ export function applyEulerPatch(target: Object3D): void { - target.__onChangeBaseEuler = target.rotation._onChangeCallback; - if (target.scene?.__smartRendering) { - setEulerChangeCallbackSR(target); - } else { - setEulerChangeCallback(target); - } + target.__onChangeBaseEuler = target.rotation._onChangeCallback; + if (target.scene?.__smartRendering) { + setEulerChangeCallbackSR(target); + } else { + setEulerChangeCallback(target); + } } /** @internal */ export function setEulerChangeCallbackSR(target: Object3D): void { - target.rotation._onChangeCallback = () => { - target.__onChangeBaseEuler(); - target.needsRender = true; - target.__eventsDispatcher.dispatch("rotationchange"); - }; + target.rotation._onChangeCallback = () => { + target.__onChangeBaseEuler(); + target.needsRender = true; + target.__eventsDispatcher.dispatch('rotationchange'); + }; } /** @internal */ export function setEulerChangeCallback(target: Object3D): void { - target.rotation._onChangeCallback = () => { - target.__onChangeBaseEuler(); - target.__eventsDispatcher.dispatch("rotationchange"); - }; + target.rotation._onChangeCallback = () => { + target.__onChangeBaseEuler(); + target.__eventsDispatcher.dispatch('rotationchange'); + }; } diff --git a/src/patch/Material.ts b/src/patch/Material.ts index 1b8ed2f..b6e9b4e 100644 --- a/src/patch/Material.ts +++ b/src/patch/Material.ts @@ -1,19 +1,19 @@ -import { Material } from "three"; -import { Tween } from "../tweening/Tween.js"; +import { Material } from 'three'; +import { Tween } from '../tweening/Tween.js'; /** * Represents the prototype for extended Material functionality. */ export interface MaterialExtPrototype { - /** + /** * Initiates a Tween animation for the material. * @param id - Unique identifier. If you start a new tween, the old one with the same id (if specified) will be stopped. * @template T - The type of the target. * @returns A Tween instance for further configuration. */ - tween(id?: string): Tween; + tween(id?: string): Tween; } Material.prototype.tween = function (id?: string) { - return new Tween(this as T).setId(id); + return new Tween(this as T).setId(id); }; diff --git a/src/patch/Matrix4.ts b/src/patch/Matrix4.ts index f88cb9d..00cd9b6 100644 --- a/src/patch/Matrix4.ts +++ b/src/patch/Matrix4.ts @@ -1,38 +1,38 @@ -import { Object3D } from "three"; +import { Object3D } from 'three'; /** @internal Override compose method because is called for every rendered object and can be performance critical after Vector3 patch. */ export function applyMatrix4Patch(parent: Object3D): void { - parent.matrix.compose = function (position: any, quaternion: any, scale: any) { - const te = this.elements; - - const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; - const x2 = x + x, y2 = y + y, z2 = z + z; - const xx = x * x2, xy = x * y2, xz = x * z2; - const yy = y * y2, yz = y * z2, zz = z * z2; - const wx = w * x2, wy = w * y2, wz = w * z2; - - const sx = scale._x, sy = scale._y, sz = scale._z; - - te[0] = (1 - (yy + zz)) * sx; - te[1] = (xy + wz) * sx; - te[2] = (xz - wy) * sx; - te[3] = 0; - - te[4] = (xy - wz) * sy; - te[5] = (1 - (xx + zz)) * sy; - te[6] = (yz + wx) * sy; - te[7] = 0; - - te[8] = (xz + wy) * sz; - te[9] = (yz - wx) * sz; - te[10] = (1 - (xx + yy)) * sz; - te[11] = 0; - - te[12] = position._x; - te[13] = position._y; - te[14] = position._z; - te[15] = 1; - - return this; - }; + parent.matrix.compose = function (position: any, quaternion: any, scale: any) { + const te = this.elements; + + const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; + const x2 = x + x, y2 = y + y, z2 = z + z; + const xx = x * x2, xy = x * y2, xz = x * z2; + const yy = y * y2, yz = y * z2, zz = z * z2; + const wx = w * x2, wy = w * y2, wz = w * z2; + + const sx = scale._x, sy = scale._y, sz = scale._z; + + te[0] = (1 - (yy + zz)) * sx; + te[1] = (xy + wz) * sx; + te[2] = (xz - wy) * sx; + te[3] = 0; + + te[4] = (xy - wz) * sy; + te[5] = (1 - (xx + zz)) * sy; + te[6] = (yz + wx) * sy; + te[7] = 0; + + te[8] = (xz + wy) * sz; + te[9] = (yz - wx) * sz; + te[10] = (1 - (xx + yy)) * sz; + te[11] = 0; + + te[12] = position._x; + te[13] = position._y; + te[14] = position._z; + te[15] = 1; + + return this; + }; } diff --git a/src/patch/Object3D.ts b/src/patch/Object3D.ts index 805be8f..ee16daf 100644 --- a/src/patch/Object3D.ts +++ b/src/patch/Object3D.ts @@ -1,38 +1,38 @@ -import { Object3D, Scene } from "three"; -import { Binding, BindingCallback } from "../binding/Binding.js"; -import { Cursor } from "../events/CursorManager.js"; -import { Default } from "../events/Default.js"; -import { Events, InteractionEvents } from "../events/Events.js"; -import { EventsDispatcher } from "../events/EventsDispatcher.js"; -import { Hitbox } from "../events/Hitbox.js"; -import { Tween } from "../tweening/Tween.js"; -import { querySelector, querySelectorAll } from "../utils/Query.js"; -import { applyEulerPatch } from "./Euler.js"; -import { applyMatrix4Patch } from "./Matrix4.js"; -import { applyQuaternionPatch } from "./Quaternion.js"; -import { removeSceneReference, setSceneReference } from "./Scene.js"; -import { applyVec3Patch } from "./Vector3.js"; +import { Object3D, Scene } from 'three'; +import { Binding, BindingCallback } from '../binding/Binding.js'; +import { Cursor } from '../events/CursorManager.js'; +import { Default } from '../events/Default.js'; +import { Events, InteractionEvents } from '../events/Events.js'; +import { EventsDispatcher } from '../events/EventsDispatcher.js'; +import { Hitbox } from '../events/Hitbox.js'; +import { Tween } from '../tweening/Tween.js'; +import { querySelector, querySelectorAll } from '../utils/Query.js'; +import { applyEulerPatch } from './Euler.js'; +import { applyMatrix4Patch } from './Matrix4.js'; +import { applyQuaternionPatch } from './Quaternion.js'; +import { removeSceneReference, setSceneReference } from './Scene.js'; +import { applyVec3Patch } from './Vector3.js'; /** * Represents the prototype for extended Object3D functionality. */ export interface Object3DExtPrototype { - /** @internal */ __boundCallbacks: BindingCallback[]; - /** @internal */ __manualDetection: boolean; - /** @internal */ __eventsDispatcher: EventsDispatcher; - /** @internal */ __vec3Patched: boolean; - /** @internal */ __rotationPatched: boolean; - /** @internal */ __smartRenderingPatched: boolean; - /** @internal */ __enabled: boolean; - /** @internal */ __visible: boolean; - /** @internal */ __hovered: boolean; - /** @internal */ __focused: boolean; - /** @internal */ __clicking: boolean; - /** @internal */ __dragging: boolean; - /** @internal */ __isDropTarget: boolean; - /** @internal */ __baseVisibleDescriptor: PropertyDescriptor; - /** @internal */ __onChangeBaseEuler: () => void; - /** @internal */ __onChangeBaseQuat: () => void; + /** @internal */ __boundCallbacks: BindingCallback[]; + /** @internal */ __manualDetection: boolean; + /** @internal */ __eventsDispatcher: EventsDispatcher; + /** @internal */ __vec3Patched: boolean; + /** @internal */ __rotationPatched: boolean; + /** @internal */ __smartRenderingPatched: boolean; + /** @internal */ __enabled: boolean; + /** @internal */ __visible: boolean; + /** @internal */ __hovered: boolean; + /** @internal */ __focused: boolean; + /** @internal */ __clicking: boolean; + /** @internal */ __dragging: boolean; + /** @internal */ __isDropTarget: boolean; + /** @internal */ __baseVisibleDescriptor: PropertyDescriptor; + /** @internal */ __onChangeBaseEuler: () => void; + /** @internal */ __onChangeBaseQuat: () => void; /** * Determines if the object is enabled. Default is `true`. * If set to true, it allows triggering all InteractionEvents; otherwise, events are disabled. @@ -176,33 +176,33 @@ Object3D.prototype.__dragging = false; Object3D.prototype.__hovered = false; Object3D.prototype.__visible = true; -Object.defineProperty(Object3D.prototype, "visible", { - get: function (this: Object3D) { return this.__visible }, +Object.defineProperty(Object3D.prototype, 'visible', { + get: function (this: Object3D) { return this.__visible; }, set: function (this: Object3D, value: boolean) { if (this.__visible !== value) { this.__visible = value; - this.__eventsDispatcher.dispatchDescendant("visiblechange", { value, target: this }); + this.__eventsDispatcher.dispatchDescendant('visiblechange', { value, target: this }); } }, configurable: true }); Object3D.prototype.__enabled = true; -Object.defineProperty(Object3D.prototype, "enabled", { - get: function (this: Object3D) { return this.__enabled }, +Object.defineProperty(Object3D.prototype, 'enabled', { + get: function (this: Object3D) { return this.__enabled; }, set: function (this: Object3D, value: boolean) { if (this.__enabled !== value) { if (!value) { this.applyBlur(); } this.__enabled = value; - this.__eventsDispatcher.dispatchDescendant("enabledchange", { value, target: this }); + this.__eventsDispatcher.dispatchDescendant('enabledchange', { value, target: this }); } }, configurable: true }); -Object.defineProperty(Object3D.prototype, "firstFocusable", { +Object.defineProperty(Object3D.prototype, 'firstFocusable', { get: function (this: Object3D) { let obj = this; while (obj?.focusable === false) { @@ -212,7 +212,7 @@ Object.defineProperty(Object3D.prototype, "firstFocusable", { } }); -Object.defineProperty(Object3D.prototype, "enabledState", { +Object.defineProperty(Object3D.prototype, 'enabledState', { get: function (this: Object3D) { if (!this.__enabled) return false; @@ -220,12 +220,12 @@ Object.defineProperty(Object3D.prototype, "enabledState", { do { if (!obj.__enabled) return false; } while ((obj = obj.parent)); - + return true; } }); -Object.defineProperty(Object3D.prototype, "visibilityState", { +Object.defineProperty(Object3D.prototype, 'visibilityState', { get: function (this: Object3D) { let obj = this; do { @@ -235,7 +235,7 @@ Object.defineProperty(Object3D.prototype, "visibilityState", { } }); -Object.defineProperty(Object3D.prototype, "needsRender", { +Object.defineProperty(Object3D.prototype, 'needsRender', { get: function (this: Object3D) { return this.scene?.needsRender; }, @@ -245,32 +245,32 @@ Object.defineProperty(Object3D.prototype, "needsRender", { } }); -Object.defineProperty(Object3D.prototype, "hovered", { +Object.defineProperty(Object3D.prototype, 'hovered', { get: function (this: Object3D) { return this.__hovered; } }); -Object.defineProperty(Object3D.prototype, "focused", { +Object.defineProperty(Object3D.prototype, 'focused', { get: function (this: Object3D) { return this.__focused; } }); -Object.defineProperty(Object3D.prototype, "clicking", { +Object.defineProperty(Object3D.prototype, 'clicking', { get: function (this: Object3D) { return this.__clicking; } }); -Object.defineProperty(Object3D.prototype, "isDragging", { +Object.defineProperty(Object3D.prototype, 'isDragging', { get: function (this: Object3D) { return this.__dragging; } }); Object3D.prototype.on = function (this: Object3D, types: K | K[], listener: (event: Events[K]) => void): (event: Events[K]) => void { - if (typeof types === "string") { + if (typeof types === 'string') { return this.__eventsDispatcher.add(types, listener); } for (const type of types) { @@ -281,21 +281,21 @@ Object3D.prototype.on = function (this: Object3D, types: Object3D.prototype.hasEvent = function (type: K, listener: (event: Events[K]) => void): boolean { return this.__eventsDispatcher.has(type, listener); -} +}; Object3D.prototype.off = function (type: K, listener: (event: Events[K]) => void): void { this.__eventsDispatcher.remove(type, listener); -} +}; Object3D.prototype.trigger = function (type: T, event?: Events[T]): void { this.__eventsDispatcher.dispatchManual(type, event); -} +}; Object3D.prototype.triggerAncestor = function (type: T, event?: Events[T]): void { this.__eventsDispatcher.dispatchAncestorManual(type, event); -} +}; -Object.defineProperty(Object3D.prototype, "userData", { // needed to inject code in constructor +Object.defineProperty(Object3D.prototype, 'userData', { // needed to inject code in constructor set: function (this: Object3D, value) { this.focusable = Default.focusable; this.draggable = Default.draggable; @@ -304,7 +304,7 @@ Object.defineProperty(Object3D.prototype, "userData", { // needed to inject code this.__boundCallbacks = []; this.__eventsDispatcher = new EventsDispatcher(this); - Object.defineProperty(this, "userData", { + Object.defineProperty(this, 'userData', { value, writable: true, configurable: true }); }, diff --git a/src/patch/Quaternion.ts b/src/patch/Quaternion.ts index e830f04..1751a0c 100644 --- a/src/patch/Quaternion.ts +++ b/src/patch/Quaternion.ts @@ -1,28 +1,28 @@ -import { Object3D } from "three"; +import { Object3D } from 'three'; /** @internal */ export function applyQuaternionPatch(target: Object3D): void { - target.__onChangeBaseQuat = target.quaternion._onChangeCallback; - if (target.scene?.__smartRendering) { - setQuatChangeCallbackSR(target); - } else { - setQuatChangeCallback(target); - } + target.__onChangeBaseQuat = target.quaternion._onChangeCallback; + if (target.scene?.__smartRendering) { + setQuatChangeCallbackSR(target); + } else { + setQuatChangeCallback(target); + } } /** @internal */ export function setQuatChangeCallback(target: Object3D): void { - target.quaternion._onChangeCallback = () => { - target.__onChangeBaseQuat(); - target.__eventsDispatcher.dispatch("rotationchange"); - }; + target.quaternion._onChangeCallback = () => { + target.__onChangeBaseQuat(); + target.__eventsDispatcher.dispatch('rotationchange'); + }; } /** @internal */ export function setQuatChangeCallbackSR(target: Object3D): void { - target.quaternion._onChangeCallback = () => { - target.__onChangeBaseQuat(); - target.needsRender = true; - target.__eventsDispatcher.dispatch("rotationchange"); - }; + target.quaternion._onChangeCallback = () => { + target.__onChangeBaseQuat(); + target.needsRender = true; + target.__eventsDispatcher.dispatch('rotationchange'); + }; } diff --git a/src/patch/Scene.ts b/src/patch/Scene.ts index 477283f..cc149d4 100644 --- a/src/patch/Scene.ts +++ b/src/patch/Scene.ts @@ -1,54 +1,54 @@ -import { Object3D, Scene } from "three"; -import { EventsCache } from "../events/MiscEventsManager.js"; -import { activeSmartRendering, applySmartRenderingPatch, removeSmartRenderingPatch } from "./SmartRendering.js"; -import { Binding } from "../binding/Binding.js"; -import { FocusEventExt, IntersectionExt } from "../events/Events.js"; -import { addBase, removeBase } from "./Object3D.js"; -import { EventsDispatcher } from "../events/EventsDispatcher.js"; -import { Default } from "../events/Default.js"; +import { Object3D, Scene } from 'three'; +import { EventsCache } from '../events/MiscEventsManager.js'; +import { activeSmartRendering, applySmartRenderingPatch, removeSmartRenderingPatch } from './SmartRendering.js'; +import { Binding } from '../binding/Binding.js'; +import { FocusEventExt, IntersectionExt } from '../events/Events.js'; +import { addBase, removeBase } from './Object3D.js'; +import { EventsDispatcher } from '../events/EventsDispatcher.js'; +import { Default } from '../events/Default.js'; /** * Represents the prototype for extending Scene functionality. */ export interface SceneExtPrototype { - /** @internal */ __boundObjects: Set; - /** @internal */ __smartRendering: boolean; - /** + /** @internal */ __boundObjects: Set; + /** @internal */ __smartRendering: boolean; + /** * A flag indicating whether continuous raycasting is enabled (default: false). * When set to true, main raycasting occurs every frame, while false triggers raycasting only upon mouse movement. * Additionally, if set to true, the 'pointerintersection' event will be fired every frame. */ - continuousRaycasting: boolean; - /** + continuousRaycasting: boolean; + /** * A flag indicating whether continuous raycasting is enabled when searching for drop targets (default: false). - * When set to true, main raycasting for drop targets occurs every frame, while false triggers it only upon mouse movement. + * When set to true, main raycasting for drop targets occurs every frame, while false triggers it only upon mouse movement. * Additionally, if set to true, the 'dragover' event will be fired every frame. */ - continuousRaycastingDropTarget: boolean; - /** An array of intersections computed from the pointer (primary pointer only). */ - intersections: IntersectionExt[]; - /** An array of intersections computed from the pointer if an object is dragged and has 'findDropTarget' set to true (primary pointer only). */ - intersectionsDropTarget: IntersectionExt[]; - /** A reference to the currently focused Object3D within the scene. */ - focusedObject: Object3D; - /** + continuousRaycastingDropTarget: boolean; + /** An array of intersections computed from the pointer (primary pointer only). */ + intersections: IntersectionExt[]; + /** An array of intersections computed from the pointer if an object is dragged and has 'findDropTarget' set to true (primary pointer only). */ + intersectionsDropTarget: IntersectionExt[]; + /** A reference to the currently focused Object3D within the scene. */ + focusedObject: Object3D; + /** * A flag indicating whether to blur the focused Object3D when clicking outside of any object. */ - blurOnClickOut: boolean; - /** The time scale for scene animations. */ - timeScale: number; - /** The total time elapsed in the scene. */ - totalTime: number; - /** + blurOnClickOut: boolean; + /** The time scale for scene animations. */ + timeScale: number; + /** The total time elapsed in the scene. */ + totalTime: number; + /** * Activates smart rendering for the scene. * @returns The updated instance of the scene. */ - activeSmartRendering(): this; - /** + activeSmartRendering(): this; + /** * Sets the focus to the specified Object3D within the scene, or clears the focus if no target is provided. * @param target Optional. The Object3D to focus on. If not provided, the focus is cleared. */ - focus(target?: Object3D): void; + focus(target?: Object3D): void; } Scene.prototype.continuousRaycasting = false; @@ -58,95 +58,94 @@ Scene.prototype.blurOnClickOut = false; Scene.prototype.timeScale = 1; Scene.prototype.totalTime = 0; Scene.prototype.__smartRendering = false; -Scene.prototype.cursor = "default"; +Scene.prototype.cursor = 'default'; Scene.prototype.activeSmartRendering = function () { - activeSmartRendering(this); - return this; + activeSmartRendering(this); + return this; }; Scene.prototype.focus = function (target?: Object3D): void { - const focusableObj = target?.firstFocusable; - if ((!target || focusableObj?.enabledState) && this.focusedObject !== focusableObj) { - const oldFocusedObj = this.focusedObject; - this.focusedObject = focusableObj; - - if (oldFocusedObj?.enabledState) { - oldFocusedObj.__focused = false; - oldFocusedObj.__eventsDispatcher.dispatchDOMAncestor("blur", new FocusEventExt(focusableObj)); - oldFocusedObj.__eventsDispatcher.dispatchDOM("focusout", new FocusEventExt(focusableObj)); - } - - if (focusableObj) { - focusableObj.__focused = true - focusableObj.__eventsDispatcher.dispatchDOMAncestor("focus", new FocusEventExt(oldFocusedObj)); - focusableObj.__eventsDispatcher.dispatchDOM("focusin", new FocusEventExt(oldFocusedObj)); - } - - this.needsRender = true; + const focusableObj = target?.firstFocusable; + if ((!target || focusableObj?.enabledState) && this.focusedObject !== focusableObj) { + const oldFocusedObj = this.focusedObject; + this.focusedObject = focusableObj; + + if (oldFocusedObj?.enabledState) { + oldFocusedObj.__focused = false; + oldFocusedObj.__eventsDispatcher.dispatchDOMAncestor('blur', new FocusEventExt(focusableObj)); + oldFocusedObj.__eventsDispatcher.dispatchDOM('focusout', new FocusEventExt(focusableObj)); } -} -Scene.prototype.add = function (object: Object3D) { - addBase.call(this, ...arguments); - if (arguments.length === 1 && object?.isObject3D && object !== this) { - setSceneReference(object, this); - this.needsRender = true; + if (focusableObj) { + focusableObj.__focused = true; + focusableObj.__eventsDispatcher.dispatchDOMAncestor('focus', new FocusEventExt(oldFocusedObj)); + focusableObj.__eventsDispatcher.dispatchDOM('focusin', new FocusEventExt(oldFocusedObj)); } - return this; -} -Scene.prototype.remove = function (object: Object3D) { - if (arguments.length === 1 && this.children.indexOf(object) > -1) { - removeSceneReference(object); - this.needsRender = true; - } - removeBase.call(this, ...arguments); - return this; + this.needsRender = true; + } }; +Scene.prototype.add = function (object: Object3D) { + addBase.call(this, ...arguments); + if (arguments.length === 1 && object?.isObject3D && object !== this) { + setSceneReference(object, this); + this.needsRender = true; + } + return this; +}; -Object.defineProperty(Scene.prototype, "userData", { // needed to inject code in constructor - set: function (this: Scene, value) { - this.focusable = false; - this.draggable = Default.draggable; - this.interceptByRaycaster = Default.interceptByRaycaster; - this.tags = new Set(); - this.__boundCallbacks = []; - this.__eventsDispatcher = new EventsDispatcher(this); - - this.intersections = []; - this.intersectionsDropTarget = []; - this.scene = this; - this.__boundObjects = new Set(); +Scene.prototype.remove = function (object: Object3D) { + if (arguments.length === 1 && this.children.indexOf(object) > -1) { + removeSceneReference(object); + this.needsRender = true; + } + removeBase.call(this, ...arguments); + return this; +}; - Object.defineProperty(this, "userData", { - value, writable: true, configurable: true - }); - }, - configurable: true -}) +Object.defineProperty(Scene.prototype, 'userData', { // needed to inject code in constructor + set: function (this: Scene, value) { + this.focusable = false; + this.draggable = Default.draggable; + this.interceptByRaycaster = Default.interceptByRaycaster; + this.tags = new Set(); + this.__boundCallbacks = []; + this.__eventsDispatcher = new EventsDispatcher(this); + + this.intersections = []; + this.intersectionsDropTarget = []; + this.scene = this; + this.__boundObjects = new Set(); + + Object.defineProperty(this, 'userData', { + value, writable: true, configurable: true + }); + }, + configurable: true +}); /** @internal */ export function setSceneReference(target: Object3D, scene: Scene) { - target.scene = scene; - EventsCache.update(target); - applySmartRenderingPatch(target); - Binding.bindToScene(target); - - for (const object of target.children) { - setSceneReference(object, scene); - } + target.scene = scene; + EventsCache.update(target); + applySmartRenderingPatch(target); + Binding.bindToScene(target); + + for (const object of target.children) { + setSceneReference(object, scene); + } } /** @internal */ export function removeSceneReference(target: Object3D) { - EventsCache.removeAll(target); - removeSmartRenderingPatch(target); - Binding.unbindFromScene(target); - target.scene = undefined; - - for (const object of target.children) { - removeSceneReference(object); - } + EventsCache.removeAll(target); + removeSmartRenderingPatch(target); + Binding.unbindFromScene(target); + target.scene = undefined; + + for (const object of target.children) { + removeSceneReference(object); + } } diff --git a/src/patch/SmartRendering.ts b/src/patch/SmartRendering.ts index e9c891c..38af788 100644 --- a/src/patch/SmartRendering.ts +++ b/src/patch/SmartRendering.ts @@ -1,75 +1,75 @@ -import { Object3D, Scene } from "three"; -import { applyObject3DRotationPatch, applyObject3DVector3Patch } from "./Object3D.js"; -import { setVec3ChangeCallback, setVec3ChangeCallbackSR } from "./Vector3.js"; -import { setQuatChangeCallback, setQuatChangeCallbackSR } from "./Quaternion.js"; -import { setEulerChangeCallback, setEulerChangeCallbackSR } from "./Euler.js"; +import { Object3D, Scene } from 'three'; +import { applyObject3DRotationPatch, applyObject3DVector3Patch } from './Object3D.js'; +import { setVec3ChangeCallback, setVec3ChangeCallbackSR } from './Vector3.js'; +import { setQuatChangeCallback, setQuatChangeCallbackSR } from './Quaternion.js'; +import { setEulerChangeCallback, setEulerChangeCallbackSR } from './Euler.js'; /** @internal */ export function applySmartRenderingPatch(target: Object3D): void { - if (target.scene.__smartRendering && !target.__smartRenderingPatched) { - applyPatch(target); - } + if (target.scene.__smartRendering && !target.__smartRenderingPatched) { + applyPatch(target); + } } /** @internal */ export function removeSmartRenderingPatch(target: Object3D): void { - if (target.__smartRenderingPatched) { - setVec3ChangeCallback(target); - setQuatChangeCallback(target); - setEulerChangeCallback(target); - restoreVisible(target); - target.__smartRenderingPatched = false; - } + if (target.__smartRenderingPatched) { + setVec3ChangeCallback(target); + setQuatChangeCallback(target); + setEulerChangeCallback(target); + restoreVisible(target); + target.__smartRenderingPatched = false; + } } /** @internal */ export function activeSmartRendering(scene: Scene): void { - scene.__smartRendering = true; - applySmartRenderingPatchRecursive(scene); + scene.__smartRendering = true; + applySmartRenderingPatchRecursive(scene); } function overrideVisible(target: Object3D): void { - target.__baseVisibleDescriptor = Object.getOwnPropertyDescriptor(target, "visible"); - Object.defineProperty(target, "visible", { - get: function (this: Object3D) { return this.__visible }, - set: function (this: Object3D, value: boolean) { - if (this.__visible !== value) { - if (!value) { - this.applyBlur(); - } - this.__visible = value; - this.needsRender = true; - this.__eventsDispatcher.dispatchDescendant("visiblechange", { value, target: this }); - } - }, - configurable: true - }); + target.__baseVisibleDescriptor = Object.getOwnPropertyDescriptor(target, 'visible'); + Object.defineProperty(target, 'visible', { + get: function (this: Object3D) { return this.__visible; }, + set: function (this: Object3D, value: boolean) { + if (this.__visible !== value) { + if (!value) { + this.applyBlur(); + } + this.__visible = value; + this.needsRender = true; + this.__eventsDispatcher.dispatchDescendant('visiblechange', { value, target: this }); + } + }, + configurable: true + }); } function restoreVisible(target: Object3D): void { - const descriptor = target.__baseVisibleDescriptor; - if (descriptor) { - Object.defineProperty(target, "visible", descriptor); - } else { - delete target.visible; - } + const descriptor = target.__baseVisibleDescriptor; + if (descriptor) { + Object.defineProperty(target, 'visible', descriptor); + } else { + delete target.visible; + } } function applySmartRenderingPatchRecursive(target: Object3D): void { - if (!target.__smartRenderingPatched) { - applyPatch(target); - } - for (const child of target.children) { - applySmartRenderingPatchRecursive(child); - } + if (!target.__smartRenderingPatched) { + applyPatch(target); + } + for (const child of target.children) { + applySmartRenderingPatchRecursive(child); + } } function applyPatch(target: Object3D): void { - applyObject3DVector3Patch(target); - applyObject3DRotationPatch(target); - setVec3ChangeCallbackSR(target); - setQuatChangeCallbackSR(target); - setEulerChangeCallbackSR(target); - overrideVisible(target); - target.__smartRenderingPatched = true; + applyObject3DVector3Patch(target); + applyObject3DRotationPatch(target); + setVec3ChangeCallbackSR(target); + setQuatChangeCallbackSR(target); + setEulerChangeCallbackSR(target); + overrideVisible(target); + target.__smartRenderingPatched = true; } diff --git a/src/patch/Vector3.ts b/src/patch/Vector3.ts index 286c890..f393bdd 100644 --- a/src/patch/Vector3.ts +++ b/src/patch/Vector3.ts @@ -1,582 +1,581 @@ -import { BufferAttribute, Camera, Color, Cylindrical, Euler, MathUtils, Matrix3, Matrix4, Object3D, Quaternion, Spherical, Vector3 } from "three"; +import { BufferAttribute, Camera, Color, Cylindrical, Euler, MathUtils, Matrix3, Matrix4, Object3D, Quaternion, Spherical, Vector3 } from 'three'; /** @internal */ export function applyVec3Patch(target: Object3D): void { - patchVector(target.position as Vector3Ext); - patchVector(target.scale as Vector3Ext); - if (target.scene?.__smartRendering) { - setVec3ChangeCallbackSR(target); - } else { - setVec3ChangeCallback(target); - } + patchVector(target.position as Vector3Ext); + patchVector(target.scale as Vector3Ext); + if (target.scene?.__smartRendering) { + setVec3ChangeCallbackSR(target); + } else { + setVec3ChangeCallback(target); + } } /** @internal */ export function setVec3ChangeCallback(target: Object3D): void { - (target.position as Vector3Ext)._onChangeCallback = () => target.__eventsDispatcher.dispatch("positionchange"); - (target.scale as Vector3Ext)._onChangeCallback = () => target.__eventsDispatcher.dispatch("scalechange"); + (target.position as Vector3Ext)._onChangeCallback = () => target.__eventsDispatcher.dispatch('positionchange'); + (target.scale as Vector3Ext)._onChangeCallback = () => target.__eventsDispatcher.dispatch('scalechange'); } /** @internal */ export function setVec3ChangeCallbackSR(target: Object3D): void { - (target.position as Vector3Ext)._onChangeCallback = () => { - target.needsRender = true; - target.__eventsDispatcher.dispatch("positionchange"); - }; - (target.scale as Vector3Ext)._onChangeCallback = () => { - target.needsRender = true; - target.__eventsDispatcher.dispatch("scalechange"); - }; + (target.position as Vector3Ext)._onChangeCallback = () => { + target.needsRender = true; + target.__eventsDispatcher.dispatch('positionchange'); + }; + (target.scale as Vector3Ext)._onChangeCallback = () => { + target.needsRender = true; + target.__eventsDispatcher.dispatch('scalechange'); + }; } function patchVector(vec3: Vector3Ext): void { - vec3._x = vec3.x; - vec3._y = vec3.y; - vec3._z = vec3.z; - delete vec3.x; - delete vec3.y; - delete vec3.z; - Object.setPrototypeOf(vec3, Vector3Ext.prototype); + vec3._x = vec3.x; + vec3._y = vec3.y; + vec3._z = vec3.z; + delete vec3.x; + delete vec3.y; + delete vec3.z; + Object.setPrototypeOf(vec3, Vector3Ext.prototype); } /** @LASTREV 162 Vector3 */ class Vector3Ext { - public distanceToManhattan: (v: Vector3) => number; //remove when fix deprecated d.ts - public lengthManhattan: () => number; //remove when fix deprecated d.ts - public _x: number; - public _y: number; - public _z: number; - public _onChangeCallback: () => void; - public isVector3: true; - - public get x() { return this._x } - public set x(value: number) { - this._x = value; - this._onChangeCallback(); - } - - public get y() { return this._y } - public set y(value: number) { - this._y = value; - this._onChangeCallback(); - } - - public get z() { return this._z } - public set z(value: number) { - this._z = value; - this._onChangeCallback(); - } - - set(x: number, y: number, z: number) { - if (z === undefined) z = this._z; - this._x = x; - this._y = y; - this._z = z; - this._onChangeCallback(); - return this; - } - - setScalar(scalar: number) { - this._x = scalar; - this._y = scalar; - this._z = scalar; - this._onChangeCallback(); - return this; - } - - setX(x: number) { - this._x = x; - this._onChangeCallback(); - return this; - } - - setY(y: number) { - this._y = y; - this._onChangeCallback(); - return this; - } - - setZ(z: number) { - this._z = z; - this._onChangeCallback(); - return this; - } - - setComponent(index: number, value: number) { - switch (index) { - case 0: this._x = value; break; - case 1: this._y = value; break; - case 2: this._z = value; break; - default: throw new Error('index is out of range: ' + index); - } - this._onChangeCallback(); - return this; - } - - getComponent(index: number) { - switch (index) { - case 0: return this._x; - case 1: return this._y; - case 2: return this._z; - default: throw new Error('index is out of range: ' + index); - } - } - - clone() { - return new (Vector3.prototype as any).constructor(this._x, this._y, this._z); - } - - copy(v: Vector3, update?: boolean) { - this._x = v.x; - this._y = v.y; - this._z = v.z; - if (update !== false) this._onChangeCallback(); - return this; - } - - add(v: Vector3) { - this._x += v.x; - this._y += v.y; - this._z += v.z; - this._onChangeCallback(); - return this; - } - - addScalar(s: number) { - this._x += s; - this._y += s; - this._z += s; - this._onChangeCallback(); - return this; - } - - addVectors(a: Vector3, b: Vector3) { - this._x = a.x + b.x; - this._y = a.y + b.y; - this._z = a.z + b.z; - this._onChangeCallback(); - return this; - } - - addScaledVector(v: Vector3, s: number) { - this._x += v.x * s; - this._y += v.y * s; - this._z += v.z * s; - this._onChangeCallback(); - return this; - } - - sub(v: Vector3) { - this._x -= v.x; - this._y -= v.y; - this._z -= v.z; - this._onChangeCallback(); - return this; - } - - subScalar(s: number) { - this._x -= s; - this._y -= s; - this._z -= s; - this._onChangeCallback(); - return this; - } - - subVectors(a: Vector3, b: Vector3) { - this._x = a.x - b.x; - this._y = a.y - b.y; - this._z = a.z - b.z; - this._onChangeCallback(); - return this; - } - - multiply(v: Vector3) { - this._x *= v.x; - this._y *= v.y; - this._z *= v.z; - this._onChangeCallback(); - return this; - } - - multiplyScalar(scalar: number, update?: boolean) { - this._x *= scalar; - this._y *= scalar; - this._z *= scalar; - if (update !== false) this._onChangeCallback(); - return this; - } - - multiplyVectors(a: Vector3, b: Vector3) { - this._x = a.x * b.x; - this._y = a.y * b.y; - this._z = a.z * b.z; - this._onChangeCallback(); - return this; - } - - applyEuler(euler: Euler) { - return this.applyQuaternion(_quaternion.setFromEuler(euler)); - } - - applyAxisAngle(axis: Vector3, angle: number) { - return this.applyQuaternion(_quaternion.setFromAxisAngle(axis, angle)); - } - - applyMatrix3(m: Matrix3, update?: boolean) { - const x = this._x, y = this._y, z = this._z; - const e = m.elements; - this._x = e[0] * x + e[3] * y + e[6] * z; - this._y = e[1] * x + e[4] * y + e[7] * z; - this._z = e[2] * x + e[5] * y + e[8] * z; - if (update !== false) this._onChangeCallback(); - return this; - } - - applyNormalMatrix(m: Matrix3) { - return this.applyMatrix3(m, false).normalize(); - } - - applyMatrix4(m: Matrix4, update?: boolean) { - const x = this._x, y = this._y, z = this._z; - const e = m.elements; - const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]); - this._x = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w; - this._y = (e[1] * x + e[5] * y + e[9] * z + e[13]) * w; - this._z = (e[2] * x + e[6] * y + e[10] * z + e[14]) * w; - if (update !== false) this._onChangeCallback(); - return this; - } - - applyQuaternion(q: Quaternion) { - const vx = this._x, vy = this._y, vz = this._z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - const tx = 2 * (qy * vz - qz * vy); - const ty = 2 * (qz * vx - qx * vz); - const tz = 2 * (qx * vy - qy * vx); - this._x = vx + qw * tx + qy * tz - qz * ty; - this._y = vy + qw * ty + qz * tx - qx * tz; - this._z = vz + qw * tz + qx * ty - qy * tx; - this._onChangeCallback(); - return this; - } - - project(camera: Camera) { - return this.applyMatrix4(camera.matrixWorldInverse, false).applyMatrix4(camera.projectionMatrix); - } - - unproject(camera: Camera) { - return this.applyMatrix4(camera.projectionMatrixInverse, false).applyMatrix4(camera.matrixWorld); - } - - transformDirection(m: Matrix4) { - const x = this._x, y = this._y, z = this._z; - const e = m.elements; - this._x = e[0] * x + e[4] * y + e[8] * z; - this._y = e[1] * x + e[5] * y + e[9] * z; - this._z = e[2] * x + e[6] * y + e[10] * z; - return this.normalize(); - } - - divide(v: Vector3) { - this._x /= v.x; - this._y /= v.y; - this._z /= v.z; - this._onChangeCallback(); - return this; - } - - divideScalar(scalar: number, update?: boolean) { - return this.multiplyScalar(1 / scalar, update); - } - - min(v: Vector3) { - this._x = Math.min(this._x, v.x); - this._y = Math.min(this._y, v.y); - this._z = Math.min(this._z, v.z); - this._onChangeCallback(); - return this; - } - - max(v: Vector3) { - this._x = Math.max(this._x, v.x); - this._y = Math.max(this._y, v.y); - this._z = Math.max(this._z, v.z); - this._onChangeCallback(); - return this; - } - - clamp(min: Vector3, max: Vector3) { - this._x = Math.max(min.x, Math.min(max.x, this._x)); - this._y = Math.max(min.y, Math.min(max.y, this._y)); - this._z = Math.max(min.z, Math.min(max.z, this._z)); - this._onChangeCallback(); - return this; - } - - clampScalar(minVal: number, maxVal: number) { - this._x = Math.max(minVal, Math.min(maxVal, this._x)); - this._y = Math.max(minVal, Math.min(maxVal, this._y)); - this._z = Math.max(minVal, Math.min(maxVal, this._z)); - this._onChangeCallback(); - return this; - } - - clampLength(min: number, max: number) { - const length = this.length(); - return this.divideScalar(length || 1, false).multiplyScalar(Math.max(min, Math.min(max, length))); - } - - floor() { - this._x = Math.floor(this._x); - this._y = Math.floor(this._y); - this._z = Math.floor(this._z); - this._onChangeCallback(); - return this; - } - - ceil() { - this._x = Math.ceil(this._x); - this._y = Math.ceil(this._y); - this._z = Math.ceil(this._z); - this._onChangeCallback(); - return this; - } - - round() { - this._x = Math.round(this._x); - this._y = Math.round(this._y); - this._z = Math.round(this._z); - this._onChangeCallback(); - return this; - } - - roundToZero() { - this._x = Math.trunc(this._x); - this._y = Math.trunc(this._y); - this._z = Math.trunc(this._z); - this._onChangeCallback(); - return this; - } - - negate() { - this._x = - this._x; - this._y = - this._y; - this._z = - this._z; - this._onChangeCallback(); - return this; - } - - dot(v: Vector3) { - return this._x * v.x + this._y * v.y + this._z * v.z; - } - - lengthSq() { - return this._x * this._x + this._y * this._y + this._z * this._z; - } - - length() { - return Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z); - } - - manhattanLength() { - return Math.abs(this._x) + Math.abs(this._y) + Math.abs(this._z); - } - - normalize(update?: boolean) { - return this.divideScalar(this.length() || 1, update); - } - - setLength(length: number) { - return this.normalize(false).multiplyScalar(length); - } - - lerp(v: Vector3, alpha: number) { - this._x += (v.x - this._x) * alpha; - this._y += (v.y - this._y) * alpha; - this._z += (v.z - this._z) * alpha; - this._onChangeCallback(); - return this; - } - - lerpVectors(v1: Vector3, v2: Vector3, alpha: number) { - this._x = v1.x + (v2.x - v1.x) * alpha; - this._y = v1.y + (v2.y - v1.y) * alpha; - this._z = v1.z + (v2.z - v1.z) * alpha; - this._onChangeCallback(); - return this; - } - - cross(v: Vector3) { - return this.crossVectors(this, v); - } - - crossVectors(a: Vector3, b: Vector3) { - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; - this._x = ay * bz - az * by; - this._y = az * bx - ax * bz; - this._z = ax * by - ay * bx; - this._onChangeCallback(); - return this; - } - - projectOnVector(v: Vector3) { - const denominator = v.lengthSq(); - if (denominator === 0) return this.set(0, 0, 0); - const scalar = v.dot(this) / denominator; - return this.copy(v, false).multiplyScalar(scalar); - } - - projectOnPlane(planeNormal: Vector3) { - _vector.copy(this).projectOnVector(planeNormal); - return this.sub(_vector); - } - - reflect(normal: Vector3) { - return this.sub(_vector.copy(normal).multiplyScalar(2 * this.dot(normal))); - } - - angleTo(v: Vector3) { - const denominator = Math.sqrt(this.lengthSq() * v.lengthSq()); - if (denominator === 0) return Math.PI / 2; - const theta = this.dot(v) / denominator; - return Math.acos(MathUtils.clamp(theta, - 1, 1)); - } - - distanceTo(v: Vector3) { - return Math.sqrt(this.distanceToSquared(v)); - } - - distanceToSquared(v: Vector3) { - const dx = this._x - v.x, dy = this._y - v.y, dz = this._z - v.z; - return dx * dx + dy * dy + dz * dz; - } - - manhattanDistanceTo(v: Vector3) { - return Math.abs(this._x - v.x) + Math.abs(this._y - v.y) + Math.abs(this._z - v.z); - } - - setFromSpherical(s: Spherical) { - return this.setFromSphericalCoords(s.radius, s.phi, s.theta); - } - - setFromSphericalCoords(radius: number, phi: number, theta: number) { - const sinPhiRadius = Math.sin(phi) * radius; - this._x = sinPhiRadius * Math.sin(theta); - this._y = Math.cos(phi) * radius; - this._z = sinPhiRadius * Math.cos(theta); - this._onChangeCallback(); - return this; - } - - setFromCylindrical(c: Cylindrical) { - return this.setFromCylindricalCoords(c.radius, c.theta, c.y); - } - - setFromCylindricalCoords(radius: number, theta: number, y: number) { - this._x = radius * Math.sin(theta); - this._y = y; - this._z = radius * Math.cos(theta); - this._onChangeCallback(); - return this; - } - - setFromMatrixPosition(m: Matrix4) { - const e = m.elements; - this._x = e[12]; - this._y = e[13]; - this._z = e[14]; - this._onChangeCallback(); - return this; - } - - setFromMatrixScale(m: Matrix4) { - const sx = this.setFromMatrixColumn(m, 0).length(); - const sy = this.setFromMatrixColumn(m, 1).length(); - const sz = this.setFromMatrixColumn(m, 2).length(); - this._x = sx; - this._y = sy; - this._z = sz; - this._onChangeCallback(); - return this; - } - - setFromMatrixColumn(m: Matrix4, index: number) { - return this.fromArray(m.elements, index * 4); - } - - setFromMatrix3Column(m: Matrix3, index: number) { - return this.fromArray(m.elements, index * 3); - } - - setFromEuler(e: any) { - this._x = e._x; - this._y = e._y; - this._z = e._z; - this._onChangeCallback(); - return this; - } - - setFromColor(c: Color) { - this._x = c.r; - this._y = c.g; - this._z = c.b; - this._onChangeCallback(); - return this; - } - - equals(v: Vector3) { - return ((v.x === this._x) && (v.y === this._y) && (v.z === this._z)); - } - - fromArray(array: number[], offset = 0) { - this._x = array[offset]; - this._y = array[offset + 1]; - this._z = array[offset + 2]; - this._onChangeCallback(); - return this; - } - - toArray(array: number[] = [], offset = 0): any { - array[offset] = this._x; - array[offset + 1] = this._y; - array[offset + 2] = this._z; - return array; - } - - fromBufferAttribute(attribute: BufferAttribute, index: number) { - this._x = attribute.getX(index); - this._y = attribute.getY(index); - this._z = attribute.getZ(index); - this._onChangeCallback(); - return this; - } - - random() { - this._x = Math.random(); - this._y = Math.random(); - this._z = Math.random(); - this._onChangeCallback(); - return this; - } - - randomDirection() { - const theta = Math.random() * Math.PI * 2; - const u = Math.random() * 2 - 1; - const c = Math.sqrt(1 - u * u); - this.x = c * Math.cos(theta); - this.y = u; - this.z = c * Math.sin(theta); - this._onChangeCallback(); - return this; - } - - *[Symbol.iterator]() { - yield this._x; - yield this._y; - yield this._z; - } - + public distanceToManhattan: (v: Vector3) => number; // remove when fix deprecated d.ts + public lengthManhattan: () => number; // remove when fix deprecated d.ts + public _x: number; + public _y: number; + public _z: number; + public _onChangeCallback: () => void; + public isVector3: true; + + public get x() { return this._x; } + public set x(value: number) { + this._x = value; + this._onChangeCallback(); + } + + public get y() { return this._y; } + public set y(value: number) { + this._y = value; + this._onChangeCallback(); + } + + public get z() { return this._z; } + public set z(value: number) { + this._z = value; + this._onChangeCallback(); + } + + set(x: number, y: number, z: number) { + if (z === undefined) z = this._z; + this._x = x; + this._y = y; + this._z = z; + this._onChangeCallback(); + return this; + } + + setScalar(scalar: number) { + this._x = scalar; + this._y = scalar; + this._z = scalar; + this._onChangeCallback(); + return this; + } + + setX(x: number) { + this._x = x; + this._onChangeCallback(); + return this; + } + + setY(y: number) { + this._y = y; + this._onChangeCallback(); + return this; + } + + setZ(z: number) { + this._z = z; + this._onChangeCallback(); + return this; + } + + setComponent(index: number, value: number) { + switch (index) { + case 0: this._x = value; break; + case 1: this._y = value; break; + case 2: this._z = value; break; + default: throw new Error('index is out of range: ' + index); + } + this._onChangeCallback(); + return this; + } + + getComponent(index: number) { + switch (index) { + case 0: return this._x; + case 1: return this._y; + case 2: return this._z; + default: throw new Error('index is out of range: ' + index); + } + } + + clone() { + return new (Vector3.prototype as any).constructor(this._x, this._y, this._z); + } + + copy(v: Vector3, update?: boolean) { + this._x = v.x; + this._y = v.y; + this._z = v.z; + if (update !== false) this._onChangeCallback(); + return this; + } + + add(v: Vector3) { + this._x += v.x; + this._y += v.y; + this._z += v.z; + this._onChangeCallback(); + return this; + } + + addScalar(s: number) { + this._x += s; + this._y += s; + this._z += s; + this._onChangeCallback(); + return this; + } + + addVectors(a: Vector3, b: Vector3) { + this._x = a.x + b.x; + this._y = a.y + b.y; + this._z = a.z + b.z; + this._onChangeCallback(); + return this; + } + + addScaledVector(v: Vector3, s: number) { + this._x += v.x * s; + this._y += v.y * s; + this._z += v.z * s; + this._onChangeCallback(); + return this; + } + + sub(v: Vector3) { + this._x -= v.x; + this._y -= v.y; + this._z -= v.z; + this._onChangeCallback(); + return this; + } + + subScalar(s: number) { + this._x -= s; + this._y -= s; + this._z -= s; + this._onChangeCallback(); + return this; + } + + subVectors(a: Vector3, b: Vector3) { + this._x = a.x - b.x; + this._y = a.y - b.y; + this._z = a.z - b.z; + this._onChangeCallback(); + return this; + } + + multiply(v: Vector3) { + this._x *= v.x; + this._y *= v.y; + this._z *= v.z; + this._onChangeCallback(); + return this; + } + + multiplyScalar(scalar: number, update?: boolean) { + this._x *= scalar; + this._y *= scalar; + this._z *= scalar; + if (update !== false) this._onChangeCallback(); + return this; + } + + multiplyVectors(a: Vector3, b: Vector3) { + this._x = a.x * b.x; + this._y = a.y * b.y; + this._z = a.z * b.z; + this._onChangeCallback(); + return this; + } + + applyEuler(euler: Euler) { + return this.applyQuaternion(_quaternion.setFromEuler(euler)); + } + + applyAxisAngle(axis: Vector3, angle: number) { + return this.applyQuaternion(_quaternion.setFromAxisAngle(axis, angle)); + } + + applyMatrix3(m: Matrix3, update?: boolean) { + const x = this._x, y = this._y, z = this._z; + const e = m.elements; + this._x = e[0] * x + e[3] * y + e[6] * z; + this._y = e[1] * x + e[4] * y + e[7] * z; + this._z = e[2] * x + e[5] * y + e[8] * z; + if (update !== false) this._onChangeCallback(); + return this; + } + + applyNormalMatrix(m: Matrix3) { + return this.applyMatrix3(m, false).normalize(); + } + + applyMatrix4(m: Matrix4, update?: boolean) { + const x = this._x, y = this._y, z = this._z; + const e = m.elements; + const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]); + this._x = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w; + this._y = (e[1] * x + e[5] * y + e[9] * z + e[13]) * w; + this._z = (e[2] * x + e[6] * y + e[10] * z + e[14]) * w; + if (update !== false) this._onChangeCallback(); + return this; + } + + applyQuaternion(q: Quaternion) { + const vx = this._x, vy = this._y, vz = this._z; + const qx = q.x, qy = q.y, qz = q.z, qw = q.w; + const tx = 2 * (qy * vz - qz * vy); + const ty = 2 * (qz * vx - qx * vz); + const tz = 2 * (qx * vy - qy * vx); + this._x = vx + qw * tx + qy * tz - qz * ty; + this._y = vy + qw * ty + qz * tx - qx * tz; + this._z = vz + qw * tz + qx * ty - qy * tx; + this._onChangeCallback(); + return this; + } + + project(camera: Camera) { + return this.applyMatrix4(camera.matrixWorldInverse, false).applyMatrix4(camera.projectionMatrix); + } + + unproject(camera: Camera) { + return this.applyMatrix4(camera.projectionMatrixInverse, false).applyMatrix4(camera.matrixWorld); + } + + transformDirection(m: Matrix4) { + const x = this._x, y = this._y, z = this._z; + const e = m.elements; + this._x = e[0] * x + e[4] * y + e[8] * z; + this._y = e[1] * x + e[5] * y + e[9] * z; + this._z = e[2] * x + e[6] * y + e[10] * z; + return this.normalize(); + } + + divide(v: Vector3) { + this._x /= v.x; + this._y /= v.y; + this._z /= v.z; + this._onChangeCallback(); + return this; + } + + divideScalar(scalar: number, update?: boolean) { + return this.multiplyScalar(1 / scalar, update); + } + + min(v: Vector3) { + this._x = Math.min(this._x, v.x); + this._y = Math.min(this._y, v.y); + this._z = Math.min(this._z, v.z); + this._onChangeCallback(); + return this; + } + + max(v: Vector3) { + this._x = Math.max(this._x, v.x); + this._y = Math.max(this._y, v.y); + this._z = Math.max(this._z, v.z); + this._onChangeCallback(); + return this; + } + + clamp(min: Vector3, max: Vector3) { + this._x = Math.max(min.x, Math.min(max.x, this._x)); + this._y = Math.max(min.y, Math.min(max.y, this._y)); + this._z = Math.max(min.z, Math.min(max.z, this._z)); + this._onChangeCallback(); + return this; + } + + clampScalar(minVal: number, maxVal: number) { + this._x = Math.max(minVal, Math.min(maxVal, this._x)); + this._y = Math.max(minVal, Math.min(maxVal, this._y)); + this._z = Math.max(minVal, Math.min(maxVal, this._z)); + this._onChangeCallback(); + return this; + } + + clampLength(min: number, max: number) { + const length = this.length(); + return this.divideScalar(length || 1, false).multiplyScalar(Math.max(min, Math.min(max, length))); + } + + floor() { + this._x = Math.floor(this._x); + this._y = Math.floor(this._y); + this._z = Math.floor(this._z); + this._onChangeCallback(); + return this; + } + + ceil() { + this._x = Math.ceil(this._x); + this._y = Math.ceil(this._y); + this._z = Math.ceil(this._z); + this._onChangeCallback(); + return this; + } + + round() { + this._x = Math.round(this._x); + this._y = Math.round(this._y); + this._z = Math.round(this._z); + this._onChangeCallback(); + return this; + } + + roundToZero() { + this._x = Math.trunc(this._x); + this._y = Math.trunc(this._y); + this._z = Math.trunc(this._z); + this._onChangeCallback(); + return this; + } + + negate() { + this._x = -this._x; + this._y = -this._y; + this._z = -this._z; + this._onChangeCallback(); + return this; + } + + dot(v: Vector3) { + return this._x * v.x + this._y * v.y + this._z * v.z; + } + + lengthSq() { + return this._x * this._x + this._y * this._y + this._z * this._z; + } + + length() { + return Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z); + } + + manhattanLength() { + return Math.abs(this._x) + Math.abs(this._y) + Math.abs(this._z); + } + + normalize(update?: boolean) { + return this.divideScalar(this.length() || 1, update); + } + + setLength(length: number) { + return this.normalize(false).multiplyScalar(length); + } + + lerp(v: Vector3, alpha: number) { + this._x += (v.x - this._x) * alpha; + this._y += (v.y - this._y) * alpha; + this._z += (v.z - this._z) * alpha; + this._onChangeCallback(); + return this; + } + + lerpVectors(v1: Vector3, v2: Vector3, alpha: number) { + this._x = v1.x + (v2.x - v1.x) * alpha; + this._y = v1.y + (v2.y - v1.y) * alpha; + this._z = v1.z + (v2.z - v1.z) * alpha; + this._onChangeCallback(); + return this; + } + + cross(v: Vector3) { + return this.crossVectors(this, v); + } + + crossVectors(a: Vector3, b: Vector3) { + const ax = a.x, ay = a.y, az = a.z; + const bx = b.x, by = b.y, bz = b.z; + this._x = ay * bz - az * by; + this._y = az * bx - ax * bz; + this._z = ax * by - ay * bx; + this._onChangeCallback(); + return this; + } + + projectOnVector(v: Vector3) { + const denominator = v.lengthSq(); + if (denominator === 0) return this.set(0, 0, 0); + const scalar = v.dot(this) / denominator; + return this.copy(v, false).multiplyScalar(scalar); + } + + projectOnPlane(planeNormal: Vector3) { + _vector.copy(this).projectOnVector(planeNormal); + return this.sub(_vector); + } + + reflect(normal: Vector3) { + return this.sub(_vector.copy(normal).multiplyScalar(2 * this.dot(normal))); + } + + angleTo(v: Vector3) { + const denominator = Math.sqrt(this.lengthSq() * v.lengthSq()); + if (denominator === 0) return Math.PI / 2; + const theta = this.dot(v) / denominator; + return Math.acos(MathUtils.clamp(theta, -1, 1)); + } + + distanceTo(v: Vector3) { + return Math.sqrt(this.distanceToSquared(v)); + } + + distanceToSquared(v: Vector3) { + const dx = this._x - v.x, dy = this._y - v.y, dz = this._z - v.z; + return dx * dx + dy * dy + dz * dz; + } + + manhattanDistanceTo(v: Vector3) { + return Math.abs(this._x - v.x) + Math.abs(this._y - v.y) + Math.abs(this._z - v.z); + } + + setFromSpherical(s: Spherical) { + return this.setFromSphericalCoords(s.radius, s.phi, s.theta); + } + + setFromSphericalCoords(radius: number, phi: number, theta: number) { + const sinPhiRadius = Math.sin(phi) * radius; + this._x = sinPhiRadius * Math.sin(theta); + this._y = Math.cos(phi) * radius; + this._z = sinPhiRadius * Math.cos(theta); + this._onChangeCallback(); + return this; + } + + setFromCylindrical(c: Cylindrical) { + return this.setFromCylindricalCoords(c.radius, c.theta, c.y); + } + + setFromCylindricalCoords(radius: number, theta: number, y: number) { + this._x = radius * Math.sin(theta); + this._y = y; + this._z = radius * Math.cos(theta); + this._onChangeCallback(); + return this; + } + + setFromMatrixPosition(m: Matrix4) { + const e = m.elements; + this._x = e[12]; + this._y = e[13]; + this._z = e[14]; + this._onChangeCallback(); + return this; + } + + setFromMatrixScale(m: Matrix4) { + const sx = this.setFromMatrixColumn(m, 0).length(); + const sy = this.setFromMatrixColumn(m, 1).length(); + const sz = this.setFromMatrixColumn(m, 2).length(); + this._x = sx; + this._y = sy; + this._z = sz; + this._onChangeCallback(); + return this; + } + + setFromMatrixColumn(m: Matrix4, index: number) { + return this.fromArray(m.elements, index * 4); + } + + setFromMatrix3Column(m: Matrix3, index: number) { + return this.fromArray(m.elements, index * 3); + } + + setFromEuler(e: any) { + this._x = e._x; + this._y = e._y; + this._z = e._z; + this._onChangeCallback(); + return this; + } + + setFromColor(c: Color) { + this._x = c.r; + this._y = c.g; + this._z = c.b; + this._onChangeCallback(); + return this; + } + + equals(v: Vector3) { + return ((v.x === this._x) && (v.y === this._y) && (v.z === this._z)); + } + + fromArray(array: number[], offset = 0) { + this._x = array[offset]; + this._y = array[offset + 1]; + this._z = array[offset + 2]; + this._onChangeCallback(); + return this; + } + + toArray(array: number[] = [], offset = 0): any { + array[offset] = this._x; + array[offset + 1] = this._y; + array[offset + 2] = this._z; + return array; + } + + fromBufferAttribute(attribute: BufferAttribute, index: number) { + this._x = attribute.getX(index); + this._y = attribute.getY(index); + this._z = attribute.getZ(index); + this._onChangeCallback(); + return this; + } + + random() { + this._x = Math.random(); + this._y = Math.random(); + this._z = Math.random(); + this._onChangeCallback(); + return this; + } + + randomDirection() { + const theta = Math.random() * Math.PI * 2; + const u = Math.random() * 2 - 1; + const c = Math.sqrt(1 - u * u); + this.x = c * Math.cos(theta); + this.y = u; + this.z = c * Math.sin(theta); + this._onChangeCallback(); + return this; + } + + *[Symbol.iterator]() { + yield this._x; + yield this._y; + yield this._z; + } } Vector3Ext.prototype.isVector3 = true; diff --git a/src/patch/WebGLRenderer.ts b/src/patch/WebGLRenderer.ts index b8cc753..8d50823 100644 --- a/src/patch/WebGLRenderer.ts +++ b/src/patch/WebGLRenderer.ts @@ -1,47 +1,47 @@ -import { Camera, Scene, Vector4, WebGLRenderer } from "three"; -import { Main } from "../core/Main.js"; -import { ViewportResizeEvent } from "../events/Events.js"; -import { EventsCache } from "../events/MiscEventsManager.js"; +import { Camera, Scene, Vector4, WebGLRenderer } from 'three'; +import { Main } from '../core/Main.js'; +import { ViewportResizeEvent } from '../events/Events.js'; +import { EventsCache } from '../events/MiscEventsManager.js'; const viewportSize = new Vector4(); const lastViewportSizes: { [x: number]: Vector4 } = {}; /** @internal */ export function applyWebGLRendererPatch(main: Main): void { - const renderer = main.renderer; - const baseRender = renderer.render.bind(renderer); - - renderer.render = function (scene: Scene, camera: Camera) { - this.getViewport(viewportSize); - handleViewportResize(this, scene, camera); - - if (main._showStats) main._stats.beginQuery(); - baseRender(scene, camera); - if (main._showStats) main._stats.endQuery(); - } + const renderer = main.renderer; + const baseRender = renderer.render.bind(renderer); + + renderer.render = function (scene: Scene, camera: Camera) { + this.getViewport(viewportSize); + handleViewportResize(this, scene, camera); + + if (main._showStats) main._stats.beginQuery(); + baseRender(scene, camera); + if (main._showStats) main._stats.endQuery(); + }; } function handleViewportResize(renderer: WebGLRenderer, scene: Scene, camera: Camera): void { - let event: ViewportResizeEvent; - - if (!lastViewportSizes[scene.id]) { - lastViewportSizes[scene.id] = new Vector4(-1); - } - - const lastSceneSize = lastViewportSizes[scene.id]; - if (lastSceneSize.z !== viewportSize.z || lastSceneSize.w !== viewportSize.w) { - lastSceneSize.copy(viewportSize); - event = { renderer, camera, width: viewportSize.z, height: viewportSize.w }; - EventsCache.dispatchEventExcludeCameras(scene, "viewportresize", event); - } - - if (!lastViewportSizes[camera.id]) { - lastViewportSizes[camera.id] = new Vector4(-1); - } - - const lastCameraSize = lastViewportSizes[camera.id]; - if (lastCameraSize.z !== viewportSize.z || lastCameraSize.w !== viewportSize.w) { - lastCameraSize.copy(viewportSize); - camera.__eventsDispatcher?.dispatch("viewportresize", event ?? { renderer, camera, width: viewportSize.z, height: viewportSize.w }); - } + let event: ViewportResizeEvent; + + if (!lastViewportSizes[scene.id]) { + lastViewportSizes[scene.id] = new Vector4(-1); + } + + const lastSceneSize = lastViewportSizes[scene.id]; + if (lastSceneSize.z !== viewportSize.z || lastSceneSize.w !== viewportSize.w) { + lastSceneSize.copy(viewportSize); + event = { renderer, camera, width: viewportSize.z, height: viewportSize.w }; + EventsCache.dispatchEventExcludeCameras(scene, 'viewportresize', event); + } + + if (!lastViewportSizes[camera.id]) { + lastViewportSizes[camera.id] = new Vector4(-1); + } + + const lastCameraSize = lastViewportSizes[camera.id]; + if (lastCameraSize.z !== viewportSize.z || lastCameraSize.w !== viewportSize.w) { + lastCameraSize.copy(viewportSize); + camera.__eventsDispatcher?.dispatch('viewportresize', event ?? { renderer, camera, width: viewportSize.z, height: viewportSize.w }); + } } diff --git a/src/rendering/RenderManager.ts b/src/rendering/RenderManager.ts index dc7386e..f3f1a59 100644 --- a/src/rendering/RenderManager.ts +++ b/src/rendering/RenderManager.ts @@ -1,6 +1,6 @@ -import { Camera, Color, ColorRepresentation, Scene, Vector2, WebGLRenderer } from "three"; -import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"; -import { RenderView, ViewParameters } from "./RenderView.js"; +import { Camera, Color, ColorRepresentation, Scene, Vector2, WebGLRenderer } from 'three'; +import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; +import { RenderView, ViewParameters } from './RenderView.js'; /** @internal */ export class RenderManager { @@ -16,22 +16,22 @@ export class RenderManager { private _resized = false; private readonly _resizeObserver = new ResizeObserver(() => this._resized = true); - public get activeScene(): Scene { return this.activeView?.scene } - public get hoveredScene(): Scene { return this.hoveredView?.scene } + public get activeScene(): Scene { return this.activeView?.scene; } + public get hoveredScene(): Scene { return this.hoveredView?.scene; } - public get fullscreen(): boolean { return this._fullscreen } + public get fullscreen(): boolean { return this._fullscreen; } public set fullscreen(value: boolean) { this._fullscreen = value; this.updateRenderSize(); } - public get backgroundColor(): Color { return this._backgroundColor } + public get backgroundColor(): Color { return this._backgroundColor; } public set backgroundColor(value: ColorRepresentation) { this._backgroundColor = new Color(value); this.renderer.setClearColor(this._backgroundColor, this._backgroundAlpha); } - public get backgroundAlpha(): number { return this._backgroundAlpha } + public get backgroundAlpha(): number { return this._backgroundAlpha; } public set backgroundAlpha(value: number) { this._backgroundAlpha = value; this.renderer.setClearColor(this._backgroundColor, this._backgroundAlpha); @@ -44,7 +44,7 @@ export class RenderManager { this._fullscreen = fullscreen; this._backgroundAlpha = backgroundAlpha; this._backgroundColor = new Color(backgroundColor); - window.addEventListener("resize", () => this._resized = true); + window.addEventListener('resize', () => this._resized = true); this._resizeObserver.observe(this.renderer.domElement); this.updateRenderSize(); renderer.setClearColor(this._backgroundColor, this._backgroundAlpha); diff --git a/src/rendering/RenderView.ts b/src/rendering/RenderView.ts index 4422806..0877d68 100644 --- a/src/rendering/RenderView.ts +++ b/src/rendering/RenderView.ts @@ -1,5 +1,5 @@ -import { Camera, Color, ColorRepresentation, Scene, Vector2 } from "three"; -import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"; +import { Camera, Color, ColorRepresentation, Scene, Vector2 } from 'three'; +import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; /** * Represents an object defining the dimensions and position of a viewport. @@ -44,7 +44,7 @@ export interface ViewParameters { } /** - * Represents a render view with specific parameters. + * Represents a render view with specific parameters. * Don't instantiate this manually. */ export class RenderView implements ViewParameters { @@ -63,7 +63,7 @@ export class RenderView implements ViewParameters { private _onBeforeRender: () => void; private _onAfterRender: () => void; - public get visible(): boolean { return this._visible } + public get visible(): boolean { return this._visible; } public set visible(value: boolean) { if (this._visible === value) return; this._visible = value; diff --git a/src/tweening/Actions.ts b/src/tweening/Actions.ts index f3f5409..1b93a53 100644 --- a/src/tweening/Actions.ts +++ b/src/tweening/Actions.ts @@ -1,7 +1,7 @@ -import { Color, ColorRepresentation, Euler, MathUtils, Quaternion, Vector2, Vector3, Vector4 } from "three"; -import { Easing, EasingFunction, Easings } from "./Easings.js"; -import { RunningAction } from "./RunningTween.js"; -import { Tween } from "./Tween.js"; +import { Color, ColorRepresentation, Euler, MathUtils, Quaternion, Vector2, Vector3, Vector4 } from 'three'; +import { Easing, EasingFunction, Easings } from './Easings.js'; +import { RunningAction } from './RunningTween.js'; +import { Tween } from './Tween.js'; const easings = new Easings(); export type Vector = Vector2 | Vector3 | Vector4; @@ -20,24 +20,24 @@ export type SetMotion = { [key in keyof T]?: T[key] }; * @template T - The type of the target object being tweened. */ export interface MotionConfig { - /** The easing function to control the animation's progression. */ - easing?: Easing; - /** + /** The easing function to control the animation's progression. */ + easing?: Easing; + /** * A callback function to execute when the animation completes. * @param target - The target object that was tweened. */ - onComplete?: (target: T) => void; - /** + onComplete?: (target: T) => void; + /** * A callback function to execute when the animation starts. * @param target - The target object that is being tweened. */ - onStart?: (target: T) => void; - /** + onStart?: (target: T) => void; + /** * A callback function to be executed after each property has been updated. * @param target - The target object that is being tweened. */ - onUpdate?: (target: T) => void; - /** + onUpdate?: (target: T) => void; + /** * A callback function to be executed before each property is updated. * @param target - The target object that is being tweened. * @param key - The key or property being animated. @@ -46,166 +46,166 @@ export interface MotionConfig { * @param alpha - The current animation progress as a normalized value (0 to 1). * @returns If `false`, will not assign a new value to the property. */ - onProgress?: (target: T, key: string, start: AllowedTypes, end: AllowedTypes, alpha: number) => boolean | void; + onProgress?: (target: T, key: string, start: AllowedTypes, end: AllowedTypes, alpha: number) => boolean | void; } /** @internal */ export interface ActionDescriptor { - actions?: RunningAction[]; - tweens?: Tween[]; - config?: MotionConfig; + actions?: RunningAction[]; + tweens?: Tween[]; + config?: MotionConfig; } /** @internal */ export interface IAction { - init?(target: T): ActionDescriptor; - hasActions: boolean; - isRepeat?: boolean; - isYoyo?: boolean; - isTween?: boolean; - times?: number; + init?(target: T): ActionDescriptor; + hasActions: boolean; + isRepeat?: boolean; + isYoyo?: boolean; + isTween?: boolean; + times?: number; } /** @internal */ export class ActionRepeat implements IAction { - public hasActions = false; - public isRepeat = true; - constructor(public times: number) { } + public hasActions = false; + public isRepeat = true; + constructor(public times: number) { } } /** @internal */ export class ActionYoyo implements IAction { - public hasActions = false; - public isYoyo = true; - constructor(public times: number) { } + public hasActions = false; + public isYoyo = true; + constructor(public times: number) { } } /** @internal */ export class ActionTween implements IAction { - public hasActions = true; - public isTween = true; - public tweens: Tween[] = []; + public hasActions = true; + public isTween = true; + public tweens: Tween[] = []; - constructor(...tweens: Tween[]) { - for (const tween of tweens) { - this.tweens.push(tween.clone()); - } + constructor(...tweens: Tween[]) { + for (const tween of tweens) { + this.tweens.push(tween.clone()); } + } } /** @internal */ export class ActionCallback implements IAction { - public hasActions = true; - constructor(public callback: () => void) { } + public hasActions = true; + constructor(public callback: () => void) { } - public init(): ActionDescriptor { - return { actions: [{ callback: this.callback, time: 0 }] }; - } + public init(): ActionDescriptor { + return { actions: [{ callback: this.callback, time: 0 }] }; + } } /** @internal */ export class ActionDelay implements IAction { - public hasActions = true; - constructor(public time: number) { } + public hasActions = true; + constructor(public time: number) { } - public init(): ActionDescriptor { - return { actions: [{ callback: () => { }, time: this.time }] }; - } + public init(): ActionDescriptor { + return { actions: [{ callback: () => { }, time: this.time }] }; + } } /** @internal */ export class ActionMotion implements IAction { - public hasActions = true; - constructor(public time: number, public motion: Motion | SetMotion, public config: MotionConfig, public isBy: boolean) { } - - public init(target: T): ActionDescriptor { - const actions: RunningAction[] = []; - for (const key in this.motion) { - if (key === "easing") continue; - const actionValue = this.motion[key]; - const targetValue = target[key]; - const action = this.vector(key, actionValue as Vector, targetValue as Vector) - ?? this.quaternion(key, actionValue as Quaternion, targetValue as Quaternion) - ?? this.euler(key, actionValue as Euler, targetValue as Euler) - ?? this.color(key, actionValue as Color, targetValue as Color) - ?? this.number(target, key, actionValue as number); - if (action) { - actions.push(action); - } - } - return { actions, config: this.config }; + public hasActions = true; + constructor(public time: number, public motion: Motion | SetMotion, public config: MotionConfig, public isBy: boolean) { } + + public init(target: T): ActionDescriptor { + const actions: RunningAction[] = []; + for (const key in this.motion) { + if (key === 'easing') continue; + const actionValue = this.motion[key]; + const targetValue = target[key]; + const action = this.vector(key, actionValue as Vector, targetValue as Vector) + ?? this.quaternion(key, actionValue as Quaternion, targetValue as Quaternion) + ?? this.euler(key, actionValue as Euler, targetValue as Euler) + ?? this.color(key, actionValue as Color, targetValue as Color) + ?? this.number(target, key, actionValue as number); + if (action) { + actions.push(action); + } } - - private getEasing(): EasingFunction { - const easing = this.config?.easing ?? Easings.DEFAULT_EASING; - return typeof easing === "string" ? (easings[easing].bind(easings) ?? easings.linear) : easing; + return { actions, config: this.config }; + } + + private getEasing(): EasingFunction { + const easing = this.config?.easing ?? Easings.DEFAULT_EASING; + return typeof easing === 'string' ? (easings[easing].bind(easings) ?? easings.linear) : easing; + } + + private vector(key: string, actionValue: Vector | number, targetValue: Vector): RunningAction { + if (!targetValue) return; + if ((targetValue as Vector2).isVector2 || (targetValue as Vector3).isVector3 || (targetValue as Vector4).isVector4) { + const value = typeof actionValue === 'number' ? targetValue.clone().setScalar(actionValue) : actionValue; + return { + key, + time: this.time, + easing: this.getEasing(), + start: targetValue.clone(), + end: this.isBy ? value.clone().add(targetValue as Vector4) : value, + callback: (start, end, alpha) => { targetValue.lerpVectors(start as Vector4, end as Vector4, alpha); } + }; } - - private vector(key: string, actionValue: Vector | number, targetValue: Vector): RunningAction { - if (!targetValue) return; - if ((targetValue as Vector2).isVector2 || (targetValue as Vector3).isVector3 || (targetValue as Vector4).isVector4) { - const value = typeof actionValue === "number" ? targetValue.clone().setScalar(actionValue) : actionValue; - return { - key, - time: this.time, - easing: this.getEasing(), - start: targetValue.clone(), - end: this.isBy ? value.clone().add(targetValue as Vector4) : value, - callback: (start, end, alpha) => { targetValue.lerpVectors(start as Vector4, end as Vector4, alpha) } - }; - } + } + + private quaternion(key: string, actionValue: Quaternion, targetValue: Quaternion): RunningAction { + if (targetValue?.isQuaternion) { + return { + key, + time: this.time, + easing: this.getEasing(), + start: targetValue.clone(), + end: this.isBy ? actionValue.clone().premultiply(targetValue) : actionValue, + callback: (start, end, alpha) => { targetValue.slerpQuaternions(start, end, alpha); } + }; } - - private quaternion(key: string, actionValue: Quaternion, targetValue: Quaternion): RunningAction { - if (targetValue?.isQuaternion) { - return { - key, - time: this.time, - easing: this.getEasing(), - start: targetValue.clone(), - end: this.isBy ? actionValue.clone().premultiply(targetValue) : actionValue, - callback: (start, end, alpha) => { targetValue.slerpQuaternions(start, end, alpha) } - }; + } + + private euler(key: string, actionValue: Euler, targetValue: Euler): RunningAction { + if (targetValue?.isEuler) { + return { + key, + time: this.time, + easing: this.getEasing(), + start: targetValue.clone(), + end: this.isBy ? new Euler(actionValue.x + targetValue.x, actionValue.y + targetValue.y, actionValue.z + targetValue.z) : actionValue, + callback: (start, end, alpha) => { + targetValue.set(MathUtils.lerp(start.x, end.x, alpha), MathUtils.lerp(start.y, end.y, alpha), MathUtils.lerp(start.z, end.z, alpha)); } + }; } - - private euler(key: string, actionValue: Euler, targetValue: Euler): RunningAction { - if (targetValue?.isEuler) { - return { - key, - time: this.time, - easing: this.getEasing(), - start: targetValue.clone(), - end: this.isBy ? new Euler(actionValue.x + targetValue.x, actionValue.y + targetValue.y, actionValue.z + targetValue.z) : actionValue, - callback: (start, end, alpha) => { - targetValue.set(MathUtils.lerp(start.x, end.x, alpha), MathUtils.lerp(start.y, end.y, alpha), MathUtils.lerp(start.z, end.z, alpha)); - } - }; - } - } - - private color(key: string, actionValue: ColorRepresentation, targetValue: Color): RunningAction { - if (targetValue?.isColor) { - return { - key, - time: this.time, - easing: this.getEasing(), - start: targetValue.clone(), - end: this.isBy ? new Color(actionValue).add(targetValue) : new Color(actionValue), - callback: (start, end, alpha) => { targetValue.lerpColors(start, end, alpha) } - }; - } - } - - private number(target: T, key: string, actionValue: number): RunningAction { - if (typeof actionValue === "number") - return { - key, - time: this.time, - easing: this.getEasing(), - start: target[key], - end: this.isBy ? actionValue + target[key] : actionValue, - callback: (start, end, alpha) => { target[key] = MathUtils.lerp(start, end, alpha) } - }; + } + + private color(key: string, actionValue: ColorRepresentation, targetValue: Color): RunningAction { + if (targetValue?.isColor) { + return { + key, + time: this.time, + easing: this.getEasing(), + start: targetValue.clone(), + end: this.isBy ? new Color(actionValue).add(targetValue) : new Color(actionValue), + callback: (start, end, alpha) => { targetValue.lerpColors(start, end, alpha); } + }; } + } + + private number(target: T, key: string, actionValue: number): RunningAction { + if (typeof actionValue === 'number') + return { + key, + time: this.time, + easing: this.getEasing(), + start: target[key], + end: this.isBy ? actionValue + target[key] : actionValue, + callback: (start, end, alpha) => { target[key] = MathUtils.lerp(start, end, alpha); } + }; + } } diff --git a/src/tweening/Easings.ts b/src/tweening/Easings.ts index 552f4c0..266a481 100644 --- a/src/tweening/Easings.ts +++ b/src/tweening/Easings.ts @@ -8,140 +8,140 @@ export type Easing = keyof Easings | EasingFunction; * For more info on these easing functions, check https://easings.net. */ export class Easings { - /** The default easing function used when no easing is specified. */ - public static DEFAULT_EASING: keyof Easings = "easeInOutExpo"; + /** The default easing function used when no easing is specified. */ + public static DEFAULT_EASING: keyof Easings = 'easeInOutExpo'; - public linear(x: number): number { - return x; - } - - public easeInSine(x: number): number { - return 1 - Math.cos((x * Math.PI) / 2); - } - - public easeOutSine(x: number): number { - return Math.sin((x * Math.PI) / 2); - } - - public easeInOutSine(x: number): number { - return -(Math.cos(Math.PI * x) - 1) / 2; - } - - public easeInQuad(x: number): number { - return x * x; - } - - public easeOutQuad(x: number): number { - return 1 - (1 - x) * (1 - x); - } - - public easeInOutQuad(x: number): number { - return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; - } - - public easeInCubic(x: number): number { - return x * x * x; - } - - public easeOutCubic(x: number): number { - return 1 - Math.pow(1 - x, 3); - } - - public easeInOutCubic(x: number): number { - return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; - } - - public easeInQuart(x: number): number { - return x * x * x * x; - } - - public easeOutQuart(x: number): number { - return 1 - Math.pow(1 - x, 4); - } - - public easeInOutQuart(x: number): number { - return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; - } - - public easeInQuint(x: number): number { - return x * x * x * x * x; - } - - public easeOutQuint(x: number): number { - return 1 - Math.pow(1 - x, 5); - } - - public easeInOutQuint(x: number): number { - return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; - } - - public easeInExpo(x: number): number { - return x === 0 ? 0 : Math.pow(2, 10 * x - 10); - } - - public easeOutExpo(x: number): number { - return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); - } - - public easeInOutExpo(x: number): number { - return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 : (2 - Math.pow(2, -20 * x + 10)) / 2; - } - - public easeInCirc(x: number): number { - return 1 - Math.sqrt(1 - Math.pow(x, 2)); - } - - public easeOutCirc(x: number): number { - return Math.sqrt(1 - Math.pow(x - 1, 2)); - } - - public easeInOutCirc(x: number): number { - return x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; - } - - public easeInBack(x: number): number { - return 2.70158 * x * x * x - 1.70158 * x * x; - } - - public easeOutBack(x: number): number { - return 1 + 2.70158 * Math.pow(x - 1, 3) + 1.70158 * Math.pow(x - 1, 2); - } - - public easeInOutBack(x: number): number { - const c2 = 1.70158 * 1.525; - return x < 0.5 ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; - } + public linear(x: number): number { + return x; + } - public easeInElastic(x: number): number { - return x === 0 ? 0 : x === 1 ? 1 : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * (2 * Math.PI) / 3); - } + public easeInSine(x: number): number { + return 1 - Math.cos((x * Math.PI) / 2); + } - public easeOutElastic(x: number): number { - return x === 0 ? 0 : x === 1 ? 1 : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * (2 * Math.PI) / 3) + 1; - } + public easeOutSine(x: number): number { + return Math.sin((x * Math.PI) / 2); + } - public easeInOutElastic(x: number): number { - const c5 = (2 * Math.PI) / 4.5; - return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1; - } + public easeInOutSine(x: number): number { + return -(Math.cos(Math.PI * x) - 1) / 2; + } - public easeInBounce(x: number): number { - return 1 - this.easeOutBounce(1 - x); - } + public easeInQuad(x: number): number { + return x * x; + } - public easeOutBounce(x: number): number { - if (x < 1 / 2.75) { - return 7.5625 * x * x; - } else if (x < 2 / 2.75) { - return 7.5625 * (x -= 1.5 / 2.75) * x + 0.75; - } else if (x < 2.5 / 2.75) { - return 7.5625 * (x -= 2.25 / 2.75) * x + 0.9375; - } else { - return 7.5625 * (x -= 2.625 / 2.75) * x + 0.984375; - } - } - - public easeInOutBounce(x: number): number { - return x < 0.5 ? (1 - this.easeOutBounce(1 - 2 * x)) / 2 : (1 + this.easeOutBounce(2 * x - 1)) / 2; - } + public easeOutQuad(x: number): number { + return 1 - (1 - x) * (1 - x); + } + + public easeInOutQuad(x: number): number { + return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; + } + + public easeInCubic(x: number): number { + return x * x * x; + } + + public easeOutCubic(x: number): number { + return 1 - Math.pow(1 - x, 3); + } + + public easeInOutCubic(x: number): number { + return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; + } + + public easeInQuart(x: number): number { + return x * x * x * x; + } + + public easeOutQuart(x: number): number { + return 1 - Math.pow(1 - x, 4); + } + + public easeInOutQuart(x: number): number { + return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; + } + + public easeInQuint(x: number): number { + return x * x * x * x * x; + } + + public easeOutQuint(x: number): number { + return 1 - Math.pow(1 - x, 5); + } + + public easeInOutQuint(x: number): number { + return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; + } + + public easeInExpo(x: number): number { + return x === 0 ? 0 : Math.pow(2, 10 * x - 10); + } + + public easeOutExpo(x: number): number { + return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); + } + + public easeInOutExpo(x: number): number { + return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 : (2 - Math.pow(2, -20 * x + 10)) / 2; + } + + public easeInCirc(x: number): number { + return 1 - Math.sqrt(1 - Math.pow(x, 2)); + } + + public easeOutCirc(x: number): number { + return Math.sqrt(1 - Math.pow(x - 1, 2)); + } + + public easeInOutCirc(x: number): number { + return x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; + } + + public easeInBack(x: number): number { + return 2.70158 * x * x * x - 1.70158 * x * x; + } + + public easeOutBack(x: number): number { + return 1 + 2.70158 * Math.pow(x - 1, 3) + 1.70158 * Math.pow(x - 1, 2); + } + + public easeInOutBack(x: number): number { + const c2 = 1.70158 * 1.525; + return x < 0.5 ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; + } + + public easeInElastic(x: number): number { + return x === 0 ? 0 : x === 1 ? 1 : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * (2 * Math.PI) / 3); + } + + public easeOutElastic(x: number): number { + return x === 0 ? 0 : x === 1 ? 1 : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * (2 * Math.PI) / 3) + 1; + } + + public easeInOutElastic(x: number): number { + const c5 = (2 * Math.PI) / 4.5; + return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1; + } + + public easeInBounce(x: number): number { + return 1 - this.easeOutBounce(1 - x); + } + + public easeOutBounce(x: number): number { + if (x < 1 / 2.75) { + return 7.5625 * x * x; + } else if (x < 2 / 2.75) { + return 7.5625 * (x -= 1.5 / 2.75) * x + 0.75; + } else if (x < 2.5 / 2.75) { + return 7.5625 * (x -= 2.25 / 2.75) * x + 0.9375; + } else { + return 7.5625 * (x -= 2.625 / 2.75) * x + 0.984375; + } + } + + public easeInOutBounce(x: number): number { + return x < 0.5 ? (1 - this.easeOutBounce(1 - 2 * x)) / 2 : (1 + this.easeOutBounce(2 * x - 1)) / 2; + } } diff --git a/src/tweening/RunningTween.ts b/src/tweening/RunningTween.ts index d5a1769..66f93ca 100644 --- a/src/tweening/RunningTween.ts +++ b/src/tweening/RunningTween.ts @@ -1,30 +1,30 @@ -import { ActionTween, MotionConfig } from "./Actions.js"; -import { EasingFunction } from "./Easings.js"; -import { Tween } from "./Tween.js"; -import { TweenManager } from "./TweenManager.js"; +import { ActionTween, MotionConfig } from './Actions.js'; +import { EasingFunction } from './Easings.js'; +import { Tween } from './Tween.js'; +import { TweenManager } from './TweenManager.js'; type UpdateCallback = (start?: T, end?: T, alpha?: number) => void; interface RunningBlock { - tweens?: Tween[]; - runningTweens?: RunningTween[]; - actions?: RunningAction[]; - elapsedTime: number; - totalTime: number; - reversed?: boolean; - originallyReversed?: boolean; - tweensStarted?: boolean; - config?: MotionConfig; + tweens?: Tween[]; + runningTweens?: RunningTween[]; + actions?: RunningAction[]; + elapsedTime: number; + totalTime: number; + reversed?: boolean; + originallyReversed?: boolean; + tweensStarted?: boolean; + config?: MotionConfig; } /** @internal */ export interface RunningAction { - time: number; - callback: UpdateCallback; - easing?: EasingFunction; - start?: T; - end?: T; - key?: string; + time: number; + callback: UpdateCallback; + easing?: EasingFunction; + start?: T; + end?: T; + key?: string; } /** @@ -34,308 +34,308 @@ export interface RunningAction { * @template T - The type of the target object. */ export class RunningTween { - /** @internal */ public root: RunningTween; - /** @internal */ public tween: Tween; - /** @internal */ public target: T; - /** @internal */ public actionIndex = -1; - /** @internal */ public currentBlock?: RunningBlock; - /** @internal */ public history: RunningBlock[] = []; - /** @internal */ public reversed?: boolean; - /** @internal */ public originallyReversed?: boolean; - /** @internal */ public repeat?: boolean; - /** @internal */ public repetitions: { [x: number]: number } = {}; - /** @internal */ public _finished = false; - /** + /** @internal */ public root: RunningTween; + /** @internal */ public tween: Tween; + /** @internal */ public target: T; + /** @internal */ public actionIndex = -1; + /** @internal */ public currentBlock?: RunningBlock; + /** @internal */ public history: RunningBlock[] = []; + /** @internal */ public reversed?: boolean; + /** @internal */ public originallyReversed?: boolean; + /** @internal */ public repeat?: boolean; + /** @internal */ public repetitions: { [x: number]: number } = {}; + /** @internal */ public _finished = false; + /** * Indicates whether the execution of the running tween is paused. * If set to `true`, the tween will not progress until it is resumed. */ - public paused = false; - /** + public paused = false; + /** * The time scale factor for the running tween. * It determines the speed at which the tween progresses. * A value of `1` represents normal speed, while `0.5` would be half-speed, and `2` would be double-speed. */ - public timeScale = 1; + public timeScale = 1; - /** + /** * Indicates whether the running tween has finished executing. */ - public get finished(): boolean { return this._finished } + public get finished(): boolean { return this._finished; } - /** + /** * Don't instantiate this manually. */ - constructor(target: T, tween: Tween) { - this.target = target; - this.tween = tween; - } + constructor(target: T, tween: Tween) { + this.target = target; + this.tween = tween; + } - /** + /** * Set the time scale for the running tween. * @param value - The time scale value to apply. * @returns The updated RunningTween instance. */ - public setTimeScale(value: number): this { - this.timeScale = value; - return this; - } + public setTimeScale(value: number): this { + this.timeScale = value; + return this; + } - /** + /** * Pause the execution of the running tween. */ - public pause(): void { - this.paused = true; - } + public pause(): void { + this.paused = true; + } - /** + /** * Resume the execution of the running tween if it was paused. */ - public resume(): void { - this.paused = false; - } + public resume(): void { + this.paused = false; + } - /** + /** * Stop the running tween, causing it to finish immediately. */ - public stop(): void { - TweenManager.stop(this); - } + public stop(): void { + TweenManager.stop(this); + } - /** + /** * Complete the running tween, causing it to finish immediately. */ - public complete(): void { - TweenManager.complete(this); - } + public complete(): void { + TweenManager.complete(this); + } - /** + /** * Revert the running tween to its initial state (Not implemented yet). */ - public revert(): void { - console.error("Revert method not implemented yet."); // handle (!blockHistory) - } + public revert(): void { + console.error('Revert method not implemented yet.'); // handle (!blockHistory) + } - /** @internal */ - public getBlock(): RunningBlock { - this.currentBlock?.config?.onComplete?.call(this.target, this.target); - const block = this.getCurrentBlock(); - block?.config?.onStart?.call(this.target, this.target); - if (!this.tween.blockHistory && !this.reversed && !this.repeat && block) { - this.history.push(block); - } - this.currentBlock = block; - return block; + /** @internal */ + public getBlock(): RunningBlock { + this.currentBlock?.config?.onComplete?.call(this.target, this.target); + const block = this.getCurrentBlock(); + block?.config?.onStart?.call(this.target, this.target); + if (!this.tween.blockHistory && !this.reversed && !this.repeat && block) { + this.history.push(block); } + this.currentBlock = block; + return block; + } - /** @internal */ - private getCurrentBlock(): RunningBlock { - if (this.reversed) return this.getPrevBlock(); - return this.repeat ? this.getRepeatBlock() : this.getNextBlock(); - } + /** @internal */ + private getCurrentBlock(): RunningBlock { + if (this.reversed) return this.getPrevBlock(); + return this.repeat ? this.getRepeatBlock() : this.getNextBlock(); + } - /** @internal */ - private getPrevBlock(): RunningBlock { - if (this.actionIndex > 0) { - const block = this.history[--this.actionIndex]; - block.reversed = !block.originallyReversed; - block.elapsedTime = 0; - block.tweensStarted = false; - return block; - } + /** @internal */ + private getPrevBlock(): RunningBlock { + if (this.actionIndex > 0) { + const block = this.history[--this.actionIndex]; + block.reversed = !block.originallyReversed; + block.elapsedTime = 0; + block.tweensStarted = false; + return block; } + } - /** @internal */ - private getRepeatBlock(): RunningBlock { - if (this.actionIndex < this.history.length - 1) { - const block = this.history[++this.actionIndex]; - block.reversed = block.originallyReversed; - block.elapsedTime = 0; - block.tweensStarted = false; - return block; - } + /** @internal */ + private getRepeatBlock(): RunningBlock { + if (this.actionIndex < this.history.length - 1) { + const block = this.history[++this.actionIndex]; + block.reversed = block.originallyReversed; + block.elapsedTime = 0; + block.tweensStarted = false; + return block; } + } - /** @internal */ - private getNextBlock(): RunningBlock { - while (++this.actionIndex < this.tween.actions.length) { - const action = this.tween.actions[this.actionIndex]; - if (action.isRepeat) { - this.handleRepetition(action.times); - } else if (action.isYoyo) { - const block = this.handleYoyo(action.times); - if (block) return block; - } else if (action.isTween) { - return this.handleTween(action as ActionTween); - } else { - return this.handleMotion(); - } - } + /** @internal */ + private getNextBlock(): RunningBlock { + while (++this.actionIndex < this.tween.actions.length) { + const action = this.tween.actions[this.actionIndex]; + if (action.isRepeat) { + this.handleRepetition(action.times); + } else if (action.isYoyo) { + const block = this.handleYoyo(action.times); + if (block) return block; + } else if (action.isTween) { + return this.handleTween(action as ActionTween); + } else { + return this.handleMotion(); + } } + } - /** @internal */ - private cloneBlock(block: RunningBlock): RunningBlock { - return { - elapsedTime: 0, - totalTime: block.totalTime, - reversed: !block.reversed, - originallyReversed: !block.reversed, - actions: block.actions, - tweens: block.tweens, - config: block.config, - runningTweens: this.cloneRunningTweens(block.runningTweens) - } - } + /** @internal */ + private cloneBlock(block: RunningBlock): RunningBlock { + return { + elapsedTime: 0, + totalTime: block.totalTime, + reversed: !block.reversed, + originallyReversed: !block.reversed, + actions: block.actions, + tweens: block.tweens, + config: block.config, + runningTweens: this.cloneRunningTweens(block.runningTweens) + }; + } - /** @internal */ - private cloneRunningTweens(runningTweens: RunningTween[]): RunningTween[] { - if (!runningTweens) return; - const ret: RunningTween[] = []; - for (const runningTween of runningTweens) { - const runningCloned = new RunningTween(runningTween.target, runningTween.tween); - runningCloned.timeScale = runningTween.timeScale; - runningCloned.root = runningTween.root; - runningCloned.history = runningTween.history; - runningCloned.actionIndex = !runningTween.reversed ? runningTween.history.length : -1; - runningCloned.originallyReversed = !runningTween.reversed; - runningCloned.repeat = runningTween.reversed - ret.push(runningCloned); - } - return ret; + /** @internal */ + private cloneRunningTweens(runningTweens: RunningTween[]): RunningTween[] { + if (!runningTweens) return; + const ret: RunningTween[] = []; + for (const runningTween of runningTweens) { + const runningCloned = new RunningTween(runningTween.target, runningTween.tween); + runningCloned.timeScale = runningTween.timeScale; + runningCloned.root = runningTween.root; + runningCloned.history = runningTween.history; + runningCloned.actionIndex = !runningTween.reversed ? runningTween.history.length : -1; + runningCloned.originallyReversed = !runningTween.reversed; + runningCloned.repeat = runningTween.reversed; + ret.push(runningCloned); } + return ret; + } - /** @internal */ - private handleMotion(): RunningBlock { - const descriptor = this.tween.actions[this.actionIndex].init(this.target); - return { - config: descriptor.config, - actions: descriptor.actions, - elapsedTime: 0, - totalTime: Math.max(...descriptor.actions.map(x => x.time)), - }; - } + /** @internal */ + private handleMotion(): RunningBlock { + const descriptor = this.tween.actions[this.actionIndex].init(this.target); + return { + config: descriptor.config, + actions: descriptor.actions, + elapsedTime: 0, + totalTime: Math.max(...descriptor.actions.map((x) => x.time)) + }; + } - /** @internal */ - private handleTween(action: ActionTween): RunningBlock { - return { - tweens: action.tweens, - elapsedTime: 0, - totalTime: 0 - }; - } + /** @internal */ + private handleTween(action: ActionTween): RunningBlock { + return { + tweens: action.tweens, + elapsedTime: 0, + totalTime: 0 + }; + } - /** @internal */ - private handleRepetition(times: number): void { - const repeat = this.repetitions; - repeat[this.actionIndex] ??= 0; - if (repeat[this.actionIndex] < times) { - repeat[this.actionIndex]++; - do { - this.actionIndex--; - } while (this.actionIndex > -1 && !this.tween.actions[this.actionIndex].hasActions); - this.actionIndex--; - } + /** @internal */ + private handleRepetition(times: number): void { + const repeat = this.repetitions; + repeat[this.actionIndex] ??= 0; + if (repeat[this.actionIndex] < times) { + repeat[this.actionIndex]++; + do { + this.actionIndex--; + } while (this.actionIndex > -1 && !this.tween.actions[this.actionIndex].hasActions); + this.actionIndex--; } + } - /** @internal */ - private handleYoyo(times: number): RunningBlock { - const repeat = this.repetitions; - repeat[this.actionIndex] ??= 0; - if (repeat[this.actionIndex] < times) { - repeat[this.actionIndex]++; - if (repeat[this.actionIndex--] < 3) { - return this.cloneBlock(this.history[this.history.length - 1]); - } - const block = this.history[this.history.length - 2]; - block.elapsedTime = 0; - block.tweensStarted = false; - return block; - } + /** @internal */ + private handleYoyo(times: number): RunningBlock { + const repeat = this.repetitions; + repeat[this.actionIndex] ??= 0; + if (repeat[this.actionIndex] < times) { + repeat[this.actionIndex]++; + if (repeat[this.actionIndex--] < 3) { + return this.cloneBlock(this.history[this.history.length - 1]); + } + const block = this.history[this.history.length - 2]; + block.elapsedTime = 0; + block.tweensStarted = false; + return block; } + } - /** @internal */ - public execute(delta: number): boolean { - if (this.paused) return true; - delta *= this.timeScale; - do { - delta = Math.min(this.executeBlock(delta), this.getTweensDelta(this.currentBlock)); - } while (delta >= 0 && this.getBlock()); - return delta < 0; - } + /** @internal */ + public execute(delta: number): boolean { + if (this.paused) return true; + delta *= this.timeScale; + do { + delta = Math.min(this.executeBlock(delta), this.getTweensDelta(this.currentBlock)); + } while (delta >= 0 && this.getBlock()); + return delta < 0; + } - /** @internal */ - private executeBlock(delta: number): number { - const block = this.currentBlock; - block.elapsedTime += delta; - this.executeActions(block); - this.executeTweens(block, delta); - return block.elapsedTime - block.totalTime; - } + /** @internal */ + private executeBlock(delta: number): number { + const block = this.currentBlock; + block.elapsedTime += delta; + this.executeActions(block); + this.executeTweens(block, delta); + return block.elapsedTime - block.totalTime; + } - /** @internal */ - private executeActions(block: RunningBlock): void { - if (block.actions) { - for (const action of block.actions) { - const elapsedTime = Math.min(1, block.elapsedTime / (action.time + 10 ** -12)); - const alpha = block.reversed ? 1 - elapsedTime : elapsedTime; - const easedAlpha = action.easing?.call(this, alpha) ?? alpha; - const applyValue = block.config?.onProgress?.call(this.target, this.target, action.key, action.start, action.end, easedAlpha); - if (applyValue !== false) { - action.callback(action.start, action.end, easedAlpha); - } - } - block.config?.onUpdate?.call(this.target, this.target); + /** @internal */ + private executeActions(block: RunningBlock): void { + if (block.actions) { + for (const action of block.actions) { + const elapsedTime = Math.min(1, block.elapsedTime / (action.time + 10 ** -12)); + const alpha = block.reversed ? 1 - elapsedTime : elapsedTime; + const easedAlpha = action.easing?.call(this, alpha) ?? alpha; + const applyValue = block.config?.onProgress?.call(this.target, this.target, action.key, action.start, action.end, easedAlpha); + if (applyValue !== false) { + action.callback(action.start, action.end, easedAlpha); } + } + block.config?.onUpdate?.call(this.target, this.target); } + } - /** @internal */ - private executeTweens(block: RunningBlock, delta: number): void { - if (block.tweens && !block.tweensStarted) { - if (!block.runningTweens) { - block.runningTweens = []; - for (const tween of block.tweens) { - this.executeTween(block, delta, tween); - } - } else { - for (const runningTween of block.runningTweens) { - runningTween.executeExistingTween(delta, this.reversed); - } - } - block.tweensStarted = true; + /** @internal */ + private executeTweens(block: RunningBlock, delta: number): void { + if (block.tweens && !block.tweensStarted) { + if (!block.runningTweens) { + block.runningTweens = []; + for (const tween of block.tweens) { + this.executeTween(block, delta, tween); + } + } else { + for (const runningTween of block.runningTweens) { + runningTween.executeExistingTween(delta, this.reversed); } + } + block.tweensStarted = true; } + } - /** @internal */ - private executeTween(block: RunningBlock, delta: number, tween: Tween): void { - const runningTween = TweenManager.createChildren(this.target, tween, this.root ?? this); - block.runningTweens.push(runningTween); - if (runningTween.execute(delta)) { - TweenManager.addChildren(runningTween); - } + /** @internal */ + private executeTween(block: RunningBlock, delta: number, tween: Tween): void { + const runningTween = TweenManager.createChildren(this.target, tween, this.root ?? this); + block.runningTweens.push(runningTween); + if (runningTween.execute(delta)) { + TweenManager.addChildren(runningTween); } + } - /** @internal */ - private executeExistingTween(delta: number, reversed: boolean): void { - this.reversed = reversed ? !this.originallyReversed : this.originallyReversed; - this.repeat = !this.reversed; - this.actionIndex = this.reversed ? this.history.length : -1; - this.getBlock(); - if (this.execute(delta)) { - TweenManager.addChildren(this); - } + /** @internal */ + private executeExistingTween(delta: number, reversed: boolean): void { + this.reversed = reversed ? !this.originallyReversed : this.originallyReversed; + this.repeat = !this.reversed; + this.actionIndex = this.reversed ? this.history.length : -1; + this.getBlock(); + if (this.execute(delta)) { + TweenManager.addChildren(this); } + } - /** @internal */ - public getTweensDelta(block: RunningBlock): number { - let delta = Number.MAX_SAFE_INTEGER; - if (block.runningTweens) { - for (const exTween of block.runningTweens) { - const indexLastBlock = (exTween.repeat || exTween.reversed) ? exTween.actionIndex : exTween.history.length - 1; - const lastBlock = exTween.history[indexLastBlock]; - delta = Math.min(delta, lastBlock.elapsedTime - lastBlock.totalTime, exTween.getTweensDelta(lastBlock)); - } - } - return delta; + /** @internal */ + public getTweensDelta(block: RunningBlock): number { + let delta = Number.MAX_SAFE_INTEGER; + if (block.runningTweens) { + for (const exTween of block.runningTweens) { + const indexLastBlock = (exTween.repeat || exTween.reversed) ? exTween.actionIndex : exTween.history.length - 1; + const lastBlock = exTween.history[indexLastBlock]; + delta = Math.min(delta, lastBlock.elapsedTime - lastBlock.totalTime, exTween.getTweensDelta(lastBlock)); + } } + return delta; + } } -//TODO write rever function and consider also to create set action +// TODO write rever function and consider also to create set action diff --git a/src/tweening/Tween.ts b/src/tweening/Tween.ts index d4ff28c..a7c315c 100644 --- a/src/tweening/Tween.ts +++ b/src/tweening/Tween.ts @@ -1,230 +1,230 @@ -import { ActionCallback, ActionDelay, ActionMotion, ActionRepeat, ActionTween, ActionYoyo, IAction, Motion, MotionConfig, SetMotion } from "./Actions.js"; -import { RunningTween } from "./RunningTween.js"; -import { TweenManager } from "./TweenManager.js"; +import { ActionCallback, ActionDelay, ActionMotion, ActionRepeat, ActionTween, ActionYoyo, IAction, Motion, MotionConfig, SetMotion } from './Actions.js'; +import { RunningTween } from './RunningTween.js'; +import { TweenManager } from './TweenManager.js'; /** * A Tween represents a series of actions that can be applied to a target object to create animations or sequences of events. * @template T - The type of the target object. */ export class Tween { - /** @internal */ public actions: IAction[] = []; - /** @internal */ public blockHistory = false; - /** @internal */ public infiniteLoop = false; - /** The object to apply the tween to. */ - public target: T; - /** Tags used for filtering and management. */ - public tags: string[] = []; - /** Unique identifier. If specified, the old tween with the same id will be stopped. */ - public id: string; - - /** + /** @internal */ public actions: IAction[] = []; + /** @internal */ public blockHistory = false; + /** @internal */ public infiniteLoop = false; + /** The object to apply the tween to. */ + public target: T; + /** Tags used for filtering and management. */ + public tags: string[] = []; + /** Unique identifier. If specified, the old tween with the same id will be stopped. */ + public id: string; + + /** * @param target - The object to apply the tween to. */ - constructor(target: T) { - this.target = target; - } + constructor(target: T) { + this.target = target; + } - /** + /** * Set a unique identifier for the Tween. If specified, stops the old tween with the same id. * @param id The identifier to assign to the Tween. * @returns The updated Tween instance. */ - public setId(id: string): this { - this.id = id; - return this; - } + public setId(id: string): this { + this.id = id; + return this; + } - /** + /** * Set tags for the Tween, which can be used for filtering and management. * @param tags - Tags to associate with the Tween. * @returns The updated Tween instance. */ - public setTags(...tags: string[]): this { - this.tags = tags; - return this; - } + public setTags(...tags: string[]): this { + this.tags = tags; + return this; + } - /** + /** * Set the target object for the Tween. * @param target - The object to apply the tween to. * @returns The updated Tween instance. */ - public setTarget(target: T): this { - this.target = target; - return this; - } + public setTarget(target: T): this { + this.target = target; + return this; + } - /** + /** * Define a motion from the current state to a new state over a specified time. * @param time - The duration of the motion in milliseconds. * @param action - The motion configuration. * @param config - Additional motion configuration options. * @returns The updated Tween instance. */ - public to(time: number, action: Motion, config?: MotionConfig): this { - this.actions.push(new ActionMotion(time, action, config, false)); - return this; - } + public to(time: number, action: Motion, config?: MotionConfig): this { + this.actions.push(new ActionMotion(time, action, config, false)); + return this; + } - /** + /** * Define a relative motion from the current state. * @param time - The duration of the motion in milliseconds. * @param action - The motion configuration. * @param config - Additional motion configuration options. * @returns The updated Tween instance. */ - public by(time: number, action: Motion, config?: MotionConfig): this { - this.actions.push(new ActionMotion(time, action, config, true)); - return this; - } + public by(time: number, action: Motion, config?: MotionConfig): this { + this.actions.push(new ActionMotion(time, action, config, true)); + return this; + } - /** + /** * Define a movement from the current state to a new state instantaneously. * @param action - The motion configuration. * @returns The updated Tween instance. */ - public set(action: SetMotion): this { - this.actions.push(new ActionMotion(0, action, {}, false)); - return this; - } + public set(action: SetMotion): this { + this.actions.push(new ActionMotion(0, action, {}, false)); + return this; + } - /** + /** * Add a callback action to the Tween. * @param callback - The callback function to execute. * @returns The updated Tween instance. */ - public call(callback: () => void): this { - this.actions.push(new ActionCallback(callback)); - return this; - } + public call(callback: () => void): this { + this.actions.push(new ActionCallback(callback)); + return this; + } - /** + /** * Add a delay before executing the next action. * @param time - The duration of the delay in milliseconds. * @returns The updated Tween instance. */ - public delay(time: number): this { - this.actions.push(new ActionDelay(time)); - return this; - } + public delay(time: number): this { + this.actions.push(new ActionDelay(time)); + return this; + } - /** + /** * Repeat the last action for a specific number of times. * @param times - The number of times to repeat the action. * @returns The updated Tween instance. */ - public repeat(times = 1): this { - if (times === Infinity) { - this.blockHistory = true; - this.infiniteLoop = true; - } - if (this.actions[this.actions.length - 1].isRepeat) { - this.actions[this.actions.length - 1].times += times; - } else { - this.actions.push(new ActionRepeat(times)); - } - return this; + public repeat(times = 1): this { + if (times === Infinity) { + this.blockHistory = true; + this.infiniteLoop = true; } + if (this.actions[this.actions.length - 1].isRepeat) { + this.actions[this.actions.length - 1].times += times; + } else { + this.actions.push(new ActionRepeat(times)); + } + return this; + } - /** + /** * Repeat the last action indefinitely. * @returns The updated Tween instance. */ - public repeatForever(): this { - return this.repeat(Infinity); - } + public repeatForever(): this { + return this.repeat(Infinity); + } - /** + /** * Apply a yoyo effect to the last action, making it reverse its motion, for a specific number of times. * @param times - The number of times to yoyo the last action. * @returns The updated Tween instance. */ - public yoyo(times = 1): this { - if (times === Infinity) { - this.infiniteLoop = true; - } - if (this.actions[this.actions.length - 1].isYoyo) { - this.actions[this.actions.length - 1].times += times; - } else { - this.actions.push(new ActionYoyo(times)); - } - return this; + public yoyo(times = 1): this { + if (times === Infinity) { + this.infiniteLoop = true; } + if (this.actions[this.actions.length - 1].isYoyo) { + this.actions[this.actions.length - 1].times += times; + } else { + this.actions.push(new ActionYoyo(times)); + } + return this; + } - /** + /** * Apply a yoyo effect to the last action, making it reverse its motion, indefinitely. * @returns The updated Tween instance. */ - public yoyoForever(): this { - return this.yoyo(Infinity); - } + public yoyoForever(): this { + return this.yoyo(Infinity); + } - /** + /** * Chain another Tween to execute after this Tween. * @param tween - The Tween to chain. * @returns The updated Tween instance. */ - public then(tween: Tween): this { - this.actions.push(new ActionTween(tween)); - this.infiniteLoop ||= tween.infiniteLoop; - return this; - } + public then(tween: Tween): this { + this.actions.push(new ActionTween(tween)); + this.infiniteLoop ||= tween.infiniteLoop; + return this; + } - /** + /** * Run multiple Tweens in parallel. * @param tweens - The Tweens to run in parallel. * @returns The updated Tween instance. */ - public parallel(...tweens: Tween[]): this { - this.actions.push(new ActionTween(...tweens)); - this.infiniteLoop ||= tweens.some((x) => x.infiniteLoop); - return this; - } + public parallel(...tweens: Tween[]): this { + this.actions.push(new ActionTween(...tweens)); + this.infiniteLoop ||= tweens.some((x) => x.infiniteLoop); + return this; + } - /** + /** * Run multiple Tweens in sequence. * @param tweens - The Tweens to run in sequence. * @returns The updated Tween instance. */ - public sequence(...tweens: Tween[]): this { - for (const tween of tweens) { - this.actions.push(new ActionTween(tween)); - this.infiniteLoop ||= tween.infiniteLoop; - } - return this; + public sequence(...tweens: Tween[]): this { + for (const tween of tweens) { + this.actions.push(new ActionTween(tween)); + this.infiniteLoop ||= tween.infiniteLoop; } + return this; + } - /** + /** * Chain actions from another Tween to this Tween. * @param tween - The Tween containing actions to chain. * @returns The updated Tween instance. */ - public chain(tween: Tween): this { - this.actions.push(...tween.actions); - this.infiniteLoop ||= tween.infiniteLoop; - return this; - } + public chain(tween: Tween): this { + this.actions.push(...tween.actions); + this.infiniteLoop ||= tween.infiniteLoop; + return this; + } - /** + /** * Clone the Tween instance. * @returns A new Tween instance with the same configuration. */ - public clone(): Tween { - const tween = new Tween(this.target); - tween.actions = [...this.actions]; - tween.tags = [...this.tags]; - tween.infiniteLoop = this.infiniteLoop; - return tween; - } + public clone(): Tween { + const tween = new Tween(this.target); + tween.actions = [...this.actions]; + tween.tags = [...this.tags]; + tween.infiniteLoop = this.infiniteLoop; + return tween; + } - /** + /** * Start the Tween and create a RunningTween instance. * @returns A RunningTween instance that controls the execution of the Tween. */ - public start(): RunningTween { - if (this.id !== undefined) { - TweenManager.stopById(this.id); - } - return TweenManager.create(this.target, this); + public start(): RunningTween { + if (this.id !== undefined) { + TweenManager.stopById(this.id); } + return TweenManager.create(this.target, this); + } } diff --git a/src/tweening/TweenManager.ts b/src/tweening/TweenManager.ts index aff63df..3f3d1d5 100644 --- a/src/tweening/TweenManager.ts +++ b/src/tweening/TweenManager.ts @@ -1,140 +1,140 @@ -import { RunningTween } from "./RunningTween.js"; -import { Tween } from "./Tween.js"; +import { RunningTween } from './RunningTween.js'; +import { Tween } from './Tween.js'; /** * This class is responsible for managing and controlling running tweens. */ export class TweenManager { - private static _running: RunningTween[] = []; - private static _runningChildren: RunningTween[] = []; - - /** @internal */ - public static create(target: T, tween: Tween): RunningTween { - const runningTween = new RunningTween(target, tween); - runningTween.getBlock(); - this._running.push(runningTween); - return runningTween; + private static _running: RunningTween[] = []; + private static _runningChildren: RunningTween[] = []; + + /** @internal */ + public static create(target: T, tween: Tween): RunningTween { + const runningTween = new RunningTween(target, tween); + runningTween.getBlock(); + this._running.push(runningTween); + return runningTween; + } + + /** @internal */ + public static createChildren(target: T, tween: Tween, root: RunningTween): RunningTween { + const runningTween = new RunningTween(target, tween); + runningTween.root = root; + runningTween.getBlock(); + return runningTween; + } + + /** @internal */ + public static addChildren(runningTween: RunningTween): void { + this._runningChildren.push(runningTween); + } + + /** @internal */ + public static update(delta: number): void { + const runningChildren = this._runningChildren; + for (let i = runningChildren.length - 1; i >= 0; i--) { + const rc = runningChildren[i]; + if (!rc.execute(delta * rc.root.timeScale)) { + runningChildren.splice(i, 1); + } } - /** @internal */ - public static createChildren(target: T, tween: Tween, root: RunningTween): RunningTween { - const runningTween = new RunningTween(target, tween); - runningTween.root = root; - runningTween.getBlock(); - return runningTween; + const running = this._running; + for (let i = running.length - 1; i >= 0; i--) { + if (!running[i].execute(delta)) { + running[i]._finished = true; + running.splice(i, 1); + } } - - /** @internal */ - public static addChildren(runningTween: RunningTween): void { - this._runningChildren.push(runningTween); - } - - /** @internal */ - public static update(delta: number): void { - const runningChildren = this._runningChildren; - for (let i = runningChildren.length - 1; i >= 0; i--) { - const rc = runningChildren[i]; - if (!rc.execute(delta * rc.root.timeScale)) { - runningChildren.splice(i, 1); - } - } - - const running = this._running; - for (let i = running.length - 1; i >= 0; i--) { - if (!running[i].execute(delta)) { - running[i]._finished = true; - running.splice(i, 1); - } - } + } + + /** @internal */ + public static stop(runningTween: RunningTween): void { + const index = this._running.indexOf(runningTween); + if (index < 0) return; + this._running.splice(index, 1); + runningTween._finished = true; + runningTween.paused = false; + + const runningChildren = this._runningChildren; + for (let i = runningChildren.length - 1; i >= 0; i--) { + if (runningChildren[i].root === runningTween) { + runningChildren.splice(i, 1); + } } - - /** @internal */ - public static stop(runningTween: RunningTween): void { - const index = this._running.indexOf(runningTween); - if (index < 0) return; - this._running.splice(index, 1); - runningTween._finished = true; - runningTween.paused = false; - - const runningChildren = this._runningChildren; - for (let i = runningChildren.length - 1; i >= 0; i--) { - if (runningChildren[i].root === runningTween) { - runningChildren.splice(i, 1); - } - } + } + + /** @internal */ + public static complete(runningTween: RunningTween): void { + const index = this._running.indexOf(runningTween); + if (index < 0) return; + runningTween.paused = false; + + const runningChildren = this._runningChildren; + for (let i = runningChildren.length - 1; i >= 0; i--) { + if (runningChildren[i].root === runningTween && !runningChildren[i].execute(Infinity)) { + runningChildren.splice(i, 1); + } } - /** @internal */ - public static complete(runningTween: RunningTween): void { - const index = this._running.indexOf(runningTween); - if (index < 0) return; - runningTween.paused = false; - - const runningChildren = this._runningChildren; - for (let i = runningChildren.length - 1; i >= 0; i--) { - if (runningChildren[i].root === runningTween && !runningChildren[i].execute(Infinity)) { - runningChildren.splice(i, 1); - } - } - - if (runningTween.tween.infiniteLoop || !runningTween.execute(Infinity)) { - this._running.splice(index, 1); - runningTween._finished = true; - } + if (runningTween.tween.infiniteLoop || !runningTween.execute(Infinity)) { + this._running.splice(index, 1); + runningTween._finished = true; } + } - /** + /** * Stop the running tween with a specific id. * @param id Tween identifier. */ - public static stopById(id: string): void { - for (let i = this._running.length - 1; i >= 0; i--) { - if (this._running[i].tween.id === id) { - this._running[i].stop(); - return; - } - } + public static stopById(id: string): void { + for (let i = this._running.length - 1; i >= 0; i--) { + if (this._running[i].tween.id === id) { + this._running[i].stop(); + return; + } } + } - /** + /** * Stop all running tweens. */ - public static stopAll(): void { - for (let i = this._running.length - 1; i >= 0; i--) { - this._running[i].stop(); - } + public static stopAll(): void { + for (let i = this._running.length - 1; i >= 0; i--) { + this._running[i].stop(); } + } - /** + /** * Stop all running tweens with a specific tag. * @param tag - The tag to filter running tweens. */ - public static stopAllByTag(tag: string): void { - for (let i = this._running.length - 1; i >= 0; i--) { - if (this._running[i].tween.tags.indexOf(tag) > -1) { - this._running[i].stop(); - } - } + public static stopAllByTag(tag: string): void { + for (let i = this._running.length - 1; i >= 0; i--) { + if (this._running[i].tween.tags.indexOf(tag) > -1) { + this._running[i].stop(); + } } + } - /** + /** * Complete all running tweens. */ - public static completeAll(): void { - for (let i = this._running.length - 1; i >= 0; i--) { - this._running[i].complete(); - } + public static completeAll(): void { + for (let i = this._running.length - 1; i >= 0; i--) { + this._running[i].complete(); } + } - /** + /** * Complete all running tweens with a specific tag. * @param tag - The tag to filter running tweens. */ - public static completeAllByTag(tag: string): void { - for (let i = this._running.length - 1; i >= 0; i--) { - if (this._running[i].tween.tags.indexOf(tag) > -1) { - this._running[i].complete(); - } - } + public static completeAllByTag(tag: string): void { + for (let i = this._running.length - 1; i >= 0; i--) { + if (this._running[i].tween.tags.indexOf(tag) > -1) { + this._running[i].complete(); + } } + } } diff --git a/src/utils/Asset.ts b/src/utils/Asset.ts index a8ea101..137c670 100644 --- a/src/utils/Asset.ts +++ b/src/utils/Asset.ts @@ -46,7 +46,6 @@ export interface ResourceConfig { onLoad: (result: unknown) => void; } - /** * The Asset class is a manager for loading and handling resources in a scene. */ @@ -78,7 +77,7 @@ export class Asset { */ public static get(...path: string[]): T[]; public static get(args: string | string[]): T | T[] { - if (typeof args === "string") return this._results[args]; + if (typeof args === 'string') return this._results[args]; const ret = []; for (const path of args) { diff --git a/src/utils/IntersectionUtils.ts b/src/utils/IntersectionUtils.ts index 95eb150..0b1a5d8 100644 --- a/src/utils/IntersectionUtils.ts +++ b/src/utils/IntersectionUtils.ts @@ -5,9 +5,8 @@ import { ObjVec3, TEMP, VectorObject3D, VectorUtils } from './VectorUtils.js'; * Class that provides a set of utilities for calculating intersections between 2D and 3D geometric objects. */ export class IntersectionUtils { - - // https://paulbourke.net/geometry/pointlineplane/ - /** + // https://paulbourke.net/geometry/pointlineplane/ + /** * Computes the intersection between two 2D lines defined by points `a1` and `a2`, and `b1` and `b2`. * * @param a1 - The first point of the first line. @@ -16,18 +15,18 @@ export class IntersectionUtils { * @param b2 - The second point of the second line. * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. * @returns The intersection point of the two lines or `undefined` if the lines are parallel. - * + * * @see {@link https://paulbourke.net/geometry/pointlineplane/} */ - public static line_line_2D(a1: VectorObject3D, a2: VectorObject3D, b1: VectorObject3D, b2: VectorObject3D, target = new Vector3()): Vector3 { - const [a1c, a2c, b1c, b2c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); - const denominator = (b2c.y - b1c.y) * (a2c.x - a1c.x) - (b2c.x - b1c.x) * (a2c.y - a1c.y); - if (denominator === 0) return; // parallel - const ua = ((b2c.x - b1c.x) * (a1c.y - b1c.y) - (b2c.y - b1c.y) * (a1c.x - b1c.x)) / denominator; - return target.set(a1c.x + ua * (a2c.x - a1c.x), a1c.y + ua * (a2c.y - a1c.y), 0); - } - - /** + public static line_line_2D(a1: VectorObject3D, a2: VectorObject3D, b1: VectorObject3D, b2: VectorObject3D, target = new Vector3()): Vector3 { + const [a1c, a2c, b1c, b2c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); + const denominator = (b2c.y - b1c.y) * (a2c.x - a1c.x) - (b2c.x - b1c.x) * (a2c.y - a1c.y); + if (denominator === 0) return; // parallel + const ua = ((b2c.x - b1c.x) * (a1c.y - b1c.y) - (b2c.y - b1c.y) * (a1c.x - b1c.x)) / denominator; + return target.set(a1c.x + ua * (a2c.x - a1c.x), a1c.y + ua * (a2c.y - a1c.y), 0); + } + + /** * Computes the intersection between two 2D line segments defined by points `a1` and `a2`, and `b1` and `b2`. * * @param a1 - The first point of the first segment. @@ -36,20 +35,20 @@ export class IntersectionUtils { * @param b2 - The second point of the second segment. * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. * @returns The intersection point of the two segments or `undefined` if the segments do not intersect. - * + * * @see {@link https://paulbourke.net/geometry/pointlineplane/} */ - public static segment_segment_2D(a1: VectorObject3D, a2: VectorObject3D, b1: VectorObject3D, b2: VectorObject3D, target = new Vector3()): Vector3 { - const [a1c, a2c, b1c, b2c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); - const denominator = (b2c.y - b1c.y) * (a2c.x - a1c.x) - (b2c.x - b1c.x) * (a2c.y - a1c.y); - if (denominator === 0) return; // parallel - const ua = ((b2c.x - b1c.x) * (a1c.y - b1c.y) - (b2c.y - b1c.y) * (a1c.x - b1c.x)) / denominator; - const ub = ((a2c.x - a1c.x) * (a1c.y - b1c.y) - (a2c.y - a1c.y) * (a1c.x - b1c.x)) / denominator; - if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return; - return target.set(a1c.x + ua * (a2c.x - a1c.x), a1c.y + ua * (a2c.y - a1c.y), 0); - } - - /** + public static segment_segment_2D(a1: VectorObject3D, a2: VectorObject3D, b1: VectorObject3D, b2: VectorObject3D, target = new Vector3()): Vector3 { + const [a1c, a2c, b1c, b2c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); + const denominator = (b2c.y - b1c.y) * (a2c.x - a1c.x) - (b2c.x - b1c.x) * (a2c.y - a1c.y); + if (denominator === 0) return; // parallel + const ua = ((b2c.x - b1c.x) * (a1c.y - b1c.y) - (b2c.y - b1c.y) * (a1c.x - b1c.x)) / denominator; + const ub = ((a2c.x - a1c.x) * (a1c.y - b1c.y) - (a2c.y - a1c.y) * (a1c.x - b1c.x)) / denominator; + if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return; + return target.set(a1c.x + ua * (a2c.x - a1c.x), a1c.y + ua * (a2c.y - a1c.y), 0); + } + + /** * Computes the intersection between two 3D lines defined by points `a1` and `a2`, and `b1` and `b2`. * * @param a1 - The first point of the first line. @@ -59,40 +58,40 @@ export class IntersectionUtils { * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. * @param tolerance - (Optional) The tolerance for evaluating the intersection. The default value is 10^-6. * @returns The intersection point of the two lines or `undefined` if the lines are parallel or do not intersect. - * + * * @see {@link https://paulbourke.net/geometry/pointlineplane/} */ - public static line_line_3D(a1: ObjVec3, a2: ObjVec3, b1: ObjVec3, b2: ObjVec3, target = new Vector3(), tolerance = 10 ** -6): Vector3 { - const [p1c, p2c, p3c, p4c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); + public static line_line_3D(a1: ObjVec3, a2: ObjVec3, b1: ObjVec3, b2: ObjVec3, target = new Vector3(), tolerance = 10 ** -6): Vector3 { + const [p1c, p2c, p3c, p4c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); - const p13 = TEMP[0].subVectors(p1c, p3c); - const p43 = TEMP[1].subVectors(p4c, p3c); + const p13 = TEMP[0].subVectors(p1c, p3c); + const p43 = TEMP[1].subVectors(p4c, p3c); - if (p43.lengthSq() < tolerance) return; - const p21 = TEMP[2].subVectors(p2c, p1c); - if (p21.lengthSq() < tolerance) return; + if (p43.lengthSq() < tolerance) return; + const p21 = TEMP[2].subVectors(p2c, p1c); + if (p21.lengthSq() < tolerance) return; - const d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z; - const d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z; - const d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z; - const d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z; - const d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z; + const d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z; + const d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z; + const d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z; + const d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z; + const d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z; - const denom = d2121 * d4343 - d4321 * d4321; - if (Math.abs(denom) < tolerance) return; + const denom = d2121 * d4343 - d4321 * d4321; + if (Math.abs(denom) < tolerance) return; - const numer = d1343 * d4321 - d1321 * d4343; + const numer = d1343 * d4321 - d1321 * d4343; - const mua = numer / denom; - const Pa = target.set(p1c.x + mua * p21.x, p1c.y + mua * p21.y, p1c.z + mua * p21.z); + const mua = numer / denom; + const Pa = target.set(p1c.x + mua * p21.x, p1c.y + mua * p21.y, p1c.z + mua * p21.z); - // const mub = (d1343 + d4321 * mua) / d4343; - // const Pb = new Vector3(p3.x + mub * p43.x, p3.y + mub * p43.y, p3.z + mub * p43.z); + // const mub = (d1343 + d4321 * mua) / d4343; + // const Pb = new Vector3(p3.x + mub * p43.x, p3.y + mub * p43.y, p3.z + mub * p43.z); - return Pa; - } + return Pa; + } - /** + /** * Checks if a 3D line intersects an Axis-Aligned Bounding Box (AABB) defined by `box`. * * @param rayOrigin - The origin of the line. @@ -100,56 +99,56 @@ export class IntersectionUtils { * @param box - The AABB to check for intersection with. * @returns `true` if the line intersects the AABB, otherwise `false`. */ - public static line_boxAABB(rayOrigin: Vector3, rayDir: Vector3, box: Box3): boolean { - const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; - let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; - - if (invdirx >= 0) { - bmin = box.min.x; - bmax = box.max.x; - } else { - bmin = box.max.x; - bmax = box.min.x; - } - - dmin = (bmin - rayOrigin.x) * invdirx; - dmax = (bmax - rayOrigin.x) * invdirx; - - tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error - tmax = dmax < tmax ? dmax : tmax; - - if (invdiry >= 0) { - bmin = box.min.y; - bmax = box.max.y; - } else { - bmin = box.max.y; - bmax = box.min.y; - } - - dmin = (bmin - rayOrigin.y) * invdiry; - dmax = (bmax - rayOrigin.y) * invdiry; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - if (invdirz >= 0) { - bmin = box.min.z; - bmax = box.max.z; - } else { - bmin = box.max.z; - bmax = box.min.z; - } - - dmin = (bmin - rayOrigin.z) * invdirz; - dmax = (bmax - rayOrigin.z) * invdirz; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - return tmin <= tmax; + public static line_boxAABB(rayOrigin: Vector3, rayDir: Vector3, box: Box3): boolean { + const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; + let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; + + if (invdirx >= 0) { + bmin = box.min.x; + bmax = box.max.x; + } else { + bmin = box.max.x; + bmax = box.min.x; + } + + dmin = (bmin - rayOrigin.x) * invdirx; + dmax = (bmax - rayOrigin.x) * invdirx; + + tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error + tmax = dmax < tmax ? dmax : tmax; + + if (invdiry >= 0) { + bmin = box.min.y; + bmax = box.max.y; + } else { + bmin = box.max.y; + bmax = box.min.y; } - /** + dmin = (bmin - rayOrigin.y) * invdiry; + dmax = (bmax - rayOrigin.y) * invdiry; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + if (invdirz >= 0) { + bmin = box.min.z; + bmax = box.max.z; + } else { + bmin = box.max.z; + bmax = box.min.z; + } + + dmin = (bmin - rayOrigin.z) * invdirz; + dmax = (bmax - rayOrigin.z) * invdirz; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + return tmin <= tmax; + } + + /** * Checks if a 3D line segment defined by points `p1` and `p2` intersects an Axis-Aligned Bounding Box (AABB) defined by `box`. * * @param p1 - The first point of the segment. @@ -157,54 +156,54 @@ export class IntersectionUtils { * @param box - The AABB to check for intersection with. * @returns `true` if the segment intersects the AABB and is within the segment's length, otherwise `false`. */ - public static segment_boxAABB(p1: Vector3, p2: Vector3, box: Box3): boolean { - const rayDir = TEMP[0].subVectors(p2, p1).normalize(); - const distance = p1.distanceTo(p2); - const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; - let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; - - if (invdirx >= 0) { - bmin = box.min.x; - bmax = box.max.x; - } else { - bmin = box.max.x; - bmax = box.min.x; - } - - dmin = (bmin - p1.x) * invdirx; - dmax = (bmax - p1.x) * invdirx; - - tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error - tmax = dmax < tmax ? dmax : tmax; - - if (invdiry >= 0) { - bmin = box.min.y; - bmax = box.max.y; - } else { - bmin = box.max.y; - bmax = box.min.y; - } - - dmin = (bmin - p1.y) * invdiry; - dmax = (bmax - p1.y) * invdiry; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - if (invdirz >= 0) { - bmin = box.min.z; - bmax = box.max.z; - } else { - bmin = box.max.z; - bmax = box.min.z; - } - - dmin = (bmin - p1.z) * invdirz; - dmax = (bmax - p1.z) * invdirz; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - return tmin <= tmax && distance >= tmin; + public static segment_boxAABB(p1: Vector3, p2: Vector3, box: Box3): boolean { + const rayDir = TEMP[0].subVectors(p2, p1).normalize(); + const distance = p1.distanceTo(p2); + const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; + let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; + + if (invdirx >= 0) { + bmin = box.min.x; + bmax = box.max.x; + } else { + bmin = box.max.x; + bmax = box.min.x; + } + + dmin = (bmin - p1.x) * invdirx; + dmax = (bmax - p1.x) * invdirx; + + tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error + tmax = dmax < tmax ? dmax : tmax; + + if (invdiry >= 0) { + bmin = box.min.y; + bmax = box.max.y; + } else { + bmin = box.max.y; + bmax = box.min.y; } + + dmin = (bmin - p1.y) * invdiry; + dmax = (bmax - p1.y) * invdiry; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + if (invdirz >= 0) { + bmin = box.min.z; + bmax = box.max.z; + } else { + bmin = box.max.z; + bmax = box.min.z; + } + + dmin = (bmin - p1.z) * invdirz; + dmax = (bmax - p1.z) * invdirz; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + return tmin <= tmax && distance >= tmin; + } } diff --git a/src/utils/Query.ts b/src/utils/Query.ts index c9b469e..448e2e1 100644 --- a/src/utils/Query.ts +++ b/src/utils/Query.ts @@ -1,4 +1,4 @@ -import { Object3D } from "three"; +import { Object3D } from 'three'; interface Attribute { key: string; @@ -88,7 +88,7 @@ function searchAll(target: Object3D, blocks: QueryBlock[][], result: Object3D[]) } for (const child of target.children) { - searchAll(child, newBlocks, result) + searchAll(child, newBlocks, result); } } @@ -157,7 +157,6 @@ function parse(query: string): QueryBlock[] { const result = getBlock(query, i); if (result) { - if (result.char === ',') { currentBlock = { attributes: [], tags: [] }; blocks.push(currentBlock); @@ -226,7 +225,7 @@ function addType(query: string, start: number, end: number, block: QueryBlock): function addAttribute(query: string, start: number, end: number, block: QueryBlock): void { const sub = query.substring(start + 1, end - 1); - const split = sub.split("="); + const split = sub.split('='); const lastChar = split[0][split[0].length - 1]; if (lastChar === '*' || lastChar === '$' || lastChar === '^') { block.attributes.push({ key: split[0].slice(0, -1), value: split[1], operator: lastChar }); diff --git a/src/utils/Stats.ts b/src/utils/Stats.ts index 62169c4..fcd9758 100644 --- a/src/utils/Stats.ts +++ b/src/utils/Stats.ts @@ -1,4 +1,4 @@ -import { WebGLRenderer } from "three"; +import { WebGLRenderer } from 'three'; export class Stats { public dom = document.createElement('div'); @@ -21,8 +21,8 @@ export class Stats { this.showPanel(++this.mode % this.dom.children.length); }; - public get minimal() { return this._minimal } - public set minimal(value: boolean) { this.switchMinimal(value) } + public get minimal() { return this._minimal; } + public set minimal(value: boolean) { this.switchMinimal(value); } constructor(renderer: WebGLRenderer) { this.renderer = renderer; @@ -275,7 +275,7 @@ export class TextPanel { `Calls ${this._formatNumber(calls)}`, `Tris ${this._formatNumber(triangles)}`, `Lines ${this._formatNumber(lines)}`, - `Points ${this._formatNumber(points)}`, + `Points ${this._formatNumber(points)}` ]; let maxRowLenght = 0; @@ -292,9 +292,9 @@ export class TextPanel { this.dom.style.cssText = `width:${this.WIDTH / this.PR}px;height:${ this.HEIGHT / this.PR }px`; - - this.context.font = "bold " + 10 * this.PR + "px monospace"; - this.context.textBaseline = "top"; + + this.context.font = 'bold ' + 10 * this.PR + 'px monospace'; + this.context.textBaseline = 'top'; this.context.fillStyle = this.bg; this.context.globalAlpha = 1; @@ -331,11 +331,11 @@ export class TextPanel { */ private _formatNumber(input: number): string { if (input >= 1e9) { - return (input / 1e9).toFixed(2) + "ʙ"; + return (input / 1e9).toFixed(2) + 'ʙ'; } else if (input >= 1e6) { - return (input / 1e6).toFixed(2) + "ᴍ"; + return (input / 1e6).toFixed(2) + 'ᴍ'; } else if (input >= 1e3) { - return (input / 1e3).toFixed(2) + "ᴋ"; + return (input / 1e3).toFixed(2) + 'ᴋ'; } else { return input.toString(); } diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index b8f39a2..e6dd56a 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,4 +1,4 @@ -import { Camera, Object3D, Plane, Ray, SkinnedMesh, Vector3 } from "three"; +import { Camera, Object3D, Plane, Ray, SkinnedMesh, Vector3 } from 'three'; /** * A type definition representing a collection of 3D nodes where each node is identified by a unique string key. @@ -6,78 +6,78 @@ import { Camera, Object3D, Plane, Ray, SkinnedMesh, Vector3 } from "three"; export type Nodes = { [x: string]: Object3D }; /** - * + * * A utility class providing helper methods for various operations. */ export class Utils { - private static _plane = new Plane(); - private static _temp = new Vector3(); + private static _plane = new Plane(); + private static _temp = new Vector3(); - /** + /** * Calculates the intersection point of a ray with a plane in world coordinates. * @param ray - The ray to intersect with the plane. * @param camera - The camera used as a reference for the plane's orientation. * @param distance - The distance from the camera to the plane. * @returns The intersection point as Vector3. */ - public static getSceneIntersection(ray: Ray, camera: Camera, distance: number): Vector3 { - this._plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(this._plane.normal), camera.getWorldPosition(this._temp)); - this._plane.translate(this._temp.copy(this._plane.normal).setLength(distance)); - return ray.intersectPlane(this._plane, this._temp); - } + public static getSceneIntersection(ray: Ray, camera: Camera, distance: number): Vector3 { + this._plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(this._plane.normal), camera.getWorldPosition(this._temp)); + this._plane.translate(this._temp.copy(this._plane.normal).setLength(distance)); + return ray.intersectPlane(this._plane, this._temp); + } - /** + /** * Set for all children of the target, the draggable flag to true and a dragTarget. * @param target - The Object3D whose children you want to enable as draggable elements. * @param dragTarget - The Object3D that will act as the drag target for the children. */ - public static setChildrenDragTarget(target: Object3D, dragTarget: Object3D): void { - target.traverse((obj) => { - obj.draggable = true; - obj.dragTarget = dragTarget; - }); - } + public static setChildrenDragTarget(target: Object3D, dragTarget: Object3D): void { + target.traverse((obj) => { + obj.draggable = true; + obj.dragTarget = dragTarget; + }); + } - /** + /** * Computes bounding spheres for child objects within the specified Object3D hierarchy. * @param target - The root Object3D from which to start computing bounding spheres for children. */ - public static computeBoundingSphereChildren(target: Object3D): void { - target.traverse((obj) => { - obj.updateMatrixWorld(); - if ((obj as SkinnedMesh).computeBoundingSphere) { - (obj as SkinnedMesh).computeBoundingSphere(); - } - }); - } + public static computeBoundingSphereChildren(target: Object3D): void { + target.traverse((obj) => { + obj.updateMatrixWorld(); + if ((obj as SkinnedMesh).computeBoundingSphere) { + (obj as SkinnedMesh).computeBoundingSphere(); + } + }); + } - /** + /** * Retrieves a map of objects in the scene graph (Object3D) starting from a root object. * Each object is mapped using its unique name as the key in the resulting object. * @param target - The root object to begin generating the object map from. * @returns An object containing objects mapped by their names. */ - public static getNodes(target: Object3D): Nodes { - return this.generateNodesFromObject(target, {}, {}); - } + public static getNodes(target: Object3D): Nodes { + return this.generateNodesFromObject(target, {}, {}); + } - protected static generateNodesFromObject(object: Object3D, nodes: Nodes, nameCollision: { [x: string]: number }): Nodes { - const name = this.getNodeName(object, nameCollision); - nodes[name] = object; + protected static generateNodesFromObject(object: Object3D, nodes: Nodes, nameCollision: { [x: string]: number }): Nodes { + const name = this.getNodeName(object, nameCollision); + nodes[name] = object; - for (const child of object.children) { - this.generateNodesFromObject(child, nodes, nameCollision); - } + for (const child of object.children) { + this.generateNodesFromObject(child, nodes, nameCollision); + } - return nodes; - } + return nodes; + } - protected static getNodeName(object: Object3D, nameCollision: { [x: string]: number }): string { - const key = object.name; - if (nameCollision[key] === undefined) { - nameCollision[key] = 0; - return key; - } - return `${key}_${++nameCollision[key]}`; - } + protected static getNodeName(object: Object3D, nameCollision: { [x: string]: number }): string { + const key = object.name; + if (nameCollision[key] === undefined) { + nameCollision[key] = 0; + return key; + } + return `${key}_${++nameCollision[key]}`; + } } diff --git a/test/utils/Query.test.ts b/test/utils/Query.test.ts index 78aa613..7ea553a 100644 --- a/test/utils/Query.test.ts +++ b/test/utils/Query.test.ts @@ -250,8 +250,6 @@ describe('Query standard cases', () => { expect(result[18]).toEqual(scene.children[3].children[2]); expect(result[19]).toEqual(scene.children[3].children[3]); }); - - }); describe('Query special cases', () => { @@ -262,15 +260,15 @@ describe('Query special cases', () => { beforeAll(() => { scene = new Scene(); - scene.tags.add("x"); + scene.tags.add('x'); group = new Group(); - group.tags.add("x").add("y"); + group.tags.add('x').add('y'); group2 = new Group(); - group2.tags.add("y"); + group2.tags.add('y'); mesh = new Mesh(); - mesh.tags.add("z"); + mesh.tags.add('z'); - scene.add(group.add(group2.add(mesh))) + scene.add(group.add(group2.add(mesh))); }); it('querySelector: special case', () => { @@ -284,5 +282,4 @@ describe('Query special cases', () => { expect(result[0]).toEqual(group2); expect(result[1]).toEqual(mesh); }); - }); diff --git a/vite.config.ts b/vite.config.ts index e9e85c3..8e56524 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,7 +2,7 @@ import { resolve } from 'path'; import { defineConfig } from 'vite'; import { viteStaticCopy } from 'vite-plugin-static-copy'; import dts from 'vite-plugin-dts'; -import { externalizeDeps } from 'vite-plugin-externalize-deps' +import { externalizeDeps } from 'vite-plugin-externalize-deps'; export default defineConfig(({ command }) => ({ publicDir: command === 'build' ? false : 'public', @@ -11,8 +11,8 @@ export default defineConfig(({ command }) => ({ lib: { entry: resolve(__dirname, 'src/index.ts'), fileName: 'index', - formats: ['es', 'cjs'], - }, + formats: ['es', 'cjs'] + } }, plugins: [ externalizeDeps(), From 8bd3d73a815ef8c27b9b44dffdb658ec1dc73980 Mon Sep 17 00:00:00 2001 From: Andrea Gargaro Date: Fri, 15 Nov 2024 13:09:34 +0100 Subject: [PATCH 05/11] wip --- eslint.config.js | 9 +- src/patch/Vector3.ts | 12 ++- src/tweening/RunningTween.ts | 2 +- src/utils/IntersectionUtils.ts | 2 +- src/utils/Utils.ts | 137 ++++++++++++++++-------------- src/utils/VectorUtils.ts | 151 ++++++++++++++++----------------- 6 files changed, 162 insertions(+), 151 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 88f3a37..608fb95 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,14 +10,18 @@ export default [ semi: true, commaDangle: 'never', arrowParens: true, - braceStyle: '1tbs' + braceStyle: '1tbs', + blockSpacing: true, + indent: 2, + quoteProps: 'as-needed', + quotes: 'single' }), { ignores: ['dist', 'docs', 'packages'] }, { rules: { - 'camelcase': 'warn', + camelcase: 'warn', 'no-unused-vars': 'off', 'no-undef': 'off', '@typescript-eslint/no-explicit-any': 'off', @@ -25,6 +29,7 @@ export default [ '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-wrapper-object-types': 'off', '@typescript-eslint/no-dynamic-delete': 'off' + // "@typescript-eslint/explicit-function-return-type": "error", } } ]; diff --git a/src/patch/Vector3.ts b/src/patch/Vector3.ts index f393bdd..f133638 100644 --- a/src/patch/Vector3.ts +++ b/src/patch/Vector3.ts @@ -104,9 +104,15 @@ class Vector3Ext { setComponent(index: number, value: number) { switch (index) { - case 0: this._x = value; break; - case 1: this._y = value; break; - case 2: this._z = value; break; + case 0: + this._x = value; + break; + case 1: + this._y = value; + break; + case 2: + this._z = value; + break; default: throw new Error('index is out of range: ' + index); } this._onChangeCallback(); diff --git a/src/tweening/RunningTween.ts b/src/tweening/RunningTween.ts index 66f93ca..d8d58ab 100644 --- a/src/tweening/RunningTween.ts +++ b/src/tweening/RunningTween.ts @@ -275,7 +275,7 @@ export class RunningTween { private executeActions(block: RunningBlock): void { if (block.actions) { for (const action of block.actions) { - const elapsedTime = Math.min(1, block.elapsedTime / (action.time + 10 ** -12)); + const elapsedTime = Math.min(1, block.elapsedTime / (action.time + 1e-12)); const alpha = block.reversed ? 1 - elapsedTime : elapsedTime; const easedAlpha = action.easing?.call(this, alpha) ?? alpha; const applyValue = block.config?.onProgress?.call(this.target, this.target, action.key, action.start, action.end, easedAlpha); diff --git a/src/utils/IntersectionUtils.ts b/src/utils/IntersectionUtils.ts index 0b1a5d8..1485348 100644 --- a/src/utils/IntersectionUtils.ts +++ b/src/utils/IntersectionUtils.ts @@ -61,7 +61,7 @@ export class IntersectionUtils { * * @see {@link https://paulbourke.net/geometry/pointlineplane/} */ - public static line_line_3D(a1: ObjVec3, a2: ObjVec3, b1: ObjVec3, b2: ObjVec3, target = new Vector3(), tolerance = 10 ** -6): Vector3 { + public static line_line_3D(a1: ObjVec3, a2: ObjVec3, b1: ObjVec3, b2: ObjVec3, target = new Vector3(), tolerance = 1e-6): Vector3 { const [p1c, p2c, p3c, p4c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); const p13 = TEMP[0].subVectors(p1c, p3c); diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index e6dd56a..fb9817a 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -4,80 +4,89 @@ import { Camera, Object3D, Plane, Ray, SkinnedMesh, Vector3 } from 'three'; * A type definition representing a collection of 3D nodes where each node is identified by a unique string key. */ export type Nodes = { [x: string]: Object3D }; +type Object3DLike = { position: Vector3 }; +type Vec3Obj3D = Object3DLike | Vector3; -/** - * - * A utility class providing helper methods for various operations. - */ -export class Utils { - private static _plane = new Plane(); - private static _temp = new Vector3(); - - /** - * Calculates the intersection point of a ray with a plane in world coordinates. - * @param ray - The ray to intersect with the plane. - * @param camera - The camera used as a reference for the plane's orientation. - * @param distance - The distance from the camera to the plane. - * @returns The intersection point as Vector3. - */ - public static getSceneIntersection(ray: Ray, camera: Camera, distance: number): Vector3 { - this._plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(this._plane.normal), camera.getWorldPosition(this._temp)); - this._plane.translate(this._temp.copy(this._plane.normal).setLength(distance)); - return ray.intersectPlane(this._plane, this._temp); - } +const _plane = new Plane(); +const _temp = new Vector3(); - /** - * Set for all children of the target, the draggable flag to true and a dragTarget. - * @param target - The Object3D whose children you want to enable as draggable elements. - * @param dragTarget - The Object3D that will act as the drag target for the children. - */ - public static setChildrenDragTarget(target: Object3D, dragTarget: Object3D): void { - target.traverse((obj) => { - obj.draggable = true; - obj.dragTarget = dragTarget; - }); - } +/** @internal */ +export function getPosition(object: Vec3Obj3D): Vector3 { + return (object as Object3DLike).position ?? object as Vector3; +} - /** - * Computes bounding spheres for child objects within the specified Object3D hierarchy. - * @param target - The root Object3D from which to start computing bounding spheres for children. - */ - public static computeBoundingSphereChildren(target: Object3D): void { - target.traverse((obj) => { - obj.updateMatrixWorld(); - if ((obj as SkinnedMesh).computeBoundingSphere) { - (obj as SkinnedMesh).computeBoundingSphere(); - } - }); +/** @internal */ // TODO capire se tenere o eliminare +export function getPositions(objects: Vec3Obj3D[]): Vector3[] { + for (let i = 0, l = objects.length; i < l; i++) { + objects[i] = getPosition(objects[i]); } + return objects as Vector3[]; +} - /** - * Retrieves a map of objects in the scene graph (Object3D) starting from a root object. - * Each object is mapped using its unique name as the key in the resulting object. - * @param target - The root object to begin generating the object map from. - * @returns An object containing objects mapped by their names. - */ - public static getNodes(target: Object3D): Nodes { - return this.generateNodesFromObject(target, {}, {}); - } +/** + * Calculates the intersection point of a ray with a plane in world coordinates. + * @param ray - The ray to intersect with the plane. + * @param camera - The camera used as a reference for the plane's orientation. + * @param distance - The distance from the camera to the plane. + * @returns The intersection point as Vector3. + */ +export function getSceneIntersection(ray: Ray, camera: Camera, distance: number): Vector3 { + _plane.setFromNormalAndCoplanarPoint(camera.getWorldDirection(_plane.normal), camera.getWorldPosition(_temp)); + _plane.translate(_temp.copy(_plane.normal).setLength(distance)); + return ray.intersectPlane(_plane, _temp); +} - protected static generateNodesFromObject(object: Object3D, nodes: Nodes, nameCollision: { [x: string]: number }): Nodes { - const name = this.getNodeName(object, nameCollision); - nodes[name] = object; +/** + * Set for all children of the target, the draggable flag to true and a dragTarget. + * @param target - The Object3D whose children you want to enable as draggable elements. + * @param dragTarget - The Object3D that will act as the drag target for the children. + */ +export function setChildrenDragTarget(target: Object3D, dragTarget: Object3D): void { + target.traverse((obj) => { + obj.draggable = true; + obj.dragTarget = dragTarget; + }); +} - for (const child of object.children) { - this.generateNodesFromObject(child, nodes, nameCollision); +/** + * Computes bounding spheres for child objects within the specified Object3D hierarchy. + * @param target - The root Object3D from which to start computing bounding spheres for children. + */ +export function computeBoundingSphereChildren(target: Object3D): void { + target.traverse((obj) => { + obj.updateMatrixWorld(); + if ((obj as SkinnedMesh).computeBoundingSphere) { + (obj as SkinnedMesh).computeBoundingSphere(); } + }); +} + +/** + * Retrieves a map of objects in the scene graph (Object3D) starting from a root object. + * Each object is mapped using its unique name as the key in the resulting object. + * @param target - The root object to begin generating the object map from. + * @returns An object containing objects mapped by their names. + */ +export function getNodes(target: Object3D): Nodes { + return generateNodesFromObject(target, {}, {}); +} + +function generateNodesFromObject(object: Object3D, nodes: Nodes, nameCollision: { [x: string]: number }): Nodes { + const name = getNodeName(object, nameCollision); + nodes[name] = object; - return nodes; + for (const child of object.children) { + generateNodesFromObject(child, nodes, nameCollision); } - protected static getNodeName(object: Object3D, nameCollision: { [x: string]: number }): string { - const key = object.name; - if (nameCollision[key] === undefined) { - nameCollision[key] = 0; - return key; - } - return `${key}_${++nameCollision[key]}`; + return nodes; +} + +function getNodeName(object: Object3D, nameCollision: { [x: string]: number }): string { + const key = object.name; + if (nameCollision[key] === undefined) { + nameCollision[key] = 0; + return key; } + return `${key}_${++nameCollision[key]}`; } diff --git a/src/utils/VectorUtils.ts b/src/utils/VectorUtils.ts index 288211a..194f9fa 100644 --- a/src/utils/VectorUtils.ts +++ b/src/utils/VectorUtils.ts @@ -1,101 +1,92 @@ -import { Object3D, Vector2, Vector3 } from 'three'; +import { Vector3 } from 'three'; -export type VectorObject3D = Vector2 | Vector3 | Object3D; -export type ObjVec3 = Vector3 | Object3D; -/** @internal */ export const TEMP: Vector3[] = [...Array(4)].map(() => new Vector3()); -const ORIGIN = new Vector3(); +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _origin = new Vector3(); -export class VectorUtils { - public static readonly DEFAULT_NORMAL = new Vector3(0, 0, 1); +export function computeSign(point: Vector3, origin: Vector3, normal: Vector3): number { + _v1.subVectors(point, origin); + return Math.sign(_v1.dot(normal)); +} - public static getPositionFromObject3D(item: VectorObject3D): Vector3 { - return (item as Object3D).isObject3D ? (item as Object3D).position : (item as Vector3); - } +export function haveSameDirection(v1: Vector3, v2: Vector3, tolerance = 1e-2): boolean { + _v1.copy(v1).normalize(); + _v2.copy(v2).normalize(); + return _v1.dot(_v2) > 1 - tolerance; +} - public static getPositionsFromObject3D(items: VectorObject3D[]): Vector3[] { - const ret: Vector3[] = []; - for (const item of items) { - ret.push(this.getPositionFromObject3D(item)); - } - return ret; - } +export function haveOppositeDirection(v1: Vector3, v2: Vector3, tolerance = 1e-2): boolean { + _v1.copy(v1).normalize(); + _v2.copy(v2).normalize(); + return _v1.dot(_v2) < tolerance - 1; +} - public static computeSign(point: Vector3, origin: Vector3, normal: Vector3): number { - return Math.sign(TEMP[0].subVectors(point, origin).dot(normal)); - } +export function perpendicular(dir: Vector3, normal: Vector3, target = new Vector3()) { + return target.crossVectors(dir, normal); +} - public static haveSameDirection(v1: Vector3, v2: Vector3, tolerance = 10 ** -2): boolean { - return TEMP[0].copy(v1).normalize().dot(TEMP[1].copy(v2).normalize()) > 1 - tolerance; - } +export function perpendicularSigned(dir: Vector3, signPoint: Vector3, normal: Vector3, target = new Vector3()) { + target.crossVectors(dir, normal); + return computeSign(signPoint, _origin, target) >= 0 ? target : target.multiplyScalar(-1); +} - public static haveOppositeDirection(v1: Vector3, v2: Vector3, tolerance = 10 ** -2): boolean { - return TEMP[0].copy(v1).normalize().dot(TEMP[1].copy(v2).normalize()) < tolerance - 1; - } +export function perpendicularByPoints(p1: Vector3, p2: Vector3, normal: Vector3, target = new Vector3()) { + _v1.subVectors(p1, p2); + return target.crossVectors(_v1, normal); +} - public static perpendicular(dir: Vector3, target = new Vector3(), normal = this.DEFAULT_NORMAL) { - return target.crossVectors(dir, normal); - } +export function perpendicularSignedByPoints(p1: Vector3, p2: Vector3, signPoint: Vector3, normal: Vector3, target = new Vector3()) { + _v1.subVectors(p1, p2); + return perpendicularSigned(_v1, signPoint, normal, target); +} - public static perpendicularSigned(dir: Vector3, referencePoint: Vector3, target = new Vector3(), normal = this.DEFAULT_NORMAL) { - target.crossVectors(dir, normal); - return this.computeSign(referencePoint, ORIGIN, target) !== 1 ? target : target.multiplyScalar(-1); - } +export function bisector(v1: Vector3, v2: Vector3, target = new Vector3()) { + _v1.copy(v1).normalize(); + _v2.copy(v2).normalize(); + return target.addVectors(_v1, _v2).normalize(); +} - public static perpendicularByPoints(p1: ObjVec3, p2: ObjVec3, target = new Vector3(), normal = this.DEFAULT_NORMAL) { - const [p1c, p2c] = this.getPositionsFromObject3D([p1, p2]); - return target.crossVectors(TEMP[0].subVectors(p1c, p2c), normal); - } +export function bisectorByPoints(p1: Vector3, p2: Vector3, center: Vector3, target = new Vector3()) { + return bisector(_v1.subVectors(p1, center), _v2.subVectors(p2, center), target); +} - public static perpendicularSignedByPoints(p1: ObjVec3, p2: ObjVec3, refPoint: ObjVec3, target = new Vector3(), normal = this.DEFAULT_NORMAL) { - const [p1c, p2c, r] = this.getPositionsFromObject3D([p1, p2, refPoint]); - target.crossVectors(TEMP[0].subVectors(p1c, p2c), normal); - return this.computeSign(r, p1c, target) !== 1 ? target : target.multiplyScalar(-1); - } +export function arePointsOnSameSide(origin: Vector3, dir: Vector3, ...points: Vector3[]): boolean { + const sign = computeSign(points[0], origin, dir); - public static bisector(v1: Vector3, v2: Vector3, target = new Vector3()) { - TEMP[0].copy(v1).normalize(); - TEMP[1].copy(v2).normalize(); - return target.addVectors(TEMP[0], TEMP[1]).normalize(); + for (let i = 1, l = points.length; i < l; i++) { + if (sign !== computeSign(points[i], origin, dir)) return false; } - public static bisectorByPoints(p1: ObjVec3, p2: ObjVec3, center: ObjVec3, target = new Vector3()) { - const [p1c, p2c, c] = this.getPositionsFromObject3D([p1, p2, center]); - return this.bisector(TEMP[2].subVectors(p1c, c), TEMP[3].subVectors(p2c, c), target); - } + return true; +} - public static arePointsOnSameSide(origin: ObjVec3, dir: Vector3, points: ObjVec3[]): boolean { - const [o, ...p] = this.getPositionsFromObject3D([origin, ...points]); - const sign = this.computeSign(p[0], o, dir); - for (let i = 1; i < points.length; i++) { - if (sign !== this.computeSign(p[i], o, dir)) return false; - } - return true; - } +export function arePointsOnSameSideByPoints(p1: Vector3, p2: Vector3, normal: Vector3, ...points: Vector3[]): boolean { + const dir = perpendicularByPoints(p1, p2, normal, _v1); + const sign = computeSign(points[0], p1, dir); - public static arePointsOnSameSideByPoints(p1: ObjVec3, p2: ObjVec3, points: ObjVec3[], normal = this.DEFAULT_NORMAL): boolean { - const [p1c, p2c, ...p] = this.getPositionsFromObject3D([p1, p2, ...points]); - const dir = this.perpendicularByPoints(p1c, p2c, TEMP[0], normal); - const sign = this.computeSign(p[0], p1c, dir); - for (let i = 1; i < points.length; i++) { - if (sign !== this.computeSign(p[i], p1c, dir)) return false; - } - return true; + for (let i = 1, l = points.length; i < l; i++) { + if (sign !== computeSign(points[i], p1, dir)) return false; } - // normal must be normalized - public static angleSignedFromOrigin(a: Vector3, b: Vector3, normal = this.DEFAULT_NORMAL): number { - return Math.atan2(TEMP[0].crossVectors(a, b).dot(normal), a.dot(b)); - } + return true; +} - public static angleSignedByPoints(p1: ObjVec3, p2: ObjVec3, center: ObjVec3, normal = this.DEFAULT_NORMAL): number { - const [p1c, p2c, c] = this.getPositionsFromObject3D([p1, p2, center]); - const a = TEMP[0].subVectors(p1c, c); - const b = TEMP[1].subVectors(p2c, c); - return Math.atan2(TEMP[2].crossVectors(a, b).dot(normal), a.dot(b)); - } +// normal must be normalized +export function angleSignedFromOrigin(a: Vector3, b: Vector3, normal: Vector3): number { + _v1.crossVectors(a, b); + return Math.atan2(_v1.dot(normal), a.dot(b)); +} - public static projectOnLine(point: ObjVec3, l1: ObjVec3, l2: ObjVec3, target = new Vector3()): Vector3 { - const [vc, p1c, p2c] = this.getPositionsFromObject3D([point, l1, l2]); - return target.subVectors(vc, p1c).projectOnVector(TEMP[0].subVectors(p1c, p2c)).add(p1c); - } +// normal must be normalized +export function angleSignedByPoints(p1: Vector3, p2: Vector3, center: Vector3, normal: Vector3): number { + _v1.subVectors(p1, center); + _v2.subVectors(p2, center); + const dotAB = _v1.dot(_v2); + _v1.crossVectors(_v1, _v2); + return Math.atan2(_v1.dot(normal), dotAB); +} + +export function projectOnLine(point: Vector3, lineA: Vector3, lineB: Vector3, target = new Vector3()): Vector3 { + _v1.subVectors(lineA, lineB); + return target.subVectors(point, lineA).projectOnVector(_v1).add(lineA); } From ddcf80f9c52e0d83cb6ce20cb9e5350321a1cc91 Mon Sep 17 00:00:00 2001 From: Andrea Gargaro Date: Fri, 15 Nov 2024 14:48:14 +0100 Subject: [PATCH 06/11] wip --- src/binding/Binding.ts | 141 ++++++++++++++++++++--------------------- src/patch/Scene.ts | 6 +- 2 files changed, 72 insertions(+), 75 deletions(-) diff --git a/src/binding/Binding.ts b/src/binding/Binding.ts index cfc09fb..44da9f8 100644 --- a/src/binding/Binding.ts +++ b/src/binding/Binding.ts @@ -7,104 +7,101 @@ export interface BindingCallback { key: string; } -/** @internal */ -export class Binding { - public static detectChanges(target: Object3D, resursive: boolean): void { - this.executeAllCallbacks(target); - if (resursive) { - for (const child of target.children) { - this.detectChanges(child, true); - } +export function detectChanges(target: Object3D, resursive: boolean): void { + executeAllCallbacks(target); + if (resursive) { + for (const child of target.children) { + detectChanges(child, true); } } +} - public static bindProperty(key: string, target: Object3D, getValue: () => T, renderOnChange?: boolean): void { - if (this.getIndexByKey(target, key) > -1) { - console.error('Cannot override property already bound.'); - return; - } - - this.addToBoundCallbacks(key, target, getValue.bind(target), renderOnChange); - if (target.scene) { - this.bindToScene(target); - } +export function bindProperty(key: string, target: Object3D, getValue: () => T, renderOnChange?: boolean): void { + if (getIndexByKey(target, key) > -1) { + console.error('Cannot override property already bound.'); + return; } - private static addToBoundCallbacks(key: string, target: Object3D, getValue: () => T, renderOnChange: boolean): void { - const setValue = this.createSetValue(key, target, renderOnChange); - const bindingCallback: BindingCallback = { key, getValue, setValue }; - target.__boundCallbacks.push(bindingCallback); - this.executeCallback(bindingCallback); + addToBoundCallbacks(key, target, getValue.bind(target), renderOnChange); + if (target.scene) { + bindToScene(target); } +} - private static createSetValue(key: string, target: Object3D, renderOnChange: boolean): (value: T) => void { - if (renderOnChange) { - return (value) => { - if (value !== target[key]) { - target[key] = value; - target.needsRender = true; - } - }; - } +function addToBoundCallbacks(key: string, target: Object3D, getValue: () => T, renderOnChange: boolean): void { + const setValue = createSetValue(key, target, renderOnChange); + const bindingCallback: BindingCallback = { key, getValue, setValue }; + target.__boundCallbacks.push(bindingCallback); + executeCallback(bindingCallback); +} + +function createSetValue(key: string, target: Object3D, renderOnChange: boolean): (value: T) => void { + if (renderOnChange) { return (value) => { if (value !== target[key]) { target[key] = value; + target.needsRender = true; } }; } - - private static getIndexByKey(target: Object3D, key: string): number { - const boundCallbacks = target.__boundCallbacks; - for (let i = 0; i < boundCallbacks.length; i++) { - if (boundCallbacks[i].key === key) return i; + return (value) => { + if (value !== target[key]) { + target[key] = value; } - return -1; - } + }; +} - public static setManualDetectionMode(target: Object3D): void { - if (target.__manualDetection) return; - if (target.__boundCallbacks.length > 0) { - console.error('Cannot change detectChangesMode if a binding is already created.'); - } else { - target.__manualDetection = true; - } +function getIndexByKey(target: Object3D, key: string): number { + const boundCallbacks = target.__boundCallbacks; + for (let i = 0; i < boundCallbacks.length; i++) { + if (boundCallbacks[i].key === key) return i; } + return -1; +} - public static bindToScene(target: Object3D): void { - if (target.__boundCallbacks.length > 0) { - target.scene.__boundObjects.add(target); - } +export function setManualDetectionMode(target: Object3D): void { + if (target.__manualDetection) return; + if (target.__boundCallbacks.length > 0) { + console.error('Cannot change detectChangesMode if a binding is already created.'); + } else { + target.__manualDetection = true; } +} - public static unbindFromScene(target: Object3D): void { - target.scene.__boundObjects.delete(target); +export function bindToScene(target: Object3D): void { + if (target.__boundCallbacks.length > 0) { + target.scene.__boundObjects.add(target); } +} - public static unbindProperty(target: Object3D, key: string): void { - const index = this.getIndexByKey(target, key); - if (index > -1) { - target.__boundCallbacks.splice(index, 1); - if (target.scene) { - this.unbindFromScene(target); - } +export function unbindFromScene(target: Object3D): void { + target.scene.__boundObjects.delete(target); +} + +export function unbindProperty(target: Object3D, key: string): void { + const index = getIndexByKey(target, key); + if (index > -1) { + target.__boundCallbacks.splice(index, 1); + if (target.scene) { + unbindFromScene(target); } } +} - private static executeCallback(bindingCallback: BindingCallback): void { - bindingCallback.setValue(bindingCallback.getValue()); - } +function executeCallback(bindingCallback: BindingCallback): void { + bindingCallback.setValue(bindingCallback.getValue()); +} - private static executeAllCallbacks(target: Object3D): void { - const callbacks = target.__boundCallbacks; - for (const callback of callbacks) { - this.executeCallback(callback); - } +function executeAllCallbacks(target: Object3D): void { + const callbacks = target.__boundCallbacks; + for (const callback of callbacks) { + executeCallback(callback); } +} - public static compute(scene: Scene): void { - const boundObjs = scene.__boundObjects; - for (const target of boundObjs) { - this.executeAllCallbacks(target); - } +export function compute(scene: Scene): void { + const boundObjs = scene.__boundObjects; + for (const target of boundObjs) { + executeAllCallbacks(target); } } diff --git a/src/patch/Scene.ts b/src/patch/Scene.ts index cc149d4..8a48290 100644 --- a/src/patch/Scene.ts +++ b/src/patch/Scene.ts @@ -1,11 +1,11 @@ import { Object3D, Scene } from 'three'; import { EventsCache } from '../events/MiscEventsManager.js'; import { activeSmartRendering, applySmartRenderingPatch, removeSmartRenderingPatch } from './SmartRendering.js'; -import { Binding } from '../binding/Binding.js'; import { FocusEventExt, IntersectionExt } from '../events/Events.js'; import { addBase, removeBase } from './Object3D.js'; import { EventsDispatcher } from '../events/EventsDispatcher.js'; import { Default } from '../events/Default.js'; +import { bindToScene, unbindFromScene } from '../binding/Binding.js'; /** * Represents the prototype for extending Scene functionality. @@ -131,7 +131,7 @@ export function setSceneReference(target: Object3D, scene: Scene) { target.scene = scene; EventsCache.update(target); applySmartRenderingPatch(target); - Binding.bindToScene(target); + bindToScene(target); for (const object of target.children) { setSceneReference(object, scene); @@ -142,7 +142,7 @@ export function setSceneReference(target: Object3D, scene: Scene) { export function removeSceneReference(target: Object3D) { EventsCache.removeAll(target); removeSmartRenderingPatch(target); - Binding.unbindFromScene(target); + unbindFromScene(target); target.scene = undefined; for (const object of target.children) { From b94fc5afdfd93a1091b16844adcb4d8b2f1be66f Mon Sep 17 00:00:00 2001 From: Andrea Gargaro Date: Fri, 15 Nov 2024 16:03:25 +0100 Subject: [PATCH 07/11] wip --- src/utils/IntersectionUtils.ts | 389 ++++++++++++++++----------------- src/utils/VectorUtils.ts | 8 +- 2 files changed, 196 insertions(+), 201 deletions(-) diff --git a/src/utils/IntersectionUtils.ts b/src/utils/IntersectionUtils.ts index 1485348..9d6f7dc 100644 --- a/src/utils/IntersectionUtils.ts +++ b/src/utils/IntersectionUtils.ts @@ -1,209 +1,204 @@ -import { Box3, Vector3 } from 'three'; -import { ObjVec3, TEMP, VectorObject3D, VectorUtils } from './VectorUtils.js'; +import { Box3, Vector2, Vector2Like, Vector3, Vector3Like } from 'three'; + +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _v3 = new Vector3(); +const abs = Math.abs; + +/** + * Computes the intersection between two 2D lines defined by points `a1` and `a2`, and `b1` and `b2`. + * + * @param a1 - The first point of the first line. + * @param a2 - The second point of the first line. + * @param b1 - The first point of the second line. + * @param b2 - The second point of the second line. + * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. + * @returns The intersection point of the two lines or `undefined` if the lines are parallel. + * + * @see {@link https://paulbourke.net/geometry/pointlineplane/} + */ +export function lineLine2D(a1: Vector2Like, a2: Vector2Like, b1: Vector2Like, b2: Vector2Like, target: Vector2Like = new Vector2()): Vector2Like { + const x1 = a1.x, y1 = a1.y, x2 = a2.x, y2 = a2.y, x3 = b1.x, y3 = b1.y, x4 = b2.x, y4 = b2.y; + const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + if (denominator === 0) return; // parallel + const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator; + return (target as Vector2).set(x1 + ua * (x2 - x1), y1 + ua * (y2 - y1)); +} + +/** + * Computes the intersection between two 2D line segments defined by points `a1` and `a2`, and `b1` and `b2`. + * + * @param a1 - The first point of the first segment. + * @param a2 - The second point of the first segment. + * @param b1 - The first point of the second segment. + * @param b2 - The second point of the second segment. + * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. + * @returns The intersection point of the two segments or `undefined` if the segments do not intersect. + * + * @see {@link https://paulbourke.net/geometry/pointlineplane/} + */ +export function segmentSegment2D(a1: Vector2Like, a2: Vector2Like, b1: Vector2Like, b2: Vector2Like, target: Vector2Like = new Vector2()): Vector2Like { + const x1 = a1.x, y1 = a1.y, x2 = a2.x, y2 = a2.y, x3 = b1.x, y3 = b1.y, x4 = b2.x, y4 = b2.y; + const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + if (denominator === 0) return; // parallel + const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator; + const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator; + if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return; // intersection is not on segment + return (target as Vector2).set(x1 + ua * (x2 - x1), y1 + ua * (y2 - y1)); +} /** - * Class that provides a set of utilities for calculating intersections between 2D and 3D geometric objects. - */ -export class IntersectionUtils { - // https://paulbourke.net/geometry/pointlineplane/ - /** - * Computes the intersection between two 2D lines defined by points `a1` and `a2`, and `b1` and `b2`. - * - * @param a1 - The first point of the first line. - * @param a2 - The second point of the first line. - * @param b1 - The first point of the second line. - * @param b2 - The second point of the second line. - * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. - * @returns The intersection point of the two lines or `undefined` if the lines are parallel. - * - * @see {@link https://paulbourke.net/geometry/pointlineplane/} - */ - public static line_line_2D(a1: VectorObject3D, a2: VectorObject3D, b1: VectorObject3D, b2: VectorObject3D, target = new Vector3()): Vector3 { - const [a1c, a2c, b1c, b2c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); - const denominator = (b2c.y - b1c.y) * (a2c.x - a1c.x) - (b2c.x - b1c.x) * (a2c.y - a1c.y); - if (denominator === 0) return; // parallel - const ua = ((b2c.x - b1c.x) * (a1c.y - b1c.y) - (b2c.y - b1c.y) * (a1c.x - b1c.x)) / denominator; - return target.set(a1c.x + ua * (a2c.x - a1c.x), a1c.y + ua * (a2c.y - a1c.y), 0); + * Computes the intersection between two 3D lines defined by points `a1` and `a2`, and `b1` and `b2`. + * + * @param a1 - The first point of the first line. + * @param a2 - The second point of the first line. + * @param b1 - The first point of the second line. + * @param b2 - The second point of the second line. + * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. + * @param EPS - (Optional) The tolerance for evaluating the intersection. The default value is 10^-6. + * @returns The intersection point of the two lines or `undefined` if the lines are parallel or do not intersect. + * + * @see {@link https://paulbourke.net/geometry/pointlineplane/} + */ +export function lineLine3D(a1: Vector3Like, a2: Vector3Like, b1: Vector3Like, b2: Vector3Like, target: Vector3Like = new Vector3(), EPS = 1e-6): Vector3Like { + _v1.subVectors(a1, b1); + _v2.subVectors(b2, b1); + + if (abs(_v2.x) < EPS && abs(_v2.y) < EPS && abs(_v2.z) < EPS) return; + _v3.subVectors(a2, a1); + if (abs(_v3.x) < EPS && abs(_v3.y) < EPS && abs(_v3.z) < EPS) return; + + const d1343 = _v1.dot(_v2); + const d4321 = _v2.dot(_v3); + const d1321 = _v1.dot(_v3); + const d4343 = _v2.dot(_v2); + const d2121 = _v3.dot(_v3); + + const denom = d2121 * d4343 - d4321 * d4321; + if (Math.abs(denom) < EPS) return; + const numer = d1343 * d4321 - d1321 * d4343; + + const mua = numer / denom; + const Pa = (target as Vector3).set(a1.x + mua * _v3.x, a1.y + mua * _v3.y, a1.z + mua * _v3.z); + + // const mub = (d1343 + d4321 * mua) / d4343; + // const Pb = new Vector3(p3.x + mub * p43.x, p3.y + mub * p43.y, p3.z + mub * p43.z); + + return Pa; +} + +/** + * Checks if a 3D line intersects an Axis-Aligned Bounding Box (AABB) defined by `box`. + * + * @param rayOrigin - The origin of the line. + * @param rayDir - The direction of the line. + * @param box - The AABB to check for intersection with. + * @returns `true` if the line intersects the AABB, otherwise `false`. + */ +export function lineBox(rayOrigin: Vector3, rayDir: Vector3, box: Box3): boolean { + const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; + let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; + + if (invdirx >= 0) { // TODO align this with bvh.js + bmin = box.min.x; + bmax = box.max.x; + } else { + bmin = box.max.x; + bmax = box.min.x; + } + + dmin = (bmin - rayOrigin.x) * invdirx; + dmax = (bmax - rayOrigin.x) * invdirx; + + tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error + tmax = dmax < tmax ? dmax : tmax; + + if (invdiry >= 0) { + bmin = box.min.y; + bmax = box.max.y; + } else { + bmin = box.max.y; + bmax = box.min.y; } - /** - * Computes the intersection between two 2D line segments defined by points `a1` and `a2`, and `b1` and `b2`. - * - * @param a1 - The first point of the first segment. - * @param a2 - The second point of the first segment. - * @param b1 - The first point of the second segment. - * @param b2 - The second point of the second segment. - * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. - * @returns The intersection point of the two segments or `undefined` if the segments do not intersect. - * - * @see {@link https://paulbourke.net/geometry/pointlineplane/} - */ - public static segment_segment_2D(a1: VectorObject3D, a2: VectorObject3D, b1: VectorObject3D, b2: VectorObject3D, target = new Vector3()): Vector3 { - const [a1c, a2c, b1c, b2c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); - const denominator = (b2c.y - b1c.y) * (a2c.x - a1c.x) - (b2c.x - b1c.x) * (a2c.y - a1c.y); - if (denominator === 0) return; // parallel - const ua = ((b2c.x - b1c.x) * (a1c.y - b1c.y) - (b2c.y - b1c.y) * (a1c.x - b1c.x)) / denominator; - const ub = ((a2c.x - a1c.x) * (a1c.y - b1c.y) - (a2c.y - a1c.y) * (a1c.x - b1c.x)) / denominator; - if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return; - return target.set(a1c.x + ua * (a2c.x - a1c.x), a1c.y + ua * (a2c.y - a1c.y), 0); + dmin = (bmin - rayOrigin.y) * invdiry; + dmax = (bmax - rayOrigin.y) * invdiry; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + if (invdirz >= 0) { + bmin = box.min.z; + bmax = box.max.z; + } else { + bmin = box.max.z; + bmax = box.min.z; } - /** - * Computes the intersection between two 3D lines defined by points `a1` and `a2`, and `b1` and `b2`. - * - * @param a1 - The first point of the first line. - * @param a2 - The second point of the first line. - * @param b1 - The first point of the second line. - * @param b2 - The second point of the second line. - * @param target - (Optional) The vector to store the intersection point. If omitted, a new vector will be created. - * @param tolerance - (Optional) The tolerance for evaluating the intersection. The default value is 10^-6. - * @returns The intersection point of the two lines or `undefined` if the lines are parallel or do not intersect. - * - * @see {@link https://paulbourke.net/geometry/pointlineplane/} - */ - public static line_line_3D(a1: ObjVec3, a2: ObjVec3, b1: ObjVec3, b2: ObjVec3, target = new Vector3(), tolerance = 1e-6): Vector3 { - const [p1c, p2c, p3c, p4c] = VectorUtils.getPositionsFromObject3D([a1, a2, b1, b2]); - - const p13 = TEMP[0].subVectors(p1c, p3c); - const p43 = TEMP[1].subVectors(p4c, p3c); - - if (p43.lengthSq() < tolerance) return; - const p21 = TEMP[2].subVectors(p2c, p1c); - if (p21.lengthSq() < tolerance) return; - - const d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z; - const d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z; - const d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z; - const d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z; - const d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z; - - const denom = d2121 * d4343 - d4321 * d4321; - if (Math.abs(denom) < tolerance) return; - - const numer = d1343 * d4321 - d1321 * d4343; - - const mua = numer / denom; - const Pa = target.set(p1c.x + mua * p21.x, p1c.y + mua * p21.y, p1c.z + mua * p21.z); - - // const mub = (d1343 + d4321 * mua) / d4343; - // const Pb = new Vector3(p3.x + mub * p43.x, p3.y + mub * p43.y, p3.z + mub * p43.z); - - return Pa; + dmin = (bmin - rayOrigin.z) * invdirz; + dmax = (bmax - rayOrigin.z) * invdirz; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + return tmin <= tmax; +} + +/** + * Checks if a 3D line segment defined by points `p1` and `p2` intersects an Axis-Aligned Bounding Box (AABB) defined by `box`. + * + * @param p1 - The first point of the segment. + * @param p2 - The second point of the segment. + * @param box - The AABB to check for intersection with. + * @returns `true` if the segment intersects the AABB and is within the segment's length, otherwise `false`. + */ +export function segmentBox(p1: Vector3, p2: Vector3, box: Box3): boolean { + const rayDir = _v1.subVectors(p2, p1).normalize(); + const distance = p1.distanceTo(p2); + const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; + let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; + + if (invdirx >= 0) { + bmin = box.min.x; + bmax = box.max.x; + } else { + bmin = box.max.x; + bmax = box.min.x; } - /** - * Checks if a 3D line intersects an Axis-Aligned Bounding Box (AABB) defined by `box`. - * - * @param rayOrigin - The origin of the line. - * @param rayDir - The direction of the line. - * @param box - The AABB to check for intersection with. - * @returns `true` if the line intersects the AABB, otherwise `false`. - */ - public static line_boxAABB(rayOrigin: Vector3, rayDir: Vector3, box: Box3): boolean { - const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; - let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; - - if (invdirx >= 0) { - bmin = box.min.x; - bmax = box.max.x; - } else { - bmin = box.max.x; - bmax = box.min.x; - } - - dmin = (bmin - rayOrigin.x) * invdirx; - dmax = (bmax - rayOrigin.x) * invdirx; - - tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error - tmax = dmax < tmax ? dmax : tmax; - - if (invdiry >= 0) { - bmin = box.min.y; - bmax = box.max.y; - } else { - bmin = box.max.y; - bmax = box.min.y; - } - - dmin = (bmin - rayOrigin.y) * invdiry; - dmax = (bmax - rayOrigin.y) * invdiry; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - if (invdirz >= 0) { - bmin = box.min.z; - bmax = box.max.z; - } else { - bmin = box.max.z; - bmax = box.min.z; - } - - dmin = (bmin - rayOrigin.z) * invdirz; - dmax = (bmax - rayOrigin.z) * invdirz; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - return tmin <= tmax; + dmin = (bmin - p1.x) * invdirx; + dmax = (bmax - p1.x) * invdirx; + + tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error + tmax = dmax < tmax ? dmax : tmax; + + if (invdiry >= 0) { + bmin = box.min.y; + bmax = box.max.y; + } else { + bmin = box.max.y; + bmax = box.min.y; } - /** - * Checks if a 3D line segment defined by points `p1` and `p2` intersects an Axis-Aligned Bounding Box (AABB) defined by `box`. - * - * @param p1 - The first point of the segment. - * @param p2 - The second point of the segment. - * @param box - The AABB to check for intersection with. - * @returns `true` if the segment intersects the AABB and is within the segment's length, otherwise `false`. - */ - public static segment_boxAABB(p1: Vector3, p2: Vector3, box: Box3): boolean { - const rayDir = TEMP[0].subVectors(p2, p1).normalize(); - const distance = p1.distanceTo(p2); - const invdirx = 1 / rayDir.x, invdiry = 1 / rayDir.y, invdirz = 1 / rayDir.z; - let tmin = 0, tmax = Infinity, bmin: number, bmax: number, dmin: number, dmax: number; - - if (invdirx >= 0) { - bmin = box.min.x; - bmax = box.max.x; - } else { - bmin = box.max.x; - bmax = box.min.x; - } - - dmin = (bmin - p1.x) * invdirx; - dmax = (bmax - p1.x) * invdirx; - - tmin = dmin > tmin ? dmin : tmin; // in this order ignore NaN error - tmax = dmax < tmax ? dmax : tmax; - - if (invdiry >= 0) { - bmin = box.min.y; - bmax = box.max.y; - } else { - bmin = box.max.y; - bmax = box.min.y; - } - - dmin = (bmin - p1.y) * invdiry; - dmax = (bmax - p1.y) * invdiry; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - if (invdirz >= 0) { - bmin = box.min.z; - bmax = box.max.z; - } else { - bmin = box.max.z; - bmax = box.min.z; - } - - dmin = (bmin - p1.z) * invdirz; - dmax = (bmax - p1.z) * invdirz; - - tmin = dmin > tmin ? dmin : tmin; - tmax = dmax < tmax ? dmax : tmax; - - return tmin <= tmax && distance >= tmin; + dmin = (bmin - p1.y) * invdiry; + dmax = (bmax - p1.y) * invdiry; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + if (invdirz >= 0) { + bmin = box.min.z; + bmax = box.max.z; + } else { + bmin = box.max.z; + bmax = box.min.z; } + + dmin = (bmin - p1.z) * invdirz; + dmax = (bmax - p1.z) * invdirz; + + tmin = dmin > tmin ? dmin : tmin; + tmax = dmax < tmax ? dmax : tmax; + + return tmin <= tmax && distance >= tmin; } diff --git a/src/utils/VectorUtils.ts b/src/utils/VectorUtils.ts index 194f9fa..4889f45 100644 --- a/src/utils/VectorUtils.ts +++ b/src/utils/VectorUtils.ts @@ -9,16 +9,16 @@ export function computeSign(point: Vector3, origin: Vector3, normal: Vector3): n return Math.sign(_v1.dot(normal)); } -export function haveSameDirection(v1: Vector3, v2: Vector3, tolerance = 1e-2): boolean { +export function haveSameDirection(v1: Vector3, v2: Vector3, EPS = 1e-2): boolean { _v1.copy(v1).normalize(); _v2.copy(v2).normalize(); - return _v1.dot(_v2) > 1 - tolerance; + return _v1.dot(_v2) > 1 - EPS; } -export function haveOppositeDirection(v1: Vector3, v2: Vector3, tolerance = 1e-2): boolean { +export function haveOppositeDirection(v1: Vector3, v2: Vector3, EPS = 1e-2): boolean { _v1.copy(v1).normalize(); _v2.copy(v2).normalize(); - return _v1.dot(_v2) < tolerance - 1; + return _v1.dot(_v2) < EPS - 1; } export function perpendicular(dir: Vector3, normal: Vector3, target = new Vector3()) { From 85969cdaa9f0a9456b7fb363afc44c9d47d7d466 Mon Sep 17 00:00:00 2001 From: Andrea Gargaro Date: Fri, 15 Nov 2024 17:07:55 +0100 Subject: [PATCH 08/11] wip --- eslint.config.js | 4 +- index.html | 2 +- packages/InstancedMesh2/examples/example.ts | 130 +++++----- packages/InstancedMesh2/examples/example2.ts | 92 +++---- packages/Interaction/examples/main.ts | 52 ++-- src/core/Main.ts | 16 +- src/patch/Object3D.ts | 19 +- src/tweening/RunningTween.ts | 24 +- src/tweening/Tween.ts | 6 +- src/tweening/TweenManager.ts | 213 ++++++++-------- src/utils/Asset.ts | 247 +++++++++---------- 11 files changed, 397 insertions(+), 408 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 608fb95..5e9c3c9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -28,7 +28,9 @@ export default [ '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-wrapper-object-types': 'off', - '@typescript-eslint/no-dynamic-delete': 'off' + '@typescript-eslint/no-dynamic-delete': 'off', + '@typescript-eslint/no-invalid-void-type': 'off', + '@typescript-eslint/no-this-alias': 'off' // "@typescript-eslint/explicit-function-return-type": "error", } } diff --git a/index.html b/index.html index e7cbac6..4bec442 100644 --- a/index.html +++ b/index.html @@ -25,7 +25,7 @@ - + \ No newline at end of file diff --git a/packages/InstancedMesh2/examples/example.ts b/packages/InstancedMesh2/examples/example.ts index abe0cab..8816de5 100644 --- a/packages/InstancedMesh2/examples/example.ts +++ b/packages/InstancedMesh2/examples/example.ts @@ -1,80 +1,80 @@ -import { Asset, Main, PerspectiveCameraAuto } from '../../../src'; -import { ACESFilmicToneMapping, AmbientLight, BufferGeometry, DirectionalLight, FogExp2, Mesh, MeshLambertMaterial, MeshStandardMaterial, PlaneGeometry, Scene, Vector3 } from 'three'; -import { MapControls } from 'three/examples/jsm/controls/MapControls'; -import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min'; -import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; -import { Sky } from 'three/examples/jsm/objects/Sky'; -import { CullingStatic, InstancedMesh2 } from '../src/InstancedMesh2'; +// import { Asset, Main, PerspectiveCameraAuto } from '../../../src'; +// import { ACESFilmicToneMapping, AmbientLight, BufferGeometry, DirectionalLight, FogExp2, Mesh, MeshLambertMaterial, MeshStandardMaterial, PlaneGeometry, Scene, Vector3 } from 'three'; +// import { MapControls } from 'three/examples/jsm/controls/MapControls'; +// import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min'; +// import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +// import { Sky } from 'three/examples/jsm/objects/Sky'; +// import { CullingStatic, InstancedMesh2 } from '../src/InstancedMesh2'; -const count = 1000000; -const terrainSize = 200000; +// const count = 1000000; +// const terrainSize = 200000; -const main = new Main({ rendererParameters: { antialias: true } }); // init renderer and other stuff -main.renderer.toneMapping = ACESFilmicToneMapping; -main.renderer.toneMappingExposure = 0.5; +// const main = new Main({ rendererParameters: { antialias: true } }); // init renderer and other stuff +// main.renderer.toneMapping = ACESFilmicToneMapping; +// main.renderer.toneMappingExposure = 0.5; -const camera = new PerspectiveCameraAuto(70, 0.1, 4000).translateY(300).translateZ(-900); -const scene = new Scene(); +// const camera = new PerspectiveCameraAuto(70, 0.1, 4000).translateY(300).translateZ(-900); +// const scene = new Scene(); -const treeGLTF = (await Asset.load(GLTFLoader, '../tree.glb')).scene.children[0] as Mesh; +// const treeGLTF = (await Asset.load(GLTFLoader, '../tree.glb')).scene.children[0] as Mesh; -const trees = new InstancedMesh2(treeGLTF.geometry, treeGLTF.material, count, { - behaviour: CullingStatic, - // createEntities: false, - verbose: true, - bvhParams: { maxDepth: 30, maxLeaves: 5 }, - onInstanceCreation: (obj, index) => { - obj.position.setX(Math.random() * terrainSize - terrainSize / 2).setZ(Math.random() * terrainSize - terrainSize / 2); - obj.scale.setScalar(Math.random() * 0.1 + 0.1); - obj.rotateY(Math.random() * Math.PI * 2).rotateZ(Math.random() * 0.3 - 0.15); - } -}); +// const trees = new InstancedMesh2(treeGLTF.geometry, treeGLTF.material, count, { +// behaviour: CullingStatic, +// // createEntities: false, +// verbose: true, +// bvhParams: { maxDepth: 30, maxLeaves: 5 }, +// onInstanceCreation: (obj, index) => { +// obj.position.setX(Math.random() * terrainSize - terrainSize / 2).setZ(Math.random() * terrainSize - terrainSize / 2); +// obj.scale.setScalar(Math.random() * 0.1 + 0.1); +// obj.rotateY(Math.random() * Math.PI * 2).rotateZ(Math.random() * 0.3 - 0.15); +// } +// }); -const terrain = new Mesh(new PlaneGeometry(terrainSize, terrainSize, 10, 10), new MeshLambertMaterial({ color: 0x004622 })); -terrain.rotateX(Math.PI / -2); +// const terrain = new Mesh(new PlaneGeometry(terrainSize, terrainSize, 10, 10), new MeshLambertMaterial({ color: 0x004622 })); +// terrain.rotateX(Math.PI / -2); -const sun = new Vector3(); -const sky = new Sky(); -sky.scale.setScalar(450000); -const uniforms = sky.material.uniforms; -uniforms['turbidity'].value = 5; -uniforms['rayleigh'].value = 2; +// const sun = new Vector3(); +// const sky = new Sky(); +// sky.scale.setScalar(450000); +// const uniforms = sky.material.uniforms; +// uniforms['turbidity'].value = 5; +// uniforms['rayleigh'].value = 2; -sky.on('animate', (e) => { - sun.setFromSphericalCoords(1, Math.PI / -1.9 + e.total * 0.02, Math.PI / 1.4); - uniforms['sunPosition'].value.copy(sun); -}); +// sky.on('animate', (e) => { +// sun.setFromSphericalCoords(1, Math.PI / -1.9 + e.total * 0.02, Math.PI / 1.4); +// uniforms['sunPosition'].value.copy(sun); +// }); -const dirLight = new DirectionalLight('white'); -dirLight.on('animate', (e) => { - dirLight.intensity = sun.y > 0.1 ? 10 : Math.max(0, sun.y / 0.1 * 10); - dirLight.position.copy(sun).multiplyScalar(terrainSize); - dirLight.target.position.copy(sun).multiplyScalar(-terrainSize); -}); +// const dirLight = new DirectionalLight('white'); +// dirLight.on('animate', (e) => { +// dirLight.intensity = sun.y > 0.1 ? 10 : Math.max(0, sun.y / 0.1 * 10); +// dirLight.position.copy(sun).multiplyScalar(terrainSize); +// dirLight.target.position.copy(sun).multiplyScalar(-terrainSize); +// }); -scene.add(sky, trees, terrain, new AmbientLight(), dirLight, dirLight.target); +// scene.add(sky, trees, terrain, new AmbientLight(), dirLight, dirLight.target); -scene.fog = new FogExp2('white', 0.0004); -scene.on('animate', (e) => scene.fog.color.setHSL(0, 0, sun.y)); +// scene.fog = new FogExp2('white', 0.0004); +// scene.on('animate', (e) => scene.fog.color.setHSL(0, 0, sun.y)); -main.createView({ - scene, camera, onBeforeRender: () => { - camera.updateMatrixWorld(true); - trees.updateCulling(camera); - treeCount.updateDisplay(); - } -}); +// main.createView({ +// scene, camera, onBeforeRender: () => { +// camera.updateMatrixWorld(true); +// trees.updateCulling(camera); +// treeCount.updateDisplay(); +// } +// }); -const controls = new MapControls(camera, main.renderer.domElement); -controls.maxPolarAngle = Math.PI / 2.1; -controls.minDistance = 100; -controls.maxDistance = 1000; +// const controls = new MapControls(camera, main.renderer.domElement); +// controls.maxPolarAngle = Math.PI / 2.1; +// controls.minDistance = 100; +// controls.maxDistance = 1000; -scene.on("pointermove", (e) => console.log(e.intersection)); -sky.interceptByRaycaster = false; -terrain.interceptByRaycaster = false; +// scene.on("pointermove", (e) => console.log(e.intersection)); +// sky.interceptByRaycaster = false; +// terrain.interceptByRaycaster = false; -const gui = new GUI(); -gui.add(trees.instances as any, 'length').name("instances total").disable(); -const treeCount = gui.add(trees, 'count').name("instances rendered").disable(); -gui.add(camera, 'far', 1000, 10000, 100).name("camera far").onChange(() => camera.updateProjectionMatrix()); +// const gui = new GUI(); +// gui.add(trees.instances as any, 'length').name("instances total").disable(); +// const treeCount = gui.add(trees, 'count').name("instances rendered").disable(); +// gui.add(camera, 'far', 1000, 10000, 100).name("camera far").onChange(() => camera.updateProjectionMatrix()); diff --git a/packages/InstancedMesh2/examples/example2.ts b/packages/InstancedMesh2/examples/example2.ts index 0ee6ec1..8796902 100644 --- a/packages/InstancedMesh2/examples/example2.ts +++ b/packages/InstancedMesh2/examples/example2.ts @@ -1,46 +1,46 @@ -import { Main, PerspectiveCameraAuto } from '../../../src/'; -import { MeshNormalMaterial, Scene, SphereGeometry, Vector3 } from 'three'; -import { FlyControls } from 'three/examples/jsm/controls/FlyControls'; -import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min'; -import { CullingDynamic, InstancedMesh2 } from '../src'; - -const count = 150000; -const worldSize = 20000; - -const main = new Main({ rendererParameters: { antialias: true } }); // init renderer and other stuff -const camera = new PerspectiveCameraAuto(70, 0.1, 500); -const scene = new Scene(); - -const spheres = new InstancedMesh2<{ dir: Vector3 }>(new SphereGeometry(1, 16), new MeshNormalMaterial(), count, { - behaviour: CullingDynamic, - onInstanceCreation: (obj, index) => { - obj.position.randomDirection().multiplyScalar(((Math.random() * 0.99 + 0.01) * worldSize) / 2); - obj.dir = new Vector3().randomDirection(); - }, -}); - -spheres.on('animate', (e) => { - for (const mesh of spheres.instances) { - mesh.position.add(mesh.dir.setLength((e.delta || 0.01) * 5)); - mesh.updateMatrix(); - } -}); - -scene.add(spheres); - -main.createView({ scene, camera, enabled: false, onBeforeRender: () => { - camera.updateMatrixWorld(true); - spheres.updateCulling(camera); - spheresCount.updateDisplay(); - } -}); - -const controls = new FlyControls(camera, main.renderer.domElement); -controls.rollSpeed = Math.PI / 10; -controls.movementSpeed = 50; -scene.on('animate', (e) => controls.update(e.delta)); - -const gui = new GUI(); -gui.add(spheres.instances as any, 'length').name('instances total').disable(); -const spheresCount = gui.add(spheres, 'count').name('instances rendered').disable(); -gui.add(camera, 'far', 100, 5000, 100).name('camera far').onChange(() => camera.updateProjectionMatrix()); +// import { Main, PerspectiveCameraAuto } from '../../../src/'; +// import { MeshNormalMaterial, Scene, SphereGeometry, Vector3 } from 'three'; +// import { FlyControls } from 'three/examples/jsm/controls/FlyControls'; +// import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min'; +// import { CullingDynamic, InstancedMesh2 } from '../src'; + +// const count = 150000; +// const worldSize = 20000; + +// const main = new Main({ rendererParameters: { antialias: true } }); // init renderer and other stuff +// const camera = new PerspectiveCameraAuto(70, 0.1, 500); +// const scene = new Scene(); + +// const spheres = new InstancedMesh2<{ dir: Vector3 }>(new SphereGeometry(1, 16), new MeshNormalMaterial(), count, { +// behaviour: CullingDynamic, +// onInstanceCreation: (obj, index) => { +// obj.position.randomDirection().multiplyScalar(((Math.random() * 0.99 + 0.01) * worldSize) / 2); +// obj.dir = new Vector3().randomDirection(); +// }, +// }); + +// spheres.on('animate', (e) => { +// for (const mesh of spheres.instances) { +// mesh.position.add(mesh.dir.setLength((e.delta || 0.01) * 5)); +// mesh.updateMatrix(); +// } +// }); + +// scene.add(spheres); + +// main.createView({ scene, camera, enabled: false, onBeforeRender: () => { +// camera.updateMatrixWorld(true); +// spheres.updateCulling(camera); +// spheresCount.updateDisplay(); +// } +// }); + +// const controls = new FlyControls(camera, main.renderer.domElement); +// controls.rollSpeed = Math.PI / 10; +// controls.movementSpeed = 50; +// scene.on('animate', (e) => controls.update(e.delta)); + +// const gui = new GUI(); +// gui.add(spheres.instances as any, 'length').name('instances total').disable(); +// const spheresCount = gui.add(spheres, 'count').name('instances rendered').disable(); +// gui.add(camera, 'far', 100, 5000, 100).name('camera far').onChange(() => camera.updateProjectionMatrix()); diff --git a/packages/Interaction/examples/main.ts b/packages/Interaction/examples/main.ts index 9e7fda9..287a00f 100644 --- a/packages/Interaction/examples/main.ts +++ b/packages/Interaction/examples/main.ts @@ -1,35 +1,35 @@ -import { BoxGeometry, Mesh, MeshBasicMaterial, PerspectiveCamera, Scene, WebGLRenderer } from 'three'; -import { InteractionManager } from '../src'; +// import { BoxGeometry, Mesh, MeshBasicMaterial, PerspectiveCamera, Scene, WebGLRenderer } from 'three'; +// import { InteractionManager } from '../src'; -const camera = new PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100); -camera.position.z = 2; +// const camera = new PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100); +// camera.position.z = 2; -const scene = new Scene(); +// const scene = new Scene(); -const renderer = new WebGLRenderer({ antialias: true }); -renderer.setPixelRatio(window.devicePixelRatio); -renderer.setSize(window.innerWidth, window.innerHeight); -document.body.appendChild(renderer.domElement); +// const renderer = new WebGLRenderer({ antialias: true }); +// renderer.setPixelRatio(window.devicePixelRatio); +// renderer.setSize(window.innerWidth, window.innerHeight); +// document.body.appendChild(renderer.domElement); -const interactionManager = new InteractionManager(renderer, scene, camera); +// const interactionManager = new InteractionManager(renderer, scene, camera); -window.addEventListener('resize', () => { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); -}); +// window.addEventListener('resize', () => { +// camera.aspect = window.innerWidth / window.innerHeight; +// camera.updateProjectionMatrix(); +// renderer.setSize(window.innerWidth, window.innerHeight); +// }); -const mesh = new Mesh(new BoxGeometry(), new MeshBasicMaterial({ color: 'white' })); -scene.add(mesh); +// const mesh = new Mesh(new BoxGeometry(), new MeshBasicMaterial({ color: 'white' })); +// scene.add(mesh); -mesh.draggable = true; +// mesh.draggable = true; -mesh.on('animate', (e) => { - if (mesh.hovered) return; - mesh.rotation.z += e.delta; -}); +// mesh.on('animate', (e) => { +// if (mesh.hovered) return; +// mesh.rotation.z += e.delta; +// }); -renderer.setAnimationLoop((time) => { - interactionManager.update(time); - renderer.render(scene, camera); -}); +// renderer.setAnimationLoop((time) => { +// interactionManager.update(time); +// renderer.render(scene, camera); +// }); diff --git a/src/core/Main.ts b/src/core/Main.ts index 295aec3..55732d0 100644 --- a/src/core/Main.ts +++ b/src/core/Main.ts @@ -1,14 +1,14 @@ import { Camera, Clock, ColorRepresentation, Raycaster, Scene, Vector2, WebGLRenderer, WebGLRendererParameters } from 'three'; import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; -import { Binding } from '../binding/Binding.js'; import { InteractionManager } from '../events/InteractionManager.js'; import { EventsCache } from '../events/MiscEventsManager.js'; import { RenderManager } from '../rendering/RenderManager.js'; import { RenderView, ViewParameters } from '../rendering/RenderView.js'; -import { TweenManager } from '../tweening/TweenManager.js'; import { Stats } from '../utils/Stats.js'; import { RaycasterSortComparer } from '../events/RaycasterManager.js'; import { applyWebGLRendererPatch } from '../patch/WebGLRenderer.js'; +import { update } from '../tweening/TweenManager.js'; +import { compute } from '../binding/Binding.js'; /** * Configuration parameters for initializing the Main class. @@ -43,12 +43,12 @@ export interface MainParameters { export class Main { /** A static counter representing the number of animation frames elapsed. */ public ticks = 0; - private _renderManager: RenderManager; - private _interactionManager: InteractionManager; + private readonly _renderManager: RenderManager; + private readonly _interactionManager: InteractionManager; /** @internal **/ public _stats: Stats; /** @internal **/ public _showStats: boolean; - private _animate: (delta: number, total: number) => void; - private _clock = new Clock(); + private readonly _animate: (delta: number, total: number) => void; // todo improve this.. readonly not worth + private readonly _clock = new Clock(); /** * The WebGLRenderer instance used for rendering the 3D scene. @@ -187,7 +187,7 @@ export class Main { const currentDelta = this._clock.getDelta(); this._interactionManager.update(); - TweenManager.update(currentDelta * 1000); + update(currentDelta * 1000); this.animate(currentDelta, this._clock.elapsedTime); @@ -200,7 +200,7 @@ export class Main { EventsCache.dispatchEvent(scene, 'beforeanimate', { delta, total }); EventsCache.dispatchEvent(scene, 'animate', { delta, total }); EventsCache.dispatchEvent(scene, 'afteranimate', { delta, total }); - Binding.compute(scene); + compute(scene); } this._renderManager.render(); diff --git a/src/patch/Object3D.ts b/src/patch/Object3D.ts index ee16daf..f2d78a9 100644 --- a/src/patch/Object3D.ts +++ b/src/patch/Object3D.ts @@ -1,5 +1,5 @@ import { Object3D, Scene } from 'three'; -import { Binding, BindingCallback } from '../binding/Binding.js'; +import { BindingCallback, bindProperty, detectChanges, setManualDetectionMode, unbindProperty } from '../binding/Binding.js'; import { Cursor } from '../events/CursorManager.js'; import { Default } from '../events/Default.js'; import { Events, InteractionEvents } from '../events/Events.js'; @@ -214,13 +214,10 @@ Object.defineProperty(Object3D.prototype, 'firstFocusable', { Object.defineProperty(Object3D.prototype, 'enabledState', { get: function (this: Object3D) { - if (!this.__enabled) return false; - - let obj = this.parent; + let obj = this; do { if (!obj.__enabled) return false; } while ((obj = obj.parent)); - return true; } }); @@ -229,8 +226,8 @@ Object.defineProperty(Object3D.prototype, 'visibilityState', { get: function (this: Object3D) { let obj = this; do { - if (!obj.visible) return false; - } while (obj = obj.parent); + if (!obj.__visible) return false; + } while ((obj = obj.parent)); return true; } }); @@ -322,20 +319,20 @@ Object3D.prototype.applyBlur = function () { }; Object3D.prototype.setManualDetectionMode = function () { - Binding.setManualDetectionMode(this); + setManualDetectionMode(this); }; Object3D.prototype.detectChanges = function (recursive = false) { - Binding.detectChanges(this, recursive); + detectChanges(this, recursive); }; Object3D.prototype.bindProperty = function (property, getValue, renderOnChange) { - Binding.bindProperty(property, this, getValue, renderOnChange); + bindProperty(property, this, getValue, renderOnChange); return this; }; Object3D.prototype.unbindProperty = function (property) { - Binding.unbindProperty(this, property); + unbindProperty(this, property); return this; }; diff --git a/src/tweening/RunningTween.ts b/src/tweening/RunningTween.ts index d8d58ab..4440b16 100644 --- a/src/tweening/RunningTween.ts +++ b/src/tweening/RunningTween.ts @@ -1,7 +1,7 @@ import { ActionTween, MotionConfig } from './Actions.js'; import { EasingFunction } from './Easings.js'; import { Tween } from './Tween.js'; -import { TweenManager } from './TweenManager.js'; +import { addChildren, complete, createChildren, stop } from './TweenManager.js'; type UpdateCallback = (start?: T, end?: T, alpha?: number) => void; @@ -88,29 +88,29 @@ export class RunningTween { } /** - * Resume the execution of the running tween if it was paused. - */ + * Resume the execution of the running tween if it was paused. + */ public resume(): void { this.paused = false; } /** - * Stop the running tween, causing it to finish immediately. - */ + * Stop the running tween, causing it to finish immediately. + */ public stop(): void { - TweenManager.stop(this); + stop(this); } /** * Complete the running tween, causing it to finish immediately. */ public complete(): void { - TweenManager.complete(this); + complete(this); } /** - * Revert the running tween to its initial state (Not implemented yet). - */ + * Revert the running tween to its initial state (Not implemented yet). + */ public revert(): void { console.error('Revert method not implemented yet.'); // handle (!blockHistory) } @@ -306,10 +306,10 @@ export class RunningTween { /** @internal */ private executeTween(block: RunningBlock, delta: number, tween: Tween): void { - const runningTween = TweenManager.createChildren(this.target, tween, this.root ?? this); + const runningTween = createChildren(this.target, tween, this.root ?? this); block.runningTweens.push(runningTween); if (runningTween.execute(delta)) { - TweenManager.addChildren(runningTween); + addChildren(runningTween); } } @@ -320,7 +320,7 @@ export class RunningTween { this.actionIndex = this.reversed ? this.history.length : -1; this.getBlock(); if (this.execute(delta)) { - TweenManager.addChildren(this); + addChildren(this); } } diff --git a/src/tweening/Tween.ts b/src/tweening/Tween.ts index a7c315c..fa44069 100644 --- a/src/tweening/Tween.ts +++ b/src/tweening/Tween.ts @@ -1,6 +1,6 @@ import { ActionCallback, ActionDelay, ActionMotion, ActionRepeat, ActionTween, ActionYoyo, IAction, Motion, MotionConfig, SetMotion } from './Actions.js'; import { RunningTween } from './RunningTween.js'; -import { TweenManager } from './TweenManager.js'; +import { create, stopById } from './TweenManager.js'; /** * A Tween represents a series of actions that can be applied to a target object to create animations or sequences of events. @@ -223,8 +223,8 @@ export class Tween { */ public start(): RunningTween { if (this.id !== undefined) { - TweenManager.stopById(this.id); + stopById(this.id); } - return TweenManager.create(this.target, this); + return create(this.target, this); } } diff --git a/src/tweening/TweenManager.ts b/src/tweening/TweenManager.ts index 3f3d1d5..c5b9bd7 100644 --- a/src/tweening/TweenManager.ts +++ b/src/tweening/TweenManager.ts @@ -1,140 +1,135 @@ import { RunningTween } from './RunningTween.js'; import { Tween } from './Tween.js'; -/** - * This class is responsible for managing and controlling running tweens. - */ -export class TweenManager { - private static _running: RunningTween[] = []; - private static _runningChildren: RunningTween[] = []; - - /** @internal */ - public static create(target: T, tween: Tween): RunningTween { - const runningTween = new RunningTween(target, tween); - runningTween.getBlock(); - this._running.push(runningTween); - return runningTween; - } +const _running: RunningTween[] = []; +const _runningChildren: RunningTween[] = []; + +/** @internal */ +export function create(target: T, tween: Tween): RunningTween { + const runningTween = new RunningTween(target, tween); + runningTween.getBlock(); + _running.push(runningTween); + return runningTween; +} - /** @internal */ - public static createChildren(target: T, tween: Tween, root: RunningTween): RunningTween { - const runningTween = new RunningTween(target, tween); - runningTween.root = root; - runningTween.getBlock(); - return runningTween; - } +/** @internal */ +export function createChildren(target: T, tween: Tween, root: RunningTween): RunningTween { + const runningTween = new RunningTween(target, tween); + runningTween.root = root; + runningTween.getBlock(); + return runningTween; +} - /** @internal */ - public static addChildren(runningTween: RunningTween): void { - this._runningChildren.push(runningTween); - } +/** @internal */ +export function addChildren(runningTween: RunningTween): void { + _runningChildren.push(runningTween); +} - /** @internal */ - public static update(delta: number): void { - const runningChildren = this._runningChildren; - for (let i = runningChildren.length - 1; i >= 0; i--) { - const rc = runningChildren[i]; - if (!rc.execute(delta * rc.root.timeScale)) { - runningChildren.splice(i, 1); - } +/** @internal */ +export function update(delta: number): void { + const runningChildren = _runningChildren; + for (let i = runningChildren.length - 1; i >= 0; i--) { + const rc = runningChildren[i]; + if (!rc.execute(delta * rc.root.timeScale)) { + runningChildren.splice(i, 1); } + } - const running = this._running; - for (let i = running.length - 1; i >= 0; i--) { - if (!running[i].execute(delta)) { - running[i]._finished = true; - running.splice(i, 1); - } + const running = _running; + for (let i = running.length - 1; i >= 0; i--) { + if (!running[i].execute(delta)) { + running[i]._finished = true; + running.splice(i, 1); } } +} - /** @internal */ - public static stop(runningTween: RunningTween): void { - const index = this._running.indexOf(runningTween); - if (index < 0) return; - this._running.splice(index, 1); - runningTween._finished = true; - runningTween.paused = false; - - const runningChildren = this._runningChildren; - for (let i = runningChildren.length - 1; i >= 0; i--) { - if (runningChildren[i].root === runningTween) { - runningChildren.splice(i, 1); - } +/** @internal */ +export function stop(runningTween: RunningTween): void { + const index = _running.indexOf(runningTween); + if (index < 0) return; + _running.splice(index, 1); + runningTween._finished = true; + runningTween.paused = false; + + const runningChildren = _runningChildren; + for (let i = runningChildren.length - 1; i >= 0; i--) { + if (runningChildren[i].root === runningTween) { + runningChildren.splice(i, 1); } } +} - /** @internal */ - public static complete(runningTween: RunningTween): void { - const index = this._running.indexOf(runningTween); - if (index < 0) return; - runningTween.paused = false; - - const runningChildren = this._runningChildren; - for (let i = runningChildren.length - 1; i >= 0; i--) { - if (runningChildren[i].root === runningTween && !runningChildren[i].execute(Infinity)) { - runningChildren.splice(i, 1); - } - } +/** @internal */ +export function complete(runningTween: RunningTween): void { + const index = _running.indexOf(runningTween); + if (index < 0) return; + runningTween.paused = false; - if (runningTween.tween.infiniteLoop || !runningTween.execute(Infinity)) { - this._running.splice(index, 1); - runningTween._finished = true; + const runningChildren = _runningChildren; + for (let i = runningChildren.length - 1; i >= 0; i--) { + if (runningChildren[i].root === runningTween && !runningChildren[i].execute(Infinity)) { + runningChildren.splice(i, 1); } } - /** - * Stop the running tween with a specific id. - * @param id Tween identifier. - */ - public static stopById(id: string): void { - for (let i = this._running.length - 1; i >= 0; i--) { - if (this._running[i].tween.id === id) { - this._running[i].stop(); - return; - } - } + if (runningTween.tween.infiniteLoop || !runningTween.execute(Infinity)) { + _running.splice(index, 1); + runningTween._finished = true; } +} - /** - * Stop all running tweens. - */ - public static stopAll(): void { - for (let i = this._running.length - 1; i >= 0; i--) { - this._running[i].stop(); +/** + * Stop the running tween with a specific id. + * @param id Tween identifier. + */ +export function stopById(id: string): void { + for (let i = _running.length - 1; i >= 0; i--) { + if (_running[i].tween.id === id) { + _running[i].stop(); + return; } } +} - /** - * Stop all running tweens with a specific tag. - * @param tag - The tag to filter running tweens. - */ - public static stopAllByTag(tag: string): void { - for (let i = this._running.length - 1; i >= 0; i--) { - if (this._running[i].tween.tags.indexOf(tag) > -1) { - this._running[i].stop(); - } - } +/** + * Stop all running tweens. + */ +export function stopAll(): void { + for (let i = _running.length - 1; i >= 0; i--) { + _running[i].stop(); } +} - /** - * Complete all running tweens. - */ - public static completeAll(): void { - for (let i = this._running.length - 1; i >= 0; i--) { - this._running[i].complete(); +/** + * Stop all running tweens with a specific tag. + * @param tag - The tag to filter running tweens. + */ +export function stopAllByTag(tag: string): void { + for (let i = _running.length - 1; i >= 0; i--) { + if (_running[i].tween.tags.indexOf(tag) > -1) { + _running[i].stop(); } } +} - /** - * Complete all running tweens with a specific tag. - * @param tag - The tag to filter running tweens. - */ - public static completeAllByTag(tag: string): void { - for (let i = this._running.length - 1; i >= 0; i--) { - if (this._running[i].tween.tags.indexOf(tag) > -1) { - this._running[i].complete(); - } +/** + * Complete all running tweens. + */ +export function completeAll(): void { + for (let i = _running.length - 1; i >= 0; i--) { + _running[i].complete(); + } +} + +/** + * Complete all running tweens with a specific tag. + * @param tag - The tag to filter running tweens. + */ +export function completeAllByTag(tag: string): void { + for (let i = _running.length - 1; i >= 0; i--) { + if (_running[i].tween.tags.indexOf(tag) > -1) { + _running[i].complete(); } } } diff --git a/src/utils/Asset.ts b/src/utils/Asset.ts index 137c670..a38f4f7 100644 --- a/src/utils/Asset.ts +++ b/src/utils/Asset.ts @@ -6,12 +6,12 @@ import { Loader } from 'three'; export interface LoadingConfig { /** * A callback function for reporting progress during resource loading. - * This function is called with a ratio (0 to 1) to indicate the loading progress. + * function is called with a ratio (0 to 1) to indicate the loading progress. */ onProgress?: (ratio: number) => void; /** * A callback function for handling errors during resource loading. - * This function is called with an `error` object in case of loading errors. + * function is called with an `error` object in case of loading errors. */ onError?: (error: unknown) => void; /** @internal */ progress?: number; @@ -23,7 +23,7 @@ export interface LoadingConfig { */ export interface Resource { /** - * The type of loader to use for this resource. + * The type of loader to use for resource. */ loader: typeof Loader; /** @@ -47,145 +47,140 @@ export interface ResourceConfig { } /** - * The Asset class is a manager for loading and handling resources in a scene. + * Default callback function for reporting progress during resource loading. + * function is called with a ratio (0 to 1) to indicate the loading progress. */ -export class Asset { - /** - * Default callback function for reporting progress during resource loading. - * This function is called with a ratio (0 to 1) to indicate the loading progress. - */ - public static onProgress: (ratio: number) => void; - /** - * Default callback function for handling errors during resource loading. - * This function is called with an `error` object in case of loading errors. - */ - public static onError: (error: unknown) => void; - protected static _loaders = new Map Loader, Loader>(); - protected static _results: { [x: string]: any } = {}; - protected static _pending: Resource[] = []; +const _onProgress: (ratio: number) => void = null; +/** + * Default callback function for handling errors during resource loading. + * function is called with an `error` object in case of loading errors. + */ +const _onError: (error: unknown) => void = null; +const _loaders = new Map Loader, Loader>(); +const _results: { [x: string]: any } = {}; +const _pending: Resource[] = []; - /** - * Get a previously loaded result object for a specific path. - * @param path The path of the resource. - * @returns A previously loaded result object. - */ - public static get(path: string): T; - /** - * Get a list of previously loaded result objects for a series of specific paths. - * @param path An array of resource paths. - * @returns An array of previously loaded result objects. - */ - public static get(...path: string[]): T[]; - public static get(args: string | string[]): T | T[] { - if (typeof args === 'string') return this._results[args]; +/** + * Get a previously loaded result object for a specific path. + * @param path The path of the resource. + * @returns A previously loaded result object. + */ +export function get(path: string): T; +/** + * Get a list of previously loaded result objects for a series of specific paths. + * @param path An array of resource paths. + * @returns An array of previously loaded result objects. + */ +export function get(...path: string[]): T[]; +export function get(args: string | string[]): T | T[] { + if (typeof args === 'string') return _results[args]; - const ret = []; - for (const path of args) { - ret.push(this._results[path]); - } - return ret; + const ret = []; + for (const path of args) { + ret.push(_results[path]); } + return ret; +} - /** - * Get a specific loader based on the resource type. - * @param loaderType The desired loader type. - * @returns The loader associated with the resource type. - */ - public static getLoader(loaderType: new () => T): T { - if (!this._loaders.has(loaderType)) { - this._loaders.set(loaderType, new loaderType()); - } - return this._loaders.get(loaderType) as T; +/** + * Get a specific loader based on the resource type. + * @param loaderType The desired loader type. + * @returns The loader associated with the resource type. + */ +export function getLoader(loaderType: new () => T): T { + if (!_loaders.has(loaderType)) { + _loaders.set(loaderType, new loaderType()); } + return _loaders.get(loaderType) as T; +} - /** - * Load a resource using a specified loader type and path. - * @param loaderType The type of loader to use for loading the resource. - * @param path The path to the resource to be loaded. - * @param onProgress (optional) A callback function to report loading progress with a ProgressEvent. - * @returns A Promise that resolves with the loaded resource when loading is complete. - */ - public static load(loaderType: typeof Loader, path: string, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): Promise { - return new Promise((resolve) => { - if (this._results[path]) return resolve(this._results[path]); - const loader = this.getLoader(loaderType); - loader.load(path, (result) => { - this._results[path] = result; - resolve(result as T); - }, onProgress, (e) => { - if (onError) onError(e); - resolve(undefined); - }); +/** + * Load a resource using a specified loader type and path. + * @param loaderType The type of loader to use for loading the resource. + * @param path The path to the resource to be loaded. + * @param onProgress (optional) A callback function to report loading progress with a ProgressEvent. + * @returns A Promise that resolves with the loaded resource when loading is complete. + */ +export function load(loaderType: typeof Loader, path: string, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): Promise { + return new Promise((resolve) => { + if (_results[path]) return resolve(_results[path]); + const loader = getLoader(loaderType); + loader.load(path, (result) => { + _results[path] = result; + resolve(result as T); + }, onProgress, (e) => { + if (onError) onError(e); + resolve(undefined); }); - } - - /** - * Preload resources for future use. - * @param loader The loader type to be used for preloading. - * @param paths An array of resource paths or configurations to preload. - */ - public static preload(loader: typeof Loader, ...paths: (string | ResourceConfig)[]): void { - this._pending.push({ loader, paths }); - } + }); +} - /** - * Preload all pending resources and return a promise that resolves when all resources are loaded. - * @param config Optional configuration for the loading process. - * @returns A promise that resolves when all pending resources are loaded. - */ - public static preloadAllPending(config: LoadingConfig = {}): Promise { - const promise = this.loadAll(config, ...this._pending); - this._pending = []; - return promise; - } +/** + * Preload resources for future use. + * @param loader The loader type to be used for preloading. + * @param paths An array of resource paths or configurations to preload. + */ +export function preload(loader: typeof Loader, ...paths: (string | ResourceConfig)[]): void { + _pending.push({ loader, paths }); +} - /** - * Load all specified resources and return a promise that resolves when all resources are loaded. - * @param config Configuration for the loading process. - * @param resources An array of resource objects to load. - * @returns A promise that resolves when all resources are loaded. - */ - public static loadAll(config: LoadingConfig = {}, ...resources: Resource[]): Promise { - const promises: Promise[] = []; - config.onProgress ??= this.onProgress; - config.onError ??= this.onError; - config.total = 0; - config.progress = 0; - - for (const resource of resources) { - this.loadByLoader(resource, promises, config); - } +/** + * Preload all pending resources and return a promise that resolves when all resources are loaded. + * @param config Optional configuration for the loading process. + * @returns A promise that resolves when all pending resources are loaded. + */ +export function preloadAllPending(config: LoadingConfig = {}): Promise { + const promise = loadAll(config, ..._pending); + _pending.length = 0; + return promise; +} - return Promise.all(promises); +/** + * Load all specified resources and return a promise that resolves when all resources are loaded. + * @param config Configuration for the loading process. + * @param resources An array of resource objects to load. + * @returns A promise that resolves when all resources are loaded. + */ +export function loadAll(config: LoadingConfig = {}, ...resources: Resource[]): Promise { + const promises: Promise[] = []; + config.onProgress ??= _onProgress; + config.onError ??= _onError; + config.total = 0; + config.progress = 0; + + for (const resource of resources) { + loadByLoader(resource, promises, config); } - protected static loadByLoader(resource: Resource, promises: Promise[], config: LoadingConfig): void { - if (resource?.paths) { - const loader = this.getLoader(resource.loader); + return Promise.all(promises); +} + +function loadByLoader(resource: Resource, promises: Promise[], config: LoadingConfig): void { + if (resource?.paths) { + const loader = getLoader(resource.loader); - for (const path of resource.paths) { - promises.push(this.startLoad(loader, path, config)); - } + for (const path of resource.paths) { + promises.push(startLoad(loader, path, config)); } } +} - protected static startLoad(loader: Loader, value: string | ResourceConfig, config: LoadingConfig): Promise { - return new Promise((resolve) => { - const path = (value as ResourceConfig).path ?? value as string; - if (this._results[path]) return resolve(); - const onLoad = (value as ResourceConfig).onLoad; - config.total++; - - loader.load(path, (result) => { - this._results[path] = result; - if (config.onProgress) config.onProgress(++config.progress / config.total); - if (onLoad) onLoad(result); - resolve(); - }, undefined, (e) => { - if (config.onError) config.onError(e); - if (config.onProgress) config.onProgress(++config.progress / config.total); - resolve(); - }); +function startLoad(loader: Loader, value: string | ResourceConfig, config: LoadingConfig): Promise { + return new Promise((resolve) => { + const path = (value as ResourceConfig).path ?? value as string; + if (_results[path]) return resolve(); + const onLoad = (value as ResourceConfig).onLoad; + config.total++; + + loader.load(path, (result) => { + _results[path] = result; + if (config.onProgress) config.onProgress(++config.progress / config.total); + if (onLoad) onLoad(result); + resolve(); + }, undefined, (e) => { + if (config.onError) config.onError(e); + if (config.onProgress) config.onProgress(++config.progress / config.total); + resolve(); }); - } + }); } From 429f3246d0d45e991ced9e57d7255d5a62404a5f Mon Sep 17 00:00:00 2001 From: Andrea Gargaro Date: Fri, 15 Nov 2024 17:16:13 +0100 Subject: [PATCH 09/11] wip --- src/binding/Binding.ts | 2 +- src/core/Main.ts | 8 ++++---- src/tweening/RunningTween.ts | 12 ++++++------ src/tweening/Tween.ts | 6 +++--- src/tweening/TweenManager.ts | 22 +++++++++++----------- src/utils/Asset.ts | 16 ++++++++-------- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/binding/Binding.ts b/src/binding/Binding.ts index 44da9f8..14f35b8 100644 --- a/src/binding/Binding.ts +++ b/src/binding/Binding.ts @@ -99,7 +99,7 @@ function executeAllCallbacks(target: Object3D): void { } } -export function compute(scene: Scene): void { +export function computeBinding(scene: Scene): void { const boundObjs = scene.__boundObjects; for (const target of boundObjs) { executeAllCallbacks(target); diff --git a/src/core/Main.ts b/src/core/Main.ts index 55732d0..623e41a 100644 --- a/src/core/Main.ts +++ b/src/core/Main.ts @@ -7,8 +7,8 @@ import { RenderView, ViewParameters } from '../rendering/RenderView.js'; import { Stats } from '../utils/Stats.js'; import { RaycasterSortComparer } from '../events/RaycasterManager.js'; import { applyWebGLRendererPatch } from '../patch/WebGLRenderer.js'; -import { update } from '../tweening/TweenManager.js'; -import { compute } from '../binding/Binding.js'; +import { updateTweens } from '../tweening/TweenManager.js'; +import { computeBinding } from '../binding/Binding.js'; /** * Configuration parameters for initializing the Main class. @@ -187,7 +187,7 @@ export class Main { const currentDelta = this._clock.getDelta(); this._interactionManager.update(); - update(currentDelta * 1000); + updateTweens(currentDelta * 1000); this.animate(currentDelta, this._clock.elapsedTime); @@ -200,7 +200,7 @@ export class Main { EventsCache.dispatchEvent(scene, 'beforeanimate', { delta, total }); EventsCache.dispatchEvent(scene, 'animate', { delta, total }); EventsCache.dispatchEvent(scene, 'afteranimate', { delta, total }); - compute(scene); + computeBinding(scene); } this._renderManager.render(); diff --git a/src/tweening/RunningTween.ts b/src/tweening/RunningTween.ts index 4440b16..3ae2992 100644 --- a/src/tweening/RunningTween.ts +++ b/src/tweening/RunningTween.ts @@ -1,7 +1,7 @@ import { ActionTween, MotionConfig } from './Actions.js'; import { EasingFunction } from './Easings.js'; import { Tween } from './Tween.js'; -import { addChildren, complete, createChildren, stop } from './TweenManager.js'; +import { addTweenChildren, completeTween, createTweenChildren, stopTween } from './TweenManager.js'; type UpdateCallback = (start?: T, end?: T, alpha?: number) => void; @@ -98,14 +98,14 @@ export class RunningTween { * Stop the running tween, causing it to finish immediately. */ public stop(): void { - stop(this); + stopTween(this); } /** * Complete the running tween, causing it to finish immediately. */ public complete(): void { - complete(this); + completeTween(this); } /** @@ -306,10 +306,10 @@ export class RunningTween { /** @internal */ private executeTween(block: RunningBlock, delta: number, tween: Tween): void { - const runningTween = createChildren(this.target, tween, this.root ?? this); + const runningTween = createTweenChildren(this.target, tween, this.root ?? this); block.runningTweens.push(runningTween); if (runningTween.execute(delta)) { - addChildren(runningTween); + addTweenChildren(runningTween); } } @@ -320,7 +320,7 @@ export class RunningTween { this.actionIndex = this.reversed ? this.history.length : -1; this.getBlock(); if (this.execute(delta)) { - addChildren(this); + addTweenChildren(this); } } diff --git a/src/tweening/Tween.ts b/src/tweening/Tween.ts index fa44069..5394627 100644 --- a/src/tweening/Tween.ts +++ b/src/tweening/Tween.ts @@ -1,6 +1,6 @@ import { ActionCallback, ActionDelay, ActionMotion, ActionRepeat, ActionTween, ActionYoyo, IAction, Motion, MotionConfig, SetMotion } from './Actions.js'; import { RunningTween } from './RunningTween.js'; -import { create, stopById } from './TweenManager.js'; +import { createTween, stopTweenById } from './TweenManager.js'; /** * A Tween represents a series of actions that can be applied to a target object to create animations or sequences of events. @@ -223,8 +223,8 @@ export class Tween { */ public start(): RunningTween { if (this.id !== undefined) { - stopById(this.id); + stopTweenById(this.id); } - return create(this.target, this); + return createTween(this.target, this); } } diff --git a/src/tweening/TweenManager.ts b/src/tweening/TweenManager.ts index c5b9bd7..67090bb 100644 --- a/src/tweening/TweenManager.ts +++ b/src/tweening/TweenManager.ts @@ -5,7 +5,7 @@ const _running: RunningTween[] = []; const _runningChildren: RunningTween[] = []; /** @internal */ -export function create(target: T, tween: Tween): RunningTween { +export function createTween(target: T, tween: Tween): RunningTween { const runningTween = new RunningTween(target, tween); runningTween.getBlock(); _running.push(runningTween); @@ -13,7 +13,7 @@ export function create(target: T, tween: Tween): RunningTween { } /** @internal */ -export function createChildren(target: T, tween: Tween, root: RunningTween): RunningTween { +export function createTweenChildren(target: T, tween: Tween, root: RunningTween): RunningTween { const runningTween = new RunningTween(target, tween); runningTween.root = root; runningTween.getBlock(); @@ -21,12 +21,12 @@ export function createChildren(target: T, tween: Tween, root: RunningTween } /** @internal */ -export function addChildren(runningTween: RunningTween): void { +export function addTweenChildren(runningTween: RunningTween): void { _runningChildren.push(runningTween); } /** @internal */ -export function update(delta: number): void { +export function updateTweens(delta: number): void { const runningChildren = _runningChildren; for (let i = runningChildren.length - 1; i >= 0; i--) { const rc = runningChildren[i]; @@ -45,7 +45,7 @@ export function update(delta: number): void { } /** @internal */ -export function stop(runningTween: RunningTween): void { +export function stopTween(runningTween: RunningTween): void { const index = _running.indexOf(runningTween); if (index < 0) return; _running.splice(index, 1); @@ -61,7 +61,7 @@ export function stop(runningTween: RunningTween): void { } /** @internal */ -export function complete(runningTween: RunningTween): void { +export function completeTween(runningTween: RunningTween): void { const index = _running.indexOf(runningTween); if (index < 0) return; runningTween.paused = false; @@ -83,7 +83,7 @@ export function complete(runningTween: RunningTween): void { * Stop the running tween with a specific id. * @param id Tween identifier. */ -export function stopById(id: string): void { +export function stopTweenById(id: string): void { for (let i = _running.length - 1; i >= 0; i--) { if (_running[i].tween.id === id) { _running[i].stop(); @@ -95,7 +95,7 @@ export function stopById(id: string): void { /** * Stop all running tweens. */ -export function stopAll(): void { +export function stopAllTweens(): void { for (let i = _running.length - 1; i >= 0; i--) { _running[i].stop(); } @@ -105,7 +105,7 @@ export function stopAll(): void { * Stop all running tweens with a specific tag. * @param tag - The tag to filter running tweens. */ -export function stopAllByTag(tag: string): void { +export function stopAllTweensByTag(tag: string): void { for (let i = _running.length - 1; i >= 0; i--) { if (_running[i].tween.tags.indexOf(tag) > -1) { _running[i].stop(); @@ -116,7 +116,7 @@ export function stopAllByTag(tag: string): void { /** * Complete all running tweens. */ -export function completeAll(): void { +export function completeAllTweens(): void { for (let i = _running.length - 1; i >= 0; i--) { _running[i].complete(); } @@ -126,7 +126,7 @@ export function completeAll(): void { * Complete all running tweens with a specific tag. * @param tag - The tag to filter running tweens. */ -export function completeAllByTag(tag: string): void { +export function completeAllTweensByTag(tag: string): void { for (let i = _running.length - 1; i >= 0; i--) { if (_running[i].tween.tags.indexOf(tag) > -1) { _running[i].complete(); diff --git a/src/utils/Asset.ts b/src/utils/Asset.ts index a38f4f7..d542626 100644 --- a/src/utils/Asset.ts +++ b/src/utils/Asset.ts @@ -65,14 +65,14 @@ const _pending: Resource[] = []; * @param path The path of the resource. * @returns A previously loaded result object. */ -export function get(path: string): T; +export function getAsset(path: string): T; /** * Get a list of previously loaded result objects for a series of specific paths. * @param path An array of resource paths. * @returns An array of previously loaded result objects. */ -export function get(...path: string[]): T[]; -export function get(args: string | string[]): T | T[] { +export function getAsset(...path: string[]): T[]; +export function getAsset(args: string | string[]): T | T[] { if (typeof args === 'string') return _results[args]; const ret = []; @@ -101,7 +101,7 @@ export function getLoader(loaderType: new () => T): T { * @param onProgress (optional) A callback function to report loading progress with a ProgressEvent. * @returns A Promise that resolves with the loaded resource when loading is complete. */ -export function load(loaderType: typeof Loader, path: string, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): Promise { +export function loadAsset(loaderType: typeof Loader, path: string, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): Promise { return new Promise((resolve) => { if (_results[path]) return resolve(_results[path]); const loader = getLoader(loaderType); @@ -120,7 +120,7 @@ export function load(loaderType: typeof Loader, path: string, onProgress * @param loader The loader type to be used for preloading. * @param paths An array of resource paths or configurations to preload. */ -export function preload(loader: typeof Loader, ...paths: (string | ResourceConfig)[]): void { +export function preloadAsset(loader: typeof Loader, ...paths: (string | ResourceConfig)[]): void { _pending.push({ loader, paths }); } @@ -129,8 +129,8 @@ export function preload(loader: typeof Loader, ...paths: (string | Resource * @param config Optional configuration for the loading process. * @returns A promise that resolves when all pending resources are loaded. */ -export function preloadAllPending(config: LoadingConfig = {}): Promise { - const promise = loadAll(config, ..._pending); +export function preloadAllPendingAsset(config: LoadingConfig = {}): Promise { + const promise = loadAllAsset(config, ..._pending); _pending.length = 0; return promise; } @@ -141,7 +141,7 @@ export function preloadAllPending(config: LoadingConfig = {}): Promise { * @param resources An array of resource objects to load. * @returns A promise that resolves when all resources are loaded. */ -export function loadAll(config: LoadingConfig = {}, ...resources: Resource[]): Promise { +export function loadAllAsset(config: LoadingConfig = {}, ...resources: Resource[]): Promise { const promises: Promise[] = []; config.onProgress ??= _onProgress; config.onError ??= _onError; From 3aa122807b7b2cad95940986a95f68222789bec5 Mon Sep 17 00:00:00 2001 From: Andrea Gargaro Date: Fri, 15 Nov 2024 18:16:50 +0100 Subject: [PATCH 10/11] eslint --- eslint.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.js b/eslint.config.js index 5e9c3c9..a21bd09 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -24,6 +24,7 @@ export default [ camelcase: 'warn', 'no-unused-vars': 'off', 'no-undef': 'off', + 'prefer-rest-params': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-empty-object-type': 'off', From a96a0a04b64770888f72555e988c5def5114da42 Mon Sep 17 00:00:00 2001 From: agargaro Date: Tue, 26 Nov 2024 22:38:51 +0100 Subject: [PATCH 11/11] wip --- eslint.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index a21bd09..3e4fcb8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -31,8 +31,8 @@ export default [ '@typescript-eslint/no-wrapper-object-types': 'off', '@typescript-eslint/no-dynamic-delete': 'off', '@typescript-eslint/no-invalid-void-type': 'off', - '@typescript-eslint/no-this-alias': 'off' - // "@typescript-eslint/explicit-function-return-type": "error", + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/explicit-function-return-type': 'error' } } ];