@shopware-ag/dive
Version:
Shopware Spatial Framework
320 lines (267 loc) • 9.53 kB
text/typescript
import {
type Intersection,
type Object3D,
Raycaster,
Vector2,
Vector3,
} from 'three';
import {
PRODUCT_LAYER_MASK,
UI_LAYER_MASK,
} from '../constant/VisibilityLayerMask';
import { type DIVEScene } from '../scene/Scene';
import type DIVEOrbitControls from '../controls/OrbitControls';
import { type DIVEDraggable } from '../interface/Draggable';
import { type DIVEHoverable } from '../interface/Hoverable';
import { findInterface } from '../helper/findInterface/findInterface';
export type DraggableEvent = {
dragStart: Vector3;
dragCurrent: Vector3;
dragEnd: Vector3;
dragDelta: Vector3;
};
/* eslint-disable @typescript-eslint/no-unused-vars */
export abstract class DIVEBaseTool {
readonly POINTER_DRAG_THRESHOLD: number = 0.001;
public name: string;
protected _canvas: HTMLElement;
protected _scene: DIVEScene;
protected _controller: DIVEOrbitControls;
// general pointer members
protected _pointer: Vector2;
protected get _pointerAnyDown(): boolean {
return (
this._pointerPrimaryDown ||
this._pointerMiddleDown ||
this._pointerSecondaryDown
);
}
protected _pointerPrimaryDown: boolean;
protected _pointerMiddleDown: boolean;
protected _pointerSecondaryDown: boolean;
protected _lastPointerDown: Vector2;
protected _lastPointerUp: Vector2;
// raycast members
protected _raycaster: Raycaster;
protected _intersects: Intersection[];
// hovering members
protected _hovered: (Object3D & DIVEHoverable) | null;
// dragging members
protected _dragging: boolean;
protected _dragStart: Vector3;
protected _dragCurrent: Vector3;
protected _dragEnd: Vector3;
protected _dragDelta: Vector3;
protected _draggable: DIVEDraggable | null;
protected _dragRaycastOnObjects: Object3D[] | null;
protected constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
this.name = 'BaseTool';
this._canvas = controller.domElement;
this._scene = scene;
this._controller = controller;
this._pointer = new Vector2();
this._pointerPrimaryDown = false;
this._pointerMiddleDown = false;
this._pointerSecondaryDown = false;
this._lastPointerDown = new Vector2();
this._lastPointerUp = new Vector2();
this._raycaster = new Raycaster();
this._raycaster.layers.mask = PRODUCT_LAYER_MASK | UI_LAYER_MASK;
this._intersects = [];
this._hovered = null;
this._dragging = false;
this._dragStart = new Vector3();
this._dragCurrent = new Vector3();
this._dragEnd = new Vector3();
this._dragDelta = new Vector3();
this._draggable = null;
this._dragRaycastOnObjects = null;
}
public Activate(): void {}
public Deactivate(): void {}
public onPointerDown(e: PointerEvent): void {
switch (e.button) {
case 0: {
this._pointerPrimaryDown = true;
break;
}
case 1: {
this._pointerMiddleDown = true;
break;
}
case 2: {
this._pointerSecondaryDown = true;
break;
}
default: {
console.warn(
'DIVEBaseTool.onPointerDown: Unknown button: ' + e.button,
);
}
}
this._lastPointerDown.copy(this._pointer);
this._draggable =
findInterface<DIVEDraggable>(
this._intersects[0]?.object,
'isDraggable',
) || null;
}
public onDragStart(e: PointerEvent): void {
if (!this._draggable) return;
if (this._dragRaycastOnObjects !== null) {
this._intersects = this._raycaster.intersectObjects(
this._dragRaycastOnObjects,
true,
);
}
if (this._intersects.length === 0) return;
this._dragStart.copy(this._intersects[0].point.clone());
this._dragCurrent.copy(this._intersects[0].point.clone());
this._dragEnd.copy(this._dragStart.clone());
this._dragDelta.set(0, 0, 0);
if (this._draggable && this._draggable.onDragStart) {
this._draggable.onDragStart({
dragStart: this._dragStart,
dragCurrent: this._dragCurrent,
dragEnd: this._dragEnd,
dragDelta: this._dragDelta,
});
this._dragging = true;
this._controller.enabled = false;
}
}
public onPointerMove(e: PointerEvent): void {
// update pointer
this._pointer.x = (e.offsetX / this._canvas.clientWidth) * 2 - 1;
this._pointer.y = -(e.offsetY / this._canvas.clientHeight) * 2 + 1;
// set raycaster
this._raycaster.setFromCamera(this._pointer, this._controller.object);
// refresh intersects
this._intersects = this.raycast(this._scene.children);
// handle hover
const hoverable = findInterface<DIVEHoverable>(
this._intersects[0]?.object,
'isHoverable',
);
if (this._intersects[0] && hoverable) {
if (!this._hovered) {
if (hoverable.onPointerEnter)
hoverable.onPointerEnter(this._intersects[0]);
this._hovered = hoverable;
return;
}
if (this._hovered.uuid !== hoverable.uuid) {
if (this._hovered.onPointerLeave)
this._hovered.onPointerLeave();
if (hoverable.onPointerEnter)
hoverable.onPointerEnter(this._intersects[0]);
this._hovered = hoverable;
return;
}
if (hoverable.onPointerOver)
hoverable.onPointerOver(this._intersects[0]);
this._hovered = hoverable;
} else {
if (this._hovered) {
if (this._hovered.onPointerLeave)
this._hovered.onPointerLeave();
}
this._hovered = null;
}
// handle drag
if (this._pointerAnyDown) {
if (!this._dragging) {
this.onDragStart(e);
}
this.onDrag(e);
}
}
public onDrag(e: PointerEvent): void {
if (this._dragRaycastOnObjects !== null) {
this._intersects = this._raycaster.intersectObjects(
this._dragRaycastOnObjects,
true,
);
}
const intersect = this._intersects[0];
if (!intersect) return;
this._dragCurrent.copy(intersect.point.clone());
this._dragEnd.copy(intersect.point.clone());
this._dragDelta.subVectors(
this._dragCurrent.clone(),
this._dragStart.clone(),
);
if (this._draggable && this._draggable.onDrag) {
this._draggable.onDrag({
dragStart: this._dragStart,
dragCurrent: this._dragCurrent,
dragEnd: this._dragEnd,
dragDelta: this._dragDelta,
});
}
}
public onPointerUp(e: PointerEvent): void {
if (this.pointerWasDragged() || this._dragging) {
if (this._draggable) {
this.onDragEnd(e);
}
} else {
this.onClick(e);
}
switch (e.button) {
case 0:
this._pointerPrimaryDown = false;
break;
case 1:
this._pointerMiddleDown = false;
break;
case 2:
this._pointerSecondaryDown = false;
break;
}
this._lastPointerUp.copy(this._pointer);
}
public onClick(e: PointerEvent): void {}
public onDragEnd(e: PointerEvent): void {
const intersect = this._intersects[0];
if (intersect) {
this._dragEnd.copy(intersect.point.clone());
this._dragCurrent.copy(intersect.point.clone());
this._dragDelta.subVectors(
this._dragCurrent.clone(),
this._dragStart.clone(),
);
}
if (this._draggable && this._draggable.onDragEnd) {
this._draggable.onDragEnd({
dragStart: this._dragStart,
dragCurrent: this._dragCurrent,
dragEnd: this._dragEnd,
dragDelta: this._dragDelta,
});
}
this._draggable = null;
this._dragging = false;
this._dragStart.set(0, 0, 0);
this._dragCurrent.set(0, 0, 0);
this._dragEnd.set(0, 0, 0);
this._dragDelta.set(0, 0, 0);
this._controller.enabled = true;
}
public onWheel(e: WheelEvent): void {}
protected raycast(objects?: Object3D[]): Intersection[] {
if (objects !== undefined)
return this._raycaster
.intersectObjects(objects, true)
.filter((i) => i.object.visible);
return this._raycaster
.intersectObjects(this._scene.children, true)
.filter((i) => i.object.visible);
}
private pointerWasDragged(): boolean {
return (
this._lastPointerDown.distanceTo(this._pointer) >
this.POINTER_DRAG_THRESHOLD
);
}
}