Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions css/janusweb.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions media/assets/webui/apps/gazecontrol/gazecontrol-assets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
{ "assettype": "image", "name": "reticle-idle", "src": "./images/reticle-idle.png", "hasalpha": true },
{ "assettype": "image", "name": "reticle-hover", "src": "./images/reticle-hover.png", "hasalpha": true }
]
247 changes: 247 additions & 0 deletions media/assets/webui/apps/gazecontrol/gazecontrol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
elation.require([], function() {

elation.extend('janusweb.gazecontrol', class {
constructor(object) {
this.init()
this.teleporting = false
this.distance = 15
elation.events.add(null, 'janusweb_script_frame', (e) => this.update(e) )
elation.events.add(null, 'player_setroom', (e) => this.init(e) )
elation.events.add(null, 'webui_settings_store_init', this.extendSettingsUI )
elation.events.add(null, 'room_load_complete', (e) => { setTimeout( () => this.teleporting = false, 5000 ) })
elation.events.add(null, 'gazeenter', (ev) => {
const obj = this.cursor_object
if( this.gazecaster.enabled && this.cursor && this.isClickable(obj) ){
this.cursor.setImage('hover')
elation.events.fire({type: 'action-item-selected', data: this}); // XDG Sound

const maxDistance = elation.config.get('webui.settings.vr.gaze_maxdistance')
|| this.distance
if( this.isClickable(obj, maxDistance) ){
this.cursor.fuseid = setTimeout( () => this.cursor.setImage('fuse'), 1000)
}
}
})
elation.events.add(null, 'gazeleave', (ev) => {
if( this.gazecaster.enabled && this.cursor ) this.cursor.setImage('idle')
})
elation.events.add(null, 'gazeactivate', (ev) => {
if( this.cooldown > 0 || this.teleporting || !this.gazecaster.enabled || player.velocity.length() > 0.1 ) return // debounce false positives
const maxDistance = elation.config.get('webui.settings.vr.gaze_maxdistance')
|| this.distance
this.cooldown = 1000
if( this.cursor ){
this.cursor.setImage('idle')
const obj = ev.data
if( this.isClickable(obj,maxDistance) && elation.config.get('webui.settings.vr.gaze_click') !== false ){
player.mappingExecute('confirm', ev )
// re-enable: to minimize activating/gazing at another portal after teleporting
if( String(obj.componentname).match(/janusportal/) ) this.teleporting = true
elation.events.fire({type: 'action-link-released', data: this}); // XDG Sound
}
setTimeout( () => this.cooldown = 0, this.cooldown )
}
})
}

extendSettingsUI(e){
const settings = e.element
if( !settings.store['webui.settings.vr.gaze_control'] ){
settings.store['webui.settings.vr.gaze_control'] = {localStorage:true}
settings.store['webui.settings.vr.gaze_click'] = {localStorage:true}
settings.store["webui.settings.vr.mouse_move_up"] = {localStorage:true}
settings.store["webui.settings.vr.mouse_move_down"] = {localStorage:true}
}
const vrtab = settings.querySelector("ui-tab[label='VR'] ui-formgroup")
vrtab.innerHTML += `
<ui-select label="Gaze control" config="webui.settings.vr.gaze_control">
<ui-option value="fallback">Fallback when no controllers</ui-option>
<ui-option value="always">Always</ui-option>
<ui-option value="never">Never</ui-option>
</ui-select>
<ui-toggle label="Gaze auto-click" checked config="webui.settings.vr.gaze_click"></ui-toggle>
<ui-slider label="Gaze max distance" min="0" max="150" value="15"></ui-slider>
<ui-select label="Mouse move up" config="webui.settings.vr.mouse_move_up">
<ui-option value="stepforward">Step forward</ui-option>
<ui-option value="navback">Teleport last room</ui-option>
<ui-option value="navforward">Teleport next room</ui-option>
<ui-option value="click">Confirm</ui-option>
<ui-option value="stepbackward">Step backward</ui-option>
<ui-option value="none">None</ui-option>
</ui-select>
<ui-select label="Mouse move down" config="webui.settings.vr.mouse_move_down">
<ui-option value="stepbackward">Step backward</ui-option>
<ui-option value="stepforward">Step forward</ui-option>
<ui-option value="navback">Teleport last room</ui-option>
<ui-option value="navforward">Teleport next room</ui-option>
<ui-option value="click">Confirm</ui-option>
<ui-option value="none">None</ui-option>
</ui-select>
`
}

init(e){
let newroom = e?.data || room
if (!this.gazecaster) {
this.scene = elation.engine.instances.default.systems.world.scene['world-3d']
player.gazecontrol = this
player.gazecaster = this.gazecaster = newroom.createObject('raycaster', {persist: false});
player.gazecaster.enabled = elation.config.get('webui.settings.vr.gaze_control') == 'always'
player.camera.add(player.gazecaster._target);
//player.gazecaster = player.head.spawn('raycaster', null, {room: newroom, janus});
elation.events.add(this.gazecaster._target, 'raycastenter', elation.bind(this, this.handleGazeEnter));
elation.events.add(this.gazecaster._target, 'raycastleave', elation.bind(this, this.handleGazeLeave));
elation.events.add(this.gazecaster._target, 'raycastmove', elation.bind(this, this.handleGazeMove));
this.createReticle()
} else {
player.gazecaster.setRoom(newroom);
}
for( let i in this.cursors ){
// preload with disabled proxy so new rooms will not
const cursor = this.cursors[i]
room.loadNewAsset("image", {id: cursor.src, src: cursor.src, proxy:false})
}
}

update(e){
const delta = e.data
let percent
const vrdisplay = player.engine.systems.render.views.main.vrdisplay ||
player.engine.systems.render.views.xr

if( janus.engine.client.xrplayer ){
const mode = elation.config.get('webui.settings.vr.gaze_control')
player.gazecaster.enabled = mode == 'always' ||
((!mode || mode == 'fallback') && !janus.engine.client.xrplayer.trackedobjects['hand_left']) ||
((!mode || mode == 'fallback') && !janus.engine.client.xrplayer.trackedobjects['hand_right'])
}
if (this.gaze && this.gaze.object) {
var now = performance.now();
var gazetime = room.gazetime
if (this.gaze.object.gazetime) {
gazetime = this.gaze.object.gazetime;
} else if (this.gaze.object.room && this.gaze.object.room.gazetime) {
gazetime = this.gaze.object.room.gazetime;
}
var diff = now - this.gaze.start;
percent = diff / gazetime;
if (percent < 1) {
this.gaze.object.dispatchEvent({type: 'gazeprogress', data: percent});
} else if (!this.gaze.fired) {
this.gaze.object.dispatchEvent({type: 'gazeprogress', data: 1});
this.gaze.object.dispatchEvent({type: 'gazeactivate', data: this.gaze.object});
this.gaze.fired = true;
}
}
if( this.cursor ){
this.cursor.position.x = vrdisplay ? 0.015 : 0 // why?
this.cursor.visible = player.gazecaster.enabled
if( this.cursor.scale.x < this.cursor.current.scale ){
this.cursor.scale.x = this.cursor.scale.y = this.cursor.scale.z = this.cursor.scale.x + (0.8 * delta)
}

if( this.cursor.current.name == 'fuse' && elation.config.get('webui.settings.vr.gaze_click') !== false ){
this.cursor.opacity = 1 - percent
this.cursor.rotation.z += (150 * delta)
}
}
}

cancelGaze(){
//this.gaze.object.dispatchEvent({type: 'gazecancel'});
this.gaze = false;
}
handleGazeEnter(ev){
var obj = ev.data.object;
// dont enter gaze when user is clearly looking where to go
if( Math.abs(player.acceleration.x) > 0.1 ||
Math.abs(player.acceleration.y) > 0.1 ||
Math.abs(player.acceleration.y) > 0.1
){ return }

if (obj && obj.dispatchEvent && ev.data.intersection) {
this.cursor_object = obj;
obj.dispatchEvent({type: 'gazeenter', data: ev.data.intersection});

if (this.gaze) {
this.cancelGaze();
}
this.gaze = {
start: performance.now(),
object: obj,
fired: false
};
}
}
handleGazeLeave(ev){
var obj = ev.data.object;
if (obj && obj.dispatchEvent) {
this.cursor_object = '';
obj.dispatchEvent({type: 'gazeleave', data: ev.data.intersection});
}
}
handleGazeMove(ev){
var obj = ev.data.object;
if (obj && obj.dispatchEvent) {
this.cursor_object = obj.js_id || '';

if (ev.data.intersection.point) {
player.vectors.cursor_pos.copy(ev.data.intersection.point);
}
}
}

isClickable(obj,maxDistance){
if( obj && maxDistance && obj.position.distanceTo( player.position ) > maxDistance ) return false
return this.cursor &&
obj?.pickable &&
obj?.collidable &&
(
String(obj.componentname).match(/janus(portal|video|paragraph)/) ||
obj.video_id ||
obj.websurface_id ||
obj.eventlistenerproxies.click
)

}

createReticle(){
const scale = 0.28
this.cursors = {
idle: { name: 'idle', src: document.location.origin+'/media/assets/webui/apps/gazecontrol/images/reticle-idle.png', scale},
hover: { name: 'hover', src: document.location.origin+'/media/assets/webui/apps/gazecontrol/images/reticle-hover.png', scale},
fuse: { name: 'fuse', src: document.location.origin+'/media/assets/webui/apps/gazecontrol/images/reticle-hover.png', scale, scaleKeep:true},
}
this.cursor = player.createObject('object', {
pos: V(0,0,-3),
scale: V(scale,scale,scale),
col: '#FFF',
collidable: false,
pickable: false,
transparent: true,
lighting: false,
//depthTest: false,
//depthWrite: false,
proxy:false,
id: 'plane',
jsid: 'reticle',
renderorder: 10,
})
this.cursor.setImage = (cursorname) => {
this.cursor.current = this.cursors[cursorname]
this.cursor.image_id = this.cursors[cursorname].src
if( !this.cursor.current.scaleKeep ){
this.cursor.scale.x = this.cursor.scale.y = this.cursor.scale.z = scale * 0.5
}
this.cursor.opacity = 1
this.cursor.rotation.z = 0
clearTimeout(this.cursor.fuseid)
}
this.cursor.setImage('idle')
player.camera.add(this.cursor)
}

})
});

