janusweb
Version:
Web client for JanusVR worlds
735 lines (694 loc) • 25.7 kB
JavaScript
elation.require([
'janusweb.config', 'engine.things.generic','janusweb.remoteplayer', 'janusweb.room', 'janusweb.tracking', 'janusweb.multiplayermanager', 'janusweb.external.JanusFireboxParser', 'utils.proxy',
'janusweb.elements.raycaster', 'janusweb.elements.teleporter', 'janusweb.elements.linesegments', 'janusweb.elements.outliner'], function() {
elation.requireCSS('janusweb.janusweb');
elation.component.add('engine.things.janusweb', function() {
this.rooms = {};
this.version = 'janusweb-1.2';
this.settings = {
multiplayer: true,
sessiontracking: true,
jsdebugger: false,
selfavatar: false,
maxmemory: 2048,
comfortmode: false,
downloadcache: true,
antialiasing: true,
assetshaders: true,
assetimages: true,
sounds: true,
leapmotionhmd: true,
editmode: false,
crosshair: false,
gamepad: true,
portalhotkeys: false,
decouplehead: false,
mousepitch: true,
invertpitch: false,
avatarlighting: true,
uivoice: true,
launchurl: 'https://www.janusxr.com/',
server: 'presence.janusxr.org',
port: 5566,
rate: 200,
fov: 70.0,
rotationspeed: 50.0,
hidemenu: false
};
this.scriptframeargs = [
1000/60
];
this.typemap = {
'object': 'janusobject',
'link': 'janusportal',
'text': 'janustext',
'paragraph': 'janusparagraph',
///'image3d': 'janusimage',
'image': 'janusimage',
'video': 'janusvideo',
'sound': 'janussound',
'light': 'januslight',
'particle': 'janusparticle',
'ghost': 'janusghost',
'xrplayer': 'janusxrplayer',
};
this.classmap = {};
this.customElements = {};
this.postinit = function() {
this.defineProperties({
url: { type: 'string', default: false },
homepage: { type: 'string', default: "" },
corsproxy: { type: 'string', default: '' },
showui: { type: 'boolean', default: true },
shownavigation: { type: 'boolean', default: true },
showchat: { type: 'boolean', default: true },
datapath: { type: 'string', default: '/media/janusweb' },
autoload: { type: 'boolean', default: true },
networking: { type: 'boolean', default: true },
server: { type: 'string', default: null },
port: { type: 'integer', default: null },
urltemplate: { type: 'string', default: false },
muted: { type: 'boolean', default: false },
});
elation.events.add(window, 'popstate', elation.bind(this, this.handlePopstate));
try {
this.bookmarks = elation.collection.localindexed({index: 'url', storagekey: 'janusweb.bookmarks'});
} catch (e) {
this.bookmarks = elation.collection.indexed({index: 'url', storagekey: 'janusweb.bookmarks'});
}
if (this.corsproxy != '') {
elation.engine.assets.setCORSProxy(this.corsproxy);
}
this.assetpack = elation.engine.assets.loadAssetPack(this.properties.datapath + 'assets.json', this.properties.datapath);
this.parser = new JanusFireboxParser();
this.scriptingInitialized = false;
this.engine.systems.controls.addContext('janus', {
//'load_url': [ 'keyboard_tab', elation.bind(this, this.showLoadURL) ],
'toggle_menu': [ 'keyboard_esc', elation.bind(this, this.toggleMenu) ],
//'room_debug': [ 'keyboard_f6', elation.bind(this, this.showRoomDebug) ],
'chat': [ 'keyboard_t', elation.bind(this, this.showChat) ],
'bookmark': [ 'keyboard_ctrl_b', elation.bind(this, this.addBookmark) ],
'mute': [ 'keyboard_ctrl_m', elation.bind(this, this.mute) ]
});
this.engine.systems.controls.activateContext('janus');
if (this.muted) {
this.mute();
}
elation.events.add(this.engine.systems.render.views.main, 'render_view_prerender', elation.bind(this, this.updatePortals));
if (this.urltemplate) {
dust.filters.stripunsafe = function(s) {
return s.replace(/:\//g, '');
};
elation.template.add('janusweb.url', this.urltemplate);
}
this.initScripting();
this.assetpack.executeWhenLoaded(() => {
// TODO - this should be config-driven
this.registerAdditionalElements(['raycaster', 'linesegments', 'outliner']);
if (this.networking) {
this.network = elation.janusweb.multiplayermanager({
janusweb: this,
server: this.server,
player: this.engine.client.player
});
this.network.enable(this.engine.client.player);
}
this.initRoom();
});
}
this.initScripting = function() {
if (this.scriptingInitialized) return;
this.registerBuiltinElements({
'object': 'janusobject',
'link': 'janusportal',
'text': 'janustext',
'paragraph': 'janusparagraph',
'image': 'janusimage',
'image3d': 'janusimage',
'video': 'janusvideo',
'sound': 'janussound',
'light': 'januslight',
'particle': 'janusparticle',
'ghost': 'janusghost',
'xrplayer': 'janusxrplayer',
});
window.delta_time = 1000/60;
window.janus = new elation.proxy(this, {
version: ['property', 'version', { readonly: true}],
versiononline: ['property', 'versiononline', {readonly: true}],
currentkey: ['property', 'currentkey', {readonly: true}],
chat: ['property', 'chat.messagelist.items', {readonly: true}],
networkstatus: ['property', 'network.status', {readonly: true}],
networkerror: ['property', 'network.error', {readonly: true}],
roomserver: ['property', 'network.server'],
playercount: ['property', 'network.playerCount'],
bookmarkurl: ['property', 'bookmarks.items'],
bookmarkthumb: ['property', 'bookmarks.items'], // FIXME - need to filter?
playerlist: ['property', ''],
settings: ['property', 'settings'],
userid: ['property', 'userId'],
avatarlighting: ['property', 'settings.avatarlighting'],
ui: ['property', 'engine.client.ui'],
currentroom: ['property', 'currentroom'],
hmd: ['accessor', 'isVRActive'],
currenturl: ['function', 'getCurrentURL'],
tricount: ['function', 'getTriangleCount'],
locked: ['function', 'isLocked'],
getsetting: ['function', 'getSetting'],
setsetting: ['function', 'setSetting'],
roomprogress: ['function', 'currentroom.getProgress'],
launchurl: ['function', 'load'],
navback: ['function', 'navigateBack'],
navforward: ['function', 'navigateForward'],
load: ['function', 'navigateTo'],
setActiveRoom: ['function', 'setActiveRoom'],
preload: ['function', 'preload'],
createRoom: ['function', 'createRoom'],
navhome: ['function', 'navigateHome'],
chatsend: ['function', 'sendChatMessage'],
sync: ['function', 'currentroom.sync'],
reset: ['function', 'reset'],
quit: ['function', 'quit'],
focus: ['function', 'focus'],
unfocus: ['function', 'blur'],
saveroom: ['function', 'saveRoom'],
roomcode: ['function', 'getRoomCode'],
setroomcode: ['function', 'setRoomCode'],
setuserid: ['function', 'setUsername'],
getuserid: ['function', 'getUsername'],
setavatarlighting: ['function', 'setAvatarLighting'],
getavatarlighting: ['function', 'getAvatarLighting'],
resetavatar: ['function', 'resetAvatar'],
hasFocus: ['function', 'hasFocus'],
registerElement: ['function', 'registerElement'],
extendElement: ['function', 'extendElement'],
});
//THREE.Vector3.prototype.toString = function() { return this.toArray().map(function(d) { return d.toFixed(4); }).join(' '); }
window.Vector = function(x, y, z, w) {
if (x) {
if (x._target && x._target instanceof THREE.Vector3) return x._target.clone();
if (x instanceof THREE.Vector3) return new elation.physics.vector3().copy(x);
}
if (y === undefined) y = x;
if (z === undefined) z = y;
return (w === undefined ? new elation.physics.vector3(x, y, z) : new THREE.Vector4(x, y, z, w));
}
window.V = window.Vector;
window.translate = function(v1, v2) {
return new elation.physics.vector3().addVectors(v1, v2);
}
window.distance = function(v1, v2) {
return v1.distanceTo(v2);
}
window.scalarMultiply = function(v, s) {
var ret = new elation.physics.vector3().copy(v);
if (s instanceof THREE.Vector3) {
ret.x *= s.x;
ret.y *= s.y;
ret.z *= s.z;
} else {
ret.multiplyScalar(s);
}
return ret;
}
window.cross = function(v1, v2) {
return new elation.physics.vector3().crossVectors(v1, v2);
}
window.normalized = function(v) {
return new elation.physics.vector3().copy(v).normalize();
}
window.equals = function(v1, v2) {
return v1.equals(v2);
}
window.copy = function(v1, x, y, z) {
if (x instanceof THREE.Vector3) {
v1.copy(x);
} else if (elation.utils.isnumeric(x)) {
if (typeof y != 'undefined' && typeof z != 'undefined') {
v1.set(x, y, z);
} else {
v1.set(x, x, x);
}
}
return v1;
}
window.add = function(v1, v2) {
return v1.add(v2);
}
window.scale = function(v1, s) {
return v1.multiplyScalar(s);
}
window.print = function(...args) {
console.log.apply(console, args);
elation.events.fire({type: 'clientprint', element: this, data: args});
}.bind(this);
window.debug = function() {
console.log.apply(console, arguments);
}
window.removeKey = function(dict, key) {
delete dict[key];
}
var uniqueId = 1;
window.uniqueId = function() {
return uniqueId++;
}
this.scriptingInitialized = true;
}
this.createChildren = function() {
var player = this.engine.client.player;
//setTimeout(elation.bind(this, this.load, starturl, true), 5000);
//this.initScripting();
this.userId = player.getUsername();
player.player_id = this.userId; // FIXME - player spawns without an id, so we fix it up here
window.player = player.getProxyObject();
if (this.networking && this.network) {
this.network.enable(player);
}
if (!(this.autoload || this.getStartURL() != this.properties.homepage)) {
player.disable();
}
}
this.initRoom = function() {
var starturl = this.getStartURL();
if (this.autoload) {
this.load(starturl, true);
}
}
this.clear = function() {
if (this.currentroom) {
this.remove(this.currentroom);
//this.currentroom.die();
//setTimeout(elation.bind(this.currentroom, this.currentroom.die), 1000);
//this.currentroom.die();
this.currentroom.disable();
//this.leave_room(this.currentroom.url);
this.currentroom = false;
}
this.refresh();
}
this.load = function(url, makeactive, baseurl, stripreferrer) {
var roomname = url;
var room = this.spawn('janusroom', roomname, {
url: url,
janus: this,
baseurl: baseurl,
corsproxy: this.corsproxy,
deferload: true
}, makeactive && typeof makeactive != 'undefined');
if (this.currentroom && !stripreferrer) {
room.referrer = this.currentroom.url;
} else {
room.referrer = null;
}
elation.events.fire({element: this, type: 'room_load_start', data: room});
room.load();
// FIXME - should be able to spawn without adding to the heirarchy yet
this.remove(room);
if (this.networking) {
this.network.registerRoom(room, true);
}
this.rooms[room.roomid] = room;
//console.log('made new room', url, room);
this.loading = false;
if (room && makeactive) {
this.setActiveRoom(url, room.referrer);
}
this.initScripting();
return room;
}
this.loadFromSource = function(source, makeactive, baseurl) {
var dataurl = 'data:text/html,' + encodeURIComponent(source);
return this.load(dataurl, makeactive, baseurl)
}
this.setActiveRoom = function(url, referrer, skipURLUpdate) {
var oldroom = this.currentroom;
var room = false;
this.loading = true;
if (elation.utils.isString(url)) {
var roomid = this.roomid = this.getRoomId(url);
if (this.rooms[roomid]) {
room = this.rooms[roomid];
}
} else if (url.type == 'janusroom') {
room = url._target || url;
url = room.url;
}
var player = this.engine.client.player;
if (room) {
var changed = false;//this.properties.url != url;
if (!url) {
url = this.properties.homepage || this.properties.url;
} else {
this.properties.url = url;
}
if (oldroom !== room) {
// Set referrer so we know where this link came from
if (typeof referrer != 'undefined') {
room.referrer = referrer;
} else if (oldroom) {
room.referrer = oldroom.url;
}
this.clear();
this.currentroom = room;
window.room = this.currentroom.getProxyObject();
let urlhash = this.getRoomHash(url);
if (urlhash != room.urlhash) {
room.urlhash = urlhash;
}
player.setRoom(room);
this.add(this.currentroom);
this.currentroom.setActive();
this.properties.url = url;
this.loading = false;
changed = true;
elation.events.fire({element: this, type: 'room_change', data: url});
}
if (changed && !skipURLUpdate) {
this.updateClientURL(url);
}
if (!this.currentroom.loaded) {
if (this.currentroom.completed) {
// Room was previously loaded but deallocated, reload it
this.currentroom.load();
}
elation.events.add(this.currentroom, 'janus_room_load', elation.bind(this, function() {
this.currentroom.enable();
}));
} else {
this.currentroom.enable();
this.currentroom.setTitle(this.currentroom.title);
}
//this.enter_room(url);
} else {
this.load(url, true, null, referrer === false);
}
}
this.preload = function(url, stripreferrer) {
return this.load(url, false, null, stripreferrer);
}
this.createRoom = function(url, makeactive=true) {
let newroom = this.spawn('janusroom', url, {
url: url,
janus: this,
corsproxy: this.corsproxy,
deferload: true
}, !makeactive);
this.rooms[newroom.roomid] = newroom;
//console.log('made new room', url, room);
this.loading = false;
if (newroom && makeactive) {
this.setActiveRoom(newroom);
}
this.initScripting();
return newroom.getProxyObject();
}
this.getFixedURL = function(url) {
// Our 'clean' client URLs don't contain a : because many services have problems parsing them
var m = url.match(/^(https?)\/\/?(.*)$/i);
if (m) {
url = m[1] + '://' + m[2];
}
return url;
}
this.updateClientURL = function(url) {
if (this.urltemplate) {
var re = new RegExp(elation.template.get('janusweb.url', {url: '(.*)'}).replace('/', '\\/'));
var m = document.location.pathname.match(re);
if (m) {
var oldurl = this.getFixedURL(m[1]);
if (oldurl !== this.currentroom.url) {
var fullurl = elation.template.get('janusweb.url', {url: this.currentroom.url});
history.pushState({}, '', fullurl);
}
} else {
var fullurl = elation.template.get('janusweb.url', {url: this.currentroom.url});
history.pushState({}, '', fullurl);
}
} else {
var hashargs = elation.url();
if (url == this.properties.homepage) {
delete hashargs['janus.url'];
} else {
hashargs['janus.url'] = url;
}
var newhash = '#' + elation.utils.encodeURLParams(hashargs);
if (document.location.hash != newhash) {
document.location.hash = (newhash == '#' ? '' : newhash);
}
}
}
this.handlePopstate = function(ev) {
if (this.urltemplate) {
var re = new RegExp(elation.template.get('janusweb.url', {url: '(.*)'}).replace('/', '\\/'));
var m = document.location.pathname.match(re);
if (m) {
var url = this.getFixedURL(m[1]);
if (url != this.currentroom.url) {
this.setActiveRoom(url, this.currentroom.url, true);
}
}
} else {
var hashargs = elation.url();
var hashurl = hashargs['janus.url'];
if (hashurl && hashurl != this.properties.url && !this.loading) {
this.setActiveRoom(hashurl, this.currentroom.url, true);
} else if (!hashurl && this.properties.url != this.homepage) { // && !document.location.hash) {
this.setActiveRoom(this.homepage);
}
if (document.location.hash && document.location.hash.substr(1) in room.objects) {
let jumpto = room.objects[document.location.hash.substr(1)];
if (typeof jumpto.activate == 'function') {
jumpto.activate();
} else if (typeof jumpto.navigate == 'function') {
jumpto.navigate();
} else {
player.pos = jumpto.pos;
player.xdir.copy(jumpto.xdir);
player.ydir.copy(jumpto.ydir);
player.zdir.copy(jumpto.zdir);
}
}
}
}
this.showLoadURL = function(ev) {
if (ev.value == 1) {
this.engine.client.ui.urlbar.selectall();
this.engine.client.ui.urlbar.focus();
}
}
this.showRoomDebug = function(ev) {
if (ev.value == 1) {
this.currentroom.showDebug();
}
}
this.showChat = function(ev) {
if (ev.value == 1 && this.chat && document.activeElement != this.engine.client.ui.urlbar.inputelement) {
this.chat.focus();
}
}
this.addBookmark = function(ev) {
if (ev.value == 1) {
var room = {
url: this.currentroom.properties.url,
title: this.currentroom.title,
time: (new Date().getTime() / 1000)
};
this.bookmarks.add(room);
elation.events.fire({type: 'janusweb_bookmark_add', element: this, data: room});
}
}
this.mute = function(ev) {
if (!ev || ev.value == 1) {
this.engine.systems.sound.toggleMute();
}
}
this.setUsername = function(name) {
var player = this.engine.client.player;
player.setUsername(name);
}
this.getUsername = function() {
var player = this.engine.client.player;
return player.getUsername();
}
this.handleRoomEditOther = function(data) {
var roomId = data.roomId,
movedata = data.position,
edit = movedata.room_edit,
del = movedata.room_delete;
var room = this.rooms[roomId];
if (room) {
if (edit) {
var editxml = edit.replace(/\^/g, '"');
//console.log('RECV', editxml);
room.applyEditXML(editxml);
}
if (del) {
var deletexml = del.replace(/\^/g, '"');
room.applyDeleteXML(deletexml);
}
}
}
this.getCurrentURL = function() {
return this.properties.url;
}
this.navigateHome = function() {
return this.setActiveRoom(this.properties.homepage);
}
this.navigateBack = function() {
history.back();
}
this.navigateTo = function(url, referrer) {
return this.setActiveRoom(url, referrer);
}
this.navigateForward = function() {
history.forward();
}
this.getTriangleCount = function() {
return this.engine.systems.render.views.main.renderinfo.render.faces;
}
this.isLocked = function() {
return this.currentroom.properties.locked;
}
this.sendChatMessage = function(text) {
var network = this.currentroom.getServer();
network.send({'method': 'chat', data: text});
}
this.getSetting = function(name) {
return elation.utils.arrayget(this.settings, name);
}
this.setSetting = function(name, value) {
return elation.utils.arrayset(this.settings, name, value);
}
this.reset = function() {
}
this.quit = function() {
}
this.focus = function() {
}
this.blur = function() {
}
this.saveRoom = function() {
}
this.getRoomCode = function() {
}
this.setRoomCode = function(code) {
}
this.setAvatarLighting = function(lighting) {
}
this.getAvatarLighting = function() {
}
this.resetAvatar = function() {
}
this.hasFocus = function() {
return true;
}
this.sendScriptFrame = function(ev) {
this.scriptframeargs[0] = Math.round(ev.data.delta * 1000);
/*
this.engine.systems.world.scene['world-3d'].updateMatrixWorld(true);
//elation.events.fire({element: this.currentroom, type: 'janusweb_script_frame'});
if (this.currentroom.update) {
this.currentroom.update(1000/60);
}
*/
}
this.updatePortals = function() {
var portalpass = this.engine.systems.render.views.main.portalpass;
if (portalpass && this.currentroom) {
portalpass.portals = this.currentroom.getVisiblePortals();
}
}
this.registerElement = function(tagname, classobj, extendclass) {
if (!extendclass || !elation.engine.things[extendclass]) extendclass = 'janusbase';
tagname = tagname.toLowerCase();
//console.log('Register new SYSTEM tag type:', tagname, classobj, extendclass);
elation.component.add('engine.things.' + tagname, classobj, elation.engine.things[extendclass]);
/*
this.typemap[tagname] = tagname;
this.classmap[tagname] = {
class: classobj,
extendclass: extendclass
};
*/
this.customElements[tagname] = {
tagname: tagname,
classname: tagname,
class: classobj,
extendclass: extendclass
};
if (this.currentroom && this.currentroom.unknownElements[tagname]) {
var unknownElements = this.currentroom.unknownElements[tagname];
// console.log('Now we know about ' + tagname + ', so make some!', unknownElements);
for (var i = 0; i < unknownElements.length; i++) {
this.currentroom.createObject(tagname, unknownElements[i].args, unknownElements[i].parent);
}
delete this.currentroom.unknownElements[tagname];
}
}
this.extendElement = function(extendclass, tagname, classobj) {
this.registerElement(tagname, classobj, extendclass);
}
this.registerBuiltinElements = function(elements) {
for (var k in elements) {
var tagname = k.toLowerCase(),
classname = elements[k];
this.customElements[tagname] = {
tagname: tagname,
classname: classname,
class: elation.engine.things[classname]
};
}
}
this.registerAdditionalElements = function(elements) {
for (var k in elation.janusweb.elements) {
this.registerElement(k, elation.janusweb.elements[k].classdef);
}
}
this.getAsset = function(type, name, assetargs) {
var asset;
if (this.assetpack) {
asset = this.assetpack.get(type, name, assetargs);
}
if (!asset) {
asset = elation.engine.assets.find(type, name, true);
}
return asset;
}
this.getStartURL = function() {
var hashargs = elation.url();
return hashargs['janus.url'] || this.properties.url || this.properties.homepage;
}
this.getRoomId = function(url) {
let idx = url.indexOf('#');
if (idx != -1) {
return md5(url.substr(0, idx));
}
return md5(url);
}
this.getRoomHash = function(url) {
let idx = url.indexOf('#');
if (idx != -1) {
return url.substr(idx + 1);
}
return false;
}
this.toggleMenu = function(ev) {
if (ev.value != 1) return;
let overlay = this.engine.client.overlay;
console.log('toggle the menu', overlay, this.parent);
if (overlay) {
if (overlay.hidden) {
overlay.show();
} else {
overlay.hide();
}
}
if (document.pointerLockElement) {
document.exitPointerLock();
}
}
this.isVRActive = function() {
return (typeof XRSession != 'undefined' && this.engine.client.xrsession instanceof XRSession);
}
}, elation.engine.things.generic);
});