janusweb
Version:
Web client for JanusVR worlds
399 lines (384 loc) • 13.2 kB
JavaScript
/*
* for room-specific teleport sound use:
*
* <room teleport_sound_id="mysound" ...>
*
*/
janus.registerElement('locomotion_teleporter', {
active: false,
longpresstime: 500,
deadzone: 5,
xrplayer: null, // *TODO* use janus.engine.client.xrplayer instead (and allow desktop too)
linesegments: 100,
create() {
this.marker = this.createObject('Object', {
});
this.ring = this.marker.createObject('object', {
id: 'pipe',
col: V(0,0,155),
emissive: V(0,0,155),
scale: V(.5,.01,.5),
collidable: false,
pickable: false,
shadow: false,
shadow_cast: false,
shadow_receive: false,
renderorder: 501,
depth_test: false,
depth_write: false,
});
this.cylinder = this.marker.createObject('object', {
id: 'cylinder',
col: V(0,0,155,.4),
scale: V(.5,1.5,.5),
collidable: false,
pickable: false,
shadow: false,
opacity: .1,
shadow_cast: false,
shadow_receive: false,
renderorder: 500,
});
this.pointer = this.marker.createObject('Object', {
id: 'pyramid',
col: 'red',
rotation: V(90,0,0),
scale: V(.1,.5,.0125),
pos: V(0,.01,0),
collidable: false,
pickable: false,
shadow: false,
shadow_cast: false,
shadow_receive: false,
renderorder: 502,
});
/*
this.light = this.createObject('Light', {
col: '#009',
pos: V(0,1,0),
light_range: 20,
light_intensity: 8
});
*/
this.particles = room.createObject('Particle', {
col: V(0,.2,1,.4),
pos: V(-.25,.1,-.25),
scale: V(.05),
//vel: V(-.5,0,-.5),
particle_vel: V(-.4,2,-.4),
rand_vel: V(.8,2,.8),
rand_col: V(.5,.5,1),
rand_pos: V(.5,0,.5),
accel: V(0,-1,0),
rand_accel: V(0,2,0),
rate: 10,
count: 60,
duration: 3,
collision_id: '',
collidable: false,
pickable: false,
loop: true
});
this.shroud = this.createObject('object', {
id: 'sphere',
scale: V(2),
lighting: false,
col: 'black',
cull_face: 'none',
depth_test: false,
depth_write: false,
shadow_cast: false,
shadow_receive: false,
renderorder: 1000,
visible: false,
});
let linepositions = [];
for (let i = 0; i < this.linesegments; i++) {
linepositions.push(V(0));
linepositions.push(V(0));
}
this.laser = room.createObject('linesegments', {
positions: linepositions,
opacity: .8,
col: 'blue',
visible: false,
});
player.head.add(this.shroud._target);
this.particles.particle_vel = V(-.4, 0, -.4); // FIXME - particle velocity isn't being set on spawn
let locomotion = janus.ui.apps.default.apps.locomotion;
let asseturl = locomotion.resolveFullURL('./locomotion-assets.json');
fetch(asseturl).then(res => res.json()).then(assetlist => {
this.assetpack = elation.engine.assets.loadJSON(assetlist, locomotion.resolveFullURL('./'));
this.sound = room.createObject('Sound', { id: room.teleport_sound_id || 'button-teleport' }, this);
})
this.disableCursor();
window.addEventListener('mousemove', this.handleMouseMove);
window.addEventListener('touchmove', this.handleTouchMove, true);
this.activecontrols = this.addControlContext('teleporter', {
'teleport_x': {
defaultbindings: 'gamepad_any_axis_0,gamepad_any_axis_2',
onchange: ev => this.handleTeleportChange(ev),
onactivate: ev => this.handleTeleportTurn(ev),
},
'teleport_y_right': {
defaultbindings: 'gamepad_any_axis_1,gamepad_any_axis_3',
onchange: ev => this.handleTeleportChange(ev, 'right'),
onactivate: ev => this.handleTeleportStart(ev, 'right'),
},
/*
'teleport_y_left': {
defaultbindings: 'gamepad_6_axis_1,gamepad_6_axis_3',
onchange: ev => this.handleTeleportChange(ev, 'left'),
onactivate: ev => this.handleTeleportStart(ev, 'left'),
},
*/
'teleport_trigger': {
defaultbindings: 'gamepad_any_button_0',
onactivate: ev => this.handleTeleportTrigger(ev),
},
});
this.activateControlContext('teleporter');
// emulated mouse-events (via webxr handtracking e.g.)
room.addEventListener('mousemove', (ev) => this.handleMouseMove(ev));
room.addEventListener('mousedown', (ev) => this.handleMouseDown(ev));
room.addEventListener('mouseup', (ev) => this.handleMouseUp(ev));
elation.events.add(janus._target, 'room_change', (ev) => this.handleRoomChange(ev));
},
update() {
if (this.active) {
let hand = this.xrplayer ? this.xrplayer.trackedobjects['hand_' + this.teleporthand] : null
let startpoint,
segments = this.linesegments,
duration = .25,
speed = 4,
g = V(0, -1, 0),
p1 = V(),
v1 = V(),
v0;
if (hand) {
startpoint = hand.pointer.localToWorld(V(0, 0, 0));
v0 = hand.pointer.localToWorld(V(0, 0, -speed)).sub(startpoint);
hand.teleportPos = hand.teleportPos || v0
} else {
startpoint = player.head.localToWorld(V(0,0,0));
v0 = player.head.localToWorld(V(0,0,-speed)).sub(startpoint);
return; // Head-locked teleporter is awkward, do we really still support this?
}
let i = 0,
p0 = startpoint.clone(),
dir = v0.clone().normalize();
let laserpoints = this.laser.positions;
// Trace our arc in steps, performing a raytrace at each step
for (i = 0; i < segments; i++) {
let t = (i + 1) * duration / segments;
dir.copy(v0);
speed = dir.length();
dir.divideScalar(speed);
let hits = room.raycast(dir, p0, null, speed);
let hit = false;
for (let j = 0; j < hits.length; j++) {
if (hits[j].object !== player) {
hit = hits[j];
break;
}
}
if (hit) {
p1.copy(hit.point);
if (hit.distance < 200) {
this.pos = player.worldToLocal(hit.point);
this.localToWorld(this.particles.emitter_pos.set(0,0,0));
this.updateTeleportAngle();
// track movement to cancel longpresstimer
if( hand ) hand.teleportPos = hand.teleportPos || this.pos.clone()
}
laserpoints[i * 2].copy(p0);
laserpoints[i * 2 + 1].copy(p1);
// Zero out any further line segments
for (let k = i+1; k < segments; k++) {
laserpoints[k * 2].set(0,0,0);
laserpoints[k * 2 + 1].set(0,0,0);
}
break;
} else {
p1.copy(g).multiplyScalar(.5 * t * t).add(v0.clone().multiplyScalar(t)).add(p0);;
v1.copy(g).multiplyScalar(t).add(v0);
laserpoints[i * 2].copy(p0);
laserpoints[i * 2 + 1].copy(p1);
p0.copy(p1);
v0.copy(v1);
}
}
this.laser.updateLine();
if (this.laser.room !== room._target) room.appendChild(this.laser);
this.pointer.orientation._target.setFromEuler(new THREE.Euler(Math.PI/2, this.teleportangle, 0, "YXZ"));
} else {
if (this.shroud.visible) {
////this.worldToLocal(player.head.localToWorld(this.shroud.pos.set(0,0,0)));
if (this.shroud.opacity > .001) {
this.shroud.opacity *= .9;
if (this.shroud.opacity <= .001) {
this.shroud.visible = false;
this.shroud.opacity = 0;
}
}
}
}
},
handleRoomChange(ev) {
this.setRoom(room);
room.addEventListener('mousedown', (ev) => this.handleMouseDown(ev));
room.addEventListener('mouseup', (ev) => this.handleMouseUp(ev));
},
handleTeleportChange(ev) {
//if (!room.teleport) return;
if (this.teleportactive) {
let controls = this.activecontrols;
let xy = new THREE.Vector2(controls.teleport_x, controls['teleport_y_' + this.teleporthand]),
len = xy.length();
if (len > .8) {
this.updateTeleportAngle();
} else if (len < .01) {
this.teleport();
this.teleportactive = false;
this.teleporthand = false;
this.disableCursor();
}
}
},
handleTeleportStart(ev, hand) {
if (!room.teleport) return;
if (!this.teleportactive && Math.abs(ev.value) > .8) {
this.teleportactive = true;
this.teleporthand = hand || 'right';
this.enableCursor();
this.updateTeleportAngle();
}
},
handleTeleportTurn(ev) {
if (!this.teleportactive) {
this.showShroud();
let turn = new THREE.Quaternion();
turn.setFromEuler(new THREE.Euler(0, Math.PI / 4 * (ev.value > 0 ? -1 : 1), 0));
player.orientation.multiply(turn);
}
},
handleTeleportTrigger(ev) {
if (!room.teleport) return;
if (this.active) {
}
},
teleport(mouse) {
//if (!room.teleport) return;
let pos = player.localToWorld(this.pos.clone());
pos.y += player.fatness; // FIXME - This accounts for wonky player sphere collider, when we switch to a capsule we won't need it
player.pos = pos;
player.vel = V(0,0,.01); // "wake up" physics engine
console.log('Teleport player', pos);
this.showShroud();
if( this.sound ){
this.sound.pos = pos;
this.sound.play();
}
if( this.xrplayer && this.teleportangle && !mouse){
player.angular.set(0,0,0);
let playerangle = Math.atan2(this.pos.x, this.pos.z);
this.xrplayer.orientation._target.setFromEuler(new THREE.Euler(0, this.teleportangle - playerangle, 0));
}
},
updateTeleportAngle() {
let controls = this.activecontrols;
let controllerangle = Math.atan2(controls.teleport_x, controls['teleport_y_' + this.teleporthand]);
let playerangle = Math.atan2(this.pos.x, this.pos.z);
this.teleportangle = (controllerangle + playerangle) + Math.PI;
},
handleMouseDown(ev) {
if (!room.teleport) return;
if (ev.button == 0 && (player.enabled || janus.hmd)) {
if( ev.inputSourceObject && this.xrplayer ){ // translate webxr emulated mousedown event
this.teleporthand = ev.inputSourceObject.device.handedness
let hand = this.xrplayer.trackedobjects['hand_' + this.teleporthand];
if( hand ) hand.teleportPos = false
}
if (!this.longpresstimer) {
this.longpresstimer = setTimeout(() => { console.log('timer fired'); this.enableCursor(); }, this.longpresstime);
this.mousediff = [0,0];
}else this.cancelLongPress()
}
},
handleMouseMove(ev) {
if (!room.teleport) return;
//if (this.longpresstimer) {
// this.mousediff[0] += ev.movementX;
// this.mousediff[1] += ev.movementY
// var distance = Math.sqrt(this.mousediff[0] * this.mousediff[0] + this.mousediff[1] * this.mousediff[1]);
// if (distance > this.deadzone) this.cancelLongPress()
//}
},
handleTouchMove(ev) {
if (!room.teleport || !this.active) return;
if (this.longpresstimer) {
var touch = ev.changedTouches[0];
if (this.lasttouch) {
this.mousediff[0] += touch.clientX - this.lasttouch[0];
this.mousediff[1] += touch.clientY - this.lasttouch[1];
} else {
this.mousediff[0] = 0;
this.mousediff[1] = 0;
}
this.lasttouch = [touch.clientX, touch.clientY];
var distance = Math.sqrt(this.mousediff[0] * this.mousediff[0] + this.mousediff[1] * this.mousediff[1]);
if (distance > this.deadzone) this.cancelLongPress()
if (this.active) {
ev.stopPropagation();
ev.preventDefault();
}
}
},
handleMouseUp(ev) {
if (!room.teleport) return;
if (this.teleportactive) {
this.teleport(true);
this.active = false
this.teleportactive = false;
this.teleporthand = false;
this.disableCursor();
}else if( this.longpresstimer ) this.disableCursor();
},
enableCursor() {
if (!room.teleport) return;
if( this.xrplayer ){
let hand = this.xrplayer.trackedobjects['hand_' + this.teleporthand];
if( hand ) hand.pointer.laser.visible = false // prevent (portal)clicks
}
this.active = true;
this.teleportactive = true;
this.visible = true;
this.particles.resetParticles();
this.particles.visible = true;
if (this.laser.room !== room._target) room.appendChild(this.laser);
if (this.particles.room !== room._target) room.appendChild(this.particles);
this.laser.visible = true;
this.particles.start();
},
disableCursor() {
if( this.xrplayer ){
let hand = this.xrplayer.trackedobjects['hand_' + this.teleporthand];
if( hand ) hand.pointer.laser.visible = true // restore original laser
}
this.teleportactive = false;
this.visible = false;
this.particles.visible = false;
this.laser.visible = false;
this.active = false;
this.particles.stop();
this.cancelLongPress()
},
showShroud() {
this.shroud.visible = true;
this.shroud.opacity = 1;
},
cancelLongPress(){
clearTimeout(this.longpresstimer);
this.longpresstimer = false
}
});