From c04cfcbf8c6b3b34e1261065d5fad12d113ffdbe Mon Sep 17 00:00:00 2001 From: rodri Date: Fri, 29 Nov 2024 17:23:26 +0000 Subject: [PATCH 1/5] fix EventDispatcher types --- src/controls/EventDispatcher.ts | 138 ++++++++++++++++++++++++++++++++ src/controls/OrbitControls.ts | 23 +++++- 2 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 src/controls/EventDispatcher.ts diff --git a/src/controls/EventDispatcher.ts b/src/controls/EventDispatcher.ts new file mode 100644 index 0000000..a9bf0fb --- /dev/null +++ b/src/controls/EventDispatcher.ts @@ -0,0 +1,138 @@ +/* +Due to @types/three r168 breaking change +we have to manually copy the EventDispatcher class from three.js. +So this files merges the declarations from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/EventDispatcher.d.ts +with the implementation from https://github.com/mrdoob/three.js/blob/dev/src/core/EventDispatcher.js +*/ + +/** + * The minimal basic Event that can be dispatched by a {@link EventDispatcher<>}. + */ +export interface BaseEvent { + readonly type: TEventType; + // not defined in @types/three + target: any; +} + +/** + * The minimal expected contract of a fired Event that was dispatched by a {@link EventDispatcher<>}. + */ +export interface Event { + readonly type: TEventType; + readonly target: TTarget; +} + +export type EventListener = ( + event: TEventData & Event, +) => void; + +export class EventDispatcher { + // not defined in @types/three + private _listeners: any; + + /** + * Adds a listener to an event type. + * @param type The type of event to listen to. + * @param listener The function that gets called when the event is fired. + */ + addEventListener>( + type: T, + listener: EventListener, + ): void { + + if ( this._listeners === undefined ) this._listeners = {}; + + const listeners = this._listeners; + + if ( listeners[ type ] === undefined ) { + + listeners[ type ] = []; + + } + + if ( listeners[ type ].indexOf( listener ) === - 1 ) { + + listeners[ type ].push( listener ); + + } + + } + + /** + * Checks if listener is added to an event type. + * @param type The type of event to listen to. + * @param listener The function that gets called when the event is fired. + */ + hasEventListener>( + type: T, + listener: EventListener, + ): boolean { + + if ( this._listeners === undefined ) return false; + + const listeners = this._listeners; + + return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; + + } + + /** + * Removes a listener from an event type. + * @param type The type of the listener that gets removed. + * @param listener The listener function that gets removed. + */ + removeEventListener>( + type: T, + listener: EventListener, + ): void { + + if ( this._listeners === undefined ) return; + + const listeners = this._listeners; + const listenerArray = listeners[ type ]; + + if ( listenerArray !== undefined ) { + + const index = listenerArray.indexOf( listener ); + + if ( index !== - 1 ) { + + listenerArray.splice( index, 1 ); + + } + + } + + } + + /** + * Fire an event type. + * @param event The event that gets fired. + */ + dispatchEvent>(event: BaseEvent & TEventMap[T]): void { + + if ( this._listeners === undefined ) return; + + const listeners = this._listeners; + const listenerArray = listeners[ event.type ]; + + if ( listenerArray !== undefined ) { + + event.target = this; + + // Make a copy, in case listeners are removed while iterating. + const array = listenerArray.slice( 0 ); + + for ( let i = 0, l = array.length; i < l; i ++ ) { + + array[ i ].call( this, event ); + + } + + event.target = null; + + } + + } + +} \ No newline at end of file diff --git a/src/controls/OrbitControls.ts b/src/controls/OrbitControls.ts index 979d08d..2cb7e79 100644 --- a/src/controls/OrbitControls.ts +++ b/src/controls/OrbitControls.ts @@ -1,5 +1,4 @@ import { - EventDispatcher, Matrix4, MOUSE, OrthographicCamera, @@ -12,6 +11,7 @@ import { Ray, Plane, } from 'three' +import { EventDispatcher } from './EventDispatcher' const _ray = new Ray() const _plane = new Plane() @@ -26,7 +26,24 @@ const TILT_LIMIT = Math.cos(70 * (Math.PI / 180)) const moduloWrapAround = (offset: number, capacity: number) => ((offset % capacity) + capacity) % capacity -class OrbitControls extends EventDispatcher { +interface OrbitControlsEventMap { + /** + * Fires when the camera has been transformed by the controls. + */ + change: {}; + + /** + * Fires when an interaction was initiated. + */ + start: {}; + + /** + * Fires when an interaction has finished. + */ + end: {}; +} + +class OrbitControls extends EventDispatcher { object: PerspectiveCamera | OrthographicCamera domElement: HTMLElement | undefined // Set to false to disable this control @@ -1141,4 +1158,4 @@ class MapControls extends OrbitControls { } } -export { OrbitControls, MapControls } +export { OrbitControls, MapControls, OrbitControlsEventMap } From 00b525e1ea74fd10069534d32293e553a3a0a9b9 Mon Sep 17 00:00:00 2001 From: rodri Date: Fri, 29 Nov 2024 17:33:19 +0000 Subject: [PATCH 2/5] fix: Use new EventDispatcher --- src/controls/ArcballControls.ts | 5 ++-- src/controls/DeviceOrientationControls.ts | 6 ++-- src/controls/DragControls.ts | 32 +++++++++++++++++++-- src/controls/FirstPersonControls.ts | 6 ++-- src/controls/FlyControls.ts | 12 ++++++-- src/controls/OrbitControls.ts | 22 ++------------ src/controls/PointerLockControls.ts | 22 ++++++++++++-- src/controls/StandardControlsEventMap.ts | 16 +++++++++++ src/controls/TrackballControls.ts | 6 ++-- src/controls/experimental/CameraControls.ts | 4 +-- 10 files changed, 96 insertions(+), 35 deletions(-) create mode 100644 src/controls/StandardControlsEventMap.ts diff --git a/src/controls/ArcballControls.ts b/src/controls/ArcballControls.ts index 8a65882..9a64a49 100644 --- a/src/controls/ArcballControls.ts +++ b/src/controls/ArcballControls.ts @@ -18,8 +18,9 @@ import { OrthographicCamera, Mesh, Material, - EventDispatcher, } from 'three' +import { EventDispatcher } from './EventDispatcher' +import { StandardControlsEventMap } from './StandardControlsEventMap' type Camera = OrthographicCamera | PerspectiveCamera type Operation = 'PAN' | 'ROTATE' | 'ZOOM' | 'FOV' @@ -82,7 +83,7 @@ const _endEvent = { type: 'end' } * @param {HTMLElement=null} domElement Renderer's dom element * @param {Scene=null} scene The scene to be rendered */ -class ArcballControls extends EventDispatcher { +class ArcballControls extends EventDispatcher { private camera: OrthographicCamera | PerspectiveCamera | null private domElement: HTMLElement | null | undefined private scene: Scene | null | undefined diff --git a/src/controls/DeviceOrientationControls.ts b/src/controls/DeviceOrientationControls.ts index ab6a821..b3cfe68 100644 --- a/src/controls/DeviceOrientationControls.ts +++ b/src/controls/DeviceOrientationControls.ts @@ -1,10 +1,12 @@ -import { Camera, Euler, EventDispatcher, MathUtils, Quaternion, Vector3 } from 'three' +import { Camera, Euler, MathUtils, Quaternion, Vector3 } from 'three' +import { EventDispatcher } from './EventDispatcher' +import { StandardControlsEventMap } from './StandardControlsEventMap' /** * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html) */ -class DeviceOrientationControls extends EventDispatcher { +class DeviceOrientationControls extends EventDispatcher { public object: Camera private changeEvent = { type: 'change' } diff --git a/src/controls/DragControls.ts b/src/controls/DragControls.ts index 86877de..6c6fb52 100644 --- a/src/controls/DragControls.ts +++ b/src/controls/DragControls.ts @@ -1,6 +1,34 @@ -import { Camera, EventDispatcher, Intersection, Matrix4, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three' +import { Camera, Intersection, Matrix4, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three' +import { EventDispatcher } from './EventDispatcher' + +export interface DragControlsEventMap { + /** + * Fires when the pointer is moved onto a 3D object, or onto one of its children. + */ + hoveron: { object: Object3D }; + + /** + * Fires when the pointer is moved out of a 3D object. + */ + hoveroff: { object: Object3D }; + + /** + * Fires when the user starts to drag a 3D object. + */ + dragstart: { object: Object3D }; + + /** + * Fires when the user drags a 3D object. + */ + drag: { object: Object3D }; + + /** + * Fires when the user has finished dragging a 3D object. + */ + dragend: { object: Object3D }; +} -class DragControls extends EventDispatcher { +class DragControls extends EventDispatcher { public enabled = true public transformGroup = false diff --git a/src/controls/FirstPersonControls.ts b/src/controls/FirstPersonControls.ts index e6bbd25..b69983f 100644 --- a/src/controls/FirstPersonControls.ts +++ b/src/controls/FirstPersonControls.ts @@ -1,8 +1,10 @@ -import { MathUtils, Spherical, Vector3, EventDispatcher, Camera } from 'three' +import { MathUtils, Spherical, Vector3, Camera } from 'three' +import { EventDispatcher } from './EventDispatcher' +import { StandardControlsEventMap } from './StandardControlsEventMap' const targetPosition = new Vector3() -export class FirstPersonControls extends EventDispatcher { +export class FirstPersonControls extends EventDispatcher<{}> { public object: Camera public domElement?: HTMLElement | null diff --git a/src/controls/FlyControls.ts b/src/controls/FlyControls.ts index bbd8da7..4d473bd 100644 --- a/src/controls/FlyControls.ts +++ b/src/controls/FlyControls.ts @@ -1,10 +1,18 @@ -import { Camera, EventDispatcher, Quaternion, Vector3 } from 'three' +import { Camera, Quaternion, Vector3 } from 'three' +import { EventDispatcher } from './EventDispatcher' function contextmenu(event: Event): void { event.preventDefault() } -class FlyControls extends EventDispatcher { +export interface FlyControlsEventMap { + /** + * Fires when the camera has been transformed by the controls. + */ + change: {}; +} + +class FlyControls extends EventDispatcher { public object: Camera public domElement: HTMLElement | Document = null! diff --git a/src/controls/OrbitControls.ts b/src/controls/OrbitControls.ts index 2cb7e79..93d8a76 100644 --- a/src/controls/OrbitControls.ts +++ b/src/controls/OrbitControls.ts @@ -12,6 +12,7 @@ import { Plane, } from 'three' import { EventDispatcher } from './EventDispatcher' +import { StandardControlsEventMap } from './StandardControlsEventMap' const _ray = new Ray() const _plane = new Plane() @@ -26,24 +27,7 @@ const TILT_LIMIT = Math.cos(70 * (Math.PI / 180)) const moduloWrapAround = (offset: number, capacity: number) => ((offset % capacity) + capacity) % capacity -interface OrbitControlsEventMap { - /** - * Fires when the camera has been transformed by the controls. - */ - change: {}; - - /** - * Fires when an interaction was initiated. - */ - start: {}; - - /** - * Fires when an interaction has finished. - */ - end: {}; -} - -class OrbitControls extends EventDispatcher { +class OrbitControls extends EventDispatcher { object: PerspectiveCamera | OrthographicCamera domElement: HTMLElement | undefined // Set to false to disable this control @@ -1158,4 +1142,4 @@ class MapControls extends OrbitControls { } } -export { OrbitControls, MapControls, OrbitControlsEventMap } +export { OrbitControls, MapControls } diff --git a/src/controls/PointerLockControls.ts b/src/controls/PointerLockControls.ts index 1aa7f2b..ee93891 100644 --- a/src/controls/PointerLockControls.ts +++ b/src/controls/PointerLockControls.ts @@ -1,4 +1,5 @@ -import { Euler, Camera, EventDispatcher, Vector3 } from 'three' +import { Euler, Camera, Vector3 } from 'three' +import { EventDispatcher } from './EventDispatcher' const _euler = new Euler(0, 0, 0, 'YXZ') const _vector = new Vector3() @@ -7,7 +8,24 @@ const _lockEvent = { type: 'lock' } const _unlockEvent = { type: 'unlock' } const _PI_2 = Math.PI / 2 -class PointerLockControls extends EventDispatcher { +export interface PointerLockControlsEventMap { + /** + * Fires when the user moves the mouse. + */ + change: {}; + + /** + * Fires when the pointer lock status is "locked" (in other words: the mouse is captured). + */ + lock: {}; + + /** + * Fires when the pointer lock status is "unlocked" (in other words: the mouse is not captured anymore). + */ + unlock: {}; +} + +class PointerLockControls extends EventDispatcher { public camera: Camera public domElement?: HTMLElement public isLocked: boolean diff --git a/src/controls/StandardControlsEventMap.ts b/src/controls/StandardControlsEventMap.ts new file mode 100644 index 0000000..cd3bd3c --- /dev/null +++ b/src/controls/StandardControlsEventMap.ts @@ -0,0 +1,16 @@ +export interface StandardControlsEventMap { + /** + * Fires when the camera has been transformed by the controls. + */ + change: {}; + + /** + * Fires when an interaction was initiated. + */ + start: {}; + + /** + * Fires when an interaction has finished. + */ + end: {}; + } \ No newline at end of file diff --git a/src/controls/TrackballControls.ts b/src/controls/TrackballControls.ts index b523b5a..5991f4f 100644 --- a/src/controls/TrackballControls.ts +++ b/src/controls/TrackballControls.ts @@ -1,6 +1,8 @@ -import { EventDispatcher, MOUSE, Quaternion, Vector2, Vector3, PerspectiveCamera, OrthographicCamera } from 'three' +import { MOUSE, Quaternion, Vector2, Vector3, PerspectiveCamera, OrthographicCamera } from 'three' +import { EventDispatcher } from './EventDispatcher' +import { StandardControlsEventMap } from './StandardControlsEventMap' -class TrackballControls extends EventDispatcher { +class TrackballControls extends EventDispatcher { public enabled = true public screen = { left: 0, top: 0, width: 0, height: 0 } diff --git a/src/controls/experimental/CameraControls.ts b/src/controls/experimental/CameraControls.ts index c894ec6..dd6e25c 100644 --- a/src/controls/experimental/CameraControls.ts +++ b/src/controls/experimental/CameraControls.ts @@ -1,5 +1,4 @@ import { - EventDispatcher, MOUSE, Matrix4, OrthographicCamera, @@ -10,6 +9,7 @@ import { Vector2, Vector3, } from 'three' +import { EventDispatcher } from '../EventDispatcher' export type CHANGE_EVENT = { type: 'change' | 'start' | 'end' @@ -26,7 +26,7 @@ export const STATE = { TOUCH_DOLLY_ROTATE: 6, } -class CameraControls extends EventDispatcher { +class CameraControls extends EventDispatcher> { object: PerspectiveCamera | OrthographicCamera domElement: HTMLElement From 43576604a6f1284a44dbd0384a85f903e62499ef Mon Sep 17 00:00:00 2001 From: rodri Date: Fri, 29 Nov 2024 17:36:51 +0000 Subject: [PATCH 3/5] chore: reference issue --- src/controls/EventDispatcher.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controls/EventDispatcher.ts b/src/controls/EventDispatcher.ts index a9bf0fb..370c33b 100644 --- a/src/controls/EventDispatcher.ts +++ b/src/controls/EventDispatcher.ts @@ -3,6 +3,7 @@ Due to @types/three r168 breaking change we have to manually copy the EventDispatcher class from three.js. So this files merges the declarations from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/EventDispatcher.d.ts with the implementation from https://github.com/mrdoob/three.js/blob/dev/src/core/EventDispatcher.js +More info in https://github.com/pmndrs/three-stdlib/issues/387 */ /** From 940ec056ebea5d7d5f1261ee3db4670773cdf98c Mon Sep 17 00:00:00 2001 From: rodri Date: Thu, 12 Dec 2024 14:14:55 +0000 Subject: [PATCH 4/5] loose EventDispatcher types --- src/controls/EventDispatcher.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/controls/EventDispatcher.ts b/src/controls/EventDispatcher.ts index 370c33b..0bdb2ff 100644 --- a/src/controls/EventDispatcher.ts +++ b/src/controls/EventDispatcher.ts @@ -36,10 +36,16 @@ export class EventDispatcher { * @param type The type of event to listen to. * @param listener The function that gets called when the event is fired. */ + addEventListener(type: string, listener: EventListener<{}, string, this>): void addEventListener>( type: T, listener: EventListener, - ): void { + ): void + addEventListener>( + type: T | string, + listener: EventListener | EventListener<{}, string, this>, + ): void + { if ( this._listeners === undefined ) this._listeners = {}; @@ -64,9 +70,14 @@ export class EventDispatcher { * @param type The type of event to listen to. * @param listener The function that gets called when the event is fired. */ + hasEventListener(type: string, listener: EventListener<{}, string, this>): boolean; hasEventListener>( type: T, listener: EventListener, + ): boolean; + hasEventListener>( + type: T | string, + listener: EventListener | EventListener<{}, string, this>, ): boolean { if ( this._listeners === undefined ) return false; @@ -82,9 +93,14 @@ export class EventDispatcher { * @param type The type of the listener that gets removed. * @param listener The listener function that gets removed. */ + removeEventListener(type: string, listener: EventListener<{}, string, this>): void; removeEventListener>( type: T, listener: EventListener, + ): void + removeEventListener>( + type: T|string, + listener: EventListener | EventListener<{}, string, this>, ): void { if ( this._listeners === undefined ) return; From 5bbe14dfdf0ebc7f8cb48ca971b21ef8f1fa8720 Mon Sep 17 00:00:00 2001 From: rodri Date: Wed, 18 Dec 2024 14:17:49 +0000 Subject: [PATCH 5/5] export EventDispatcher --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 5f81f53..4206f09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,6 +65,7 @@ export * from './math/ColorConverter' export * from './math/ImprovedNoise' export * from './math/Octree' export * from './math/Lut' +export * from './controls/EventDispatcher' export * from './controls/experimental/CameraControls' export * from './controls/FirstPersonControls' export * from './controls/TransformControls'