if( !player.gazecaster ) new elation.janusweb.gazecontrol(player);
10 changes: 10 additions & 0 deletions media/assets/webui/apps/gazecontrol/gazecontrol.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"templates": {
},
"scripts": [
"./gazecontrol.js"
],
"css": []
}


Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions media/assets/webui/apps/locomotion/locomotion-assets.json

This file was deleted.

10 changes: 1 addition & 9 deletions media/assets/webui/apps/locomotion/teleporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,6 @@ janus.registerElement('locomotion_teleporter', {
this.particles.particle_vel = V(-.4, 0, -.4); // FIXME - particle velocity isn't being set on spawn

let locomotion = janus.ui.apps.default.apps.locomotion;
let asseturl = locomotion.resolveFullURL('./locomotion-assets.json');
fetch(asseturl).then(res => res.json()).then(assetlist => {
this.assetpack = elation.engine.assets.loadJSON(assetlist, locomotion.resolveFullURL('./'));
this.sound = room.createObject('Sound', { id: room.teleport_sound_id || 'button-teleport' }, this);
})

this.disableCursor();
window.addEventListener('mousemove', this.handleMouseMove);
Expand Down Expand Up @@ -288,10 +283,7 @@ janus.registerElement('locomotion_teleporter', {
player.vel = V(0,0,.01); // "wake up" physics engine
console.log('Teleport player', pos);
this.showShroud();
if( this.sound ){
this.sound.pos = pos;
this.sound.play();
}
elation.events.fire({element: this, type: 'action-button-pressed', data: this});
if( this.xrplayer && this.teleportangle && !mouse){
player.angular.set(0,0,0);
let playerangle = Math.atan2(this.pos.x, this.pos.z);
Expand Down
Loading