UNPKG

onsight

Version:

Interactive, easy to use JavaScript game framework.

227 lines (208 loc) 9.94 kB
import { MOUSE_CLICK_TIME, MOUSE_SLOP, } from '../../constants.js'; import { ArrayUtils } from '../../utils/ArrayUtils.js'; import { Keyboard } from '../../core/input/Keyboard.js'; import { Pointer } from '../../core/input/Pointer.js'; import { ResizeHelper } from '../helpers/ResizeHelper.js'; import { RubberBandBox } from '../helpers/RubberBandBox.js'; import { Vector2 } from '../../math/Vector2.js'; const _cameraPoint = new Vector2(); const _center = new Vector2(); const _size = new Vector2(); class SelectControls { constructor() { this.dom = document.createElement('div'); // event dispatcher element this.selection = []; this.resizeHelper = null; this.rubberBandBox = null; this.downTimer = 0; // FLAG this.transformed = false; // INTERNAL this._renderer = null; this._existingSelection = []; this._mouseStart = new Vector2(); this._mouseNow = new Vector2(); this._wantsRubberBand = false; } update(renderer) { const camera = renderer.camera; const scene = renderer.scene; const pointer = renderer.pointer; const keyboard = renderer.keyboard; if (!camera || !scene || !pointer || !keyboard) return; if (renderer) this._renderer = renderer; let newSelection = [ ...this.selection ]; // Transformed Flag if (this.transformed) { this.dom.dispatchEvent(new Event('selection-transformed')); this.transformed = false; } // Pointer in World (camera) Coordinates _cameraPoint.copy(renderer.screenToWorld(pointer.position)); // Button Press if (pointer.buttonJustPressed(Pointer.LEFT)) { this._mouseStart.copy(_cameraPoint); const underMouse = ArrayUtils.filterThings(renderer.getWorldPointIntersections(_cameraPoint), { pointerEvents: true }); // Holding Ctrl/Meta/Shift (Toggle Selection) if (keyboard.ctrlPressed() || keyboard.metaPressed() || keyboard.shiftPressed()) { let resizerClicked = false; if (underMouse.length > 0) { const topObject = underMouse[0]; resizerClicked = resizerClicked || (topObject.type === 'Resizer'); resizerClicked = resizerClicked || (topObject.type === 'Rotater'); } if (!resizerClicked) { const selectableOnly = ArrayUtils.filterThings(underMouse, { selectable: true }); if (selectableOnly.length > 0) { const object = selectableOnly[0]; if (object.isSelected) { newSelection = ArrayUtils.removeThingFromArray(object, newSelection); } else { newSelection = ArrayUtils.combineThingArrays(newSelection, [ object ]); } } else { this._existingSelection = [ ...this.selection ]; this._wantsRubberBand = true; } } // Single Click / Click Timer } else { this.downTimer = performance.now(); // Clear Previous Selection if (underMouse.length === 0) { newSelection = []; this._existingSelection = []; this._wantsRubberBand = true; // New Selected Object } else if (underMouse.length > 0 && underMouse[0].isHelper !== true) { const selectableOnly = ArrayUtils.filterThings(underMouse, { selectable: true }); const object = selectableOnly[0]; if (object && object.selectable && ArrayUtils.compareThingArrays(object, this.selection) === false) { newSelection = [ object ]; this.downTimer = 0; } } } } // Mouse Distance this._mouseNow.copy(_cameraPoint); // Rubber Band Box if (pointer.buttonPressed(Pointer.LEFT)) { const mouseTravel = this._mouseStart.manhattanDistanceTo(this._mouseNow); if (this._wantsRubberBand) { // Create Rubber Band Box? if (this.rubberBandBox == null) { if (mouseTravel >= MOUSE_SLOP) { const rubberBandBox = new RubberBandBox(); scene.traverse((child) => { rubberBandBox.layer = Math.max(rubberBandBox.layer, child.layer + 1); }); renderer.addHelper(rubberBandBox); this.rubberBandBox = rubberBandBox; renderer.setDragObject(this.rubberBandBox); } } // Update Rubber Band Box if (this.rubberBandBox) { // Center const viewportStart = renderer.worldToScreen(this._mouseStart); const viewportEnd = renderer.worldToScreen(this._mouseNow); _center.addVectors(viewportStart, viewportEnd).divideScalar(2); _center.copy(renderer.screenToWorld(_center)); this.rubberBandBox.position.copy(_center); // Size _size.subVectors(viewportStart, viewportEnd).abs().divideScalar(2); this.rubberBandBox.box.min.set(-_size.x, -_size.y); this.rubberBandBox.box.max.set(+_size.x, +_size.y); this.rubberBandBox.computeBoundingBox(); // Transform this.rubberBandBox.rotation = -camera.rotation; this.rubberBandBox.scale.set(1 / camera.scale, 1 / camera.scale); this.rubberBandBox.updateMatrix(true); // Selection let intersectedObjects; if (this._existingSelection.length > 0) { intersectedObjects = this.rubberBandBox.intersected(this._existingSelection[0].parent, false /* includeChildren */); } else { intersectedObjects = this.rubberBandBox.intersected(scene, true /* includeChildren */); } newSelection = ArrayUtils.combineThingArrays(this._existingSelection, intersectedObjects); } } } // Pointer Released if (pointer.buttonJustReleased(Pointer.LEFT)) { const mouseTravel = this._mouseStart.manhattanDistanceTo(this._mouseNow); const shortClick = performance.now() - this.downTimer < MOUSE_CLICK_TIME; // Stop Rubber Band this._wantsRubberBand = false; if (this.rubberBandBox) { if (renderer.dragObject === this.rubberBandBox) renderer.setDragObject(null); this.rubberBandBox.destroy(); this.rubberBandBox = null; // Select Object } else if (shortClick && mouseTravel <= MOUSE_SLOP) { const underMouse = renderer.getWorldPointIntersections(_cameraPoint); const withoutResizeHelper = ArrayUtils.filterThings(underMouse, { isHelper: undefined }); // Clear Previous Selection if (withoutResizeHelper.length === 0) { newSelection = []; // New Selected Object } else if (withoutResizeHelper.length > 0) { const object = withoutResizeHelper[0]; if (object.selectable && ArrayUtils.compareThingArrays(object, this.selection) === false) { newSelection = [ object ]; } } } } // Selection Changed? Remove objects that don't share parents... if (ArrayUtils.compareThingArrays(this.selection, newSelection) === false) { const siblings = []; if (newSelection.length > 0) { const wantsParent = newSelection[0].parent; for (const object of newSelection) { if (object.parent === wantsParent) siblings.push(object); } } newSelection = [ ...siblings ]; } // Selection Still Changed? Update Resize Tool if (ArrayUtils.compareThingArrays(this.selection, newSelection) === false) { this.setSelection(newSelection); if (newSelection.length > 0) { if (this.rubberBandBox == null && this.resizeHelper) { renderer.setDragObject(this.resizeHelper); } } this.dom.dispatchEvent(new Event('selection-changed')); } } setSelection(...things) { if (!this._renderer || !this._renderer.scene) return; if (things.length > 0 && Array.isArray(things[0])) things = things[0]; // Locals const renderer = this._renderer; const scene = renderer.scene; // Mark New Selection scene.traverse((child) => { child.isSelected = false; }); things.forEach((object) => { object.isSelected = true; }); // Clear Old Tool if (this.resizeHelper) { this.resizeHelper.objects = []; this.resizeHelper.destroy(); this.resizeHelper = null; } // Create New Tool? if (things.length > 0) { const resizeHelper = new ResizeHelper(things, this); renderer.addHelper(resizeHelper); resizeHelper.onUpdate(renderer); this.resizeHelper = resizeHelper; } // Save Selection this.selection = [ ...things ]; } } export { SelectControls };