janusweb 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -278,6 +278,9 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
278
278
  this.refresh();
279
279
  }
280
280
  this.summarizeXML = function() {
281
+ // Without a tag there's no valid element to emit; returning markup here
282
+ // would produce a malformed nameless tag (e.g. "< />").
283
+ if (!this.tag) return '';
281
284
  let proxy = this.getProxyObject(),
282
285
 
283
286
  propdefs = this._thingdef.properties,
@@ -287,7 +290,9 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
287
290
  for (let k in proxydefs) {
288
291
  let proxydef = proxydefs[k],
289
292
  propdef = elation.utils.arrayget(propdefs, proxydef[1]);
290
- if ( k != 'room' && k != 'tagName' && k != 'classList' && proxydef[0] == 'property' && propdef) {
293
+ // jsid / classname are read-aliases of js_id / class; emitting them too
294
+ // would duplicate the attribute on every element.
295
+ if ( k != 'room' && k != 'tagName' && k != 'classList' && k != 'jsid' && k != 'classname' && proxydef[0] == 'property' && propdef) {
291
296
  let val = elation.utils.arrayget(this.properties, proxydef[1]);
292
297
  let defaultval = propdef.default;
293
298
  if (val instanceof THREE.Vector2) {
@@ -320,7 +325,16 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
320
325
  }
321
326
  }
322
327
  }
323
- let xml = '<' + this.tag.toLowerCase();
328
+ let tagname = this.tag.toLowerCase();
329
+ // A property whose name matches the tag is the element's content, not an
330
+ // attribute (<text>foo</text>, never <text text="foo" />). This mirrors the
331
+ // network serializer (getChanges) so source, save, and wire formats agree.
332
+ let content = null;
333
+ if (tagname in attrs) {
334
+ content = attrs[tagname];
335
+ delete attrs[tagname];
336
+ }
337
+ let xml = '<' + tagname;
324
338
  for (let k in attrs) {
325
339
  xml += ' ' + k + '="' + attrs[k] + '"';
326
340
  }
@@ -330,13 +344,17 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
330
344
  children.push(k);
331
345
  }
332
346
  }
347
+ let hascontent = (content !== null && content !== '');
333
348
  if (children.length > 0) {
334
349
  xml += '>\n';
350
+ if (hascontent) xml += ' ' + content + '\n';
335
351
  for (let i = 0; i < children.length; i++) {
336
352
  let k = children[i];
337
353
  xml += ' ' + this.children[k].summarizeXML().replace(/\n/g, '\n ').replace(/\s*$/, '\n');
338
354
  }
339
- xml += '</' + this.tag.toLowerCase() + '>\n';
355
+ xml += '</' + tagname + '>\n';
356
+ } else if (hascontent) {
357
+ xml += '>' + content + '</' + tagname + '>\n';
340
358
  } else {
341
359
  xml += ' />\n';
342
360
  }
