janusweb
Version:
Web client for JanusVR worlds
1,207 lines (1,149 loc) • 55.7 kB
JavaScript
elation.require(['engine.things.player', 'janusweb.external.JanusVOIP', 'ui.button'], function() {
elation.requireCSS('janusweb.janusplayer');
elation.component.add('engine.things.janusplayer', function() {
//this.defaultavatar = '<FireBoxRoom>\n <Assets>\n <AssetObject id="screen" src="https://web.janusxr.org/media/assets/hoverscreen.obj" mtl="https://web.janusxr.org/media/assets/hoverscreen.mtl" />\n </Assets>\n <Room>\n <Ghost id="januswebuser" col="#ffffff" lighting="true" head_pos="0 1.4 0" body_id="" eye_pos="0 1.6 0" userid_pos="0 0.5 0" cull_face="back" screen_name="screen_Cube.004">\n <Object id="screen" js_id="head" />\n </Ghost>\n </Room>\n</FireBoxRoom>'
//this.defaultavatar = '<FireBoxRoom>\n<Assets>\n<AssetObject id="screen" src="https://vesta.janusxr.org/avatars/static/janusweb/janusweb.obj.gz" mtl="https://vesta.janusxr.org/user_avatar/12689/janusweb.mtl" /></Assets>\n<Room>\n<Ghost id="januswebuser" head_id="screen" head_pos="0 1.4 0" body_id="" eye_pos="0 1.6 0" userid_pos="0 0.5 0" cull_face="back" />\n</Room>\n</FireBoxRoom>';
this.defaultavatar = `
<FireBoxRoom>
<assets>
<assetobject id="body" src="http://www.baicoianu.com/~bai/janusweb/test/janus-avatar-base.glb" />
<assetobject id="avatar_animations" src="http://www.baicoianu.com/~bai/janusweb/test/janus-avatar-animations.glb" />
</assets>
<room>
<ghost scale=".01 .01 .01" col="#e0d5a1" body_id="body" bone_head="head" />
</room>
</FireBoxRoom>
`;
this.postinit = function() {
elation.engine.things.janusplayer.extendclass.postinit.call(this);
try {
this.settings = elation.collection.localindexed({storagekey: 'janusweb.player.settings', index: 'key'});
} catch (e) {
this.settings = elation.collection.indexed({storagekey: 'janusweb.player.settings', index: 'key'});
}
this.defineProperties({
janus: {type: 'object' },
room: {type: 'object' },
cursor_visible: {type: 'boolean', default: true, set: this.toggleCursorVisibility},
cursor_opacity: {type: 'float', default: 1.0, set: this.toggleCursorVisibility},
usevoip: {type: 'boolean', default: false },
defaultanimation: {type: 'string', default: 'idle' },
collision_radius: {type: 'float', default: .25, set: this.updateCollider},
party_mode: { type: 'boolean', set: this.updatePartyMode },
avatarsrc: { type: 'string' },
cameraview: { type: 'string', default: 'firstperson' },
camerazoom: { type: 'float', default: 0 },
cameraheight: { type: 'float', default: 0 },
cameraangle: { type: 'float', default: 0 },
decouplehead: { type: 'boolean', default: false },
});
var controllerconfig = this.getSetting('controls.settings');
if (controllerconfig) {
elation.utils.merge(controllerconfig, this.engine.systems.controls.settings);
}
elation.events.add(this.engine.systems.controls, 'settings_change', elation.bind(this, function() {
this.setSetting('controls.settings', this.engine.systems.controls.settings);
}));
elation.events.add(this.engine.client.view.container, 'touchstart', elation.bind(this, this.handleTouchStart));
elation.events.add(this.engine.client.view.container, 'touchmove', elation.bind(this, this.handleTouchMove));
elation.events.add(this.engine.client.view.container, 'touchend', elation.bind(this, this.handleTouchEnd));
elation.events.add(this.engine.client.view.container, 'touchcancel', elation.bind(this, this.handleTouchEnd));
this.controlstate2 = this.engine.systems.controls.addContext('janusplayer', {
'toggle_view': ['keyboard_nomod_v,keyboard_shift_v', elation.bind(this, this.toggleCamera)],
'zoom_out': ['mouse_wheel_down', ev => this.zoomView(-1, ev)],
'zoom_in': ['mouse_wheel_up', ev => this.zoomView(1, ev)],
//'browse_back': ['gamepad_any_button_4', elation.bind(this, this.browseBack)],
//'browse_forward': ['gamepad_any_button_5', elation.bind(this, this.browseForward)],
});
this.vectors = {
xdir: new THREE.Vector3(1, 0, 0),
ydir: new THREE.Vector3(0, 1, 0),
zdir: new THREE.Vector3(0, 0, 1),
eye_pos: new THREE.Vector3(0, 1.6, 0),
head_pos: new THREE.Vector3(0, 1.6, 0),
view_xdir: new THREE.Vector3(1, 0, 0),
view_ydir: new THREE.Vector3(0, 1, 0),
view_zdir: new THREE.Vector3(0, 0, 1),
cursor_xdir: new THREE.Vector3(1, 0, 0),
cursor_ydir: new THREE.Vector3(0, 1, 0),
cursor_zdir: new THREE.Vector3(0, 0, 1),
cursor_pos: new THREE.Vector3(0, 0, 0),
lookat_pos: new THREE.Vector3(0, 0, 0),
};
this.hands = {
left: {
active: false,
position: new THREE.Vector3(0, 0, 0),
xdir: new THREE.Vector3(1, 0, 0),
ydir: new THREE.Vector3(0, 1, 0),
zdir: new THREE.Vector3(0, 0, 1),
p0: new THREE.Vector3(0, 0, 0),
p1: new THREE.Vector3(0, 0, 0),
p2: new THREE.Vector3(0, 0, 0),
p3: new THREE.Vector3(0, 0, 0),
p4: new THREE.Vector3(0, 0, 0),
},
right: {
active: false,
position: new THREE.Vector3(0, 0, 0),
xdir: new THREE.Vector3(1, 0, 0),
ydir: new THREE.Vector3(0, 1, 0),
zdir: new THREE.Vector3(0, 0, 1),
p0: new THREE.Vector3(0, 0, 0),
p1: new THREE.Vector3(0, 0, 0),
p2: new THREE.Vector3(0, 0, 0),
p3: new THREE.Vector3(0, 0, 0),
p4: new THREE.Vector3(0, 0, 0),
}
};
this.cursor_active = false;
this.cursor_style = 'default';
this.cursor_object = '';
this.lookat_object = '';
window.addEventListener('blur', ev => {
if (document.pointerLockElement && document.activeElement instanceof HTMLIFrameElement) {
document.pointerLockElement.focus();
//this.disable();
this.engine.systems.controls.deactivateContext('janusplayer');
this.engine.systems.controls.activateContext('janusplayer');
let click = new MouseEvent('click', { });
document.body.dispatchEvent(click);
//setTimeout(() => this.enable(), 100);
}
});
if (this.usevoip) {
this.voip = new JanusVOIPRecorder({audioScale: 1024});
this.voipqueue = [];
this.voipbutton = elation.ui.button({classname: 'janusweb_voip', label: 'VOIP'});
this.engine.client.buttons.add('voip', this.voipbutton);
elation.events.add(this.voipbutton, 'mousedown,touchstart', elation.bind(this.voip, this.voip.start));
elation.events.add(this.voipbutton, 'mouseup,touchend', elation.bind(this.voip, this.voip.stop));
elation.events.add(this.voip, 'voip_start', elation.bind(this, this.handleVOIPStart));
elation.events.add(this.voip, 'voip_stop', elation.bind(this, this.handleVOIPStop));
elation.events.add(this.voip, 'voip_data', elation.bind(this, this.handleVOIPData));
elation.events.add(this.voip, 'voip_error', elation.bind(this, this.handleVOIPError));
}
elation.events.add(this.engine.systems.render.views.main, 'render_view_prerender', elation.bind(this, this.updateCursor));
elation.events.add(this.engine, 'engine_frame', elation.bind(this, this.handleFrame));
elation.events.add(this.engine.client.container, 'mousedown', elation.bind(this, this.updateMouseStatus));
elation.events.add(this.engine.client.container, 'mouseup', elation.bind(this, this.updateMouseStatus));
this.updateCursorStyle = elation.bind(this, this.updateCursorStyle);
this.touchcache = {
positions: []
};
this.touchindex = [null, null];
//this.updateVRButton();
this.party_mode = this.getSetting('partymode.enabled', false);
this.currentavatar = '';
this.getAvatarData().then(d => this.currentavatar = d);
this.morphtargetchanges = {};
this.eventlistenerproxies = {};
if (this.cameraview == 'thirdperson') {
this.camerazoom = 2;
this.cameraheight = .25;
}
this.updateCamera();
this.handleFrameUpdates = elation.bind(this, this.handleFrameUpdates);
}
this.createChildren = function() {
elation.engine.things.janusplayer.extendclass.createChildren.call(this);
this.cursor = new THREE.Sprite(new THREE.SpriteMaterial({color: 0xffffff, depthTest: false, depthWrite: false, transparent: true, map: null, fog: false, transparent: true}));
this.cursor.renderOrder = 500;
this.engine.systems.world.scene['world-3d'].add(this.cursor);
this.getAvatarData().then(avatar => {;
if (avatar && false) { // FIXME - self avatar is buggy so it's disabled
/*
this.ghost = this.createObject('ghost', {
ghost_id: this.getUsername(),
avatar_src: 'data:text/plain,' + encodeURIComponent(avatar),
showlabel: false
});
*/
}
});
this.updateCamera();
this.updateCollider();
}
this.enable = function() {
elation.engine.things.janusplayer.extendclass.enable.call(this);
this.engine.systems.controls.activateContext('janusplayer');
this.dispatchEvent({type: 'player_enable'});
}
this.disable = function() {
this.engine.systems.controls.deactivateContext('janusplayer');
elation.engine.things.janusplayer.extendclass.disable.call(this);
this.dispatchEvent({type: 'player_disable'});
}
this.updateVRButton = function() {
if (this.engine.client.view.vrdisplay) {
if (!this.vrbutton) {
this.vrbutton = elation.ui.button({classname: 'janusweb_vr', label: 'Enter VR'});
this.engine.client.buttons.add('vr', this.vrbutton);
elation.events.add(this.vrbutton, 'ui_button_click', elation.bind(this, function(ev) { this.engine.client.toggleVR(ev); }));
elation.events.add(this.engine.client.view, elation.bind(this, function(ev) {
var vrdisplay = ev.data;
elation.events.add(ev.data, 'vrdisplaypresentchange', elation.bind(this, function() { this.vrbutton.label = (vrdisplay && vrdisplay.isPresenting ? 'Exit' : 'Enter') + ' VR'; }));
}));
}
}
}
this.activateVOIP = function(ev) {
var on = (ev.value == 1);
if (this.voip) {
if (on) {
this.voip.start();
} else {
this.voip.stop();
}
}
}
this.updateHMD = function(vrdevice) {
if (vrdevice && !this.vrbutton) {
//this.updateVRButton();
}
elation.engine.things.janusplayer.extendclass.updateHMD.call(this, vrdevice);
}
this.handleVOIPStart = function() {
this.voipbutton.addclass('state_recording');
elation.events.fire('janusvr_voip_start');
}
this.handleVOIPStop = function() {
elation.events.fire('janusvr_voip_stop');
this.voipbutton.removeclass('state_recording');
}
this.handleVOIPData = function(ev) {
this.voipqueue.push(ev.data);
}
this.handleVOIPError = function(ev) {
this.voipbutton.addclass('state_error');
this.voipbutton.setTitle(ev.data.name + ': ' + ev.data.message);
elation.events.fire('janusvr_voip_error');
}
this.browseBack = function(ev) {
if (ev.value == 1) {
history.go(-1);
}
}
this.browseForward = function(ev) {
if (ev.value == 1) {
history.go(1);
}
}
this.engine_frame = (function() {
var _transform = new THREE.Matrix4();
var _tmpquat = new THREE.Quaternion();
return function(ev) {
// FIXME - this should be called in a pre-frame function, so that hands get updated before the renderer runs, for minimal delay
elation.engine.things.janusplayer.extendclass.engine_frame.call(this, ev);
if (this.tracker && this.tracker.hasHands()) {
var hands = this.tracker.getHands();
if (hands) {
this.hands.left.active = hands.left && hands.left.active;
this.hands.right.active = hands.right && hands.right.active;
if (hands.left && hands.left.position) {
var pos = hands.left.palmPosition || hands.left.position,
orient = hands.left.palmOrientation || hands.left.orientation;
if (pos instanceof THREE.Vector3) pos = pos.toArray();
if (orient instanceof THREE.Quaternion) orient = orient.toArray();
//this.localToWorld(this.hands.left.position.fromArray(pos));
if (pos) {
this.hands.left.handpos.fromArray(pos);
}
if (orient) {
_transform.makeRotationFromQuaternion(_tmpquat.fromArray(orient));
_transform.extractBasis(this.hands.left.xdir, this.hands.left.ydir, this.hands.left.zdir);
this.hands.left.xdir.normalize();
this.hands.left.ydir.normalize();
this.hands.left.zdir.normalize();
}
if (hands.left.fingers) {
hands.left.fingers[0].fingertip.getWorldPosition(this.hands.left.p0);
hands.left.fingers[1].fingertip.getWorldPosition(this.hands.left.p1);
hands.left.fingers[2].fingertip.getWorldPosition(this.hands.left.p2);
hands.left.fingers[3].fingertip.getWorldPosition(this.hands.left.p3);
hands.left.fingers[4].fingertip.getWorldPosition(this.hands.left.p4);
}
}
if (hands.right && hands.right.position) {
var pos = hands.right.palmPosition || hands.right.position,
orient = hands.right.palmOrientation || hands.right.orientation;
if (pos instanceof THREE.Vector3) pos = pos.toArray();
if (orient instanceof THREE.Quaternion) orient = orient.toArray();
//this.localToWorld(this.hands.right.position.fromArray(pos));
if (pos) {
this.hands.right.handpos.fromArray(pos);
}
if (orient) {
//_transform.makeRotationFromQuaternion(orient);
_transform.makeRotationFromQuaternion(_tmpquat.fromArray(orient));
_transform.extractBasis(this.hands.right.xdir, this.hands.right.ydir, this.hands.right.zdir);
this.hands.right.xdir.normalize();
this.hands.right.ydir.normalize();
this.hands.right.zdir.normalize();
}
if (hands.right.fingers) {
hands.right.fingers[0].fingertip.getWorldPosition(this.hands.right.p0);
hands.right.fingers[1].fingertip.getWorldPosition(this.hands.right.p1);
hands.right.fingers[2].fingertip.getWorldPosition(this.hands.right.p2);
hands.right.fingers[3].fingertip.getWorldPosition(this.hands.right.p3);
hands.right.fingers[4].fingertip.getWorldPosition(this.hands.right.p4);
}
}
}
}
if (this.ghost && !this.decouplehead) {
this.ghost.setHeadOrientation(_tmpquat.multiplyQuaternions(this.neck.orientation._target, this.head.orientation._target), true);
if (this.ghost._target.head) {
//this.ghost._target.face.position.copy(this.head.position);
this.ghost._target.head.orientation.copy(this.head.orientation).multiply(this.neck.orientation);;
}
}
}
})();
this.handleFrame = function() {
if (this.lookAtLERPtime) {
this.updateLookAtLERP();
}
if (this.ghost && this.ghost.body) {
let animid = this.getAnimationID();
this.ghost.body.anim_id = animid;
}
}
this.updateCursor = (function() {
var _tmpvec = new THREE.Vector3();
return function() {
this.updateMouseControls({data: this.controlstate});
this.updateVectors();
if (this.cursor_object == '') {
this.camera.localToWorld(this.vectors.cursor_pos.set(0,0,-10));
}
if (this.cursor) {
// Show system cursor when the mouse is unlocked and we're not in VR
// Otherwise, we'll render one in the 3d scene
var vrdisplay = this.engine.systems.render.views.main.vrdisplay;
var useSystemCursor = !(this.engine.systems.controls.pointerLockActive || (vrdisplay));
if (useSystemCursor) {
this.cursor.visible = false;
var view = this.engine.systems.render.views.main;
if (this.cursors && !view.hasclass('cursor_' + this.cursor_style)) {
var cursortypes = Object.keys(this.cursors);
for (var i = 0; i < cursortypes.length; i++) {
var thistype = cursortypes[i] == this.cursor_style,
hasclass = view.hasclass('cursor_' + cursortypes[i]);
if (thistype && !hasclass) {
view.addclass('cursor_' + this.cursor_style);
} else if (hasclass) {
view.removeclass('cursor_' + cursortypes[i]);
}
}
}
} else {
this.cursor.visible = this.cursor_visible;
var distance = this.camera.localToWorld(_tmpvec.set(0,0,0)).distanceTo(this.vectors.cursor_pos);
var size = distance / 12; // FIXME - add cursor scaling
this.cursor.position.copy(this.vectors.cursor_pos);
this.cursor.scale.set(size,size,size);
if (this.cursor_object == '') {
this.cursor.material.opacity = .5 * this.cursor_opacity;
} else {
this.cursor.material.opacity = this.cursor_opacity;
}
if (this.cursors[this.cursor_style]) {
this.cursor.material.map = this.cursors[this.cursor_style]._texture;
} else if (this.cursors['default']) {
this.cursor.material.map = this.cursors['default']._texture;
} else {
this.cursor.material.map = null;
this.cursor.visible = false;
}
}
}
this.objects['3d'].updateMatrix();
if (this.objects['3d'].matrixWorldNeedsUpdate) this.objects['3d'].updateMatrixWorld();
this.camera.objects['3d'].updateMatrix();
if (this.camera.objects['3d'].matrixWorldNeedsUpdate) this.camera.objects['3d'].updateMatrixWorld();
}
})();
this.updateVectors = function() {
var v = this.vectors;
if (this.objects['3d']) {
let playerpos = this.properties.position;
if (typeof playerpos.x == 'undefined' || isNaN(playerpos.x)) playerpos.x = 0;
if (typeof playerpos.y == 'undefined' || isNaN(playerpos.y)) playerpos.y = 0;
if (typeof playerpos.z == 'undefined' || isNaN(playerpos.z)) playerpos.z = 0;
this.objects['3d'].updateMatrix();
this.objects['3d'].updateMatrixWorld();
this.objects['3d'].matrixWorld.extractBasis(v.xdir, v.ydir, v.zdir);
}
if (this.head) {
this.head.objects['3d'].matrixWorld.extractBasis(v.view_xdir, v.view_ydir, v.view_zdir);
v.head_pos.setFromMatrixPosition(this.head.objects['3d'].matrixWorld);
}
if (this.gaze && this.gaze.object) {
var now = performance.now();
var gazetime = 1000;
if (this.gaze.object.gazetime) {
gazetime = this.gaze.object.gazetime;
} else if (this.gaze.object.room && this.gaze.object.room.gazetime) {
gazetime = this.gaze.object.room.gazetime;
}
var diff = now - this.gaze.start;
var percent = diff / gazetime;
if (percent < 1) {
this.gaze.object.dispatchEvent({type: 'gazeprogress', data: percent});
} else if (!this.gaze.fired) {
this.gaze.object.dispatchEvent({type: 'gazeprogress', data: 1});
this.gaze.object.dispatchEvent({type: 'gazeactivate', data: null});
this.gaze.fired = true;
}
}
if (this.controlstate.jump && !this.jumping) {
let jumptime = 1;
if (this.ghost && this.ghost.body && this.ghost.body.animations && this.ghost.body.animations.jump) {
let jumpanim = this.ghost.body.animations.jump;
let jumpclip = jumpanim.getClip();
jumpanim.loop = THREE.LoopOnce;
if (jumpclip) {
jumptime = jumpclip.duration;
}
this.jumping = true;
setTimeout(() => this.jumping = false, (jumptime * 1000) * .8);
}
}
}
this.updateMouseStatus = function(ev) {
if (ev.type == 'mousedown' && ev.button === 0) {
this.cursor_active = true;
} else if (ev.type == 'mouseup' && ev.button === 0) {
this.cursor_active = false;
}
}
this.updateFocusObject = (function() {
var _tmpvec = new THREE.Vector3(),
_diff = new THREE.Vector3();
return function(ev) {
var obj = ev.element;
if ((ev.type == 'mouseover' || ev.type == 'mousemove') && obj && obj.js_id) {
this.cursor_object = obj.js_id;
var worldpos = this.camera.localToWorld(_tmpvec.set(0,0,0));
_diff.copy(ev.data.point).sub(worldpos);
this.vectors.cursor_pos.copy(_diff).multiplyScalar(-.05).add(ev.data.point);
this.lookat_object = obj.js_id;
this.vectors.lookat_pos.copy(ev.data.point);
} else {
this.cursor_object = '';
this.lookat_object = '';
var distance = 20;
this.camera.localToWorld(this.vectors.cursor_pos.set(0,0,-distance));
}
}
})();
this.toggleCursorVisibility = function() {
if (this.cursor) {
this.cursor.visible = this.cursor_visible;
}
}
this.setRoom = function(newroom) {
if (this.room) {
let oldroomproxy = this.room.getProxyObject();
oldroomproxy.removeEventListener('mouseover', this.updateCursorStyle);
oldroomproxy.removeEventListener('mouseout', this.updateCursorStyle);
this.room.part();
}
this.room = newroom;
this.room.join();
/*
if (!this.gazecaster) {
this.gazecaster = newroom.createObject('raycaster', {persist: false});
this.head.add(this.gazecaster._target);
//this.gazecaster = this.head.spawn('raycaster', null, {room: this.room, janus: this.janus});
elation.events.add(this.gazecaster._target, 'raycastenter', elation.bind(this, this.handleGazeEnter));
elation.events.add(this.gazecaster._target, 'raycastleave', elation.bind(this, this.handleGazeLeave));
elation.events.add(this.gazecaster._target, 'raycastmove', elation.bind(this, this.handleGazeMove));
} else {
this.gazecaster.setRoom(newroom);
}
*/
let newroomproxy = newroom.getProxyObject();
newroomproxy.addEventListener('mouseover', this.updateCursorStyle);
newroomproxy.addEventListener('mouseout', this.updateCursorStyle);
if (!this.cursors) {
this.cursors = {
'default': janus.getAsset('image', 'cursor_crosshair'),
'crosshair': janus.getAsset('image', 'cursor_crosshair'),
'pointer': janus.getAsset('image', 'cursor_hand'),
'dot_inactive': janus.getAsset('image', 'cursor_dot_inactive'),
'dot_active': janus.getAsset('image', 'cursor_dot_active'),
};
}
newroom.appendChild(this.getProxyObject());
if (this.ghost) {
this.ghost.setRoom(newroom);
} else if (!this.ghost) { // && this.room.selfavatar) {
// FIXME - self avatar is buggy so it's disabled
this.getAvatarData().then(avatar => {
if (avatar) {
this.ghost = this.createObject('ghost', {
ghost_id: this.getUsername(),
avatar_src: 'data:text/plain,' + encodeURIComponent(avatar),
showlabel: false,
//pos: V(0, -this.fatness, 0),
//rotation: V(0, 180, 0),
renderorder: 101,
});
//this.ghost.orientation.set(0,1,0,0);
}
});
this.visible = true;
}
for (let k in this.children) {
if (this.children[k].setRoom) {
this.children[k].setRoom(room);
this.children[k].start();
}
}
//room.add(this);
this.updateGravity();
}
this.updateGravity = function(gravity) {
// FIXME - gravity is currently disabled, pending ongoing work with mesh colliders
return;
if (typeof gravity == 'undefined' && this.room) {
gravity = this.room.gravity;
}
if (!this.gravity) {
this.gravity = this.addForce('gravity', V(0, gravity, 0));
} else {
this.gravity.update(V(0, gravity, 0));
}
}
this.reset_position = function(ev) {
if (!ev || ev.value == 1) {
var room = this.engine.client.janusweb.currentroom;
if (room) {
this.properties.position.copy(room.spawnpoint.position);
this.properties.orientation.copy(room.spawnpoint.quaternion);
this.head.properties.orientation.copy(this.properties.startcameraorientation);
this.properties.velocity.set(0,0,0);
this.objects.dynamics.angular.set(0,0,0);
//this.engine.systems.controls.calibrateHMDs();
this.refresh();
}
}
}
this.start = function() {
if (!this.started) {
if (!this.static) {
elation.events.add(this.room, 'janusweb_script_frame_end', this.handleFrameUpdates);
} else {
this.handleFrameUpdates({data: {dt: 0}});
}
this.started = true;
}
for (var k in this.children) {
if (this.children[k].start) {
this.children[k].start();
}
}
this.dispatchEvent({type: 'start', bubbles: false});
}
this.stop = function() {
for (var k in this.children) {
if (this.children[k].stop) {
this.children[k].stop();
}
}
if (this.started) {
if (!this.static) {
elation.events.remove(this.room, 'janusweb_script_frame_end', this.handleFrameUpdates);
}
this.started = false;
this.dispatchEvent({type: 'stop', bubbles: false});
}
}
this.getProxyObject = function() {
if (!this._proxyobject) {
this._proxyobject = new elation.proxy(this, {
parent: ['accessor', 'parent.getProxyObject'],
room: ['property', 'room'],
pos: ['property', 'position'],
vel: ['property', 'velocity'],
accel: ['property', 'acceleration'],
eye_pos: ['property', 'vectors.eye_pos'],
head_pos: ['property', 'vectors.head_pos'],
cursor_pos: ['property', 'vectors.cursor_pos'],
cursor_xdir: ['property', 'vectors.cursor_xdir'],
cursor_ydir: ['property', 'vectors.cursor_ydir'],
cursor_zdir: ['property', 'vectors.cursor_zdir'],
view_dir: ['property', 'vectors.view_zdir'],
dir: ['property', 'vectors.zdir'],
up_dir: ['property', 'vectors.ydir'],
userid: ['property', 'janus.userId'],
enabled: ['property', 'enabled'],
flying: ['property', 'flying'],
walking: ['property', 'walking'],
running: ['property', 'running'],
//url: ['property', 'currenturl'],
//hmd_enabled: ['property', 'hmd_enabled'],
cursor_active: ['property', 'cursor_active'],
cursor_visible: ['property', 'cursor_visible'],
cursor_opacity: ['property', 'cursor_opacity'],
cursor_style: ['property', 'cursor_style'],
cursor_object: ['property', 'cursor_object'],
lookat_object: ['property', 'lookat_object'],
lookat_pos: ['property', 'vectors.lookat_pos'],
//lookat_xdir: ['property', 'properties.lookat_xdir'],
//lookat_ydir: ['property', 'properties.lookat_ydir'],
//lookat_zdir: ['property', 'properties.lookat_zdir'],
hand0_active: ['property', 'hands.left.isactive'],
hand0_pos: ['property', 'hands.left.handpos'],
hand0_xdir: ['property', 'hands.left.xdir'],
hand0_ydir: ['property', 'hands.left.ydir'],
hand0_zdir: ['property', 'hands.left.zdir'],
hand0_p0: ['property', 'hands.left.p0'],
hand0_p1: ['property', 'hands.left.p1'],
hand0_p2: ['property', 'hands.left.p2'],
hand0_p3: ['property', 'hands.left.p3'],
hand0_p4: ['property', 'hands.left.p4'],
hand1_active: ['property', 'hands.right.isactive'],
hand1_pos: ['property', 'hands.right.handpos'],
hand1_xdir: ['property', 'hands.right.xdir'],
hand1_ydir: ['property', 'hands.right.ydir'],
hand1_zdir: ['property', 'hands.right.zdir'],
hand1_p0: ['property', 'hands.right.p0'],
hand1_p1: ['property', 'hands.right.p1'],
hand1_p2: ['property', 'hands.right.p2'],
hand1_p3: ['property', 'hands.right.p3'],
hand1_p4: ['property', 'hands.right.p4'],
url: ['property', 'parent.currentroom.url'],
ghost: ['property', 'ghost'],
collision_radius: ['property', 'collision_radius'],
currentavatar: ['property', 'currentavatar'],
defaultanimation: ['property', 'defaultanimation'],
eventlistenerproxies: ['property', 'eventlistenerproxies'],
localToWorld: ['function', 'localToWorld'],
worldToLocal: ['function', 'worldToLocal'],
appendChild: ['function', 'appendChild'],
removeChild: ['function', 'removeChild'],
addForce: ['function', 'addForce'],
removeForce: ['function', 'removeForce'],
raycast: ['function', 'raycast'],
getViewFrustum:['function', 'getViewFrustum'],
getUsername: ['function', 'getUsername'],
setUsername: ['function', 'setUsername'],
getSetting: ['function', 'getSetting'],
setSetting: ['function', 'setSetting'],
setAvatar: ['function', 'setAvatar'],
addEventListener: ['function', 'addEventListenerProxy'],
dispatchEvent: ['function', 'dispatchEvent'],
removeEventListener: ['function', 'removeEventListenerProxy'],
});
}
return this._proxyobject;
}
this.getAvatarData = function() {
return new Promise((resolve, reject) => {
let avatar = this.getSetting('avatar');
if (!avatar) {
if (this.avatarsrc) {
fetch(this.avatarsrc).then(res => res.text()).then(txt => {
this.currentavatar = txt;
this.avatarNeedsUpdate = true;
resolve(txt);
});
} else {
this.currentavatar = this.defaultavatar;
this.avatarNeedsUpdate = true;
resolve(this.defaultavatar);
}
} else {
this.avatarNeedsUpdate = true;
resolve(avatar);
}
});
}
this.getCurrentAvatarData = function() {
return this.currentavatar;
}
this.setAvatar = function(avatar) {
this.avatarNeedsUpdate = true;
this.currentavatar = avatar;
let setting = this.setSetting('avatar', avatar);
if (this.ghost) {
this.ghost.die();
}
this.ghost = this.createObject('ghost', {
ghost_id: this.getUsername(),
avatar_src: 'data:text/plain,' + encodeURIComponent(avatar),
showlabel: false,
//pos: V(0, -this.fatness, 0),
//rotation: V(0, 180, 0),
renderorder: 101,
});
return setting;
}
this.hasVoipData = function() {
return this.voipqueue && this.voipqueue.length > 0;
}
this.getVoipData = function() {
var voipdata = '';
if (this.voipqueue && this.voipqueue.length > 0) {
// FIXME - we should probably just return a combined Uint8Array rather than a binary string
while (this.voipqueue.length > 0) {
var buf = this.voipqueue.shift();
var bytes = new Uint8Array(buf.buffer);
for (var i = 0; i < bytes.byteLength; i++) {
voipdata += String.fromCharCode(bytes[i]);
}
}
}
return voipdata;
}
this.getAnimationID = function() {
var animid = this.defaultanimation;
let running = this.controlstate.run;
if (this.jumping) {
animid = 'jump';
} else if (this.controlstate.move_left && Math.abs(this.controlstate.move_left) > .15) {
animid = (running ? 'run' : (this.controlstate.move_left > 0 ? 'walk_left' : 'walk_right'));
} else if (this.controlstate.move_right && Math.abs(this.controlstate.move_right) > .15) {
animid = (running ? 'run' : (this.controlstate.move_right > 0 ? 'walk_right' : 'walk_left'));
} else if (this.controlstate.move_forward) {
animid = (running ? 'run' : (this.controlstate.move_forward > 0 ? 'walk' : 'walk_back'));
} else if (this.controlstate.move_backward) {
animid = (running ? 'run' : 'walk_back');
} else if (document.activeElement && this.properties.janus.chat && document.activeElement === this.properties.janus.chat.input.inputelement) {
animid = 'type';
} else if (this.hasVoipData()) {
animid = 'speak';
}
return animid;
}
this.setHand = function(handedness, handobj) {
this.hands[handedness] = handobj;
console.log('player has hands', this.hands);
}
this.hasHands = function() {
return (this.tracker && this.tracker.hasHands());
}
this.getHandData = function() {
var handData = false;
var hands = this.tracker.getHands();
if (hands) {
handData = {};
if (hands.left && hands.left.active) {
handData.left = {
active: true,
state: hands.left.getState(player.shoulders)
};
}
if (hands.right && hands.right.active) {
handData.right = {
active: true,
state: hands.right.getState(player.shoulders)
};
}
}
return handData;
}
this.hasMorphTargetData = function() {
return Object.keys(this.morphtargetchanges).length > 0;
}
this.setMorphTargetInfluence = function(morphtarget, value) {
if (this.ghost && this.ghost.body) {
this.ghost.body.setMorphTargetInfluence(morphtarget, value);
//if (this.morphtargetchanges.indexOf(morphtarget) == -1) {
this.morphtargetchanges[morphtarget] = value;
//}
}
}
this.getMorphTargetData = function() {
if (this.hasMorphTargetData()) {
let data = {};
let body = this.ghost.body;
for (let morphtarget in this.morphtargetchanges) {
data[morphtarget] = this.morphtargetchanges[morphtarget];
delete this.morphtargetchanges[morphtarget];
}
return data;
}
return false;
}
this.getRandomUsername = function() {
var adjectives = [
"Adorable", "Beautiful", "Clean", "Drab", "Elegant", "Fancy", "Glamorous", "Handsome", "Long", "Magnificent",
"Plain", "Quaint", "Sparkling", "Ugliest", "Unsightly", "Agreeable", "Brave", "Calm", "Delightful", "Eager",
"Faithful", "Gentle", "Happy", "Jolly", "Kind", "Lively", "Nice", "Obedient", "Proud", "Relieved", "Silly",
"Thankful", "Victorious", "Witty", "Zealous", "Angry", "Bewildered", "Clumsy", "Defeated", "Embarrassed",
"Fierce", "Grumpy", "Helpless", "Itchy", "Jealous", "Lazy", "Mysterious", "Nervous", "Obnoxious", "Panicky",
"Repulsive", "Scary", "Thoughtless", "Uptight", "Worried"
];
var nouns = [
"Alligator", "Ant", "Bear", "Bee", "Bird", "Camel", "Cat", "Cheetah", "Chicken", "Chimpanzee", "Cow",
"Crocodile", "Deer", "Dog", "Dolphin", "Duck", "Eagle", "Elephant", "Fish", "Fly", "Fox", "Frog", "Giraffe",
"Goat", "Goldfish", "Hamster", "Hippopotamus", "Horse", "Kangaroo", "Kitten", "Lion", "Lobster", "Monkey",
"Octopus", "Owl", "Panda", "Pig", "Puppy", "Rabbit", "Rat", "Scorpion", "Seal", "Shark", "Sheep", "Snail",
"Snake", "Spider", "Squirrel", "Tiger", "Turtle", "Wolf", "Zebra"
];
var adj = adjectives[Math.floor(Math.random() * adjectives.length)];
var noun = nouns[Math.floor(Math.random() * nouns.length)];
var num = Math.floor(Math.random() * 1000);
return adj + noun + num
}
this.getSetting = function(key, defaultvalue) {
var setting = this.settings.get(key);
if (!setting) return defaultvalue;
return elation.utils.any(setting.value, defaultvalue);
}
this.setSetting = function(key, value) {
this.settings.add({key: key, value: value});
this.settings.save();
}
this.removeSetting = function(key) {
this.settings.remove(key);
return true;
}
this.getUsername = function() {
var username = this.getSetting('username');
if (!username) {
username = this.getRandomUsername();
}
return username;
}
this.setUsername = function(username) {
this.setSetting('username', username);
this.userid = username;
this.player_id = username;
janus.userId = username;
elation.events.fire({type: 'username_change', element: this, data: username});
}
this.getNetworkUsername = function() {
if (this.room) {
let server = janus.network.getServerForRoom(this.room);
if (server) {
return server._userId + server._useridSuffix
}
}
}
this.updateCursorStyle = function(ev) {
var vrdisplay = this.engine.systems.render.views.main.vrdisplay;
var obj = ev.element;
var proxyobj = (obj.getProxyObject ? obj.getProxyObject() : obj);
if (obj && proxyobj && (ev.type == 'mouseover' || ev.type == 'mousemove') && (
obj.onclick ||
elation.events.hasEventListener(obj, 'click') ||
elation.events.hasEventListener(proxyobj, 'click') ||
obj.onmousedown ||
elation.events.hasEventListener(obj, 'mousedown') ||
elation.events.hasEventListener(proxyobj, 'mousedown'))
) {
this.cursor_style = 'pointer';
} else if (this.engine.systems.controls.pointerLockActive || (vrdisplay && vrdisplay.isPresenting)) {
this.cursor_style = 'crosshair';
} else {
this.cursor_style = 'default';
}
}
this.createObject = function(type, args) {
if (this.room) {
return this.room.createObject(type, args, this);
}
}
this.appendChild = function(obj) {
var proxyobj = obj
if (elation.utils.isString(obj)) {
proxyobj = this.room.jsobjects[obj];
}
if (proxyobj) {
//var realobj = this.room.getObjectFromProxy(proxyobj);
if (proxyobj.parent) {
if (typeof proxyobj.parent.removeChild == 'function') {
proxyobj.parent.removeChild(proxyobj);
} else if (typeof proxyobj.parent.remove == 'function') {
proxyobj.parent.remove(proxyobj._target);
}
}
var realobj = proxyobj._target;
if (realobj) {
this.add(realobj);
if (typeof realobj.start == 'function') {
realobj.start();
}
}
}
}
this.removeChild = function(obj) {
var proxyobj = obj
if (elation.utils.isString(obj)) {
proxyobj = this.room.jsobjects[obj];
}
if (proxyobj) {
//var realobj = this.room.getObjectFromProxy(proxyobj);
var realobj = proxyobj._target;
if (realobj) {
if (typeof realobj.stop == 'function') {
realobj.stop();
}
this.remove(realobj);
}
}
}
this.updateCollider = function() {
if (this.objects['dynamics']) {
if (this.collision_radius > 0) {
/*
this.setCollider('sphere', {
radius: this.collision_radius,
});
this.collidable = false;
*/
this.pickable = false;
this.setCollider('capsule', {
radius: this.collision_radius,
length: 1,
offset: V(0, this.collision_radius, 0)
});
} else {
this.removeCollider();
}
}
}
this.removeCollider = function() {
if (this.colliders) {
for (var i = 0; i < this.colliders.children.length; i++) {
var collider = this.colliders.children[i];
collider.parent.remove(collider);
}
}
}
this.raycast = (function() {
var _pos = new THREE.Vector3(),
_dir = new THREE.Vector3(0,0,-1);
return function(dir, offset, classname, maxdist) {
if (!this.room) return [];
if (dir) {
_dir.copy(dir);
} else {
_dir.set(0,0,-1);
}
_pos.set(0,0,0);
if (offset) {
_pos.add(offset);
}
this.head.localToWorld(_pos);
this.head.objects.dynamics.localToWorldDir(_dir);
return this.room.raycast(_dir, _pos, classname, maxdist);
};
})();
this.cancelGaze = function() {
//this.gaze.object.dispatchEvent({type: 'gazecancel'});
this.gaze = false;
}
this.handleGazeEnter = function(ev) {
var obj = ev.data.object;
if (obj && obj.dispatchEvent && ev.data.intersection) {
obj.dispatchEvent({type: 'gazeenter', data: ev.data.intersection});
this.cursor_object = obj;
if (this.gaze) {
this.cancelGaze();
}
this.gaze = {
start: performance.now(),
object: obj,
fired: false
};
}
}
this.handleGazeLeave = function(ev) {
var obj = ev.data.object;
if (obj && obj.dispatchEvent) {
this.cursor_object = '';
obj.dispatchEvent({type: 'gazeleave', data: ev.data.intersection});
}
}
this.handleGazeMove = function(ev) {
var obj = ev.data.object;
if (obj && obj.dispatchEvent) {
this.cursor_object = obj.js_id || '';
if (ev.data.intersection.point) {
this.vectors.cursor_pos.copy(ev.data.intersection.point);
}
}
}
this.handleTouchStart = function(ev) {
//if (!this.enabled) return;
if (!room.pointerlock) return;
let halfscreenwidth = window.innerWidth / 2;
this.enabled = true;
for (let i = 0; i < ev.changedTouches.length; i++) {
let touch = ev.changedTouches[i];
this.touchcache.positions[touch.identifier] = [touch.clientX, touch.clientY];
if (touch.clientX < halfscreenwidth) {
this.touchindex[0] = touch.identifier;
} else {
this.touchindex[1] = touch.identifier;
}
}
if (!document.fullscreenElement) {
this.engine.client.toggleFullscreen({data: 1});
}
this.cancelTouchMovement(ev);
}
this.handleTouchMove = function(ev) {
if (!room.pointerlock) return;
if (ev.defaultPrevented) return;
//if (ev.touches.length == 1) {
//var touchindex = this.touchindex;
for (let i = 0; i < ev.touches.length; i++) {
var touch = ev.touches[i];
var distanceX = touch.clientX - this.touchcache.positions[touch.identifier][0],
distanceY = touch.clientY - this.touchcache.positions[touch.identifier][1];
var flip = this.getSetting('controls.touch.flip'),
flipx = 0,
flipy = 0;
if (flip) {
flipx = flip.x;
flipy = flip.y;
}
if (touch.identifier === this.touchindex[0]) {
let size = 32;
let dx = distanceX / size,
dy = distanceY / size;
this.controlstate.move_right = dx;
this.controlstate.move_forward = -dy;
} else if (touch.identifier === this.touchindex[1]) {
this.updateMouseControls({
data: {
mouse_look: [(flipx ? -1 : 1) * distanceX / 5, (flipy ? -1 : 1 ) * distanceY / 5]
}
}, true);
this.touchcache.positions[touch.identifier][0] = touch.clientX;
this.touchcache.positions[touch.identifier][1] = touch.clientY;
}
}
this.cancelTouchMovement(ev);
}
this.handleTouchEnd = function(ev) {
this.cancelTouchMovement(ev);
}
this.cancelTouchMovement = function(ev) {
for (let i = 0; i < ev.touches.length; i++) {
let touch = ev.touches[i];
if (touch.identifier === this.touchindex[0]) {
return;
}
}
this.controlstate.move_right = 0;
this.controlstate.move_forward = 0;
this.touchindex[0] = null;
}
this.updatePartyMode = function(key, value) {
if (typeof value != 'undefined') {
this.setSetting('partymode.enabled', value);
}
}
this.scaleTo = (function() {
let tmpvec = new THREE.Vector3(),
startscale = new THREE.Vector3(),
camscale = new THREE.Vector3();
return function(newscale, scaletime, scalecurve) {
if (newscale != this.scale) {
startscale.copy(this.properties.scale);
if (scaletime) {
let start = performance.now();
let timer = setInterval(() => {
let n = (performance.now() - start) / scaletime;
this.scale = tmpvec.set(newscale, newscale, newscale).sub(startscale).multiplyScalar(n).add(startscale);
this.camera.scale = camscale.set(1 / this.scale.x, 1 / this.scale.y, 1 / this.scale.z);
if (n >= 1) {
clearInterval(timer);
this.scale.set(newscale, newscale, newscale);
this.camera.scale = camscale.set(1 / this.scale.x, 1 / this.scale.y, 1 / this.scale.z);
}
}, 16);
} else {
this.scale.set(newscale, newscale, newscale);
this.camera.scale = camscale.set(1 / this.scale.x, 1 / this.scale.y, 1 / this.scale.z);
}
}
};
})();
this.lookAt = function(other, up, turnhead) {
if (!up) up = new THREE.Vector3(0,1,0);
if (!other) return;
var otherpos = false;
if (other.properties && other.properties.position) {
otherpos = other.localToWorld(new THREE.Vector3());
} else if (other instanceof THREE.Object3D) {
if (!otherpos) otherpos = new THREE.Vector3();
other.getWorldPosition(otherpos);
} else if (other instanceof THREE.Vector3) {
otherpos = other.clone();
}
var thispos = this.head.localToWorld(new THREE.Vector3());
if (otherpos) {
var dir = thispos.clone().sub(otherpos).normalize();
if (turnhead) {
this.head.properties.orientation.setFromEuler(new THREE.Euler(-Math.asin(dir.y), Math.atan2(dir.x, dir.z), 0, 'XYZ'));
} else {
this.properties.orientation.setFromEuler(new THREE.Euler(0, Math.atan2(dir.x, dir.z), 0));
this.head.properties.orientation.setFromEuler(new THREE.Euler(-Math.asin(dir.y), 0, 0));
}
this.refresh();
room.refresh();
this.engine.systems.render.setdirty();
}
}
this.turnTo = function(other, up) {
if (!up) up = new THREE.Vector3(0,1,0);
var otherpos = false;
if (other.properties && other.properties.position) {
otherpos = other.localToWorld(new THREE.Vector3());
} else if (other instanceof THREE.Vector3) {
otherpos = other.clone();
}
var thispos = this.localToWorld(new THREE.Vector3());
if (otherpos) {
var dir = thispos.clone().sub(otherpos).normalize();
this.properties.orientation.setFromEuler(new THREE.Euler(0, Math.atan2(dir.x, dir.z), 0));
//this.head.properties.orientation.setFromEuler(new THREE.Euler(-Math.asin(dir.y), 0, 0));
this.refresh();
room.refresh();
this.engine.systems.render.setdirty();
}
}
this.lookAtLERP = function(other, time, up) {
if (this.lerptimer) {
cancelTimeout(this.lerptimer);
}
if (time) {
let now = performance.now(),
elapsed = now - this.starttime,
n = Math.min(1, Math.max(0, elapsed / time));
this.lookAtLERPstart = now;
this.lookAtLERPtime = time;
this.lookAtLERPq1head = player.head.orientation.clone();
this.lookAtLERPq1body = player.orientation.clone();
let otherpos = false;
if (other.properties && other.properties.position) {
otherpos = other.localToWorld(new THREE.Vector3());
} else if (other instanceof THREE.Vector3) {
otherpos = other.clone();
}
let thispos = this.localToWorld(new THREE.Vector3());
if (otherpos) {
let dir = thispos.clone().sub(otherpos).normalize();
this.lookAtLERPq2body = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.atan2(dir.x, dir.z), 0));
this.lookAtLERPq2head = new THREE.Quaternion().setFromEuler(new THREE.Euler(-Math.asin(dir.y), 0, 0));
this.refresh();
room.refresh();
this.engine.systems.render.setdirty();
}
if (this.starttime && elapsed <= time) {
//this.lerptimer = setTimeout(() => this.lookAtLERP(other, time, up), 16);
}
}
}
this.updateLookAtLERP = function() {
let now = performance.now(),
elapsed = now - this.loo