janusweb 1.5.56 → 1.7.0
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.
- package/README.md +48 -12
- package/media/assets/webui/apps/editor/editor.js +13 -0
- package/media/assets/webui/apps/locomotion/locomotion-assets.json +3 -0
- package/media/assets/webui/apps/locomotion/teleport.mp3 +0 -0
- package/media/assets/webui/apps/locomotion/teleporter.js +75 -43
- package/media/assets/webui/apps/navigation/navigation.js +5 -0
- package/media/assets/webui/apps/xrmenu/images/teleport.png +0 -0
- package/media/assets/webui/apps/xrmenu/xrmenu-assets.json +2 -1
- package/media/assets/webui/apps/xrmenu/xrmenu.js +39 -15
- package/package.json +1 -1
- package/scripts/janusbase.js +13 -22
- package/scripts/janusghost.js +2 -2
- package/scripts/janusparagraph.js +39 -3
- package/scripts/janusplayer.js +9 -6
- package/scripts/janusxrplayer.js +6 -4
- package/scripts/room.js +36 -12
- package/scripts/sound.js +1 -1
- package/scripts/text.js +2 -1
- package/scripts/translators/xrfragments.js +93 -0
- package/utils/clean-build.sh +5 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center"><a href="https://web.janusvr.com" target="_blank"><img width="480" alt="JanusWeb" src="https://imgur.com/ejvyphR.jpg"></a></p>
|
|
2
2
|
|
|
3
|
-
<p align="center"><b>A web framework for building
|
|
3
|
+
<p align="center"><b>A web framework/browser for building rich virtual reality experiences.</b></p>
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
<a href="https://janusvr.com">Site</a>
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
—
|
|
12
12
|
<a href="https://web.janusvr.com">Demo</a>
|
|
13
13
|
—
|
|
14
|
-
<a href="https://github.
|
|
14
|
+
<a href="https://coderofsalvation.github.io/janus-guide/">Docs</a>
|
|
15
|
+
—
|
|
16
|
+
<a href="https://discord.gg/7eyK2wE">Discord</a>
|
|
15
17
|
</div>
|
|
16
18
|
|
|
17
19
|
|
|
@@ -39,20 +41,24 @@
|
|
|
39
41
|
|
|
40
42
|
## Features
|
|
41
43
|
|
|
44
|
+
- [Open Immersive Web-layer](https://madjin.github.io/janus-guide/#/home/about) for browsing XR experiences across the web
|
|
42
45
|
- Build immersive 3D environments for desktop, mobile, and VR devices using HTML and JS
|
|
43
46
|
- Rendering functionality provided by Three.js / WebGL
|
|
47
|
+
- Separately host WebXR experiences via ([JML](https://madjin.github.io/janus-guide/#/examples/markup) in) any textfile.
|
|
44
48
|
- Oculus Rift, Vive, GearVR, Daydream, and Cardboard support via WebVR API
|
|
45
|
-
- Realtime collaboration across all devices via built-in networking
|
|
49
|
+
- [Realtime collaboration](https://madjin.github.io/janus-guide/#/home/usecases) across all devices via built-in networking
|
|
46
50
|
- Import Collada, OBJ, glTF, and other popular 3d file formats
|
|
47
51
|
- 3D positional audio
|
|
48
52
|
- Gamepad support via the HTML5 Gamepad API
|
|
49
53
|
- Supports hand tracking peripherals like Leap Motion, Oculus Touch, and Vive controllers
|
|
50
54
|
- Support for 2d, sbs3d/ou3d, and 360 degree video textures using HTML5 Video
|
|
51
55
|
- Scriptable client enables many customized uses
|
|
56
|
+
- And [much more](https://madjin.github.io/janus-guide/#/home/toc)
|
|
57
|
+
|
|
52
58
|
|
|
53
59
|
## Using
|
|
54
60
|
|
|
55
|
-
There are several different ways to use JanusWeb, depending on how much control you want to
|
|
61
|
+
There are [several different ways](https://madjin.github.io/janus-guide/#/home/usecases) to use JanusWeb, depending on how much control you want to
|
|
56
62
|
have over the whole system.
|
|
57
63
|
|
|
58
64
|
### Use our viewer
|
|
@@ -126,7 +132,9 @@ JanusWeb supports several arguments at initialization time to control how it beh
|
|
|
126
132
|
<table>
|
|
127
133
|
<tr><th> Name </th><th> Description </th><th> Default </th></tr>
|
|
128
134
|
|
|
129
|
-
<tr><td>
|
|
135
|
+
<tr><td> autostart </td><td> call `elation.janusweb.init({..})` automatically or manually </td><td> true </td></tr>
|
|
136
|
+
|
|
137
|
+
<tr><td> autoload </td><td> Load URL by default or wait for script </td><td> true </td></tr>
|
|
130
138
|
|
|
131
139
|
<tr><td> crosshair </td><td> Show player crosshair </td><td> true </td></tr>
|
|
132
140
|
|
|
@@ -142,10 +150,6 @@ JanusWeb supports several arguments at initialization time to control how it beh
|
|
|
142
150
|
|
|
143
151
|
<tr><td> server </td><td> Presence server to connect to </td><td> wss://presence.janusvr.com:5567/</td></tr>
|
|
144
152
|
|
|
145
|
-
<tr><td> shownavigation </td><td> Control visibility of navigation bar </td><td> true </td></tr>
|
|
146
|
-
|
|
147
|
-
<tr><td> showchat </td><td> Control visibility of chat </td><td> true </td></tr>
|
|
148
|
-
|
|
149
153
|
<tr><td> stats </td><td> Enable render performance stats </td><td> false </td></tr>
|
|
150
154
|
|
|
151
155
|
<tr><td> url </td><td> Default page to load </td><td> (homepage) </td></tr>
|
|
@@ -160,12 +164,18 @@ JanusWeb supports several arguments at initialization time to control how it beh
|
|
|
160
164
|
for Opus via WebAudio) </td><td> false </td></tr>
|
|
161
165
|
</table>
|
|
162
166
|
|
|
167
|
+
> NOTE: see [client.js](https://github.com/jbaicoianu/janusweb/blob/master/scripts/client.js#L65) for the most up to date arguments
|
|
168
|
+
|
|
163
169
|
## Scripting
|
|
164
170
|
After initializing the client, `elation.janusweb.init()` returns a Promise which provides a reference to the client.
|
|
165
171
|
You can programatically control this client to do all sorts of things. For instance, we can make the client load a
|
|
166
172
|
URL, wait for the world and all of its assets to load, and then take a screenshot of the world after a specified delay:
|
|
167
173
|
|
|
168
174
|
```javascript
|
|
175
|
+
<janus-viewer autostart="false"> <!-- disable autostart, we will -->
|
|
176
|
+
</janus-viewer> <!-- call elation.janusweb.init() -->
|
|
177
|
+
|
|
178
|
+
<script>
|
|
169
179
|
var pageinfo = elation.utils.parseURL(document.location.href),
|
|
170
180
|
urlargs = pageinfo.args || {},
|
|
171
181
|
hashargs = pageinfo.hash || {};
|
|
@@ -173,11 +183,11 @@ var pageinfo = elation.utils.parseURL(document.location.href),
|
|
|
173
183
|
var url = elation.utils.any(hashargs.url, urlargs.url, 'http://www.janusvr.com/index.html'),
|
|
174
184
|
delay = elation.utils.any(hashargs.delay, urlargs.delay, 1000);
|
|
175
185
|
|
|
176
|
-
elation.janusweb.init({
|
|
186
|
+
elation.janusweb.init({
|
|
177
187
|
url: url,
|
|
178
188
|
resolution: '1920x1080',
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
uiconfig: "./media/assets/webui/default.json" // tweak ui here
|
|
190
|
+
// more options at https://github.com/jbaicoianu/janusweb/blob/master/scripts/client.js#L93
|
|
181
191
|
}).then(function(client) {
|
|
182
192
|
elation.events.add(client.janusweb.currentroom, 'room_load_complete', function() {
|
|
183
193
|
setTimeout(function() {
|
|
@@ -188,7 +198,9 @@ elation.janusweb.init({
|
|
|
188
198
|
});
|
|
189
199
|
}, delay);
|
|
190
200
|
});
|
|
201
|
+
elation.janusweb.init = function(){} // init only once
|
|
191
202
|
});
|
|
203
|
+
</script>
|
|
192
204
|
|
|
193
205
|
```
|
|
194
206
|
|
|
@@ -209,6 +221,30 @@ If you need to load a specific version of JanusWeb, all previous versions are st
|
|
|
209
221
|
|
|
210
222
|
JanusWeb versions follow the [Semantic Versioning 2.0.0](http://semver.org/) spec, which follows the format ```<major>.<minor>.<patch>``` - for example, at the time of writing (March 2017) the current stable release is **1.0.15**. So if you want to view this version, you could go to https://web.janusvr.com/1.0.15/ and if you wanted to pull this specific version into your page, you could do so with ```<script src="https://web.janusvr.com/1.0.15/janusweb.js"></script>```. We also support aliases for the most current version - for instance, https://web.janusvr.com/1.0/ will always refer to the most recent 1.0 release, https://web.janusvr.com/0.9/ the final 0.9 release, etc.
|
|
211
223
|
|
|
224
|
+
## Ecosystem
|
|
225
|
+
|
|
226
|
+
Platforms using JanusXR + JanusWeb:
|
|
227
|
+
|
|
228
|
+
| | URL | source / docker |
|
|
229
|
+
|----|-----|------------|
|
|
230
|
+
| <img src="https://imgur.com/JMYi81Z.png"/><br><br> | [vesta.janusxr.org](https://vesta.janusxr.org) | |
|
|
231
|
+
| <img src="https://codeberg.org/coderofsalvation/xrforge/media/branch/master/xrforge.jpg"/><br><br> | [xrforge.isvery.ninja](https://xrforge.isvery.ninja) | [codeberg.org](https://codeberg.org/coderofsalvation/xrforge)
|
|
232
|
+
|
|
233
|
+
Visualisations of hyperlinked Janus clusters across the web:
|
|
234
|
+
* [augmentedperception.com](https://augmentedperception.com)
|
|
235
|
+
* [panopticon](https://panopticon.spyduck.net/)
|
|
236
|
+
|
|
237
|
+
Extra Tools / Components:
|
|
238
|
+
* [custom components](https://github.com/jbaicoianu/janus-custom-components)
|
|
239
|
+
* [janusxr-cli](https://github.com/coderofsalvation/janusxr-cli) swiss army CLI knife for room health/preservation #cli
|
|
240
|
+
* [corsanywhere](https://github.com/Rob--W/cors-anywhere) for hasslefree hopping clusters #stack
|
|
241
|
+
* [janus-gateway](https://janus.conf.meetecho.com/) webrtc/voip-layer [docs](https://janus.conf.meetecho.com/) #stack
|
|
242
|
+
* [janus-server](https://github.com/janusvr/janus-server) multiuser presence layer #stack
|
|
243
|
+
|
|
244
|
+
Reference:
|
|
245
|
+
* [quick scripting reference](https://github.com/jbaicoianu/janusweb/wiki/Scripting-Support-2.0)
|
|
246
|
+
* [janus guide](https://coderofsalvation.github.io/janus-guide/)
|
|
247
|
+
|
|
212
248
|
## Contributing
|
|
213
249
|
JanusWeb is open source, and we welcome any contributions! Please do report bugs using GitHub Issues,
|
|
214
250
|
and all pull requests will be considered. We could especially use help with documentation!
|
|
@@ -320,7 +320,20 @@ console.log('set translation snap', ev.data, ev);
|
|
|
320
320
|
}
|
|
321
321
|
handleExportClick(ev) {
|
|
322
322
|
let exporter = new THREE.GLTFExporter();
|
|
323
|
+
let userDatas = new Map();
|
|
324
|
+
// Store and nullify userData objects for each object before serializing
|
|
325
|
+
room._target.objects['3d'].traverse(n => {
|
|
326
|
+
userDatas.set(n, n.userData);
|
|
327
|
+
n.userData = {};
|
|
328
|
+
});
|
|
323
329
|
exporter.parse(room._target.objects['3d'], (data) => {
|
|
330
|
+
// Restore userData objects
|
|
331
|
+
room._target.objects['3d'].traverse(n => {
|
|
332
|
+
if (userDatas.has(n)) {
|
|
333
|
+
n.userData = userDatas.get(n);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
324
337
|
let filedata = new Blob([data], {type: 'model/gltf-binary'});
|
|
325
338
|
|
|
326
339
|
var url = window.URL.createObjectURL(filedata);
|
|
Binary file
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* for room-specific teleport sound use:
|
|
3
|
+
*
|
|
4
|
+
* <room teleport_sound_id="mysound" ...>
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
janus.registerElement('locomotion_teleporter', {
|
|
2
9
|
active: false,
|
|
3
10
|
longpresstime: 500,
|
|
4
11
|
deadzone: 5,
|
|
5
|
-
xrplayer: null,
|
|
12
|
+
xrplayer: null, // *TODO* use janus.engine.client.xrplayer instead (and allow desktop too)
|
|
6
13
|
linesegments: 100,
|
|
7
14
|
|
|
8
15
|
create() {
|
|
@@ -102,7 +109,13 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
102
109
|
});
|
|
103
110
|
player.head.add(this.shroud._target);
|
|
104
111
|
this.particles.particle_vel = V(-.4, 0, -.4); // FIXME - particle velocity isn't being set on spawn
|
|
105
|
-
|
|
112
|
+
|
|
113
|
+
let locomotion = janus.ui.apps.default.apps.locomotion;
|
|
114
|
+
let asseturl = locomotion.resolveFullURL('./locomotion-assets.json');
|
|
115
|
+
fetch(asseturl).then(res => res.json()).then(assetlist => {
|
|
116
|
+
this.assetpack = elation.engine.assets.loadJSON(assetlist, locomotion.resolveFullURL('./'));
|
|
117
|
+
this.sound = room.createObject('Sound', { id: room.teleport_sound_id || 'button-teleport' }, this);
|
|
118
|
+
})
|
|
106
119
|
|
|
107
120
|
this.disableCursor();
|
|
108
121
|
window.addEventListener('mousemove', this.handleMouseMove);
|
|
@@ -133,13 +146,15 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
133
146
|
});
|
|
134
147
|
this.activateControlContext('teleporter');
|
|
135
148
|
|
|
136
|
-
//
|
|
137
|
-
|
|
149
|
+
// emulated mouse-events (via webxr handtracking e.g.)
|
|
150
|
+
room.addEventListener('mousemove', (ev) => this.handleMouseMove(ev));
|
|
151
|
+
room.addEventListener('mousedown', (ev) => this.handleMouseDown(ev));
|
|
152
|
+
room.addEventListener('mouseup', (ev) => this.handleMouseUp(ev));
|
|
138
153
|
elation.events.add(janus._target, 'room_change', (ev) => this.handleRoomChange(ev));
|
|
139
154
|
},
|
|
140
155
|
update() {
|
|
141
156
|
if (this.active) {
|
|
142
|
-
let hand = this.xrplayer.trackedobjects['hand_' + this.teleporthand]
|
|
157
|
+
let hand = this.xrplayer ? this.xrplayer.trackedobjects['hand_' + this.teleporthand] : null
|
|
143
158
|
let startpoint,
|
|
144
159
|
segments = this.linesegments,
|
|
145
160
|
duration = .25,
|
|
@@ -152,11 +167,13 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
152
167
|
if (hand) {
|
|
153
168
|
startpoint = hand.pointer.localToWorld(V(0, 0, 0));
|
|
154
169
|
v0 = hand.pointer.localToWorld(V(0, 0, -speed)).sub(startpoint);
|
|
170
|
+
hand.teleportPos = hand.teleportPos || v0
|
|
155
171
|
} else {
|
|
156
172
|
startpoint = player.head.localToWorld(V(0,0,0));
|
|
157
173
|
v0 = player.head.localToWorld(V(0,0,-speed)).sub(startpoint);
|
|
158
174
|
return; // Head-locked teleporter is awkward, do we really still support this?
|
|
159
175
|
}
|
|
176
|
+
|
|
160
177
|
let i = 0,
|
|
161
178
|
p0 = startpoint.clone(),
|
|
162
179
|
dir = v0.clone().normalize();
|
|
@@ -181,6 +198,8 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
181
198
|
this.pos = player.worldToLocal(hit.point);
|
|
182
199
|
this.localToWorld(this.particles.emitter_pos.set(0,0,0));
|
|
183
200
|
this.updateTeleportAngle();
|
|
201
|
+
// track movement to cancel longpresstimer
|
|
202
|
+
if( hand ) hand.teleportPos = hand.teleportPos || this.pos.clone()
|
|
184
203
|
}
|
|
185
204
|
laserpoints[i * 2].copy(p0);
|
|
186
205
|
laserpoints[i * 2 + 1].copy(p1);
|
|
@@ -231,7 +250,6 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
231
250
|
if (len > .8) {
|
|
232
251
|
this.updateTeleportAngle();
|
|
233
252
|
} else if (len < .01) {
|
|
234
|
-
this.showShroud();
|
|
235
253
|
this.teleport();
|
|
236
254
|
this.teleportactive = false;
|
|
237
255
|
this.teleporthand = false;
|
|
@@ -262,16 +280,23 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
262
280
|
|
|
263
281
|
}
|
|
264
282
|
},
|
|
265
|
-
teleport() {
|
|
283
|
+
teleport(mouse) {
|
|
266
284
|
//if (!room.teleport) return;
|
|
267
285
|
let pos = player.localToWorld(this.pos.clone());
|
|
268
286
|
pos.y += player.fatness; // FIXME - This accounts for wonky player sphere collider, when we switch to a capsule we won't need it
|
|
269
287
|
player.pos = pos;
|
|
270
288
|
player.vel = V(0,0,.01); // "wake up" physics engine
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
289
|
+
console.log('Teleport player', pos);
|
|
290
|
+
this.showShroud();
|
|
291
|
+
if( this.sound ){
|
|
292
|
+
this.sound.pos = pos;
|
|
293
|
+
this.sound.play();
|
|
294
|
+
}
|
|
295
|
+
if( this.xrplayer && this.teleportangle && !mouse){
|
|
296
|
+
player.angular.set(0,0,0);
|
|
297
|
+
let playerangle = Math.atan2(this.pos.x, this.pos.z);
|
|
298
|
+
this.xrplayer.orientation._target.setFromEuler(new THREE.Euler(0, this.teleportangle - playerangle, 0));
|
|
299
|
+
}
|
|
275
300
|
},
|
|
276
301
|
updateTeleportAngle() {
|
|
277
302
|
let controls = this.activecontrols;
|
|
@@ -282,27 +307,28 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
282
307
|
handleMouseDown(ev) {
|
|
283
308
|
if (!room.teleport) return;
|
|
284
309
|
if (ev.button == 0 && (player.enabled || janus.hmd)) {
|
|
310
|
+
if( ev.inputSourceObject && this.xrplayer ){ // translate webxr emulated mousedown event
|
|
311
|
+
this.teleporthand = ev.inputSourceObject.device.handedness
|
|
312
|
+
let hand = this.xrplayer.trackedobjects['hand_' + this.teleporthand];
|
|
313
|
+
if( hand ) hand.teleportPos = false
|
|
314
|
+
}
|
|
285
315
|
if (!this.longpresstimer) {
|
|
286
|
-
|
|
316
|
+
this.longpresstimer = setTimeout(() => { console.log('timer fired'); this.enableCursor(); }, this.longpresstime);
|
|
287
317
|
this.mousediff = [0,0];
|
|
288
|
-
}
|
|
289
|
-
//this.active = true;
|
|
318
|
+
}else this.cancelLongPress()
|
|
290
319
|
}
|
|
291
320
|
},
|
|
292
321
|
handleMouseMove(ev) {
|
|
293
322
|
if (!room.teleport) return;
|
|
294
|
-
if (this.longpresstimer) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this.longpresstimer = false;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
323
|
+
//if (this.longpresstimer) {
|
|
324
|
+
// this.mousediff[0] += ev.movementX;
|
|
325
|
+
// this.mousediff[1] += ev.movementY
|
|
326
|
+
// var distance = Math.sqrt(this.mousediff[0] * this.mousediff[0] + this.mousediff[1] * this.mousediff[1]);
|
|
327
|
+
// if (distance > this.deadzone) this.cancelLongPress()
|
|
328
|
+
//}
|
|
303
329
|
},
|
|
304
330
|
handleTouchMove(ev) {
|
|
305
|
-
if (!room.teleport) return;
|
|
331
|
+
if (!room.teleport || !this.active) return;
|
|
306
332
|
if (this.longpresstimer) {
|
|
307
333
|
var touch = ev.changedTouches[0];
|
|
308
334
|
|
|
@@ -315,9 +341,7 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
315
341
|
}
|
|
316
342
|
this.lasttouch = [touch.clientX, touch.clientY];
|
|
317
343
|
var distance = Math.sqrt(this.mousediff[0] * this.mousediff[0] + this.mousediff[1] * this.mousediff[1]);
|
|
318
|
-
if (distance > this.deadzone)
|
|
319
|
-
clearTimeout(this.longpresstimer);
|
|
320
|
-
}
|
|
344
|
+
if (distance > this.deadzone) this.cancelLongPress()
|
|
321
345
|
if (this.active) {
|
|
322
346
|
ev.stopPropagation();
|
|
323
347
|
ev.preventDefault();
|
|
@@ -325,42 +349,50 @@ janus.registerElement('locomotion_teleporter', {
|
|
|
325
349
|
}
|
|
326
350
|
},
|
|
327
351
|
handleMouseUp(ev) {
|
|
328
|
-
|
|
329
|
-
if (this.
|
|
330
|
-
|
|
331
|
-
this.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
this.sound.pos = this.pos;
|
|
337
|
-
this.sound.play();
|
|
338
|
-
*/
|
|
339
|
-
this.showShroud();
|
|
340
|
-
this.teleport();
|
|
341
|
-
}
|
|
342
|
-
this.disableCursor();
|
|
352
|
+
if (!room.teleport) return;
|
|
353
|
+
if (this.teleportactive) {
|
|
354
|
+
this.teleport(true);
|
|
355
|
+
this.active = false
|
|
356
|
+
this.teleportactive = false;
|
|
357
|
+
this.teleporthand = false;
|
|
358
|
+
this.disableCursor();
|
|
359
|
+
}else if( this.longpresstimer ) this.disableCursor();
|
|
343
360
|
},
|
|
344
361
|
enableCursor() {
|
|
345
362
|
if (!room.teleport) return;
|
|
363
|
+
if( this.xrplayer ){
|
|
364
|
+
let hand = this.xrplayer.trackedobjects['hand_' + this.teleporthand];
|
|
365
|
+
if( hand ) hand.pointer.laser.visible = false // prevent (portal)clicks
|
|
366
|
+
}
|
|
367
|
+
this.active = true;
|
|
368
|
+
this.teleportactive = true;
|
|
346
369
|
this.visible = true;
|
|
347
370
|
this.particles.resetParticles();
|
|
348
371
|
this.particles.visible = true;
|
|
349
372
|
if (this.laser.room !== room._target) room.appendChild(this.laser);
|
|
350
373
|
if (this.particles.room !== room._target) room.appendChild(this.particles);
|
|
351
374
|
this.laser.visible = true;
|
|
352
|
-
this.active = true;
|
|
353
375
|
this.particles.start();
|
|
354
376
|
},
|
|
355
377
|
disableCursor() {
|
|
378
|
+
if( this.xrplayer ){
|
|
379
|
+
let hand = this.xrplayer.trackedobjects['hand_' + this.teleporthand];
|
|
380
|
+
if( hand ) hand.pointer.laser.visible = true // restore original laser
|
|
381
|
+
}
|
|
382
|
+
this.teleportactive = false;
|
|
356
383
|
this.visible = false;
|
|
357
384
|
this.particles.visible = false;
|
|
358
385
|
this.laser.visible = false;
|
|
359
386
|
this.active = false;
|
|
360
387
|
this.particles.stop();
|
|
388
|
+
this.cancelLongPress()
|
|
361
389
|
},
|
|
362
390
|
showShroud() {
|
|
363
391
|
this.shroud.visible = true;
|
|
364
392
|
this.shroud.opacity = 1;
|
|
393
|
+
},
|
|
394
|
+
cancelLongPress(){
|
|
395
|
+
clearTimeout(this.longpresstimer);
|
|
396
|
+
this.longpresstimer = false
|
|
365
397
|
}
|
|
366
398
|
});
|
|
@@ -408,6 +408,11 @@ elation.elements.define('janus.ui.urlbar', class extends elation.elements.ui.pan
|
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
410
|
handleBlur(ev) {
|
|
411
|
+
// enter-key on Quest devices (onscreen keyboards) is not emitted (blur-event instead)
|
|
412
|
+
// https://developers.meta.com/horizon/documentation/web/webxr-keyboard/
|
|
413
|
+
if (navigator.userAgent.indexOf('OculusBrowser') != -1) {
|
|
414
|
+
this.handleAccept({altKey:true}) // get straight to it
|
|
415
|
+
}
|
|
411
416
|
setTimeout(() => {
|
|
412
417
|
this.suggestions.hide();
|
|
413
418
|
}, 100);
|
|
Binary file
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
{ "assettype": "image", "name": "xrmenu-button-back", "src": "./images/previous-button.png", "hasalpha": true },
|
|
4
4
|
{ "assettype": "image", "name": "xrmenu-button-reset", "src": "./images/address-location.png", "hasalpha": true },
|
|
5
5
|
{ "assettype": "image", "name": "xrmenu-button-reload", "src": "./images/redo-arrow.png", "hasalpha": true },
|
|
6
|
-
{ "assettype": "image", "name": "xrmenu-button-sound", "src": "./images/audio-settings.png", "hasalpha": true }
|
|
6
|
+
{ "assettype": "image", "name": "xrmenu-button-sound", "src": "./images/audio-settings.png", "hasalpha": true },
|
|
7
|
+
{ "assettype": "image", "name": "xrmenu-button-teleport", "src": "./images/teleport.png", "hasalpha": true }
|
|
7
8
|
]
|
|
@@ -13,6 +13,7 @@ janus.registerElement('xrmenu', {
|
|
|
13
13
|
renderorder: 9,
|
|
14
14
|
});
|
|
15
15
|
*/
|
|
16
|
+
console.log("creating xrmenu")
|
|
16
17
|
|
|
17
18
|
this.worldpos = V();
|
|
18
19
|
this.worlddir = V();
|
|
@@ -82,25 +83,22 @@ janus.registerElement('xrmenu', {
|
|
|
82
83
|
image_id: 'xrmenu-button-sound',
|
|
83
84
|
//onactivate: (ev) => this.engine.client.stopXR(),
|
|
84
85
|
}),
|
|
86
|
+
teleport: this.createObject('xrmenu-button', {
|
|
87
|
+
pos: V(.68, 0, 0),
|
|
88
|
+
label: 'Teleport',
|
|
89
|
+
image_id: 'xrmenu-button-teleport',
|
|
90
|
+
toggle: true,
|
|
91
|
+
enabled: room.teleport,
|
|
92
|
+
//onactivate: (ev) => this.engine.client.stopXR(),
|
|
93
|
+
}),
|
|
85
94
|
};
|
|
86
95
|
this.buttons.home.addEventListener('activate', ev => janus.navigateHome());
|
|
87
96
|
this.buttons.back.addEventListener('activate', ev => janus.navigateBack());
|
|
88
97
|
this.buttons.reset.addEventListener('activate', ev => player.reset_position());
|
|
89
98
|
this.buttons.reload.addEventListener('activate', ev => location.reload());
|
|
90
99
|
this.buttons.exitvr.addEventListener('activate', ev => this.engine.client.stopXR());
|
|
91
|
-
this.buttons.sound.addEventListener('activate', ev =>
|
|
92
|
-
|
|
93
|
-
this.panels['sound'] = this.buttons.sound.createObject('xrmenu-popup', {
|
|
94
|
-
content: 'janus-voip-picker'
|
|
95
|
-
});
|
|
96
|
-
} else {
|
|
97
|
-
if (this.panels['sound'].parent === this.buttons.sound) {
|
|
98
|
-
this.buttons.sound.removeChild(this.panels['sound']);
|
|
99
|
-
} else {
|
|
100
|
-
this.buttons.sound.appendChild(this.panels['sound']);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
100
|
+
this.buttons.sound.addEventListener('activate', ev => this.toggleSound());
|
|
101
|
+
this.buttons.teleport.addEventListener('activate', ev => this.toggleTeleport());
|
|
104
102
|
this.reflow();
|
|
105
103
|
});
|
|
106
104
|
},
|
|
@@ -116,6 +114,23 @@ janus.registerElement('xrmenu', {
|
|
|
116
114
|
button.pos.x = (-width / 2) + (this.buttonwidth * this.buttonmargin * (i + .5)) ;
|
|
117
115
|
}
|
|
118
116
|
},
|
|
117
|
+
toggleSound(){
|
|
118
|
+
if (!this.panels['sound']) {
|
|
119
|
+
this.panels['sound'] = this.buttons.sound.createObject('xrmenu-popup', {
|
|
120
|
+
content: 'janus-voip-picker'
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
if (this.panels['sound'].parent === this.buttons.sound) {
|
|
124
|
+
this.buttons.sound.removeChild(this.panels['sound']);
|
|
125
|
+
} else {
|
|
126
|
+
this.buttons.sound.appendChild(this.panels['sound']);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
toggleTeleport(){
|
|
131
|
+
room.teleport = !room.teleport
|
|
132
|
+
this.buttons.teleport.state(room.teleport)
|
|
133
|
+
},
|
|
119
134
|
/*
|
|
120
135
|
update() {
|
|
121
136
|
let worldpos = this.localToWorld(this.worldpos.set(0,0,0));
|
|
@@ -135,16 +150,20 @@ janus.registerElement('xrmenu', {
|
|
|
135
150
|
janus.registerElement('xrmenu-button', {
|
|
136
151
|
onactivate: null,
|
|
137
152
|
image_id: '',
|
|
153
|
+
toggle: false,
|
|
154
|
+
enabled: true,
|
|
138
155
|
|
|
139
156
|
create() {
|
|
140
157
|
this.button = this.createObject('object', {
|
|
141
158
|
id: 'plane',
|
|
142
159
|
scale: V(.1, .1, .02),
|
|
143
160
|
col: '#fff',
|
|
144
|
-
pos: V(0, 0, .01),
|
|
161
|
+
pos: V(0, 0, this.enabled ? 0.01 : 0 ),
|
|
145
162
|
collision_id: 'cube',
|
|
146
163
|
image_id: this.image_id,
|
|
147
164
|
renderorder: 10,
|
|
165
|
+
emissive: this.enabled ? (this.toggle ? '#FFF' : '#595')
|
|
166
|
+
: '#555',
|
|
148
167
|
});
|
|
149
168
|
this.button.addEventListener('mouseover', ev => this.handleMouseOver(ev));
|
|
150
169
|
this.button.addEventListener('mouseout', ev => this.handleMouseOut(ev));
|
|
@@ -153,9 +172,11 @@ janus.registerElement('xrmenu-button', {
|
|
|
153
172
|
this.button.addEventListener('click', ev => this.handleClick(ev));
|
|
154
173
|
},
|
|
155
174
|
handleMouseOver(ev) {
|
|
175
|
+
if( this.toggle ) return
|
|
156
176
|
this.button.emissive = '#595';
|
|
157
177
|
},
|
|
158
178
|
handleMouseOut(ev) {
|
|
179
|
+
if( this.toggle ) return
|
|
159
180
|
this.button.emissive = '#555';
|
|
160
181
|
},
|
|
161
182
|
handleMouseDown(ev) {
|
|
@@ -164,7 +185,10 @@ janus.registerElement('xrmenu-button', {
|
|
|
164
185
|
handleMouseUp(ev) {
|
|
165
186
|
this.button.pos.z = .01;
|
|
166
187
|
},
|
|
167
|
-
|
|
188
|
+
state(enabled){
|
|
189
|
+
this.button.emissive = (this.enabled = enabled) ? '#FFF' : '#555'
|
|
190
|
+
this.button.pos.z = enabled ? .01 : 0
|
|
191
|
+
},
|
|
168
192
|
handleClick(ev) {
|
|
169
193
|
this.dispatchEvent({type: 'activate'});
|
|
170
194
|
},
|
package/package.json
CHANGED
package/scripts/janusbase.js
CHANGED
|
@@ -350,6 +350,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
|
|
|
350
350
|
room: ['property', 'room'],
|
|
351
351
|
parts: ['property', 'jsparts'],
|
|
352
352
|
js_id: ['property', 'js_id'],
|
|
353
|
+
jsid: ['property', 'js_id'],
|
|
353
354
|
pos: ['property', 'position'],
|
|
354
355
|
rotation: ['property', 'rotation'],
|
|
355
356
|
scale: ['property', 'scale'],
|
|
@@ -377,6 +378,7 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
|
|
|
377
378
|
gazetime: ['property', 'gazetime'],
|
|
378
379
|
layers: ['property', 'layers'],
|
|
379
380
|
renderorder: ['property', 'renderorder'],
|
|
381
|
+
lighting: [ 'property', 'lighting' ],
|
|
380
382
|
|
|
381
383
|
pickable: [ 'property', 'pickable'],
|
|
382
384
|
collidable: [ 'property', 'collidable'],
|
|
@@ -674,15 +676,15 @@ elation.require(['engine.things.generic', 'utils.template', 'janusweb.parts'], f
|
|
|
674
676
|
let object = args.object;
|
|
675
677
|
if (args.mesh_verts) {
|
|
676
678
|
let geo = new THREE.BufferGeometry();
|
|
677
|
-
geo.
|
|
679
|
+
geo.setAttribute( 'position', new THREE.Float32BufferAttribute( args.mesh_verts, 3 ) );
|
|
678
680
|
if (args.mesh_faces) {
|
|
679
681
|
geo.setIndex(args.mesh_faces);
|
|
680
682
|
}
|
|
681
683
|
if (args.mesh_normals) {
|
|
682
|
-
geo.
|
|
684
|
+
geo.setAttribute( 'normal', new THREE.Float32BufferAttribute( args.mesh_normals, 3 ) );
|
|
683
685
|
}
|
|
684
686
|
if (args.mesh_uvs) {
|
|
685
|
-
geo.
|
|
687
|
+
geo.setAttribute( 'uv', new THREE.Float32BufferAttribute( args.mesh_uvs, 2 ) );
|
|
686
688
|
}
|
|
687
689
|
object = new THREE.Mesh(geo, new THREE.MeshPhongMaterial());
|
|
688
690
|
}
|
|
@@ -1328,30 +1330,19 @@ console.log('its null', k, this[k], prop);
|
|
|
1328
1330
|
}
|
|
1329
1331
|
//if (this[k] !== prop.default && this[k] !== null && this[k] !== undefined) {
|
|
1330
1332
|
}
|
|
1331
|
-
|
|
1333
|
+
|
|
1334
|
+
if (!parent) parent = this.parent || room;
|
|
1335
|
+
|
|
1336
|
+
let clone = parent.createObject(this.tag, props);
|
|
1337
|
+
|
|
1332
1338
|
if (cloneChildren) {
|
|
1333
1339
|
let children = this.children;
|
|
1334
1340
|
props.children = [];
|
|
1335
1341
|
for (let i = 0; i < children.length; i++) {
|
|
1336
|
-
props.children.push(children[i].clone(true,
|
|
1342
|
+
props.children.push(children[i].clone(true, clone));
|
|
1337
1343
|
}
|
|
1338
1344
|
}
|
|
1339
|
-
|
|
1340
|
-
if (!parent) parent = this.parent || room;
|
|
1341
|
-
|
|
1342
|
-
return parent.createObject(this.tag, props);
|
|
1343
|
-
/*
|
|
1344
|
-
{
|
|
1345
|
-
pos: this.pos.clone(),
|
|
1346
|
-
rotation: this.rotation.clone(),
|
|
1347
|
-
scale: this.scale.clone(),
|
|
1348
|
-
color: this.color.clone(),
|
|
1349
|
-
lighting: this.lighting,
|
|
1350
|
-
id: this.id,
|
|
1351
|
-
js_id: this.js_id + '_copy',
|
|
1352
|
-
collision_id: this.collision_id,
|
|
1353
|
-
});
|
|
1354
|
-
*/
|
|
1345
|
+
return clone;
|
|
1355
1346
|
}
|
|
1356
1347
|
this.addControlContext = function(name, defs) {
|
|
1357
1348
|
let legacydefs = {};
|
|
@@ -1375,7 +1366,7 @@ console.log('clone', props);
|
|
|
1375
1366
|
}
|
|
1376
1367
|
}];
|
|
1377
1368
|
}
|
|
1378
|
-
return this.engine.systems.controls.addContext(name, legacydefs);
|
|
1369
|
+
return this.engine.systems.controls.addContext(name, legacydefs, this);
|
|
1379
1370
|
}
|
|
1380
1371
|
this.activateControlContext = function(name) {
|
|
1381
1372
|
this.engine.systems.controls.activateContext(name, this);
|
package/scripts/janusghost.js
CHANGED
|
@@ -313,7 +313,7 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
|
|
|
313
313
|
if (!animasset.loaded) {
|
|
314
314
|
let animationsLoaded = false;
|
|
315
315
|
elation.events.add(animasset, 'asset_load_complete', ev => {
|
|
316
|
-
if (!animationsLoaded) {
|
|
316
|
+
if (!animationsLoaded && animasset.animations) {
|
|
317
317
|
animationsLoaded = true;
|
|
318
318
|
if (this.body.animationmixer) {
|
|
319
319
|
this.cloneAnimations(animasset);
|
|
@@ -323,7 +323,7 @@ elation.require(['janusweb.janusbase', 'engine.things.leapmotion'], function() {
|
|
|
323
323
|
});
|
|
324
324
|
animasset.load();
|
|
325
325
|
} else {
|
|
326
|
-
if (this.body.animationmixer) {
|
|
326
|
+
if (this.body.animationmixer && animasset.animations) {
|
|
327
327
|
this.cloneAnimations(animasset);
|
|
328
328
|
}
|
|
329
329
|
//this.body.setAnimation('idle');
|
|
@@ -3,7 +3,7 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
3
3
|
this.postinit = function() {
|
|
4
4
|
elation.engine.things.janusparagraph.extendclass.postinit.call(this);
|
|
5
5
|
this.defineProperties({
|
|
6
|
-
text: {type: 'string', default: '', set: this.
|
|
6
|
+
text: {type: 'string', default: '', set: this.toInlineHTML },
|
|
7
7
|
font_size: {type: 'integer', default: 16, set: this.updateTexture},
|
|
8
8
|
text_col: {type: 'color', default: 0x000000, set: this.updateTexture},
|
|
9
9
|
back_col: {type: 'color', default: 0xffffff, set: this.updateTexture},
|
|
@@ -11,6 +11,7 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
11
11
|
cull_face: { type: 'string', default: 'back', set: this.updateMaterial },
|
|
12
12
|
css: {type: 'string', set: this.updateTexture },
|
|
13
13
|
depth_write: { type: 'boolean', default: true },
|
|
14
|
+
transparent: {type: 'boolean', set: this.updateTexture },
|
|
14
15
|
depth_test: { type: 'boolean', default: true },
|
|
15
16
|
collision_id: { type: 'string', default: 'cube' },
|
|
16
17
|
collision_scale: { type: 'vector3', default: V(.5, .5, .02) },
|
|
@@ -75,7 +76,7 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
75
76
|
var basestyle = 'font-family: sans-serif;' +
|
|
76
77
|
'font-size: ' + this.font_size + 'px;' +
|
|
77
78
|
'color: ' + text_col + ';' +
|
|
78
|
-
'background: ' + back_col + ';' +
|
|
79
|
+
'background: ' + (this.transparent ? 'transparent' : back_col) + ';' +
|
|
79
80
|
'max-width: 1014px;' +
|
|
80
81
|
'padding: 5px;';
|
|
81
82
|
|
|
@@ -85,7 +86,7 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
85
86
|
// styled divs instead.
|
|
86
87
|
|
|
87
88
|
var sanitarydiv = document.createElement('div');
|
|
88
|
-
sanitarydiv.innerHTML = this.
|
|
89
|
+
sanitarydiv.innerHTML = this.html;
|
|
89
90
|
var content = sanitarydiv.innerHTML.replace(/<br\s*\/?>/g, '<div class="br"></div>');
|
|
90
91
|
content = content.replace(/<hr\s*\/?>/g, '<div class="hr"></div>');
|
|
91
92
|
content = content.replace(/<img(.*?)>/g, "<img$1 />");
|
|
@@ -150,5 +151,40 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
150
151
|
}
|
|
151
152
|
return this._proxyobject;
|
|
152
153
|
}
|
|
154
|
+
|
|
155
|
+
this.toInlineHTML = async function(){
|
|
156
|
+
this.html = this.text
|
|
157
|
+
try{
|
|
158
|
+
// find all img tags and capture src attributes
|
|
159
|
+
const imgRegex = /<img[^>]*src=["'](?!data:)([^"']+)["']/gi;
|
|
160
|
+
const matches = [];
|
|
161
|
+
let match;
|
|
162
|
+
while ((match = imgRegex.exec(this.html)) !== null) { // Collect all src values
|
|
163
|
+
matches.push(match[1]);
|
|
164
|
+
}
|
|
165
|
+
// fill array with base64 image-strings
|
|
166
|
+
const dataURLs = await Promise.all( matches.map(src => this.toDataURL(src)) );
|
|
167
|
+
// Replace each src in the html with the corresponding data URL
|
|
168
|
+
let updatedHtml = this.html;
|
|
169
|
+
matches.forEach((src, i) => {
|
|
170
|
+
updatedHtml = updatedHtml.replace( `src="${src}"`, `src="${dataURLs[i]}"`)
|
|
171
|
+
});
|
|
172
|
+
this.html = updatedHtml
|
|
173
|
+
}catch(e){ console.error(e) } // continue when inlining failed
|
|
174
|
+
this.updateTexture()
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
this.toDataURL = function(url){
|
|
178
|
+
const finalUrl = `${elation.engine.assets.corsproxy||''}${url}`
|
|
179
|
+
return fetch(finalUrl)
|
|
180
|
+
.then(response => response.blob())
|
|
181
|
+
.then(blob => new Promise((resolve, reject) => {
|
|
182
|
+
const reader = new FileReader()
|
|
183
|
+
reader.onloadend = () => resolve(reader.result)
|
|
184
|
+
reader.onerror = reject
|
|
185
|
+
reader.readAsDataURL(blob)
|
|
186
|
+
}))
|
|
187
|
+
}
|
|
188
|
+
|
|
153
189
|
}, elation.engine.things.janusbase);
|
|
154
190
|
});
|
package/scripts/janusplayer.js
CHANGED
|
@@ -56,7 +56,7 @@ elation.require(['engine.things.player', 'janusweb.external.JanusVOIP', 'ui.butt
|
|
|
56
56
|
elation.events.add(this.engine.client.view.container, 'touchcancel', elation.bind(this, this.handleTouchEnd));
|
|
57
57
|
|
|
58
58
|
this.controlstate2 = this.engine.systems.controls.addContext('janusplayer', {
|
|
59
|
-
'toggle_view': ['keyboard_nomod_v,keyboard_shift_v',
|
|
59
|
+
'toggle_view': ['keyboard_nomod_v,keyboard_shift_v', ev => { if (ev.value == 1) this.toggleCamera() }],
|
|
60
60
|
'zoom_out': ['mouse_wheel_down', ev => this.zoomView(-1, ev)],
|
|
61
61
|
'zoom_in': ['mouse_wheel_up', ev => this.zoomView(1, ev)],
|
|
62
62
|
//'browse_back': ['gamepad_any_button_4', elation.bind(this, this.browseBack)],
|
|
@@ -945,9 +945,9 @@ document.body.dispatchEvent(click);
|
|
|
945
945
|
*/
|
|
946
946
|
this.pickable = false;
|
|
947
947
|
this.setCollider('capsule', {
|
|
948
|
-
radius: this.
|
|
949
|
-
length:
|
|
950
|
-
offset: V(0, this.
|
|
948
|
+
radius: this.fatness,
|
|
949
|
+
length: this.height - (this.fatness * 2),
|
|
950
|
+
offset: V(0, this.fatness, 0)
|
|
951
951
|
});
|
|
952
952
|
} else {
|
|
953
953
|
this.removeCollider();
|
|
@@ -1230,8 +1230,11 @@ document.body.dispatchEvent(click);
|
|
|
1230
1230
|
this.defaultanimation = 'portal';
|
|
1231
1231
|
setTimeout(() => this.defaultanimation = 'idle', 3000);
|
|
1232
1232
|
}
|
|
1233
|
-
this.toggleCamera = function(
|
|
1234
|
-
if (
|
|
1233
|
+
this.toggleCamera = function(arg) {
|
|
1234
|
+
if (typeof arg == 'string') {
|
|
1235
|
+
this.setCameraView(arg);
|
|
1236
|
+
} else if (!arg || arg.value) {
|
|
1237
|
+
// Legacy handling - if arg is a control input event, check to make sure this is a button press, not a release
|
|
1235
1238
|
if (this.cameraview == 'firstperson') {
|
|
1236
1239
|
this.setCameraView('thirdperson');
|
|
1237
1240
|
} else {
|
package/scripts/janusxrplayer.js
CHANGED
|
@@ -412,7 +412,7 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
412
412
|
this.buttonwaspressed = false;
|
|
413
413
|
if (!this.menu.parent || this.menu.parent === this) {
|
|
414
414
|
player.appendChild(this.menu);
|
|
415
|
-
player.worldToLocal(this.pointer.localToWorld(this.menu.pos.set(0
|
|
415
|
+
player.worldToLocal(this.pointer.localToWorld(this.menu.pos.set(0.1,-0.07,-.2)));
|
|
416
416
|
this.menu.billboard = 'y';
|
|
417
417
|
} else {
|
|
418
418
|
//this.appendChild(this.menu);
|
|
@@ -656,8 +656,9 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
656
656
|
}
|
|
657
657
|
},
|
|
658
658
|
handleRaycastEnter(ev) {
|
|
659
|
-
let proxyobj = ev.data.object
|
|
660
|
-
|
|
659
|
+
let proxyobj = ev.data.object
|
|
660
|
+
if( !proxyobj || !proxyobj?._target ) return
|
|
661
|
+
obj = proxyobj._target;
|
|
661
662
|
let oldactiveobject = this.activeobject;
|
|
662
663
|
if (this.activeobject) {
|
|
663
664
|
this.engine.client.view.proxyEvent({
|
|
@@ -723,6 +724,7 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
723
724
|
this.engine.client.view.proxyEvent(evdata, evdata.element);
|
|
724
725
|
},
|
|
725
726
|
handleRaycastMove(ev) {
|
|
727
|
+
if( ! ev?.data?.intersection?.face?.normal ) return // skip those!
|
|
726
728
|
this.updateLaserEndpoint(ev.data.intersection.point, ev.data.intersection.face.normal);
|
|
727
729
|
if (this.activeobject) {
|
|
728
730
|
let obj = this.activeobject.objects['3d'];
|
|
@@ -779,7 +781,7 @@ elation.require(['engine.things.generic', 'janusweb.external.webxr-input-profile
|
|
|
779
781
|
}
|
|
780
782
|
},
|
|
781
783
|
handleSelect(ev) {
|
|
782
|
-
if (this.activeobject) {
|
|
784
|
+
if (this.activeobject && this.laser.visible ) {
|
|
783
785
|
let obj = this.activeobject.objects['3d'];
|
|
784
786
|
console.log('CLICK IT', this.activeobject);
|
|
785
787
|
this.engine.client.view.proxyEvent({
|
package/scripts/room.js
CHANGED
|
@@ -3,10 +3,10 @@ elation.require([
|
|
|
3
3
|
'engine.things.generic', 'engine.things.label', 'engine.things.skybox',
|
|
4
4
|
'janusweb.object', 'janusweb.portal', 'janusweb.image', 'janusweb.video', 'janusweb.text', 'janusweb.janusparagraph',
|
|
5
5
|
'janusweb.sound', 'janusweb.januslight', 'janusweb.janusparticle', 'janusweb.janusghost',
|
|
6
|
-
'janusweb.translators.bookmarks', 'janusweb.translators.reddit', 'janusweb.translators.error', 'janusweb.translators.blank', 'janusweb.translators.default', 'janusweb.translators.dat', 'janusweb.translators.janusvfs'
|
|
6
|
+
'janusweb.translators.bookmarks', 'janusweb.translators.reddit', 'janusweb.translators.error', 'janusweb.translators.blank', 'janusweb.translators.default', 'janusweb.translators.dat', 'janusweb.translators.janusvfs', 'janusweb.translators.xrfragments'
|
|
7
7
|
], function() {
|
|
8
8
|
let roomTranslators = false;
|
|
9
|
-
function initRoomTranslators() {
|
|
9
|
+
function initRoomTranslators(room) {
|
|
10
10
|
roomTranslators = {
|
|
11
11
|
'^janus-vfs:': elation.janusweb.translators.janusvfs({janus: janus}),
|
|
12
12
|
'^about:blank$': elation.janusweb.translators.blank({janus: janus}),
|
|
@@ -14,8 +14,9 @@ elation.require([
|
|
|
14
14
|
'^dat:': elation.janusweb.translators.dat({janus: janus}),
|
|
15
15
|
'^https?:\/\/(www\.)?reddit.com': elation.janusweb.translators.reddit({janus: janus}),
|
|
16
16
|
'^error$': elation.janusweb.translators.error({janus: janus}),
|
|
17
|
+
'.*\.(gltf|glb|dae)$': elation.janusweb.translators.xrfragments({janus: janus}),
|
|
17
18
|
'^default$': elation.janusweb.translators.default({janus: janus})
|
|
18
|
-
}
|
|
19
|
+
}
|
|
19
20
|
}
|
|
20
21
|
elation.component.add('engine.things.janusroom', function() {
|
|
21
22
|
this.postinit = function() {
|
|
@@ -100,7 +101,7 @@ elation.require([
|
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
if (!roomTranslators) {
|
|
103
|
-
initRoomTranslators();
|
|
104
|
+
initRoomTranslators(this);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
this.spawnpoint = new THREE.Object3D();
|
|
@@ -301,7 +302,8 @@ elation.require([
|
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
304
|
} else if (this.urlhash) {
|
|
304
|
-
|
|
305
|
+
// XR Fragments deeplink spec (Level1: URL) https://xrfragment.org/#teleport%20camera
|
|
306
|
+
let obj = this.getObjectById(this.urlhash) || this.getObjectByDeepName(this.urlhash)
|
|
305
307
|
if (obj) {
|
|
306
308
|
obj.localToWorld(spawnpoint.position.set(0,0,0));
|
|
307
309
|
spawnpoint.orientation.setFromRotationMatrix(obj.objects['3d'].matrixWorld.lookAt(spawnpoint.position, obj.localToWorld(V(0,0,-1)), obj.localToWorld(V(0,1,0).sub(spawnpoint.position))));
|
|
@@ -567,12 +569,12 @@ elation.require([
|
|
|
567
569
|
}
|
|
568
570
|
var baseurl = baseurloverride;
|
|
569
571
|
if (!baseurl) {
|
|
570
|
-
if (url && !this.baseurl) {
|
|
572
|
+
if (url && url.match("://") && !this.baseurl) {
|
|
571
573
|
baseurl = url.split('/');
|
|
572
574
|
if (baseurl.length > 3) baseurl.pop();
|
|
573
575
|
baseurl = baseurl.join('/') + '/';
|
|
574
|
-
this.baseurl = baseurl;
|
|
575
576
|
}
|
|
577
|
+
this.baseurl = baseurl || "";
|
|
576
578
|
} else {
|
|
577
579
|
this.baseurl = baseurl;
|
|
578
580
|
}
|
|
@@ -590,7 +592,7 @@ elation.require([
|
|
|
590
592
|
var fullurl = url;
|
|
591
593
|
if (fullurl[0] == '/' && fullurl[1] != '/') fullurl = this.baseurl + fullurl;
|
|
592
594
|
if (!fullurl.match(/^https?:/) && !fullurl.match(/^\/\//)) {
|
|
593
|
-
fullurl = self.location.origin + fullurl;
|
|
595
|
+
fullurl = self.location.origin + (fullurl[0] == '/' ? '' :'/') + fullurl;
|
|
594
596
|
} else if (!fullurl.match(/^https?:\/\/(localhost|127\.0\.0\.1)/) && fullurl.indexOf(document.location.origin) != 0) {
|
|
595
597
|
fullurl = proxyurl + fullurl;
|
|
596
598
|
}
|
|
@@ -706,11 +708,22 @@ elation.require([
|
|
|
706
708
|
}
|
|
707
709
|
}));
|
|
708
710
|
}
|
|
709
|
-
this.loadFromSource = function(sourcecode, baseurl) {
|
|
711
|
+
this.loadFromSource = async function(sourcecode, baseurl) {
|
|
710
712
|
if (baseurl) {
|
|
711
713
|
this.baseurl = baseurl;
|
|
712
714
|
}
|
|
713
|
-
|
|
715
|
+
|
|
716
|
+
var source
|
|
717
|
+
// scan sourcecode for non-JML microformats within translators
|
|
718
|
+
for( m in roomTranslators ){
|
|
719
|
+
let rt = roomTranslators[m]
|
|
720
|
+
if( rt.parseSource && rt.parseSource.regex ){
|
|
721
|
+
source = await rt.parseSource(sourcecode,this)
|
|
722
|
+
if( source ) break;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if( !source ) source = this.parseSource(sourcecode)
|
|
726
|
+
|
|
714
727
|
if (source && source.source) {
|
|
715
728
|
this.roomsrc = source.source;
|
|
716
729
|
var datapath = elation.config.get('janusweb.datapath', '/media/janusweb');
|
|
@@ -1955,8 +1968,19 @@ elation.require([
|
|
|
1955
1968
|
*/
|
|
1956
1969
|
|
|
1957
1970
|
}
|
|
1958
|
-
this.
|
|
1959
|
-
|
|
1971
|
+
this.getObjectByDeepName = function(name) {
|
|
1972
|
+
if( !this.janus.currentroom ) return
|
|
1973
|
+
let obj = this.janus.currentroom.objects['3d'].getObjectByName(name)
|
|
1974
|
+
if( obj ){ // return polyglot THREE/janusweb object for convenience
|
|
1975
|
+
return new Proxy(obj,{
|
|
1976
|
+
set(me,k,v){ obj[k] = v; return true; },
|
|
1977
|
+
get(me,k){
|
|
1978
|
+
if( k == 'objects' ) return { '3d': me }
|
|
1979
|
+
return obj[k];
|
|
1980
|
+
}
|
|
1981
|
+
})
|
|
1982
|
+
}
|
|
1983
|
+
return obj
|
|
1960
1984
|
}
|
|
1961
1985
|
this.getObjectsByClassName = function(classname) {
|
|
1962
1986
|
var objects = [];
|
package/scripts/sound.js
CHANGED
|
@@ -65,7 +65,7 @@ elation.require(['janusweb.janusbase'], function() {
|
|
|
65
65
|
}
|
|
66
66
|
//this.audio.panner.maxDistance = this.properties.distance;
|
|
67
67
|
if (this.dist) {
|
|
68
|
-
this.audio.setRefDistance(this.dist);
|
|
68
|
+
this.audio.setRefDistance(Math.max(0, this.dist));
|
|
69
69
|
} else {
|
|
70
70
|
//this.audio.panner.distanceModel = 'linear';
|
|
71
71
|
}
|
package/scripts/text.js
CHANGED
|
@@ -11,7 +11,7 @@ elation.require(['engine.things.label'], function() {
|
|
|
11
11
|
'align': { type: 'string', default: 'center', refreshGeometry: true },
|
|
12
12
|
'verticalalign': { type: 'string', default: 'bottom', refreshGeometry: true },
|
|
13
13
|
'zalign': { type: 'string', default: 'back', refreshGeometry: true },
|
|
14
|
-
'emissive': { type: 'color', default: 0x000000 },
|
|
14
|
+
'emissive': { type: 'color', default: 0x000000, set: this.updateMaterial },
|
|
15
15
|
'opacity': { type: 'float', default: 1.0 },
|
|
16
16
|
'depth_write': { type: 'bool', default: true },
|
|
17
17
|
'depth_test': { type: 'bool', default: true },
|
|
@@ -120,6 +120,7 @@ elation.require(['engine.things.label'], function() {
|
|
|
120
120
|
this.material.metalness = this.metalness;
|
|
121
121
|
this.material.envMap = this.getEnvmap();
|
|
122
122
|
}
|
|
123
|
+
this.material.emissive = this.emissive;
|
|
123
124
|
this.material.needsUpdate = true;
|
|
124
125
|
}
|
|
125
126
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
elation.require([], function() {
|
|
2
|
+
elation.component.add('janusweb.translators.xrfragments', function() {
|
|
3
|
+
this.init = function() {
|
|
4
|
+
this.description = "this implements the https://xrfragment.org standard for immersive 3d file browsing"
|
|
5
|
+
}
|
|
6
|
+
this.exec = function(args) {
|
|
7
|
+
return new Promise(elation.bind(this, function(resolve, reject) {
|
|
8
|
+
|
|
9
|
+
var room = this.room = args.room;
|
|
10
|
+
this.setupEvents()
|
|
11
|
+
|
|
12
|
+
var datapath = elation.config.get('janusweb.datapath', '/media/janusweb');
|
|
13
|
+
|
|
14
|
+
var roomdata = {
|
|
15
|
+
assets: {
|
|
16
|
+
assetlist: [
|
|
17
|
+
{assettype: 'model', name: 'scene', src: args.url},
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
room: {
|
|
21
|
+
pos: [0,0,0],
|
|
22
|
+
xdir: "1 0 0",
|
|
23
|
+
zdir: "0 0 1",
|
|
24
|
+
},
|
|
25
|
+
object: [
|
|
26
|
+
{id: 'scene', js_id: 0, pos: "0 0 0", xdir: "-1 0 0", zdir: "0 0 -1", lighting: "false"}
|
|
27
|
+
],
|
|
28
|
+
link: []
|
|
29
|
+
};
|
|
30
|
+
resolve(roomdata);
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.spawnUserAtFragment = function(source) {
|
|
35
|
+
// XR Fragments deeplink spec: explicit or default spawn
|
|
36
|
+
// https://xrfragment.org/#teleport%20camera
|
|
37
|
+
if( ! this.room.urlhash ) this.room.urlhash = 'spawn'
|
|
38
|
+
this.room.setPlayerPosition.apply(this.room)
|
|
39
|
+
console.log("[xrfragment] camera teleport")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.setupEvents = function(){
|
|
43
|
+
elation.events.add(room._room, 'room_load_complete', this.spawnUserAtFragment.bind(this) )
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// translate XR Fragments microformat into JML
|
|
47
|
+
this.parseSource = async function(sourcecode, room){
|
|
48
|
+
this.room = room
|
|
49
|
+
const isJML = /<fireboxroom>[\s\S]*?<\/fireboxroom>/si;
|
|
50
|
+
if( sourcecode.match(isJML) ) return // JML takes precedence over microformats
|
|
51
|
+
|
|
52
|
+
// extract href/title value
|
|
53
|
+
let el = document.createElement("div")
|
|
54
|
+
el.innerHTML = sourcecode
|
|
55
|
+
const link = el.querySelector("link[as=spatial-entrypoint]")
|
|
56
|
+
if( !link ) return
|
|
57
|
+
const title = el.querySelector("title")
|
|
58
|
+
const href = link.getAttribute("href")
|
|
59
|
+
if( !href ) return
|
|
60
|
+
console.log("[xrfragment] detected XRF microformat")
|
|
61
|
+
const hrefNoHash = href.replace(/#.*/,'')
|
|
62
|
+
|
|
63
|
+
// if microformat has xr fragment in URI, use it if room-url has no xr fragment
|
|
64
|
+
if( href.match(/#/) && !room.urlhash ){
|
|
65
|
+
room.urlhash = href.replace(/.*#/,'')
|
|
66
|
+
}
|
|
67
|
+
// check if link exists
|
|
68
|
+
const hrefFull = hrefNoHash.match('://') ? hrefNoHash : String(room.baseurl+hrefNoHash)
|
|
69
|
+
const exist = await fetch( hrefFull ,{method:'HEAD'})
|
|
70
|
+
if( !exist.ok ) return console.warn(`[xrfragment] ${link.outerHTML} resolves to invalid url ${hrefFull}`)
|
|
71
|
+
|
|
72
|
+
// setup events
|
|
73
|
+
this.setupEvents()
|
|
74
|
+
// return JML
|
|
75
|
+
return room.parseSource(`
|
|
76
|
+
<title>${ title ? title.innerText.replace(/\n.*/g,'') : room.baseurl.split("/").pop() }</title>
|
|
77
|
+
<FireBoxRoom>
|
|
78
|
+
<Assets>
|
|
79
|
+
<assetobject id="scene" src="${hrefNoHash}"/>
|
|
80
|
+
</Assets>
|
|
81
|
+
<Room>
|
|
82
|
+
<object pos="0 0 0" collision_id="scene" id="scene" />
|
|
83
|
+
</Room>
|
|
84
|
+
</FireBoxRoom>
|
|
85
|
+
`)
|
|
86
|
+
}
|
|
87
|
+
// microformat heuristic (https://xrfragment.org/#XRF%20microformat)
|
|
88
|
+
// example: <link rel="alternate" as="spatial-entrypoint" src="https://foo.org/bar.glb">
|
|
89
|
+
this.parseSource.regex = /<link\s+[^>]*rel=['"]spatial-entrypoint['"][^>]*\/?>/si;
|
|
90
|
+
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
package/utils/clean-build.sh
CHANGED