@@ -61,7 +61,7 @@ elation.require([
61
61
  this.defineProperties({
62
62
  url: { type: 'string', default: false },
63
63
  homepage: { type: 'string', default: "" },
64
- corsproxy: { type: 'string', default: '' },
64
+ corsproxy: { type: 'string', default: false },
65
65
  showui: { type: 'boolean', default: true },
66
66
  shownavigation: { type: 'boolean', default: true },
67
67
  showchat: { type: 'boolean', default: true },
@@ -81,8 +81,19 @@ elation.require([
81
81
  this.bookmarks = elation.collection.indexed({index: 'url', storagekey: 'janusweb.bookmarks'});
82
82
  }
83
83
 
84
- if (this.corsproxy != '') {
85
- elation.engine.assets.setCORSProxy(this.corsproxy);
84
+ if (this.corsproxy !== false && this.corsproxy !== null && typeof this.corsproxy != 'undefined') {
85
+ // An explicit corsproxy attribute defines the full proxy set: a
86
+ // space-separated list becomes the rotation array, and the singular
87
+ // corsproxy is its first entry. The set is closed - the built-in
88
+ // proxy availability checks won't extend it. An empty or "false"
89
+ // value disables proxying entirely; omitting the attribute keeps
90
+ // the built-in defaults.
91
+ let corsproxies = (this.corsproxy === '' || this.corsproxy == 'false')
92
+ ? []
93
+ : String(this.corsproxy).split(' ').filter(url => url);
94
+ corsproxies.explicit = true;
95
+ elation.config.set('engine.assets.corsproxies', corsproxies);
96
+ elation.engine.assets.setCORSProxy(corsproxies[0] || '');
86
97
  }
87
98
  this.assetpack = elation.engine.assets.loadAssetPack(this.properties.datapath + 'assets.json', this.properties.datapath);
88
99
  this.parser = new JanusFireboxParser();
@@ -346,6 +357,16 @@ elation.require([
346
357
  return this.load(dataurl, makeactive, baseurl)
347
358
  }
348
359
  this.setActiveRoom = function(url, referrer, skipURLUpdate) {
360
+ // skip sameroom urls (otherwise it restarts audio when
361
+ // activating portals to internal/sameroom objects like '#locationB' e.g.)
362
+ if( room && url.replace(/#.*/,'') == room.url ){
363
+ // XR Fragments spec (Level1: URL) https://xrfragment.org/#teleport%20camera
364
+ // update hash or assume 'spawn' as default spawn objectlocation
365
+ room.urlhash = url.match('#') ? url.replace(/.*#/,'') : 'spawn'
366
+ room.setPlayerPosition()
367
+ return
368
+ }
369
+
349
370
  var oldroom = this.currentroom;
350
371
 
351
372
  var room = false;
package/scripts/object.js CHANGED
@@ -83,6 +83,9 @@ elation.require(['janusweb.janusbase', 'janusweb.websurface'], function() {
83
83
  emissive_intensity: { type: 'float', default: 1, set: this.updateMaterial, comment: 'Intensity of material emissive color' },
84
84
  roughness: { type: 'float', default: null, min: 0, max: 1, set: this.updateMaterial, comment: 'Material roughness value' },
85
85
  metalness: { type: 'float', default: null, set: this.updateMaterial, comment: 'Material metalness value' },
86
+ sheen: { type: 'float', default: null, min: 0, max: 1, set: this.updateMaterial, comment: 'Sheen intensity (velvet/fabric); MeshPhysicalMaterial only' },
87
+ sheen_color: { type: 'color', default: null, set: this.updateMaterial, comment: 'Sheen tint colour' },
88
+ sheen_roughness: { type: 'float', default: null, min: 0, max: 1, set: this.updateMaterial, comment: 'Sheen roughness' },
86
89
  transmission: { type: 'float', default: 0, set: this.updateMaterial, comment: 'Material transmission value' },
87
90
  usevertexcolors: { type: 'boolean', default: true, set: this.updateMaterial },
88
91
  gain: { type: 'float', default: 1.0, set: this.updateAudioNodes },
@@ -901,6 +904,17 @@ elation.require(['janusweb.janusbase', 'janusweb.websurface'], function() {
901
904
  if (this.transmission !== null) {
902
905
  m.transmission = this.transmission;
903
906
  }
907
+ // Sheen (velvet/fabric). MeshPhysicalMaterial only; m.sheen is undefined on other
908
+ // material types, so these are no-ops there. Attributes default to null (unset).
909
+ if (this.sheen !== null && m.sheen !== undefined) {
910
+ m.sheen = this.sheen;
911
+ }
912
+ if (this.sheen_roughness !== null && m.sheenRoughness !== undefined) {
913
+ m.sheenRoughness = this.sheen_roughness;
914
+ }
915
+ if (this.sheen_color && m.sheenColor) {
916
+ m.sheenColor.copy(this.sheen_color);
917
+ }
904
918
 
905
919
  if (this.isUsingPBR() && !this.isUsingToonShader()) {
906
920
  m.envMap = this.getEnvmap();
@@ -969,7 +983,9 @@ elation.require(['janusweb.janusbase', 'janusweb.websurface'], function() {
969
983
  } else {
970
984
  m.vertexColors = false
971
985
  }
972
- m.fog = this.fog;
986
+ if (!(m instanceof THREE.ShaderMaterial)) {
987
+ m.fog = this.fog;
988
+ }
973
989
  m.wireframe = this.wireframe;
974
990
  }
975
991
  } else if (n instanceof THREE.Light && !this.lights) {
@@ -1103,19 +1119,63 @@ elation.require(['janusweb.janusbase', 'janusweb.websurface'], function() {
1103
1119
  }
1104
1120
  */
1105
1121
 
1106
- if (this.shader_chunk_replace) {
1107
- let chunkreplace = (elation.utils.isString(this.shader_chunk_replace) ? JSON.parse(this.shader_chunk_replace) : this.shader_chunk_replace);
1122
+ let chunkreplace = (this.shader_chunk_replace ? (elation.utils.isString(this.shader_chunk_replace) ? JSON.parse(this.shader_chunk_replace) : this.shader_chunk_replace) : null);
1123
+ // Parallax-corrected (box-projected) envmap reflections: opt-in per room via
1124
+ // envmap_parallax + envmap_box_center/size. Anchors reflections to a box (e.g.
1125
+ // the box bottom = floor) rather than the default infinite/camera-centered
1126
+ // projection, so a reflective floor mirrors the walls at the right world
1127
+ // position. Box uniforms are shared per-room objects, so live edits to the box
1128
+ // update every reflection without a recompile. Default off => stock behavior.
1129
+ let parallax = (this.isUsingPBR() && this.room && this.room.envmap_parallax) ? this.room.getEnvmapParallaxUniforms() : null;
1130
+ if (parallax) this.registerParallaxChunk();
1131
+ if (chunkreplace || parallax) {
1108
1132
  m.onBeforeCompile = function(shader) {
1109
- for (let oldchunkname in chunkreplace) {
1110
- let newchunkname = chunkreplace[oldchunkname];
1111
- shader.vertexShader = shader.vertexShader.replace('#include <' + oldchunkname + '>', '#include <' + newchunkname + '>');
1112
- shader.fragmentShader = shader.fragmentShader.replace('#include <' + oldchunkname + '>', '#include <' + newchunkname + '>');
1133
+ if (chunkreplace) {
1134
+ for (let oldchunkname in chunkreplace) {
1135
+ let newchunkname = chunkreplace[oldchunkname];
1136
+ shader.vertexShader = shader.vertexShader.replace('#include <' + oldchunkname + '>', '#include <' + newchunkname + '>');
1137
+ shader.fragmentShader = shader.fragmentShader.replace('#include <' + oldchunkname + '>', '#include <' + newchunkname + '>');
1138
+ }
1139
+ }
1140
+ if (parallax) {
1141
+ shader.uniforms.boxProjMin = parallax.boxProjMin;
1142
+ shader.uniforms.boxProjMax = parallax.boxProjMax;
1143
+ shader.uniforms.boxProjCenter = parallax.boxProjCenter;
1144
+ shader.vertexShader = 'varying vec3 vParallaxWorldPos;\n' + shader.vertexShader.replace(
1145
+ '#include <project_vertex>',
1146
+ '#include <project_vertex>\n\tvParallaxWorldPos = ( modelMatrix * vec4( transformed, 1.0 ) ).xyz;'
1147
+ );
1148
+ shader.fragmentShader = shader.fragmentShader.replace(
1149
+ '#include <envmap_physical_pars_fragment>',
1150
+ '#include <envmap_physical_pars_fragment_parallax>'
1151
+ );
1113
1152
  }
1114
1153
  };
1115
1154
  }
1116
1155
  }
1117
1156
  return m;
1118
1157
  }
1158
+ this.registerParallaxChunk = function() {
1159
+ // A box-projected variant of three's envmap_physical_pars_fragment: after the
1160
+ // reflection vector is put into world space it's intersected with the box
1161
+ // (boxProjMin..boxProjMax) and re-based on boxProjCenter, so the CubeUV/PMREM
1162
+ // sample is parallax-corrected. Built by patching the stock chunk so it tracks
1163
+ // the exact r150 source; if the source ever changes and the anchor line isn't
1164
+ // found, this degrades to the stock (infinite) chunk rather than breaking.
1165
+ if (THREE.ShaderChunk['envmap_physical_pars_fragment_parallax']) return;
1166
+ let anchor = 'reflectVec = inverseTransformDirection( reflectVec, viewMatrix );';
1167
+ let patched = THREE.ShaderChunk['envmap_physical_pars_fragment']
1168
+ .replace('#if defined( USE_ENVMAP )',
1169
+ '#if defined( USE_ENVMAP )\n\tuniform vec3 boxProjMin;\n\tuniform vec3 boxProjMax;\n\tuniform vec3 boxProjCenter;\n\tvarying vec3 vParallaxWorldPos;')
1170
+ .replace(anchor, anchor +
1171
+ '\n\t\t\t{ vec3 nrd = normalize( reflectVec );' +
1172
+ '\n\t\t\t vec3 rbmax = ( boxProjMax - vParallaxWorldPos ) / nrd;' +
1173
+ '\n\t\t\t vec3 rbmin = ( boxProjMin - vParallaxWorldPos ) / nrd;' +
1174
+ '\n\t\t\t vec3 rbmm = vec3( ( nrd.x > 0.0 ) ? rbmax.x : rbmin.x, ( nrd.y > 0.0 ) ? rbmax.y : rbmin.y, ( nrd.z > 0.0 ) ? rbmax.z : rbmin.z );' +
1175
+ '\n\t\t\t float fa = min( min( rbmm.x, rbmm.y ), rbmm.z );' +
1176
+ '\n\t\t\t reflectVec = ( vParallaxWorldPos + nrd * fa ) - boxProjCenter; }');
1177
+ THREE.ShaderChunk['envmap_physical_pars_fragment_parallax'] = patched;
1178
+ }
1119
1179
  this.updateSkybox = function() {
1120
1180
  let envMap = (this.isUsingPBR() ? this.getEnvmap() : null);
1121
1181
  this.traverseObjects(n => {
package/scripts/room.js CHANGED
@@ -55,6 +55,13 @@ elation.require([
55
55
  'skybox_back_id': { type: 'string', set: this.setSkybox },
56
56
  'cubemap_irradiance_id': { type: 'string' },
57
57
  'cubemap_radiance_id': { type: 'string' },
58
+ // Envmap reflection projection. Default off = infinite (camera-centered)
59
+ // projection. When on, reflections are box-projected (parallax-corrected) to
60
+ // the box defined by envmap_box_center/size -- put the box bottom at the floor
61
+ // for clean floor reflections of the walls.
62
+ 'envmap_parallax': { type: 'boolean', default: false, set: this.updateParallax },
63
+ 'envmap_box_center': { type: 'vector3', default: new THREE.Vector3(0, 0, 0), set: this.updateParallax },
64
+ 'envmap_box_size': { type: 'vector3', default: new THREE.Vector3(10, 10, 10), set: this.updateParallax },
58
65
  'fog': { type: 'boolean', default: false, set: this.setFog },
59
66
  'fog_mode': { type: 'string', default: 'exp', set: this.setFog },
60
67
  'fog_density': { type: 'float', default: 1.0, set: this.setFog },
@@ -326,6 +333,35 @@ elation.require([
326
333
  }
327
334
  return spawnpoint;
328
335
  }
336
+ // Shared box-projection uniforms for parallax-corrected envmap reflections. One
337
+ // set of THREE uniform objects per room; every PBR material's parallax shader
338
+ // references these same objects, so live edits to envmap_box_center/size update
339
+ // all reflections in place (no material recompile). See object.registerParallaxChunk.
340
+ this.getEnvmapParallaxUniforms = function() {
341
+ if (!this._parallaxUniforms) {
342
+ this._parallaxUniforms = {
343
+ boxProjMin: { value: new THREE.Vector3() },
344
+ boxProjMax: { value: new THREE.Vector3() },
345
+ boxProjCenter: { value: new THREE.Vector3() },
346
+ };
347
+ }
348
+ this.updateParallaxUniforms();
349
+ return this._parallaxUniforms;
350
+ }
351
+ this.updateParallaxUniforms = function() {
352
+ if (!this._parallaxUniforms) return;
353
+ let c = this.envmap_box_center || new THREE.Vector3();
354
+ let s = this.envmap_box_size || new THREE.Vector3(10, 10, 10);
355
+ this._parallaxUniforms.boxProjCenter.value.copy(c);
356
+ this._parallaxUniforms.boxProjMin.value.set(c.x - s.x / 2, c.y - s.y / 2, c.z - s.z / 2);
357
+ this._parallaxUniforms.boxProjMax.value.set(c.x + s.x / 2, c.y + s.y / 2, c.z + s.z / 2);
358
+ }
359
+ this.updateParallax = function() {
360
+ // Live box edits mutate the shared uniforms in place -> all reflections follow
361
+ // without a recompile. (Enabling/disabling envmap_parallax itself is applied at
362
+ // material-build time / room load.)
363
+ this.updateParallaxUniforms();
364
+ }
329
365
  this.setSkybox = function() {
330
366
  if (!this.loaded) return;
331
367
 
@@ -818,24 +854,48 @@ elation.require([
818
854
  room[k] = val;
819
855
  }
820
856
  }
821
- if (roomdata.object) {
822
- for (let i = 0; i < roomdata.object.length; i++) {
823
- let objdata = roomdata.object[i];
857
+ // Apply edits to every element type, not just <Object>. The parser groups
858
+ // <text>, <image>, <link>, etc. under their own keys, so iterating only
859
+ // `roomdata.object` silently dropped source edits to all other elements.
860
+ var skiptypes = ['assets', 'room', 'source'];
861
+ for (let type in roomdata) {
862
+ if (skiptypes.indexOf(type) != -1) continue;
863
+ let objs = roomdata[type];
864
+ if (!elation.utils.isArray(objs)) objs = [objs];
865
+ for (let i = 0; i < objs.length; i++) {
866
+ let objdata = objs[i];
824
867
  let roomobj = room.objects[objdata.js_id];
825
868
  if (roomobj) {
826
869
  for (let k in objdata) {
870
+ // `orientation` is derived by the parser from xdir/ydir/zdir and
871
+ // defaults to identity when only `rotation` is authored, so it
872
+ // disagrees with `rotation`. Applying both fights; the authored
873
+ // rotation / direction attributes are what drive orientation.
874
+ if (k === 'orientation' || k === '_content') continue;
827
875
  let val = objdata[k];
828
- if (roomobj[k] != objdata[k] && val !== null) {
876
+ if (roomobj[k] != val && val !== null) {
877
+ // Flag dirty so source edits broadcast to other clients; set
878
+ // per change because onThingChange clears sync once tracked.
879
+ roomobj.sync = true;
829
880
  roomobj[k] = val;
830
881
  }
831
882
  }
883
+ // Elements that carry their value as tag content (<text>foo</text>)
884
+ // parse to `_content`; the receiving property is named after the tag.
885
+ if (objdata._content != null && roomobj[type] != objdata._content) {
886
+ roomobj.sync = true;
887
+ roomobj[type] = objdata._content;
888
+ }
832
889
  } else {
833
890
  objdata.persist = true;
834
- this.createObject('object', objdata);
891
+ if (objdata._content != null && objdata[type] == null) objdata[type] = objdata._content;
892
+ this.createObject(type, objdata);
835
893
  }
836
894
  }
837
895
  }
838
896
  }
897
+ // Editor surfaces (scene tree, inspector, source view) refresh from this.
898
+ this.notifySceneChanged();
839
899
  }
840
900
 
841
901
  this.loadRoomAssets = function(roomdata) {
@@ -988,7 +1048,11 @@ elation.require([
988
1048
 
989
1049
  if (room.cubemap_radiance_id) this.properties.cubemap_radiance_id = room.cubemap_radiance_id;
990
1050
  if (room.cubemap_irradiance_id) this.properties.cubemap_irradiance_id = room.cubemap_irradiance_id;
991
-
1051
+
1052
+ if (typeof room.envmap_parallax != 'undefined') this.envmap_parallax = room.envmap_parallax;
1053
+ if (room.envmap_box_center) this.envmap_box_center = room.envmap_box_center;
1054
+ if (room.envmap_box_size) this.envmap_box_size = room.envmap_box_size;
1055
+
992
1056
  this.setSkybox();
993
1057
 
994
1058
  if (room.server) this.properties.server = room.server;
@@ -1282,6 +1346,9 @@ elation.require([
1282
1346
  this.applyingEdits = false;
1283
1347
  this.appliedchanges = {};
1284
1348
 
1349
+ // Editor surfaces reflect remote edits (onThingChange is gated out while
1350
+ // applyingEdits, so signal here instead).
1351
+ this.notifySceneChanged();
1285
1352
  }
1286
1353
  this.applyDeleteXML = function(deletexml) {
1287
1354
  var del = elation.utils.parseXML(deletexml);
@@ -1913,6 +1980,18 @@ elation.require([
1913
1980
  soundsystem.enableSound();
1914
1981
  }
1915
1982
  }
1983
+ // Room-level "scene changed" signal that editor surfaces subscribe to
1984
+ // (source-view auto-refresh, scene-tree/inspector reflect). Trailing-
1985
+ // debounced so a gizmo drag or burst of edits coalesces into one fire on
1986
+ // settle. Only the edit paths call it, so animation/physics ticks never
1987
+ // reach here; structural changes are signalled via thing_add/thing_remove.
1988
+ this.notifySceneChanged = function() {
1989
+ clearTimeout(this._sceneChangedTimer);
1990
+ this._sceneChangedTimer = setTimeout(elation.bind(this, function() {
1991
+ // Fire on the proxy (window.room) since editor surfaces listen there.
1992
+ elation.events.fire({type: 'scene_changed', element: this.getProxyObject()});
1993
+ }), 150);
1994
+ }
1916
1995
  this.onThingChange = function(ev) {
1917
1996
  if (this.applyingEdits) return;
1918
1997
 
@@ -1928,6 +2007,7 @@ elation.require([
1928
2007
  this.changes[js_id] = proxy;
1929
2008
  }
1930
2009
  thing.properties.sync = proxy.autosync;
2010
+ this.notifySceneChanged(); // a real edit (sync set) -> refresh editor surfaces
1931
2011
  }
1932
2012
  }
1933
2013
  }
@@ -1945,6 +2025,7 @@ elation.require([
1945
2025
  if (this.jsobjects[thing.js_id]) {
1946
2026
  delete this.jsobjects[thing.js_id];
1947
2027
  }
2028
+ this.notifySceneChanged();
1948
2029
  }
1949
2030
  this.onRoomEdit = function(ev) {
1950
2031
  var thing = ev.data;
@@ -2184,6 +2265,9 @@ elation.require([
2184
2265
  skybox_down_id: ['property', 'skybox_down_id'],
2185
2266
  skybox_front_id:['property', 'skybox_front_id'],
2186
2267
  skybox_back_id: ['property', 'skybox_back_id'],
2268
+ envmap_parallax: ['property', 'envmap_parallax'],
2269
+ envmap_box_center: ['property', 'envmap_box_center'],
2270
+ envmap_box_size: ['property', 'envmap_box_size'],
2187
2271
 
2188
2272
  pendingScripts: ['property', 'pendingScripts'],
2189
2273
  pendingCustomElements: ['property', 'pendingCustomElements'],
@@ -2590,7 +2674,11 @@ elation.require([
2590
2674
  //for (var k in this.jsobjects) {
2591
2675
  for (let k in this.children) {
2592
2676
  var object = this.children[k];
2593
- if (object.janus && typeof object.summarizeXML == 'function') {
2677
+ // Only persistent room objects belong in the source. Transient children
2678
+ // (the local player, remote-player ghosts, cursors) are not part of the
2679
+ // room markup and have no JanusML tag, so serializing them would emit
2680
+ // malformed nameless tags.
2681
+ if (object.persist && object.janus && typeof object.summarizeXML == 'function') {
2594
2682
  var markup = ' ' + object.summarizeXML().replace(/\n/g, '\n ').replace(/\s*$/, '\n');
2595
2683
  objectsrc += markup;
2596
2684
  }
@@ -2603,6 +2691,24 @@ elation.require([
2603
2691
 
2604
2692
  return roomsrc;
2605
2693
  }
2694
+ // Per-object serialization of the persistent top-level objects, for editors
2695
+ // that reconcile an authored source against the live scene without
2696
+ // regenerating the whole document.
2697
+ this.getObjectSummaries = function() {
2698
+ var out = [];
2699
+ for (let k in this.children) {
2700
+ var object = this.children[k];
2701
+ if (object.persist && object.janus && typeof object.summarizeXML == 'function' && object.tag) {
2702
+ out.push({
2703
+ js_id: object.js_id,
2704
+ id: object.id,
2705
+ name: String(object.tag).toLowerCase(),
2706
+ xml: object.summarizeXML().replace(/\s+$/, '')
2707
+ });
2708
+ }
2709
+ }
2710
+ return out;
2711
+ }
2606
2712
 
2607
2713
  // FIXME - room should inherit from janusbase and get this automatically
2608
2714
  this.addEventListenerProxy = function(name, handler, bubble) {
@@ -1,15 +0,0 @@
1
- // Meta Quest 2 results in rapid flickering when loading a new room
2
- // therefore we show the shroud while loading
3
- setActiveRoom = room.janus.setActiveRoom.bind(room.janus)
4
- room.janus.setActiveRoom = function(url,referer,skipURL){
5
- if( url.replace(/#.*/,'') != room.url ){
6
- room.fadeAudioOut()
7
- setTimeout( function(){
8
- setActiveRoom.apply(room.janus, [url, referer, skipURL])
9
- }, 500)
10
- }else{
11
- room.urlhash = url.match('#') ? url.replace(/.*#/,'') : 'spawn'
12
- room.setPlayerPosition()
13
- // ignore same-room setActiveRoom-calls (it restarts audio when clicking internal hyperlinks)
14
- }
15
- }