janusweb
Version:
Web client for JanusVR worlds
366 lines (348 loc) • 13 kB
JavaScript
JanusFireboxParser = function() {
}
JanusFireboxParser.prototype.parse = function(source, baseurl, datapath) {
var xml = this.parseXML(source, false, true);
var rooms = this.getAsArray(this.arrayget(xml, 'fireboxroom._children.room', {}));
var room = {_children: {}};
for (var i = 0; i < rooms.length; i++) {
var attrs = Object.keys(rooms[i]).filter(function(k) { return (k[0] != '_'); });
attrs.forEach(function(k) {
room[k] = rooms[i][k];
});
if (rooms[i]._children) {
Object.keys(rooms[i]._children).forEach(function(k) {
room._children[k] = rooms[i]._children[k];
});
}
}
var roomdata = this.getRoomData(xml, room, baseurl, datapath);
roomdata.source = source;
return roomdata;
}
JanusFireboxParser.prototype.getRoomData = function(xml, room, baseurl, datapath) {
var assets = this.parseAssets(xml, baseurl, datapath);
var parseNode = this.parseNode.bind(this);
var roomdata = {
assets: assets,
room: this.parseNode(room)
};
for (var k in room._children) {
var objects = this.getAsArray(room._children[k]);
roomdata[k] = objects.map(parseNode);
}
// TODO - some rooms have orphan objects, outside of the <Room> tag.
// We should parse these too, but I don't have any examples to test with
/*
var orphantypes = ['link', 'image', 'image3d', 'video', 'sound', 'text', 'paragraph', 'light', 'particle', 'ghost'];
for (var i = 0; i < orphantypes.length; i++) {
var type = orphantypes[i],
orphans = this.getAsArray(this.arrayget(xml, 'fireboxroom._children.' + type));
if (orphans && orphans.length > 0) {
if (!roomdata[type]) roomdata[type] = [];
roomdata[type].push.apply(roomdata[type], orphans);
}
};
*/
return roomdata;
}
JanusFireboxParser.prototype.getAsArray = function(arr) {
return (arr instanceof Array ? arr : [arr]);
}
JanusFireboxParser.prototype.parseAssets = function(xml, baseurl, datapath) {
var assetxml = this.arrayget(xml, 'fireboxroom._children.assets.0', {});
var objectassets = this.getAsArray(this.arrayget(assetxml, "_children.assetobject", []));
var soundassets = this.getAsArray(this.arrayget(assetxml, "_children.assetsound", []));
var imageassets = this.getAsArray(this.arrayget(assetxml, "_children.assetimage", []));
var videoassets = this.getAsArray(this.arrayget(assetxml, "_children.assetvideo", []));
var scriptassets = this.getAsArray(this.arrayget(assetxml, "_children.assetscript", []));
var ghostassets = this.getAsArray(this.arrayget(assetxml, "_children.assetghost", []));
var websurfaceassets = this.getAsArray(this.arrayget(assetxml, "_children.assetwebsurface", []));
var shaderassets = this.getAsArray(this.arrayget(assetxml, "_children.assetshader", []));
var fontassets = this.getAsArray(this.arrayget(assetxml, "_children.assetfont", []));
var assetlist = [];
if (!datapath) {
datapath = 'http://web.janusvr.com/media';
}
var fixURLEncoding = this.fixURLEncoding.bind(this);
imageassets.forEach(function(n) {
var src = fixURLEncoding(n.src.match(/^file:/) ? n.src.replace(/^file:/, datapath) : n.src);
assetlist.push({
assettype: 'image',
name: n.id,
src: src,
baseurl: baseurl,
hasalpha: ('hasalpha' in n ? n.hasalpha == "true" || n.hasalpha === true || n.hasalpha === 1 || n.hasalpha === '1' : null),
proxy: n.proxy,
vr180: n.vr180,
sbs3d: n.sbs3d,
ou3d: n.ou3d,
flipy: n.flipy,
reverse3d: n.reverse3d,
tex_linear: n.tex_linear,
equi: n.equi,
srgb: n.srgb,
});
});
videoassets.forEach(function(n) {
var src = fixURLEncoding(n.src.match(/^file:/) ? n.src.replace(/^file:/, datapath) : n.src);
assetlist.push({
assettype:'video',
name:n.id,
src: src,
loop: (n.loop && n.loop != 'false' && n.loop != '0'),
vr180: n.vr180,
sbs3d: n.sbs3d == 'true',
ou3d: n.ou3d == 'true',
hasalpha: n.hasalpha == 'true',
flipy: n.flipy,
reverse3d: n.reverse3d,
auto_play: n.auto_play == 'true',
baseurl: baseurl,
proxy: n.proxy && n.proxy != 'false'
});
});
soundassets.forEach(function(n) {
var src = fixURLEncoding(n.src.match(/^file:/) ? n.src.replace(/^file:/, datapath) : n.src);
assetlist.push({
assettype:'sound',
name:n.id,
src: src,
baseurl: baseurl
});
});
var websurfaces = {};
websurfaceassets.forEach(function(n) {
websurfaces[n.id] = n;
assetlist.push(n);
});
scriptassets.forEach(function(n) {
var src = fixURLEncoding(n.src.match(/^file:/) ? n.src.replace(/^file:/, datapath) : n.src);
assetlist.push({
assettype:'script',
name: src,
src: src,
baseurl: baseurl
});
});
shaderassets.forEach(function(n) {
n.uniforms = (n._children && n._children.uniform ? n._children.uniform : false);
assetlist.push({
assettype: 'shader',
name: n.id,
shadertype: n.shadertype || 'default',
fragment_src: n.src || n.fragment_src,
vertex_src: n.vertex_src,
hasalpha: n.hasalpha,
uniforms: n.uniforms,
baseurl: baseurl,
});
});
fontassets.forEach(function(n) {
assetlist.push({
assettype: 'font',
name: n.id,
src: n.src,
baseurl: baseurl,
});
});
var objlist = [];
objectassets.forEach(function(n) {
if (n.src) {
var src = fixURLEncoding(n.src.match(/^file:/) ? n.src.replace(/^file:/, datapath) : n.src);
var mtlsrc = (n.mtl && n.mtl.match(/^file:/) ? n.mtl.replace(/^file:/, datapath) : n.mtl);
if (mtlsrc && !mtlsrc.match(/^(https?:)?\/\//)) mtlsrc = baseurl + mtlsrc;
var srcparts = src.split(' ');
src = srcparts[0];
assetlist.push({assettype: 'model', name: n.id, src: src, mtl: mtlsrc, tex_linear: n.tex_linear, tex0: n.tex || n.tex0 || srcparts[1], tex1: n.tex1 || srcparts[2], tex2: n.tex2 || srcparts[3], tex3: n.tex3 || srcparts[4]});
}
});
var assets = {
object: objectassets,
image: imageassets,
sound: soundassets,
video: videoassets,
websurfaces: websurfaces,
scripts: scriptassets,
ghosts: ghostassets,
shaders: shaderassets,
assetlist: assetlist
};
return assets;
}
JanusFireboxParser.prototype.getVectorValue = function(vector, defaultvalue) {
if (typeof defaultvalue == 'undefined') {
defaultvalue = null;//[0,0,0];
}
if (typeof vector == 'string') {
return vector.trim().split(' ').map(parseFloat);
} else if (vector instanceof THREE.Vector3) {
return vector.toArray();
} else if (typeof vector == 'undefined') {
return defaultvalue;
}
return vector;
}
JanusFireboxParser.prototype.parseNode = function(n) {
var nodeinfo = {};
var attrs = Object.keys(n);
attrs.forEach(function(k) {
nodeinfo[k] = (n[k] == 'false' ? false : n[k]);
});
nodeinfo.pos = this.getVectorValue(n.pos);
nodeinfo.scale = this.getVectorValue(n.scale, [1,1,1]);
nodeinfo.orientation = this.getOrientation(n.xdir, n.ydir || n.up, n.zdir || n.fwd);
nodeinfo.col = (n.col ? (n.col[0] == '#' ? [parseInt(n.col.substr(1,2), 16)/255, parseInt(n.col.substr(3, 2), 16)/255, parseInt(n.col.substr(5, 2), 16)/255] : n.col) : null);
var minscale = 1e-20;
/*
nodeinfo.scale[0] = Math.max(minscale, nodeinfo.scale[0]);
nodeinfo.scale[1] = Math.max(minscale, nodeinfo.scale[1]);
nodeinfo.scale[2] = Math.max(minscale, nodeinfo.scale[2]);
*/
if (nodeinfo.scale[0] < minscale && nodeinfo.scale[0] > -minscale) nodeinfo.scale[0] = minscale;
if (nodeinfo.scale[1] < minscale && nodeinfo.scale[1] > -minscale) nodeinfo.scale[1] = minscale;
if (nodeinfo.scale[2] < minscale && nodeinfo.scale[2] > -minscale) nodeinfo.scale[2] = minscale;
return nodeinfo;
}
JanusFireboxParser.prototype.getOrientation = function(xdir, ydir, zdir) {
if (xdir) {
xdir = (xdir instanceof THREE.Vector3 ? xdir : new THREE.Vector3().fromArray(xdir.split(' '))).normalize();
} else {
xdir = new THREE.Vector3(1,0,0);
}
if (ydir) {
ydir = (ydir instanceof THREE.Vector3 ? ydir : new THREE.Vector3().fromArray(ydir.split(' '))).normalize();
} else {
ydir = new THREE.Vector3(0,1,0);
}
if (zdir) {
zdir = (zdir instanceof THREE.Vector3 ? zdir : new THREE.Vector3().fromArray(zdir.split(' '))).normalize();
} else {
zdir = new THREE.Vector3(0,0,1);
}
var newydir = ydir.clone().sub(zdir).multiplyScalar(ydir.dot(zdir)).normalize();
xdir.crossVectors(ydir, zdir).normalize();
var mat4 = new THREE.Matrix4().makeBasis(xdir, ydir, zdir);
var quat = new THREE.Quaternion();
var pos = new THREE.Vector3();
var scale = new THREE.Vector3();
//quat.setFromRotationMatrix(mat4);
mat4.decompose(pos, quat, scale);
quat.normalize();
//quat.multiply(new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0)));
return quat;
}
JanusFireboxParser.prototype.parseXML = function(imgxml, leaf, forceLower) {
var node, root, parent, xmldoc;
if (imgxml.nodeName) {
xmldoc = node = imgxml;
} else {
try {
if (window.DOMParser) {
var parser = new DOMParser();
xmldoc = parser.parseFromString(imgxml,"application/xml");
node = xmldoc.children[0];
} else {
node = new ActiveXObject("Microsoft.XMLDOM");
node.async = "false";
node.loadXML(imgxml).firstChild;
}
// Chrome doesn't throw an exception for malformed XML, so we look for a <parsererror> xml tag
// Firefox now appears to follow the same behavior, but puts the <parsererror> as the top-level element instead of as a child of the last DOM element which parsed successfully
var parsererrors = (node.tagName == 'parsererror' ? [node] : node.getElementsByTagName("parsererror"));
if (parsererrors.length > 0) {
// Extract the message from the first div child of the <parsererror> element
var errorel = parsererrors[0].getElementsByTagName('div')[0] || parsererrors[0];
throw new JanusFireboxParserException(errorel.innerHTML);
}
} catch (e) {
throw new JanusFireboxParserException(e.message);
}
}
root = {};
if (!leaf) {
var rootname = node.tagName;
if (forceLower) rootname = rootname.toLowerCase();
if (!root[rootname]) root[rootname] = {};
parent = root[rootname];
//node = parent[node.tagName];
} else {
parent = root;
}
if (node.attributes) {
for (var i = 0; i < node.attributes.length; i++) {
var name = node.attributes[i].nodeName;
if (forceLower) name = name.toLowerCase();
var value = node.attributes[i].nodeValue;
parent[name] = value;
}
}
if (node.childNodes) {
for (var j = 0; j < node.childNodes.length; j++) {
var child = node.childNodes[j];
var nodename = child.nodeName;
if (forceLower) nodename = nodename.toLowerCase();
if (!parent._children) parent._children = {};
if (!parent._children[nodename]) {
parent._children[nodename] = [];
} else if (parent._children[nodename].constructor !== Array) {
parent._children[nodename] = [parent._children[nodename]];
}
if (node.getElementsByTagName(child.tagName).length > 0) {
parent._children[nodename].push(this.parseXML(child, true, forceLower));
} else if (child.nodeName) {
if (child.nodeName == "#text" || child.nodeName == "#cdata-section") {
// this gets confused if you have multiple text/cdata nodes...
if (!child.nodeValue.match(/^[\s\n]*$/s)) {
parent._content = child.nodeValue;
}
} else {
parent._children[nodename] = parent._children[nodename].concat(this.parseXML(child, true, forceLower));
}
}
}
}
return root;
};
/**
* Retrieves specified dot-separated value from a multilevel object element
*
* @function JanusFireboxParser.arrayget
* @param {object} obj
* @param {string} name
* @param {object|number|string} [defval] default value if none found
*/
JanusFireboxParser.prototype.arrayget = function(obj, name, defval) {
var ptr = obj;
var x = name.split(".");
for (var i = 0; i < x.length; i++) {
if (ptr==null || (!(ptr[x[i]] instanceof Array) && !(ptr[x[i]] instanceof Object) && i != x.length-1)) {
ptr = null;
break;
}
ptr = ptr[x[i]];
}
if (typeof ptr == "undefined" || ptr === null) {
return (typeof defval == "undefined" ? null : defval);
}
return ptr;
};
/**
* Fixes encoding of URL strings if they weren't properly encoded
*
* @function JanusFireboxParser.fixURLEncoding
* @param {string} url
* @returns {string} encoded URL
*/
JanusFireboxParser.prototype.fixURLEncoding = function(url) {
var fixed = url;
if (url.indexOf(' ') != -1) {
fixed = encodeURI(url);
}
return fixed;
};
/**
* Exception class for XML parse errors
*/
function JanusFireboxParserException(msg) {
this.message = msg;
}