UNPKG

@wonderlandengine/components

Version:

Wonderland Engine's official component library.

257 lines 11 kB
import { vec3, quat2 } from 'gl-matrix'; import { Component, Type } from '@wonderlandengine/api'; /** * Teleport VR locomotion. * * See [Teleport Example](/showcase/teleport). */ class TeleportComponent extends Component { static TypeName = 'teleport'; static Properties = { /** Object that will be placed as indiciation forwhere the player will teleport to. */ teleportIndicatorMeshObject: { type: Type.Object }, /** Root of the player, the object that will be positioned on teleportation. */ camRoot: { type: Type.Object }, /** Non-vr camera for use outside of VR */ cam: { type: Type.Object }, /** Left eye for use in VR*/ eyeLeft: { type: Type.Object }, /** Right eye for use in VR*/ eyeRight: { type: Type.Object }, /** Handedness for VR cursors to accept trigger events only from respective controller. */ handedness: { type: Type.Enum, values: ['input component', 'left', 'right', 'none'], default: 'input component', }, /** Collision group of valid "floor" objects that can be teleported on */ floorGroup: { type: Type.Int, default: 1 }, /** How far the thumbstick needs to be pushed to have the teleport target indicator show up */ thumbstickActivationThreshhold: { type: Type.Float, default: -0.7 }, /** How far the thumbstick needs to be released to execute the teleport */ thumbstickDeactivationThreshhold: { type: Type.Float, default: 0.3 }, /** Offset to apply to the indicator object, e.g. to avoid it from Z-fighting with the floor */ indicatorYOffset: { type: Type.Float, default: 0.01 }, /** Mode for raycasting, whether to use PhysX or simple collision components */ rayCastMode: { type: Type.Enum, values: ['collision', 'physx'], default: 'collision', }, /** Max distance for PhysX raycast */ maxDistance: { type: Type.Float, default: 100.0 }, }; init() { this._prevThumbstickAxis = new Float32Array(2); this._tempVec = new Float32Array(3); this._tempVec0 = new Float32Array(3); this._currentIndicatorRotation = 0; this.input = this.object.getComponent('input'); if (!this.input) { console.error(this.object.name, 'generic-teleport-component.js: input component is required on the object'); return; } if (!this.teleportIndicatorMeshObject) { console.error(this.object.name, 'generic-teleport-component.js: Teleport indicator mesh is missing'); return; } if (!this.camRoot) { console.error(this.object.name, 'generic-teleport-component.js: camRoot not set'); return; } this.isIndicating = false; this.indicatorHidden = true; this.hitSpot = new Float32Array(3); this._hasHit = false; this._extraRotation = 0; this._currentStickAxes = new Float32Array(2); } start() { if (this.handedness == 0) { const inputComp = this.object.getComponent('input'); if (!inputComp) { console.warn('teleport component on object', this.object.name, 'was configured with handedness "input component", ' + 'but object has no input component.'); } else { this.handedness = inputComp.handedness; this.input = inputComp; } } else { this.handedness = ['left', 'right'][this.handedness - 1]; } this.onSessionStartCallback = this.setupVREvents.bind(this); this.teleportIndicatorMeshObject.active = false; } onActivate() { if (this.cam) { this.isMouseIndicating = false; canvas.addEventListener('mousedown', this.onMouseDown); canvas.addEventListener('mouseup', this.onMouseUp); } this.engine.onXRSessionStart.add(this.onSessionStartCallback); } onDeactivate() { canvas.addEventListener('mousedown', this.onMouseDown); canvas.addEventListener('mouseup', this.onMouseUp); this.engine.onXRSessionStart.remove(this.onSessionStartCallback); } /* Get current camera Y rotation */ _getCamRotation() { this.eyeLeft.getForward(this._tempVec); this._tempVec[1] = 0; vec3.normalize(this._tempVec, this._tempVec); return Math.atan2(this._tempVec[0], this._tempVec[2]); } update() { let inputLength = 0; if (this.gamepad && this.gamepad.axes) { this._currentStickAxes[0] = this.gamepad.axes[2]; this._currentStickAxes[1] = this.gamepad.axes[3]; inputLength = Math.abs(this._currentStickAxes[0]) + Math.abs(this._currentStickAxes[1]); } if (!this.isIndicating && this._prevThumbstickAxis[1] >= this.thumbstickActivationThreshhold && this._currentStickAxes[1] < this.thumbstickActivationThreshhold) { this.isIndicating = true; } else if (this.isIndicating && inputLength < this.thumbstickDeactivationThreshhold) { this.isIndicating = false; this.teleportIndicatorMeshObject.active = false; if (this._hasHit) { this._teleportPlayer(this.hitSpot, this._extraRotation); } } if (this.isIndicating && this.teleportIndicatorMeshObject && this.input) { const origin = this._tempVec0; this.object.getPositionWorld(origin); const direction = this.object.getForwardWorld(this._tempVec); let rayHit = (this.rayHit = this.rayCastMode == 0 ? this.engine.scene.rayCast(origin, direction, 1 << this.floorGroup) : this.engine.physics.rayCast(origin, direction, 1 << this.floorGroup, this.maxDistance)); if (rayHit.hitCount > 0) { this.indicatorHidden = false; this._extraRotation = Math.PI + Math.atan2(this._currentStickAxes[0], this._currentStickAxes[1]); this._currentIndicatorRotation = this._getCamRotation() + (this._extraRotation - Math.PI); this.teleportIndicatorMeshObject.resetPositionRotation(); this.teleportIndicatorMeshObject.rotateAxisAngleRad([0, 1, 0], this._currentIndicatorRotation); this.teleportIndicatorMeshObject.translate(rayHit.locations[0]); this.teleportIndicatorMeshObject.translate([ 0.0, this.indicatorYOffset, 0.0, ]); this.teleportIndicatorMeshObject.active = true; this.hitSpot.set(rayHit.locations[0]); this._hasHit = true; } else { if (!this.indicatorHidden) { this.teleportIndicatorMeshObject.active = false; this.indicatorHidden = true; } this._hasHit = false; } } else if (this.teleportIndicatorMeshObject && this.isMouseIndicating) { this.onMousePressed(); } this._prevThumbstickAxis.set(this._currentStickAxes); } setupVREvents(s) { /* If in VR, one-time bind the listener */ this.session = s; s.addEventListener('end', function () { /* Reset cache once the session ends to rebind select etc, in case * it starts again */ this.gamepad = null; this.session = null; }.bind(this)); if (s.inputSources && s.inputSources.length) { for (let i = 0; i < s.inputSources.length; i++) { let inputSource = s.inputSources[i]; if (inputSource.handedness == this.handedness) { this.gamepad = inputSource.gamepad; } } } s.addEventListener('inputsourceschange', function (e) { if (e.added && e.added.length) { for (let i = 0; i < e.added.length; i++) { let inputSource = e.added[i]; if (inputSource.handedness == this.handedness) { this.gamepad = inputSource.gamepad; } } } }.bind(this)); } onMouseDown = () => { this.isMouseIndicating = true; }; onMouseUp = () => { this.isMouseIndicating = false; this.teleportIndicatorMeshObject.active = false; if (this._hasHit) { this._teleportPlayer(this.hitSpot, 0.0); } }; onMousePressed() { let origin = [0, 0, 0]; this.cam.getPositionWorld(origin); const direction = this.cam.getForward(this._tempVec); let rayHit = (this.rayHit = this.rayCastMode == 0 ? this.engine.scene.rayCast(origin, direction, 1 << this.floorGroup) : this.engine.physics.rayCast(origin, direction, 1 << this.floorGroup, this.maxDistance)); if (rayHit.hitCount > 0) { this.indicatorHidden = false; direction[1] = 0; vec3.normalize(direction, direction); this._currentIndicatorRotation = -Math.sign(direction[2]) * Math.acos(direction[0]) - Math.PI * 0.5; this.teleportIndicatorMeshObject.resetPositionRotation(); this.teleportIndicatorMeshObject.rotateAxisAngleRad([0, 1, 0], this._currentIndicatorRotation); this.teleportIndicatorMeshObject.translate(rayHit.locations[0]); this.teleportIndicatorMeshObject.active = true; this.hitSpot = rayHit.locations[0]; this._hasHit = true; } else { if (!this.indicatorHidden) { this.teleportIndicatorMeshObject.active = false; this.indicatorHidden = true; } this._hasHit = false; } } _teleportPlayer(newPosition, rotationToAdd) { this.camRoot.rotateAxisAngleRad([0, 1, 0], rotationToAdd); const p = this._tempVec; const p1 = this._tempVec0; if (this.session) { this.eyeLeft.getPositionWorld(p); this.eyeRight.getPositionWorld(p1); vec3.add(p, p, p1); vec3.scale(p, p, 0.5); } else { this.cam.getPositionWorld(p); } this.camRoot.getPositionWorld(p1); vec3.sub(p, p1, p); p[0] += newPosition[0]; p[1] = newPosition[1]; p[2] += newPosition[2]; this.camRoot.setPositionWorld(p); } } export { TeleportComponent }; //# sourceMappingURL=teleport.js.map