UNPKG

awv3

Version:
278 lines (243 loc) 11.6 kB
import * as THREE from 'three'; import Raycaster from '../three/raycaster'; import Object3 from '../three/object3'; export default class Interaction { constructor(view = Error.log('View undefined'), options = {}) { this.view = view; this.canvas = view.canvas; this.raycaster = new Raycaster(this); this.enabled = typeof options.enabled !== 'undefined' ? options.enabled : true; this.targets = []; this.intersects = []; this.hits = {}; this.hitsArray = []; this.actions = {}; this.actionsArray = []; this.hovered = {}; this.filter = undefined; this.coordinates = new THREE.Vector3(0, 0, 0); this.click = new THREE.Vector3(0, 0, 0); this.delta = 0; this.__hovered = []; this.__frontside = {}; } update() { if (!this.enabled) return; for (let target of this.targets) { if ( target.interaction && target.interaction.enabled && (this.view.invalidateFrames > 0 || this.view.renderAlways || this.view.controls.inMotion) /*|| this.view.hudInMotion TODO: there are several hids now */ ) { target.emit(Object3.Events.Lifecycle.Rendered); } } } onMouseMove(event) { if (!this.enabled) return; this.coordinates.set(event.offsetX / this.view.width * 2 - 1, -(event.offsetY / this.view.height) * 2 + 1, 1); this.delta = Math.max( this.delta, Math.abs(this.click.x - this.coordinates.x) + Math.abs(this.click.y - this.coordinates.y), ); // View is moved or dry run (nothing under the curser when the view was clicked/touched) if (this.view.controls.interaction || (!event.fake && this.view.input.mouse.down && this.actionsArray.length == 0)) return; this.raycaster.setFromCamera(this.coordinates, this.view.camera.display); this.intersects = []; this.__hovered = []; this.__frontside = {}; if (this.targets.length > 0) { this.raycaster.castObjects(this.targets, this.intersects, this.filter); if (this.intersects.length > 0) { // Objects that have been pierced for (let data of this.intersects) { // Set unique key data.id = data.receiver.id; if (data.receiver.interaction.recursive) data.id += 'R' + data.object.id; if (data.face && data.object.userData && data.object.userData.refs) data.id += 'F' + data.face.materialIndex; // Add region specific data if (data.type === 'Region') { data.id += 'V' + data.ref.id; data.meta = data.ref.meta; data.vertexId = data.ref.id; } // Flag is set on first occurance of a receiver data.first = data === this.intersects[0]; // Current RAF timestamp data.time = this.canvas.renderer.time; // Mouse coordinates in world CS data.ray = this.raycaster.ray; // Fetch meta, if any if (data.face && data.object.userData && data.object.userData.refs) { data.meta = data.object.userData.refs[data.face.materialIndex]; } else if (data.object.userData && data.object.userData.meta) { data.meta = data.object.userData.meta; } if (data.object.material) { let isMultiMaterial = Array.isArray(data.object.material); if (data.face) { if (isMultiMaterial) { data.materialIndex = data.face.materialIndex; data.multiMaterial = data.object.material; data.material = data.object.material[data.face.materialIndex]; } else { data.material = data.object.material; } } else if (typeof data.index !== 'undefined') { if (isMultiMaterial && data.object.geometry.groups) { for (let group of data.object.geometry.groups) { if (group.start <= data.index && group.start + group.count > data.index) { data.materialIndex = group.materialIndex; data.multiMaterial = data.object.material; data.material = data.object.material[group.materialIndex]; data.meta = group.meta; data.id += 'L' + group.materialIndex; break; } } } else { data.material = data.object.material; } } } // Raycaster can return two intersections, front & back, we cast away the backside if (this.__frontside[data.id]) continue; // Mark it for hover if it hasn't been hit already if ((!data.receiver.interaction.first || data.first) && !this.hovered[data.id]) this.__hovered.push(data); this.hits[data.id] = data; this.__frontside[data.id] = true; } } else { this.actionsArray.length == 0 && this.view.setCursor(); } // Take out everything that's not under the cursor right now and that's not in the actions this.hitsArray = Object.keys(this.hits) .map(item => this.hits[item]) // Sort after scene-graph .sort((a, b) => a.receiver.depthIndex - b.receiver.depthIndex) // Filter .filter(data => { let dispose = (data.receiver.interaction.first && !data.first) || data.time != this.canvas.renderer.time; if (dispose) { if (this.hovered[data.id] && !this.actions[data.id]) { delete this.hovered[data.id]; data.receiver.emit(Object3.Events.Interaction.Unhovered, data); this.view.invalidate(); } delete this.hits[data.id]; } return !dispose; }); // Hover items this.__hovered.forEach(data => { this.hovered[data.id] = true; data.receiver.emit(Object3.Events.Interaction.Hovered, data); this.view.invalidate(); }); } // Object being dragged if (this.delta > 0.01 && Object.keys(this.actions).length > 0) { for (let data of this.actionsArray) { if (!data.receiver.interaction.first || data.first) { let vector = this.coordinates .clone() .unproject(this.view.camera.display) .sub(this.view.camera.display.position) .normalize(); data.drag = this.view.camera.display.position .clone() .add(vector.multiplyScalar(data.distance)) .add(data.offset); data.ray = this.raycaster.ray; data.receiver.emit(Object3.Events.Interaction.Dragged, data); this.view.invalidate(); } } } } onMouseOut(event) { if (!this.enabled) return; for (let data of this.hitsArray) { if (this.hovered[data.id] && !this.actions[data.id]) { delete this.hovered[data.id]; data.receiver.emit(Object3.Events.Interaction.Unhovered, data); this.view.invalidate(); } delete this.hits[data.id]; } } onMouseDown(event) { if (!this.enabled) return; this.delta = 0; this.click.set(event.offsetX / this.view.width * 2 - 1, -(event.offsetY / this.view.height) * 2 + 1, 1); // Cause a fake-mousemove to refresh hits this.onMouseMove({ ...event, fake: true }); this.actions = {}; this.actionsArray = []; for (let data of this.hitsArray) { if (!data.receiver.interaction.first || data.first) { let pos = data.receiver.parent ? data.receiver.parent.localToWorld(data.receiver.position.clone()) : data.receiver.position.clone(); data.offset = pos.sub(data.point.clone()); this.actions[data.id] = data; this.actionsArray.push(data); data.receiver.emit(Object3.Events.Interaction.Picked, data); } } this.view.invalidate(); } onMouseUp(event) { if (!this.enabled) return; // Signal Mouseup for (let data of this.actionsArray) { if (this.hovered[data.id] && !this.hits[data.id]) { delete this.hovered[data.id]; data.receiver.emit(Object3.Events.Interaction.Unhovered, data); } data.receiver.emit(Object3.Events.Interaction.Dropped, data); if (this.delta < 0.05) { data.receiver.emit(Object3.Events.Interaction.Clicked, data); } } if (this.actionsArray.length == 0 && this.delta < 0.05) { for (let target of this.targets) { target.emit(Object3.Events.Interaction.Missed); } } this.view.invalidate(); this.actions = {}; this.actionsArray = []; } removeTarget(obj) { let index = this.targets.indexOf(obj); if (index > -1) this.targets.splice(index, 1); this.hitsArray.forEach(item => item.receiver === obj && this.hitsArray.splice(this.hitsArray.indexOf(item), 1)); this.actionsArray.forEach( item => item.receiver === obj && this.actionsArray.splice(this.actionsArray.indexOf(item), 1), ); Object.keys(this.hits).forEach(item => this.hits[item].receiver === obj && delete this.hits[item]); Object.keys(this.actions).forEach(item => this.actions[item].receiver === obj && delete this.actions[item]); Object.keys(this.hovered).forEach(item => this.hovered[item].receiver === obj && delete this.hovered[item]); } getActions() { return this.actionsArray; } isHit(obj) { return ( this.hitsArray.find(data => { if (obj instanceof THREE.Material) return data.material === obj; else return data.object.id === obj.id; }) !== undefined ); } isAction(obj) { return this.actionsArray.find(item => item.object.id == obj.id); } }