Imported "ThreeJS + WebVR" Boilerplate
This commit is contained in:
305
static/js/pointer.js
Normal file
305
static/js/pointer.js
Normal file
@@ -0,0 +1,305 @@
|
||||
import {Raycaster} from "./Raycaster.js"
|
||||
export const POINTER_ENTER = "enter"
|
||||
export const POINTER_EXIT = "exit"
|
||||
export const POINTER_CLICK = "click"
|
||||
export const POINTER_MOVE = "move"
|
||||
export const POINTER_PRESS = "press"
|
||||
export const POINTER_RELEASE = "release"
|
||||
|
||||
// import * as THREE from "./node_modules/three/build/three.module.js"
|
||||
|
||||
const toRad = (degrees) => degrees*Math.PI/180
|
||||
|
||||
export class Pointer {
|
||||
constructor(scene, renderer, camera, opts) {
|
||||
this.listeners = {}
|
||||
this.opts = opts || {}
|
||||
this.opts.enableLaser = (opts.enableLaser !== undefined) ? opts.enableLaser : true
|
||||
this.opts.laserLength = (opts.laserLength !== undefined) ? opts.laserLength : 3
|
||||
this.opts.enableMoveEvents = (opts.enableMoveEvents !== undefined) ? opts.enableMoveEvents : true
|
||||
this.scene = scene
|
||||
this.renderer = renderer
|
||||
this.canvas = renderer.domElement
|
||||
this.camera = camera
|
||||
|
||||
this.raycaster = new Raycaster()
|
||||
this.waitcb = null
|
||||
this.hoverTarget = null
|
||||
|
||||
this.intersectionFilter = this.opts.intersectionFilter || (() => true)
|
||||
this.raycaster.recurseFilter = this.opts.recurseFilter || (()=> true)
|
||||
|
||||
|
||||
// setup the mouse
|
||||
this.canvas.addEventListener('mousemove', this.mouseMove.bind(this))
|
||||
this.canvas.addEventListener('click', this.mouseClick.bind(this))
|
||||
this.canvas.addEventListener('mousedown',this.mouseDown.bind(this))
|
||||
this.canvas.addEventListener('mouseup',this.mouseUp.bind(this))
|
||||
|
||||
//touch events
|
||||
this.canvas.addEventListener('touchstart',this.touchStart.bind(this))
|
||||
this.canvas.addEventListener('touchmove',this.touchMove.bind(this))
|
||||
this.canvas.addEventListener('touchend',this.touchEnd.bind(this))
|
||||
|
||||
// setup the VR controllers
|
||||
this.controller1 = this.renderer.vr.getController(0);
|
||||
this.controller1.addEventListener('selectstart', this.controllerSelectStart.bind(this));
|
||||
this.controller1.addEventListener('selectend', this.controllerSelectEnd.bind(this));
|
||||
|
||||
|
||||
this.controller2 = this.renderer.vr.getController(1);
|
||||
this.controller2.addEventListener('selectstart', this.controllerSelectStart.bind(this));
|
||||
this.controller2.addEventListener('selectend', this.controllerSelectEnd.bind(this));
|
||||
|
||||
this.setMouseSimulatesController(opts.mouseSimulatesController)
|
||||
|
||||
this.scene.add(this.controller1);
|
||||
this.scene.add(this.controller2);
|
||||
|
||||
|
||||
|
||||
if(this.opts.enableLaser) {
|
||||
//create visible lines for the two controllers
|
||||
const geometry = new THREE.BufferGeometry()
|
||||
geometry.addAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 0, 0, -this.opts.laserLength], 3));
|
||||
geometry.addAttribute('color', new THREE.Float32BufferAttribute([1.0, 0.5, 0.5, 0, 0, 0], 3));
|
||||
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
vertexColors: false,
|
||||
color: 0x880000,
|
||||
linewidth: 5,
|
||||
blending: THREE.NormalBlending
|
||||
})
|
||||
|
||||
this.controller1.add(new THREE.Line(geometry, material));
|
||||
this.controller2.add(new THREE.Line(geometry, material));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//override this to do something w/ the controllers on every tick
|
||||
tick(time) {
|
||||
this.controllerMove(this.controller1)
|
||||
this.controllerMove(this.controller2)
|
||||
}
|
||||
|
||||
|
||||
fire(obj, type, payload) {
|
||||
obj.dispatchEvent(payload)
|
||||
}
|
||||
fireSelf(type,payload) {
|
||||
if(!this.listeners[type]) return
|
||||
this.listeners[type].forEach(cb => cb(payload))
|
||||
}
|
||||
|
||||
//make the camera follow the mouse in desktop mode. Helps w/ debugging.
|
||||
cameraFollowMouse(e) {
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
const ry = ((e.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
const rx = 1 - ((e.clientY - bounds.top) / bounds.height) * 2
|
||||
this.camera.rotation.y = -ry*2
|
||||
this.camera.rotation.x = +rx
|
||||
}
|
||||
|
||||
mouseMove(e) {
|
||||
const mouse = new THREE.Vector2()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1
|
||||
this.raycaster.setFromCamera(mouse, this.camera)
|
||||
if(this.opts.mouseSimulatesController) {
|
||||
//create target from the mouse controls
|
||||
const target = new THREE.Vector3()
|
||||
target.x = mouse.x
|
||||
target.y = mouse.y
|
||||
target.z = -3
|
||||
//convert to camera space
|
||||
target.add(this.camera.position)
|
||||
this.spot.position.copy(target)
|
||||
this.controller1.lookAt(target)
|
||||
//have to flip over because the UP is down on controllers
|
||||
const flip = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0),toRad(180))
|
||||
this.controller1.quaternion.multiply(flip)
|
||||
}
|
||||
this._processMove()
|
||||
|
||||
if(this.opts.cameraFollowMouse) this.cameraFollowMouse(e)
|
||||
}
|
||||
|
||||
touchStart(e) {
|
||||
e.preventDefault()
|
||||
if(e.changedTouches.length <= 0) return
|
||||
const tch = e.changedTouches[0]
|
||||
const mouse = new THREE.Vector2()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
mouse.x = ((tch.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
mouse.y = -((tch.clientY - bounds.top) / bounds.height) * 2 + 1
|
||||
this.raycaster.setFromCamera(mouse, this.camera)
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
intersects.forEach((it) => {
|
||||
this.fire(it.object, POINTER_PRESS, {type: POINTER_PRESS})
|
||||
})
|
||||
}
|
||||
touchMove(e) {
|
||||
e.preventDefault()
|
||||
if(e.changedTouches.length <= 0) return
|
||||
const tch = e.changedTouches[0]
|
||||
const mouse = new THREE.Vector2()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
mouse.x = ((tch.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
mouse.y = -((tch.clientY - bounds.top) / bounds.height) * 2 + 1
|
||||
this.raycaster.setFromCamera(mouse, this.camera)
|
||||
this._processMove()
|
||||
}
|
||||
touchEnd(e) {
|
||||
e.preventDefault()
|
||||
if(e.changedTouches.length <= 0) return
|
||||
const tch = e.changedTouches[0]
|
||||
const mouse = new THREE.Vector2()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
mouse.x = ((tch.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
mouse.y = -((tch.clientY - bounds.top) / bounds.height) * 2 + 1
|
||||
this.raycaster.setFromCamera(mouse, this.camera)
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
intersects.forEach((it) => {
|
||||
this.fire(it.object, POINTER_RELEASE, {type: POINTER_RELEASE, point: it.point})
|
||||
})
|
||||
this._processClick()
|
||||
}
|
||||
|
||||
controllerMove(controller) {
|
||||
if(!controller.visible) return
|
||||
const c = controller
|
||||
const dir = new THREE.Vector3(0, 0, -1)
|
||||
dir.applyQuaternion(c.quaternion)
|
||||
this.raycaster.set(c.position, dir)
|
||||
this._processMove()
|
||||
}
|
||||
|
||||
_processMove() {
|
||||
if(!this.opts.enableMoveEvents)return
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
|
||||
if(intersects.length === 0 && this.hoverTarget) {
|
||||
this.fire(this.hoverTarget, POINTER_EXIT, {type: POINTER_EXIT})
|
||||
this.hoverTarget = null
|
||||
}
|
||||
if(intersects.length >= 1) {
|
||||
const it = intersects[0]
|
||||
const obj = it.object
|
||||
if (!obj) return
|
||||
this.fire(obj, POINTER_MOVE, {type: POINTER_MOVE, point: it.point, intersection:it})
|
||||
if (obj === this.hoverTarget) {
|
||||
//still inside
|
||||
} else {
|
||||
if (this.hoverTarget)
|
||||
this.fire(this.hoverTarget, POINTER_EXIT, {type: POINTER_EXIT})
|
||||
this.hoverTarget = obj
|
||||
this.fire(this.hoverTarget, POINTER_ENTER, {type: POINTER_ENTER})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_processClick() {
|
||||
if (this.waitcb) {
|
||||
this.waitcb()
|
||||
this.waitcb = null
|
||||
return
|
||||
}
|
||||
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
if(intersects.length > 0) {
|
||||
const it = intersects[0]
|
||||
this.fire(it.object, POINTER_CLICK, {type: POINTER_CLICK, point: it.point, intersection:it})
|
||||
}
|
||||
|
||||
this.fireSelf(POINTER_CLICK, {})
|
||||
}
|
||||
mouseClick(e) {
|
||||
const mouse = new THREE.Vector2()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1
|
||||
this.raycaster.setFromCamera(mouse, this.camera)
|
||||
this._processClick()
|
||||
}
|
||||
mouseDown(e) {
|
||||
const mouse = new THREE.Vector2()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1
|
||||
this.raycaster.setFromCamera(mouse, this.camera)
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
intersects.forEach((it) => {
|
||||
this.fire(it.object, POINTER_PRESS, {type: POINTER_PRESS, point: it.point, intersection:it})
|
||||
})
|
||||
}
|
||||
mouseUp(e) {
|
||||
const mouse = new THREE.Vector2()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
mouse.x = ((e.clientX - bounds.left) / bounds.width) * 2 - 1
|
||||
mouse.y = -((e.clientY - bounds.top) / bounds.height) * 2 + 1
|
||||
this.raycaster.setFromCamera(mouse, this.camera)
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
intersects.forEach((it) => {
|
||||
this.fire(it.object, POINTER_RELEASE, {type: POINTER_RELEASE, point: it.point, intersection:it})
|
||||
})
|
||||
}
|
||||
|
||||
controllerSelectStart(e) {
|
||||
e.target.userData.isSelecting = true;
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
intersects.forEach((it) => {
|
||||
this.fire(it.object, POINTER_PRESS, {type: POINTER_PRESS, point: it.point, intersection:it})
|
||||
})
|
||||
}
|
||||
|
||||
controllerSelectEnd(e) {
|
||||
e.target.userData.isSelecting = false;
|
||||
const c = e.target
|
||||
const dir = new THREE.Vector3(0, 0, -1)
|
||||
dir.applyQuaternion(c.quaternion)
|
||||
this.raycaster.set(c.position, dir)
|
||||
const intersects = this.raycaster.intersectObjects(this.scene.children, true)
|
||||
.filter(it => this.intersectionFilter(it.object))
|
||||
intersects.forEach((it) => {
|
||||
this.fire(it.object, POINTER_RELEASE, {type: POINTER_RELEASE, point: it.point})
|
||||
})
|
||||
this._processClick()
|
||||
}
|
||||
|
||||
waitSceneClick(cb) {
|
||||
this.waitcb = cb
|
||||
}
|
||||
|
||||
|
||||
on(type,cb) {
|
||||
if(!this.listeners[type]) this.listeners[type] = []
|
||||
this.listeners[type].push(cb)
|
||||
}
|
||||
off(type,cb) {
|
||||
this.listeners[type] = this.listeners[type].filter(c => c !== cb)
|
||||
}
|
||||
setMouseSimulatesController(val) {
|
||||
this.opts.mouseSimulatesController = val
|
||||
if(this.opts.mouseSimulatesController) {
|
||||
this.controller1 = new THREE.Group()
|
||||
this.controller1.position.set(0,1,-2)
|
||||
this.controller1.quaternion.setFromUnitVectors(THREE.Object3D.DefaultUp, new THREE.Vector3(0,0,1))
|
||||
this.spot = new THREE.Mesh(
|
||||
new THREE.SphereBufferGeometry(0.1),
|
||||
new THREE.MeshLambertMaterial({color: 'red'})
|
||||
)
|
||||
this.scene.add(this.spot)
|
||||
} else {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user