UNPKG

@inweb/viewer-visualize

Version:

JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS

264 lines (214 loc) 8.44 kB
/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a // license agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// import { Viewer } from "../../Viewer"; import { OrbitAction } from "../Actions/OrbitAction"; import { PanAction } from "../Actions/PanAction"; import { ZoomAction } from "../Actions/ZoomAction"; import { Point2d } from "./Geometry"; import { OdBaseDragger } from "./OdBaseDragger"; export enum GestureAction { None, Orbit, Pan, Zoom, } export class GestureManager extends OdBaseDragger { private _previousEvents = new Map<number, PointerEvent>(); private _currentEvents = new Map<number, PointerEvent>(); private _lastGestureAction = GestureAction.None; private _orbitAction: OrbitAction; private _panAction: PanAction; private _zoomAction: ZoomAction; private _initialDistance: number; private readonly _maxInitialDistanceDifference = 30 * window.devicePixelRatio; private _isSingleTouchEnabled = false; public get isSingleTouchEnabled(): boolean { return this._isSingleTouchEnabled; } public set isSingleTouchEnabled(value: boolean) { this._isSingleTouchEnabled = value; } public constructor(subject: Viewer) { super(subject); this._orbitAction = new OrbitAction(this.m_module, this.subject, this.beginInteractivity, this.endInteractivity); this._panAction = new PanAction( this.m_module, this.subject, this.beginInteractivity, this.endInteractivity, this.getViewParams, this.setViewParams ); this._zoomAction = new ZoomAction(this.m_module, this.subject); } private getMiddlePoint(events: Map<number, PointerEvent>): Point2d { if (events.size !== 2) { return undefined; } const keys = this.getKeys(events); const point0 = this.relativeCoords(events.get(keys[0])); const point1 = this.relativeCoords(events.get(keys[1])); return { x: Math.floor((point0.x + point1.x) / 2), y: Math.floor((point0.y + point1.y) / 2), }; } private getFirstPoint(events: Map<number, PointerEvent>): Point2d { if (events.size < 1) { return undefined; } const keys = this.getKeys(events); return this.relativeCoords(events.get(keys[0])); } private getDistance(events: Map<number, PointerEvent>): number { if (events.size !== 2) { return -1; } const keys = this.getKeys(events); const point0 = this.relativeCoords(events.get(keys[0])); const point1 = this.relativeCoords(events.get(keys[1])); return Math.hypot(point0.x - point1.x, point0.y - point1.y); } private updateEvent(event: PointerEvent) { const eventNotInCurrentEvents = !this._currentEvents.get(event.pointerId); if (eventNotInCurrentEvents && this._currentEvents.size === 2) { return; } const previousEvent = this._currentEvents.get(event.pointerId); if (previousEvent) { this._previousEvents.set(previousEvent.pointerId, previousEvent); } this._currentEvents.set(event.pointerId, event); if (eventNotInCurrentEvents) { this._initialDistance = this.getDistance(this._currentEvents); } } private removeEvent(event: PointerEvent) { this._currentEvents.delete(event.pointerId); this._previousEvents.delete(event.pointerId); if (this._currentEvents.size < 2) { this._initialDistance = -1; } } private getKeys(map: Map<number, PointerEvent>): number[] { return Array.from(map.keys()); } private analyzeGesture() { if (this._currentEvents.size === 2) { const currentDistance = this.getDistance(this._currentEvents); const previousDistance = this.getDistance(this._previousEvents); const currentDistanceEqualsInitialDistance = Math.abs(this._initialDistance - currentDistance) <= this._maxInitialDistanceDifference; if (currentDistanceEqualsInitialDistance) { this.executePanAction(this.getMiddlePoint(this._currentEvents)); } else { if (previousDistance !== -1 && currentDistance !== previousDistance) { this.executeZoomAction(currentDistance, previousDistance); } } } else if (this._currentEvents.size === 1 && this.isSingleTouchEnabled) { this.executeOrbitAction(this.getFirstPoint(this._currentEvents)); } } private executeZoomAction(currentDistance: number, previousDistance: number) { if (this._lastGestureAction !== GestureAction.Zoom) { this.executeEndAction(this._lastGestureAction); this._lastGestureAction = GestureAction.Zoom; OdBaseDragger.isGestureActive = true; } const zoomSpeed = (currentDistance - previousDistance) / 700; const zoomFactor = 1 + zoomSpeed; const middlePoint = this.getMiddlePoint(this._currentEvents); this._zoomAction.action(middlePoint.x, middlePoint.y, zoomFactor, middlePoint.x, middlePoint.y); this.subject.update(); } private executePanAction(currentPoint: Point2d) { if (this._lastGestureAction !== GestureAction.Pan) { this.executeEndAction(this._lastGestureAction); this._lastGestureAction = GestureAction.Pan; OdBaseDragger.isGestureActive = true; this._panAction.beginAction(currentPoint.x, currentPoint.y, currentPoint.x, currentPoint.y); } this._panAction.action(currentPoint.x, currentPoint.y, currentPoint.x, currentPoint.y); this.subject.update(); } private executeOrbitAction(currentPoint: Point2d) { if (this._lastGestureAction !== GestureAction.Orbit) { this.executeEndAction(this._lastGestureAction); this._lastGestureAction = GestureAction.Orbit; OdBaseDragger.isGestureActive = true; this._orbitAction.beginAction(currentPoint.x, currentPoint.y); } this._orbitAction.action(currentPoint.x, currentPoint.y); this.subject.update(); } private executeEndAction(gestureAction: GestureAction) { if (gestureAction === GestureAction.Orbit) this._orbitAction.endAction(); if (gestureAction === GestureAction.Pan) this._panAction.endAction(); OdBaseDragger.isGestureActive = false; } private needIgnoreEvent(event: PointerEvent): boolean { return ( !this.subject.options.enableZoomWheel || !this.subject.options.enableGestures || !this.eventIsTouchEvent(event) ); } private eventIsTouchEvent(event: PointerEvent): boolean { return event.pointerType === "touch" || event.pointerType === ""; } override pointerdown(event: PointerEvent) { if (this.needIgnoreEvent(event)) { return; } this.updateEvent(event); } override pointermove(event: PointerEvent) { if (this.needIgnoreEvent(event)) { return; } this.updateEvent(event); this.analyzeGesture(); } override pointerup(event: PointerEvent) { if (this.needIgnoreEvent(event)) { return; } this.removeEvent(event); if (this._currentEvents.size < 2) { this.executeEndAction(this._lastGestureAction); OdBaseDragger.isGestureActive = false; } this._lastGestureAction = GestureAction.None; } override pointercancel(event: PointerEvent) { if (this.needIgnoreEvent(event)) { return; } this.pointerup(event); } pointerleave(event: PointerEvent) { if (this.needIgnoreEvent(event)) { return; } this.pointerup(event); } }