Comprehensive event handling for camera interactions, transitions, and state changes with the built-in EventDispatcher system.
CameraControls extends EventDispatcher, providing event management functionality.
/**
* Event listener function type
*/
type Listener = (event?: DispatcherEvent) => void;
/**
* Base event interface
*/
interface DispatcherEvent {
type: string;
[key: string]: any;
}
/**
* Add event listener for specific event type
* @param type - Event name to listen for
* @param listener - Function to call when event fires
*/
addEventListener(type: string, listener: Listener): void;
/**
* Check if specific listener exists for event type
* @param type - Event name to check
* @param listener - Listener function to check for
* @returns True if listener exists
*/
hasEventListener(type: string, listener: Listener): boolean;
/**
* Remove specific event listener
* @param type - Event name
* @param listener - Listener function to remove
*/
removeEventListener(type: string, listener: Listener): void;
/**
* Remove all listeners for event type (or all events if no type specified)
* @param type - Optional event name (removes all if omitted)
*/
removeAllEventListeners(type?: string): void;
/**
* Fire an event to all registered listeners
* @param event - Event object to dispatch
*/
dispatchEvent(event: DispatcherEvent): void;CameraControls fires specific events during camera operations and user interactions.
interface CameraControlsEventMap {
update: { type: 'update' };
wake: { type: 'wake' };
rest: { type: 'rest' };
sleep: { type: 'sleep' };
transitionstart: { type: 'transitionstart' };
controlstart: { type: 'controlstart' };
control: { type: 'control' };
controlend: { type: 'controlend' };
}Event Descriptions:
// Listen for camera updates
cameraControls.addEventListener('update', () => {
console.log('Camera updated - position changed');
renderer.render(scene, camera);
});
// Listen for user interactions
cameraControls.addEventListener('controlstart', () => {
console.log('User started interacting with camera');
});
cameraControls.addEventListener('controlend', () => {
console.log('User stopped interacting with camera');
});
// Listen for camera rest state
cameraControls.addEventListener('rest', () => {
console.log('Camera has stopped moving');
});Performance Optimization with Update Events:
let renderRequested = false;
cameraControls.addEventListener('update', () => {
if (!renderRequested) {
renderRequested = true;
requestAnimationFrame(() => {
renderer.render(scene, camera);
renderRequested = false;
});
}
});
// Alternative: Only render on camera changes
function animate() {
const delta = clock.getDelta();
const hasControlsUpdated = cameraControls.update(delta);
if (hasControlsUpdated) {
renderer.render(scene, camera);
}
requestAnimationFrame(animate);
}UI State Management:
// Show/hide UI elements based on camera state
const loadingIndicator = document.getElementById('loading');
const cameraInfo = document.getElementById('camera-info');
cameraControls.addEventListener('transitionstart', () => {
loadingIndicator.style.display = 'block';
});
cameraControls.addEventListener('rest', () => {
loadingIndicator.style.display = 'none';
});
cameraControls.addEventListener('controlstart', () => {
cameraInfo.style.opacity = '0.5'; // Fade info during interaction
});
cameraControls.addEventListener('controlend', () => {
cameraInfo.style.opacity = '1.0'; // Restore info after interaction
});Sleep/Wake Cycle Management:
// Manage power consumption and updates
cameraControls.addEventListener('sleep', () => {
console.log('Camera controls sleeping - reducing update frequency');
// Reduce animation loop frequency or pause updates
});
cameraControls.addEventListener('wake', () => {
console.log('Camera controls waking - resuming full updates');
// Resume full update frequency
});Interaction Tracking:
let interactionCount = 0;
let isInteracting = false;
cameraControls.addEventListener('controlstart', () => {
isInteracting = true;
interactionCount++;
console.log(`User interaction #${interactionCount} started`);
});
cameraControls.addEventListener('control', () => {
if (isInteracting) {
// Update interaction UI, show coordinates, etc.
updateCameraDebugInfo();
}
});
cameraControls.addEventListener('controlend', () => {
isInteracting = false;
console.log('User interaction ended');
});
function updateCameraDebugInfo() {
const position = new THREE.Vector3();
const target = new THREE.Vector3();
cameraControls.getPosition(position);
cameraControls.getTarget(target);
document.getElementById('debug-position').textContent =
`Position: ${position.x.toFixed(2)}, ${position.y.toFixed(2)}, ${position.z.toFixed(2)}`;
document.getElementById('debug-target').textContent =
`Target: ${target.x.toFixed(2)}, ${target.y.toFixed(2)}, ${target.z.toFixed(2)}`;
}Coordinated Animations:
// Chain animations using events
async function performCameraTour() {
const positions = [
{ x: 10, y: 5, z: 10 },
{ x: -10, y: 8, z: 5 },
{ x: 0, y: 15, z: 0 },
{ x: 5, y: 2, z: -10 }
];
for (const pos of positions) {
// Start movement
const movePromise = cameraControls.moveTo(pos.x, pos.y, pos.z, true);
// Wait for transition to start
await new Promise(resolve => {
cameraControls.addEventListener('transitionstart', resolve, { once: true });
});
console.log(`Moving to position: ${pos.x}, ${pos.y}, ${pos.z}`);
// Wait for movement to complete
await movePromise;
// Wait for camera to rest
await new Promise(resolve => {
cameraControls.addEventListener('rest', resolve, { once: true });
});
console.log('Position reached, pausing...');
await new Promise(resolve => setTimeout(resolve, 1000));
}
}Event-Based State Machine:
enum CameraState {
IDLE = 'idle',
USER_CONTROLLING = 'user_controlling',
TRANSITIONING = 'transitioning',
SLEEPING = 'sleeping'
}
class CameraStateMachine {
private currentState = CameraState.IDLE;
constructor(controls: CameraControls) {
controls.addEventListener('controlstart', () => this.setState(CameraState.USER_CONTROLLING));
controls.addEventListener('controlend', () => this.setState(CameraState.IDLE));
controls.addEventListener('transitionstart', () => this.setState(CameraState.TRANSITIONING));
controls.addEventListener('rest', () => this.setState(CameraState.IDLE));
controls.addEventListener('sleep', () => this.setState(CameraState.SLEEPING));
controls.addEventListener('wake', () => this.setState(CameraState.IDLE));
}
private setState(newState: CameraState) {
const oldState = this.currentState;
this.currentState = newState;
console.log(`Camera state: ${oldState} -> ${newState}`);
// Trigger state-specific behaviors
this.onStateChange(oldState, newState);
}
private onStateChange(from: CameraState, to: CameraState) {
switch (to) {
case CameraState.USER_CONTROLLING:
document.body.classList.add('camera-interacting');
break;
case CameraState.TRANSITIONING:
document.body.classList.add('camera-transitioning');
break;
case CameraState.IDLE:
document.body.classList.remove('camera-interacting', 'camera-transitioning');
break;
case CameraState.SLEEPING:
document.body.classList.add('camera-sleeping');
break;
}
}
getCurrentState(): CameraState {
return this.currentState;
}
}
const stateMachine = new CameraStateMachine(cameraControls);Creating Custom Events:
// Extend controls with custom events
class ExtendedCameraControls extends CameraControls {
private targetObject: THREE.Object3D | null = null;
focusOnObject(object: THREE.Object3D) {
this.targetObject = object;
// Fire custom event
this.dispatchEvent({
type: 'focus-start',
target: object
});
// Perform focus operation
this.fitToBox(object, true).then(() => {
this.dispatchEvent({
type: 'focus-complete',
target: object
});
});
}
}
// Use custom events
const extendedControls = new ExtendedCameraControls(camera, renderer.domElement);
extendedControls.addEventListener('focus-start', (event) => {
console.log('Starting focus on object:', event.target);
});
extendedControls.addEventListener('focus-complete', (event) => {
console.log('Focus complete on object:', event.target);
});Proper Event Listener Management:
class CameraControlsManager {
private controls: CameraControls;
private listeners: Map<string, Listener[]> = new Map();
constructor(camera: THREE.Camera, domElement: HTMLElement) {
this.controls = new CameraControls(camera, domElement);
}
addEventListener(type: string, listener: Listener) {
this.controls.addEventListener(type, listener);
// Track listeners for cleanup
if (!this.listeners.has(type)) {
this.listeners.set(type, []);
}
this.listeners.get(type)!.push(listener);
}
dispose() {
// Remove all tracked listeners
for (const [type, listeners] of this.listeners) {
for (const listener of listeners) {
this.controls.removeEventListener(type, listener);
}
}
this.listeners.clear();
// Dispose controls
this.controls.dispose();
}
}
// Usage
const controlsManager = new CameraControlsManager(camera, renderer.domElement);
controlsManager.addEventListener('rest', () => {
console.log('Camera at rest');
});
// Clean up when done
controlsManager.dispose();