temporary disable accessibility + improve gaze and movement for phone
This commit is contained in:
parent
03b0396a5b
commit
490ff2937d
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Thu Feb 29 01:43:17 PM UTC 2024
|
||||
* v0.5.1 generated at Fri Mar 1 01:33:23 PM UTC 2024
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
@ -2284,7 +2284,7 @@ xrf.frag.src.enableSourcePortation = (opts) => {
|
|||
let frag = {}
|
||||
xrf.Parser.parse("href", url, frag)
|
||||
sphere.userData = scene.userData // allow rich href notifications/hovers
|
||||
sphere.userData.href = url.replace(/(&)?[-][\w-+\.]+(&)?/g,'&') // remove negative selectors to refer to original scene
|
||||
sphere.userData.href = url.replace(/#.*/,'') // remove fragments to refer to original scene
|
||||
sphere.userData.XRF = frag
|
||||
xrf.hashbus.pub.fragment("href", {...opts, mesh:sphere, frag, skipXRWG:true, renderer:xrf.renderer, camera:xrf.camera })
|
||||
}
|
||||
|
@ -3791,18 +3791,30 @@ window.AFRAME.registerComponent('xrf', {
|
|||
aScene.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
aScene.renderer.toneMappingExposure = 1.25;
|
||||
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
||||
|
||||
if( AFRAME.utils.device.isMobile() ){
|
||||
// aScene.setAttribute('webxr',"requiredFeatures: dom-overlay; overlayElement: canvas; referenceSpaceType: local")
|
||||
}
|
||||
|
||||
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR tags in 3D model
|
||||
ARbutton = document.querySelector('.a-enter-ar-button')
|
||||
VRbutton = document.querySelector('.a-enter-vr-button')
|
||||
if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#AR' ) )
|
||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||
aScene.addEventListener('loaded', () => {
|
||||
// this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model
|
||||
ARbutton = document.querySelector('.a-enter-ar-button')
|
||||
VRbutton = document.querySelector('.a-enter-vr-button')
|
||||
if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#-VR' ) )
|
||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||
//if( AFRAME.utils.device.checkARSupport() && VRbutton ){
|
||||
// VRbutton.style.display = 'none'
|
||||
// ARbutton.parentNode.style.right = '20px'
|
||||
//}
|
||||
})
|
||||
|
||||
aScene.addEventListener('enter-vr', () => {
|
||||
let repositionUser = (scale) => () => {
|
||||
// sometimes AFRAME resets the user position to 0,0,0 when entering VR (not sure why)
|
||||
let pos = xrf.frag.pos.last
|
||||
if( pos ){ AFRAME.XRF.camera.position.set(pos.x, pos.y, pos.z) }
|
||||
})
|
||||
if( pos ){ AFRAME.XRF.camera.position.set(pos.x, pos.y*scale, pos.z) }
|
||||
}
|
||||
aScene.addEventListener('enter-vr', repositionUser(1) )
|
||||
aScene.addEventListener('enter-ar', repositionUser(2) )
|
||||
|
||||
xrf.addEventListener('navigateLoaded', (opts) => {
|
||||
setTimeout( () => AFRAME.fade.out(),500)
|
||||
|
@ -3917,6 +3929,227 @@ window.AFRAME.registerComponent('xrf', {
|
|||
},
|
||||
|
||||
})
|
||||
/**
|
||||
* Movement Controls
|
||||
*
|
||||
* @author Don McCurdy <dm@donmccurdy.com>
|
||||
*/
|
||||
|
||||
const COMPONENT_SUFFIX = '-controls';
|
||||
const MAX_DELTA = 0.2; // ms
|
||||
const EPS = 10e-6;
|
||||
const MOVED = 'moved';
|
||||
|
||||
AFRAME.registerComponent('movement-controls', {
|
||||
|
||||
/*******************************************************************
|
||||
* Schema
|
||||
*/
|
||||
|
||||
dependencies: ['rotation'],
|
||||
|
||||
schema: {
|
||||
enabled: { default: true },
|
||||
controls: { default: ['gamepad', 'trackpad', 'keyboard', 'touch'] },
|
||||
speed: { default: 0.3, min: 0 },
|
||||
fly: { default: false },
|
||||
constrainToNavMesh: { default: false },
|
||||
camera: { default: '[movement-controls] [camera]', type: 'selector' }
|
||||
},
|
||||
|
||||
/*******************************************************************
|
||||
* Lifecycle
|
||||
*/
|
||||
|
||||
init: function () {
|
||||
const el = this.el;
|
||||
if (!this.data.camera) {
|
||||
this.data.camera = el.querySelector('[camera]');
|
||||
}
|
||||
this.velocityCtrl = null;
|
||||
|
||||
this.velocity = new THREE.Vector3();
|
||||
this.heading = new THREE.Quaternion();
|
||||
this.eventDetail = {};
|
||||
|
||||
// Navigation
|
||||
this.navGroup = null;
|
||||
this.navNode = null;
|
||||
|
||||
if (el.sceneEl.hasLoaded) {
|
||||
this.injectControls();
|
||||
} else {
|
||||
el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
update: function (prevData) {
|
||||
const el = this.el;
|
||||
const data = this.data;
|
||||
const nav = el.sceneEl.systems.nav;
|
||||
if (el.sceneEl.hasLoaded) {
|
||||
this.injectControls();
|
||||
}
|
||||
if (nav && data.constrainToNavMesh !== prevData.constrainToNavMesh) {
|
||||
data.constrainToNavMesh
|
||||
? nav.addAgent(this)
|
||||
: nav.removeAgent(this);
|
||||
}
|
||||
if (data.enabled !== prevData.enabled) {
|
||||
// Propagate the enabled change to all controls
|
||||
for (let i = 0; i < data.controls.length; i++) {
|
||||
const name = data.controls[i] + COMPONENT_SUFFIX;
|
||||
this.el.setAttribute(name, { enabled: this.data.enabled });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
injectControls: function () {
|
||||
const data = this.data;
|
||||
|
||||
for (let i = 0; i < data.controls.length; i++) {
|
||||
const name = data.controls[i] + COMPONENT_SUFFIX;
|
||||
this.el.setAttribute(name, { enabled: this.data.enabled });
|
||||
}
|
||||
},
|
||||
|
||||
updateNavLocation: function () {
|
||||
this.navGroup = null;
|
||||
this.navNode = null;
|
||||
},
|
||||
|
||||
/*******************************************************************
|
||||
* Tick
|
||||
*/
|
||||
|
||||
tick: (function () {
|
||||
const start = new THREE.Vector3();
|
||||
const end = new THREE.Vector3();
|
||||
const clampedEnd = new THREE.Vector3();
|
||||
|
||||
return function (t, dt) {
|
||||
if (!dt) return;
|
||||
|
||||
const el = this.el;
|
||||
const data = this.data;
|
||||
|
||||
if (!data.enabled) return;
|
||||
|
||||
this.updateVelocityCtrl();
|
||||
const velocityCtrl = this.velocityCtrl;
|
||||
const velocity = this.velocity;
|
||||
|
||||
if (!velocityCtrl) return;
|
||||
|
||||
// Update velocity. If FPS is too low, reset.
|
||||
if (dt / 1000 > MAX_DELTA) {
|
||||
velocity.set(0, 0, 0);
|
||||
} else {
|
||||
this.updateVelocity(dt);
|
||||
}
|
||||
|
||||
if (data.constrainToNavMesh
|
||||
&& velocityCtrl.isNavMeshConstrained !== false) {
|
||||
|
||||
if (velocity.lengthSq() < EPS) return;
|
||||
|
||||
start.copy(el.object3D.position);
|
||||
end
|
||||
.copy(velocity)
|
||||
.multiplyScalar(dt / 1000)
|
||||
.add(start);
|
||||
|
||||
const nav = el.sceneEl.systems.nav;
|
||||
this.navGroup = this.navGroup === null ? nav.getGroup(start) : this.navGroup;
|
||||
this.navNode = this.navNode || nav.getNode(start, this.navGroup);
|
||||
this.navNode = nav.clampStep(start, end, this.navGroup, this.navNode, clampedEnd);
|
||||
el.object3D.position.copy(clampedEnd);
|
||||
} else if (el.hasAttribute('velocity')) {
|
||||
el.setAttribute('velocity', velocity);
|
||||
} else {
|
||||
el.object3D.position.x += velocity.x * dt / 1000;
|
||||
el.object3D.position.y += velocity.y * dt / 1000;
|
||||
el.object3D.position.z += velocity.z * dt / 1000;
|
||||
}
|
||||
|
||||
};
|
||||
}()),
|
||||
|
||||
/*******************************************************************
|
||||
* Movement
|
||||
*/
|
||||
|
||||
updateVelocityCtrl: function () {
|
||||
const data = this.data;
|
||||
if (data.enabled) {
|
||||
for (let i = 0, l = data.controls.length; i < l; i++) {
|
||||
const control = this.el.components[data.controls[i] + COMPONENT_SUFFIX];
|
||||
if (control && control.isVelocityActive()) {
|
||||
this.velocityCtrl = control;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.velocityCtrl = null;
|
||||
}
|
||||
},
|
||||
|
||||
updateVelocity: (function () {
|
||||
const vector2 = new THREE.Vector2();
|
||||
const quaternion = new THREE.Quaternion();
|
||||
|
||||
return function (dt) {
|
||||
let dVelocity;
|
||||
const el = this.el;
|
||||
const control = this.velocityCtrl;
|
||||
const velocity = this.velocity;
|
||||
const data = this.data;
|
||||
|
||||
if (control) {
|
||||
if (control.getVelocityDelta) {
|
||||
dVelocity = control.getVelocityDelta(dt);
|
||||
} else if (control.getVelocity) {
|
||||
velocity.copy(control.getVelocity());
|
||||
return;
|
||||
} else if (control.getPositionDelta) {
|
||||
velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
|
||||
return;
|
||||
} else {
|
||||
throw new Error('Incompatible movement controls: ', control);
|
||||
}
|
||||
}
|
||||
|
||||
if (el.hasAttribute('velocity') && !data.constrainToNavMesh) {
|
||||
velocity.copy(this.el.getAttribute('velocity'));
|
||||
}
|
||||
|
||||
if (dVelocity && data.enabled) {
|
||||
const cameraEl = data.camera;
|
||||
|
||||
// Rotate to heading
|
||||
quaternion.copy(cameraEl.object3D.quaternion);
|
||||
quaternion.premultiply(el.object3D.quaternion);
|
||||
dVelocity.applyQuaternion(quaternion);
|
||||
|
||||
const factor = dVelocity.length();
|
||||
if (data.fly) {
|
||||
velocity.copy(dVelocity);
|
||||
velocity.multiplyScalar(this.data.speed * 16.66667);
|
||||
} else {
|
||||
vector2.set(dVelocity.x, dVelocity.z);
|
||||
vector2.setLength(factor * this.data.speed * 16.66667);
|
||||
velocity.x = vector2.x;
|
||||
velocity.y = 0;
|
||||
velocity.z = vector2.y;
|
||||
}
|
||||
if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
|
||||
this.eventDetail.velocity = velocity;
|
||||
this.el.emit(MOVED, this.eventDetail);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}())
|
||||
});
|
||||
// look-controls turns off autoUpdateMatrix (of player) which
|
||||
// will break teleporting and other stuff
|
||||
// overriding this is easier then adding updateMatrixWorld() everywhere else
|
||||
|
@ -3970,6 +4203,97 @@ AFRAME.components['look-controls'].Component.prototype.updateOrientation = funct
|
|||
object3D.rotation.z = this.magicWindowDeltaEuler.z;
|
||||
object3D.matrixAutoUpdate = true
|
||||
}
|
||||
/**
|
||||
* Touch-to-move-forward controls for mobile.
|
||||
*/
|
||||
AFRAME.registerComponent('touch-controls', {
|
||||
schema: {
|
||||
enabled: { default: true },
|
||||
reverseEnabled: { default: true }
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.dVelocity = new THREE.Vector3();
|
||||
this.bindMethods();
|
||||
this.direction = 0;
|
||||
},
|
||||
|
||||
play: function () {
|
||||
this.addEventListeners();
|
||||
},
|
||||
|
||||
pause: function () {
|
||||
this.removeEventListeners();
|
||||
this.dVelocity.set(0, 0, 0);
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
this.pause();
|
||||
},
|
||||
|
||||
addEventListeners: function () {
|
||||
const sceneEl = this.el.sceneEl;
|
||||
const canvasEl = sceneEl.canvas;
|
||||
|
||||
if (!canvasEl) {
|
||||
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
canvasEl.addEventListener('touchstart', this.onTouchStart);
|
||||
canvasEl.addEventListener('touchend', this.onTouchEnd);
|
||||
const vrModeUI = sceneEl.getAttribute('vr-mode-ui');
|
||||
if (vrModeUI && vrModeUI.cardboardModeEnabled) {
|
||||
sceneEl.addEventListener('enter-vr', this.onEnterVR);
|
||||
}
|
||||
},
|
||||
|
||||
removeEventListeners: function () {
|
||||
const canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
|
||||
if (!canvasEl) { return; }
|
||||
|
||||
canvasEl.removeEventListener('touchstart', this.onTouchStart);
|
||||
canvasEl.removeEventListener('touchend', this.onTouchEnd);
|
||||
this.el.sceneEl.removeEventListener('enter-vr', this.onEnterVR)
|
||||
},
|
||||
|
||||
isVelocityActive: function () {
|
||||
return this.data.enabled && !!this.direction;
|
||||
},
|
||||
|
||||
getVelocityDelta: function () {
|
||||
this.dVelocity.z = this.direction;
|
||||
return this.dVelocity.clone();
|
||||
},
|
||||
|
||||
bindMethods: function () {
|
||||
this.onTouchStart = this.onTouchStart.bind(this);
|
||||
this.onTouchEnd = this.onTouchEnd.bind(this);
|
||||
this.onEnterVR = this.onEnterVR.bind(this);
|
||||
},
|
||||
|
||||
onTouchStart: function (e) {
|
||||
this.direction = 0;
|
||||
if (this.data.reverseEnabled && e.touches ){
|
||||
if( e.touches.length === 3) this.direction = 1;
|
||||
if( e.touches.length === 2) this.direction = -1;
|
||||
}
|
||||
//e.preventDefault();
|
||||
},
|
||||
|
||||
onTouchEnd: function (e) {
|
||||
this.direction = 0;
|
||||
//e.preventDefault();
|
||||
},
|
||||
|
||||
onEnterVR: function () {
|
||||
// This is to make the Cardboard button on Chrome Android working
|
||||
//const xrSession = this.el.sceneEl.xrSession;
|
||||
//if (!xrSession) { return; }
|
||||
//xrSession.addEventListener('selectstart', this.onTouchStart);
|
||||
//xrSession.addEventListener('selectend', this.onTouchEnd);
|
||||
}
|
||||
})
|
||||
window.AFRAME.registerComponent('xrf-button', {
|
||||
schema: {
|
||||
label: {
|
||||
|
@ -4081,8 +4405,9 @@ window.AFRAME.registerComponent('xrf-button', {
|
|||
AFRAME.registerComponent('vconsole', {
|
||||
init: function () {
|
||||
//AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
|
||||
return
|
||||
let aScene = AFRAME.scenes[0]
|
||||
|
||||
return
|
||||
document.head.innerHTML += `
|
||||
<style type="text/css">
|
||||
.vc-panel {
|
||||
|
@ -4147,11 +4472,12 @@ AFRAME.registerComponent('xrf-gaze',{
|
|||
|
||||
}
|
||||
},
|
||||
setGazer: function(state){
|
||||
setGazer: function(state, fuse){
|
||||
if( !AFRAME.utils.device.isMobile() ) return
|
||||
let cam = document.querySelector("[camera]")
|
||||
if( state ){
|
||||
if( cam.innerHTML.match(/cursor/) ) return; // avoid duplicate calls
|
||||
cam.innerHTML = `<a-entity id="cursor" cursor="fuse: true; fuseTimeout: 1500"
|
||||
cam.innerHTML = `<a-entity id="cursor" cursor="fuse: ${fuse ? 'true': 'false'}; fuseTimeout: 1500"
|
||||
animation__click="property: scale; startEvents: click; easing: easeInCubic; dur: 150; from: 0.1 0.1 0.1; to: 1 1 1"
|
||||
animation__fusing="property: scale; startEvents: fusing; easing: easeInCubic; dur: 1500; from: 1 1 1; to: 0.1 0.1 0.1"
|
||||
animation__mouseleave="property: scale; startEvents: mouseleave; easing: easeInCubic; dur: 500; to: 1 1 1"
|
||||
|
@ -4160,27 +4486,22 @@ AFRAME.registerComponent('xrf-gaze',{
|
|||
position="0 0 -1"
|
||||
material="color: #BBBBBB; shader: flat">
|
||||
</a-entity>`
|
||||
cam.querySelector('#cursor').setAttribute("geometry","primitive: ring; radiusInner: 0.02; radiusOuter: 0.03")
|
||||
}else{
|
||||
cam.querySelector('#cursor').removeAttribute("geometry")
|
||||
if( document.querySelector('[cursor]') ) {
|
||||
document.querySelector('[cursor]').setAttribute("visible",false)
|
||||
}
|
||||
}
|
||||
},
|
||||
init:function(data){
|
||||
let setVisible = (state) => {
|
||||
if( AFRAME.utils.device.isMobile() ){
|
||||
this.setGazer(state)
|
||||
if( state || xrf.debug ) this.el.setAttribute("geometry","primitive: ring; radiusInner: 0.02; radiusOuter: 0.03")
|
||||
else this.el.removeAttribute("geometry")
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
this.setGazer(true);
|
||||
|
||||
document.querySelector("a-scene").addEventListener('exit-vr', () => setVisible(false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-vr', () => setVisible(true) )
|
||||
document.querySelector("a-scene").addEventListener('exit-ar', () => setVisible(false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-ar', () => setVisible(true) )
|
||||
document.querySelector("a-scene").addEventListener('exit-vr', () => this.setGazer(false,false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-vr', () => this.setGazer(true,true) )
|
||||
document.querySelector("a-scene").addEventListener('enter-ar', () => this.setGazer(true,false) )
|
||||
document.querySelector("a-scene").addEventListener('exit-ar', () => this.setGazer(false,false) )
|
||||
|
||||
let highlightMesh = (state) => (e) => {
|
||||
if( !e.target.object3D ) return
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Thu Feb 29 01:43:17 PM UTC 2024
|
||||
* v0.5.1 generated at Fri Mar 1 01:33:23 PM UTC 2024
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
@ -2282,7 +2282,7 @@ xrf.frag.src.enableSourcePortation = (opts) => {
|
|||
let frag = {}
|
||||
xrf.Parser.parse("href", url, frag)
|
||||
sphere.userData = scene.userData // allow rich href notifications/hovers
|
||||
sphere.userData.href = url.replace(/(&)?[-][\w-+\.]+(&)?/g,'&') // remove negative selectors to refer to original scene
|
||||
sphere.userData.href = url.replace(/#.*/,'') // remove fragments to refer to original scene
|
||||
sphere.userData.XRF = frag
|
||||
xrf.hashbus.pub.fragment("href", {...opts, mesh:sphere, frag, skipXRWG:true, renderer:xrf.renderer, camera:xrf.camera })
|
||||
}
|
||||
|
@ -3789,18 +3789,30 @@ window.AFRAME.registerComponent('xrf', {
|
|||
aScene.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
aScene.renderer.toneMappingExposure = 1.25;
|
||||
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
||||
|
||||
if( AFRAME.utils.device.isMobile() ){
|
||||
// aScene.setAttribute('webxr',"requiredFeatures: dom-overlay; overlayElement: canvas; referenceSpaceType: local")
|
||||
}
|
||||
|
||||
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR tags in 3D model
|
||||
ARbutton = document.querySelector('.a-enter-ar-button')
|
||||
VRbutton = document.querySelector('.a-enter-vr-button')
|
||||
if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#AR' ) )
|
||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||
aScene.addEventListener('loaded', () => {
|
||||
// this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model
|
||||
ARbutton = document.querySelector('.a-enter-ar-button')
|
||||
VRbutton = document.querySelector('.a-enter-vr-button')
|
||||
if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#-VR' ) )
|
||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||
//if( AFRAME.utils.device.checkARSupport() && VRbutton ){
|
||||
// VRbutton.style.display = 'none'
|
||||
// ARbutton.parentNode.style.right = '20px'
|
||||
//}
|
||||
})
|
||||
|
||||
aScene.addEventListener('enter-vr', () => {
|
||||
let repositionUser = (scale) => () => {
|
||||
// sometimes AFRAME resets the user position to 0,0,0 when entering VR (not sure why)
|
||||
let pos = xrf.frag.pos.last
|
||||
if( pos ){ AFRAME.XRF.camera.position.set(pos.x, pos.y, pos.z) }
|
||||
})
|
||||
if( pos ){ AFRAME.XRF.camera.position.set(pos.x, pos.y*scale, pos.z) }
|
||||
}
|
||||
aScene.addEventListener('enter-vr', repositionUser(1) )
|
||||
aScene.addEventListener('enter-ar', repositionUser(2) )
|
||||
|
||||
xrf.addEventListener('navigateLoaded', (opts) => {
|
||||
setTimeout( () => AFRAME.fade.out(),500)
|
||||
|
@ -3915,6 +3927,227 @@ window.AFRAME.registerComponent('xrf', {
|
|||
},
|
||||
|
||||
})
|
||||
/**
|
||||
* Movement Controls
|
||||
*
|
||||
* @author Don McCurdy <dm@donmccurdy.com>
|
||||
*/
|
||||
|
||||
const COMPONENT_SUFFIX = '-controls';
|
||||
const MAX_DELTA = 0.2; // ms
|
||||
const EPS = 10e-6;
|
||||
const MOVED = 'moved';
|
||||
|
||||
AFRAME.registerComponent('movement-controls', {
|
||||
|
||||
/*******************************************************************
|
||||
* Schema
|
||||
*/
|
||||
|
||||
dependencies: ['rotation'],
|
||||
|
||||
schema: {
|
||||
enabled: { default: true },
|
||||
controls: { default: ['gamepad', 'trackpad', 'keyboard', 'touch'] },
|
||||
speed: { default: 0.3, min: 0 },
|
||||
fly: { default: false },
|
||||
constrainToNavMesh: { default: false },
|
||||
camera: { default: '[movement-controls] [camera]', type: 'selector' }
|
||||
},
|
||||
|
||||
/*******************************************************************
|
||||
* Lifecycle
|
||||
*/
|
||||
|
||||
init: function () {
|
||||
const el = this.el;
|
||||
if (!this.data.camera) {
|
||||
this.data.camera = el.querySelector('[camera]');
|
||||
}
|
||||
this.velocityCtrl = null;
|
||||
|
||||
this.velocity = new THREE.Vector3();
|
||||
this.heading = new THREE.Quaternion();
|
||||
this.eventDetail = {};
|
||||
|
||||
// Navigation
|
||||
this.navGroup = null;
|
||||
this.navNode = null;
|
||||
|
||||
if (el.sceneEl.hasLoaded) {
|
||||
this.injectControls();
|
||||
} else {
|
||||
el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
update: function (prevData) {
|
||||
const el = this.el;
|
||||
const data = this.data;
|
||||
const nav = el.sceneEl.systems.nav;
|
||||
if (el.sceneEl.hasLoaded) {
|
||||
this.injectControls();
|
||||
}
|
||||
if (nav && data.constrainToNavMesh !== prevData.constrainToNavMesh) {
|
||||
data.constrainToNavMesh
|
||||
? nav.addAgent(this)
|
||||
: nav.removeAgent(this);
|
||||
}
|
||||
if (data.enabled !== prevData.enabled) {
|
||||
// Propagate the enabled change to all controls
|
||||
for (let i = 0; i < data.controls.length; i++) {
|
||||
const name = data.controls[i] + COMPONENT_SUFFIX;
|
||||
this.el.setAttribute(name, { enabled: this.data.enabled });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
injectControls: function () {
|
||||
const data = this.data;
|
||||
|
||||
for (let i = 0; i < data.controls.length; i++) {
|
||||
const name = data.controls[i] + COMPONENT_SUFFIX;
|
||||
this.el.setAttribute(name, { enabled: this.data.enabled });
|
||||
}
|
||||
},
|
||||
|
||||
updateNavLocation: function () {
|
||||
this.navGroup = null;
|
||||
this.navNode = null;
|
||||
},
|
||||
|
||||
/*******************************************************************
|
||||
* Tick
|
||||
*/
|
||||
|
||||
tick: (function () {
|
||||
const start = new THREE.Vector3();
|
||||
const end = new THREE.Vector3();
|
||||
const clampedEnd = new THREE.Vector3();
|
||||
|
||||
return function (t, dt) {
|
||||
if (!dt) return;
|
||||
|
||||
const el = this.el;
|
||||
const data = this.data;
|
||||
|
||||
if (!data.enabled) return;
|
||||
|
||||
this.updateVelocityCtrl();
|
||||
const velocityCtrl = this.velocityCtrl;
|
||||
const velocity = this.velocity;
|
||||
|
||||
if (!velocityCtrl) return;
|
||||
|
||||
// Update velocity. If FPS is too low, reset.
|
||||
if (dt / 1000 > MAX_DELTA) {
|
||||
velocity.set(0, 0, 0);
|
||||
} else {
|
||||
this.updateVelocity(dt);
|
||||
}
|
||||
|
||||
if (data.constrainToNavMesh
|
||||
&& velocityCtrl.isNavMeshConstrained !== false) {
|
||||
|
||||
if (velocity.lengthSq() < EPS) return;
|
||||
|
||||
start.copy(el.object3D.position);
|
||||
end
|
||||
.copy(velocity)
|
||||
.multiplyScalar(dt / 1000)
|
||||
.add(start);
|
||||
|
||||
const nav = el.sceneEl.systems.nav;
|
||||
this.navGroup = this.navGroup === null ? nav.getGroup(start) : this.navGroup;
|
||||
this.navNode = this.navNode || nav.getNode(start, this.navGroup);
|
||||
this.navNode = nav.clampStep(start, end, this.navGroup, this.navNode, clampedEnd);
|
||||
el.object3D.position.copy(clampedEnd);
|
||||
} else if (el.hasAttribute('velocity')) {
|
||||
el.setAttribute('velocity', velocity);
|
||||
} else {
|
||||
el.object3D.position.x += velocity.x * dt / 1000;
|
||||
el.object3D.position.y += velocity.y * dt / 1000;
|
||||
el.object3D.position.z += velocity.z * dt / 1000;
|
||||
}
|
||||
|
||||
};
|
||||
}()),
|
||||
|
||||
/*******************************************************************
|
||||
* Movement
|
||||
*/
|
||||
|
||||
updateVelocityCtrl: function () {
|
||||
const data = this.data;
|
||||
if (data.enabled) {
|
||||
for (let i = 0, l = data.controls.length; i < l; i++) {
|
||||
const control = this.el.components[data.controls[i] + COMPONENT_SUFFIX];
|
||||
if (control && control.isVelocityActive()) {
|
||||
this.velocityCtrl = control;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.velocityCtrl = null;
|
||||
}
|
||||
},
|
||||
|
||||
updateVelocity: (function () {
|
||||
const vector2 = new THREE.Vector2();
|
||||
const quaternion = new THREE.Quaternion();
|
||||
|
||||
return function (dt) {
|
||||
let dVelocity;
|
||||
const el = this.el;
|
||||
const control = this.velocityCtrl;
|
||||
const velocity = this.velocity;
|
||||
const data = this.data;
|
||||
|
||||
if (control) {
|
||||
if (control.getVelocityDelta) {
|
||||
dVelocity = control.getVelocityDelta(dt);
|
||||
} else if (control.getVelocity) {
|
||||
velocity.copy(control.getVelocity());
|
||||
return;
|
||||
} else if (control.getPositionDelta) {
|
||||
velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
|
||||
return;
|
||||
} else {
|
||||
throw new Error('Incompatible movement controls: ', control);
|
||||
}
|
||||
}
|
||||
|
||||
if (el.hasAttribute('velocity') && !data.constrainToNavMesh) {
|
||||
velocity.copy(this.el.getAttribute('velocity'));
|
||||
}
|
||||
|
||||
if (dVelocity && data.enabled) {
|
||||
const cameraEl = data.camera;
|
||||
|
||||
// Rotate to heading
|
||||
quaternion.copy(cameraEl.object3D.quaternion);
|
||||
quaternion.premultiply(el.object3D.quaternion);
|
||||
dVelocity.applyQuaternion(quaternion);
|
||||
|
||||
const factor = dVelocity.length();
|
||||
if (data.fly) {
|
||||
velocity.copy(dVelocity);
|
||||
velocity.multiplyScalar(this.data.speed * 16.66667);
|
||||
} else {
|
||||
vector2.set(dVelocity.x, dVelocity.z);
|
||||
vector2.setLength(factor * this.data.speed * 16.66667);
|
||||
velocity.x = vector2.x;
|
||||
velocity.y = 0;
|
||||
velocity.z = vector2.y;
|
||||
}
|
||||
if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
|
||||
this.eventDetail.velocity = velocity;
|
||||
this.el.emit(MOVED, this.eventDetail);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}())
|
||||
});
|
||||
// look-controls turns off autoUpdateMatrix (of player) which
|
||||
// will break teleporting and other stuff
|
||||
// overriding this is easier then adding updateMatrixWorld() everywhere else
|
||||
|
@ -3968,6 +4201,97 @@ AFRAME.components['look-controls'].Component.prototype.updateOrientation = funct
|
|||
object3D.rotation.z = this.magicWindowDeltaEuler.z;
|
||||
object3D.matrixAutoUpdate = true
|
||||
}
|
||||
/**
|
||||
* Touch-to-move-forward controls for mobile.
|
||||
*/
|
||||
AFRAME.registerComponent('touch-controls', {
|
||||
schema: {
|
||||
enabled: { default: true },
|
||||
reverseEnabled: { default: true }
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.dVelocity = new THREE.Vector3();
|
||||
this.bindMethods();
|
||||
this.direction = 0;
|
||||
},
|
||||
|
||||
play: function () {
|
||||
this.addEventListeners();
|
||||
},
|
||||
|
||||
pause: function () {
|
||||
this.removeEventListeners();
|
||||
this.dVelocity.set(0, 0, 0);
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
this.pause();
|
||||
},
|
||||
|
||||
addEventListeners: function () {
|
||||
const sceneEl = this.el.sceneEl;
|
||||
const canvasEl = sceneEl.canvas;
|
||||
|
||||
if (!canvasEl) {
|
||||
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
canvasEl.addEventListener('touchstart', this.onTouchStart);
|
||||
canvasEl.addEventListener('touchend', this.onTouchEnd);
|
||||
const vrModeUI = sceneEl.getAttribute('vr-mode-ui');
|
||||
if (vrModeUI && vrModeUI.cardboardModeEnabled) {
|
||||
sceneEl.addEventListener('enter-vr', this.onEnterVR);
|
||||
}
|
||||
},
|
||||
|
||||
removeEventListeners: function () {
|
||||
const canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
|
||||
if (!canvasEl) { return; }
|
||||
|
||||
canvasEl.removeEventListener('touchstart', this.onTouchStart);
|
||||
canvasEl.removeEventListener('touchend', this.onTouchEnd);
|
||||
this.el.sceneEl.removeEventListener('enter-vr', this.onEnterVR)
|
||||
},
|
||||
|
||||
isVelocityActive: function () {
|
||||
return this.data.enabled && !!this.direction;
|
||||
},
|
||||
|
||||
getVelocityDelta: function () {
|
||||
this.dVelocity.z = this.direction;
|
||||
return this.dVelocity.clone();
|
||||
},
|
||||
|
||||
bindMethods: function () {
|
||||
this.onTouchStart = this.onTouchStart.bind(this);
|
||||
this.onTouchEnd = this.onTouchEnd.bind(this);
|
||||
this.onEnterVR = this.onEnterVR.bind(this);
|
||||
},
|
||||
|
||||
onTouchStart: function (e) {
|
||||
this.direction = 0;
|
||||
if (this.data.reverseEnabled && e.touches ){
|
||||
if( e.touches.length === 3) this.direction = 1;
|
||||
if( e.touches.length === 2) this.direction = -1;
|
||||
}
|
||||
//e.preventDefault();
|
||||
},
|
||||
|
||||
onTouchEnd: function (e) {
|
||||
this.direction = 0;
|
||||
//e.preventDefault();
|
||||
},
|
||||
|
||||
onEnterVR: function () {
|
||||
// This is to make the Cardboard button on Chrome Android working
|
||||
//const xrSession = this.el.sceneEl.xrSession;
|
||||
//if (!xrSession) { return; }
|
||||
//xrSession.addEventListener('selectstart', this.onTouchStart);
|
||||
//xrSession.addEventListener('selectend', this.onTouchEnd);
|
||||
}
|
||||
})
|
||||
window.AFRAME.registerComponent('xrf-button', {
|
||||
schema: {
|
||||
label: {
|
||||
|
@ -4079,8 +4403,9 @@ window.AFRAME.registerComponent('xrf-button', {
|
|||
AFRAME.registerComponent('vconsole', {
|
||||
init: function () {
|
||||
//AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
|
||||
return
|
||||
let aScene = AFRAME.scenes[0]
|
||||
|
||||
return
|
||||
document.head.innerHTML += `
|
||||
<style type="text/css">
|
||||
.vc-panel {
|
||||
|
@ -4145,11 +4470,12 @@ AFRAME.registerComponent('xrf-gaze',{
|
|||
|
||||
}
|
||||
},
|
||||
setGazer: function(state){
|
||||
setGazer: function(state, fuse){
|
||||
if( !AFRAME.utils.device.isMobile() ) return
|
||||
let cam = document.querySelector("[camera]")
|
||||
if( state ){
|
||||
if( cam.innerHTML.match(/cursor/) ) return; // avoid duplicate calls
|
||||
cam.innerHTML = `<a-entity id="cursor" cursor="fuse: true; fuseTimeout: 1500"
|
||||
cam.innerHTML = `<a-entity id="cursor" cursor="fuse: ${fuse ? 'true': 'false'}; fuseTimeout: 1500"
|
||||
animation__click="property: scale; startEvents: click; easing: easeInCubic; dur: 150; from: 0.1 0.1 0.1; to: 1 1 1"
|
||||
animation__fusing="property: scale; startEvents: fusing; easing: easeInCubic; dur: 1500; from: 1 1 1; to: 0.1 0.1 0.1"
|
||||
animation__mouseleave="property: scale; startEvents: mouseleave; easing: easeInCubic; dur: 500; to: 1 1 1"
|
||||
|
@ -4158,27 +4484,22 @@ AFRAME.registerComponent('xrf-gaze',{
|
|||
position="0 0 -1"
|
||||
material="color: #BBBBBB; shader: flat">
|
||||
</a-entity>`
|
||||
cam.querySelector('#cursor').setAttribute("geometry","primitive: ring; radiusInner: 0.02; radiusOuter: 0.03")
|
||||
}else{
|
||||
cam.querySelector('#cursor').removeAttribute("geometry")
|
||||
if( document.querySelector('[cursor]') ) {
|
||||
document.querySelector('[cursor]').setAttribute("visible",false)
|
||||
}
|
||||
}
|
||||
},
|
||||
init:function(data){
|
||||
let setVisible = (state) => {
|
||||
if( AFRAME.utils.device.isMobile() ){
|
||||
this.setGazer(state)
|
||||
if( state || xrf.debug ) this.el.setAttribute("geometry","primitive: ring; radiusInner: 0.02; radiusOuter: 0.03")
|
||||
else this.el.removeAttribute("geometry")
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
this.setGazer(true);
|
||||
|
||||
document.querySelector("a-scene").addEventListener('exit-vr', () => setVisible(false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-vr', () => setVisible(true) )
|
||||
document.querySelector("a-scene").addEventListener('exit-ar', () => setVisible(false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-ar', () => setVisible(true) )
|
||||
document.querySelector("a-scene").addEventListener('exit-vr', () => this.setGazer(false,false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-vr', () => this.setGazer(true,true) )
|
||||
document.querySelector("a-scene").addEventListener('enter-ar', () => this.setGazer(true,false) )
|
||||
document.querySelector("a-scene").addEventListener('exit-ar', () => this.setGazer(false,false) )
|
||||
|
||||
let highlightMesh = (state) => (e) => {
|
||||
if( !e.target.object3D ) return
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -935,6 +935,7 @@ window.accessibility = (opts) => new Proxy({
|
|||
})
|
||||
|
||||
document.addEventListener('$menu:ready', (e) => {
|
||||
return
|
||||
try{
|
||||
accessibility = accessibility(e.detail)
|
||||
accessibility.init()
|
||||
|
@ -1819,8 +1820,13 @@ window.frontend = (opts) => new Proxy({
|
|||
window.notify('loading '+document.location.search.substr(1))
|
||||
|
||||
setTimeout( () => {
|
||||
window.notify("use WASD-keys and mouse-drag to move around",{timeout:false})
|
||||
xrf.addEventListener('navigate', () => SnackBar() ) // close dialogs when url changes
|
||||
let instructions = AFRAME.utils.device.isMobile()
|
||||
? "hold 2-3 fingers to move forward/backward"
|
||||
: "use WASD-keys and mouse-drag to move around"
|
||||
window.notify(instructions,{timeout:false})
|
||||
xrf.addEventListener('navigate', (opts) => {
|
||||
window.notify('<b class="badge">teleporting</b> to <b>'+opts.url+"</b><br><br>use back/forward browserbutton to undo")
|
||||
}) // close dialogs when url changes
|
||||
},2000 )
|
||||
|
||||
xrf.addEventListener('href', (data) => {
|
||||
|
@ -1940,7 +1946,6 @@ window.frontend = (opts) => new Proxy({
|
|||
},
|
||||
|
||||
notify(_str,opts){
|
||||
if( window.outerWidth < 800 ) return
|
||||
if( window.accessibility && window.accessibility.enabled ) return $chat.send({message:_str,class:['info']})
|
||||
opts = opts || {status:'info'}
|
||||
opts = Object.assign({ status, timeout:4000 },opts)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Thu Feb 29 01:43:17 PM UTC 2024
|
||||
* v0.5.1 generated at Fri Mar 1 01:33:23 PM UTC 2024
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
@ -2282,7 +2282,7 @@ xrf.frag.src.enableSourcePortation = (opts) => {
|
|||
let frag = {}
|
||||
xrf.Parser.parse("href", url, frag)
|
||||
sphere.userData = scene.userData // allow rich href notifications/hovers
|
||||
sphere.userData.href = url.replace(/(&)?[-][\w-+\.]+(&)?/g,'&') // remove negative selectors to refer to original scene
|
||||
sphere.userData.href = url.replace(/#.*/,'') // remove fragments to refer to original scene
|
||||
sphere.userData.XRF = frag
|
||||
xrf.hashbus.pub.fragment("href", {...opts, mesh:sphere, frag, skipXRWG:true, renderer:xrf.renderer, camera:xrf.camera })
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* v0.5.1 generated at Thu Feb 29 01:43:17 PM UTC 2024
|
||||
* v0.5.1 generated at Fri Mar 1 01:33:23 PM UTC 2024
|
||||
* https://xrfragment.org
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
@ -2282,7 +2282,7 @@ xrf.frag.src.enableSourcePortation = (opts) => {
|
|||
let frag = {}
|
||||
xrf.Parser.parse("href", url, frag)
|
||||
sphere.userData = scene.userData // allow rich href notifications/hovers
|
||||
sphere.userData.href = url.replace(/(&)?[-][\w-+\.]+(&)?/g,'&') // remove negative selectors to refer to original scene
|
||||
sphere.userData.href = url.replace(/#.*/,'') // remove fragments to refer to original scene
|
||||
sphere.userData.XRF = frag
|
||||
xrf.hashbus.pub.fragment("href", {...opts, mesh:sphere, frag, skipXRWG:true, renderer:xrf.renderer, camera:xrf.camera })
|
||||
}
|
||||
|
|
|
@ -8,10 +8,15 @@
|
|||
<script src="./../../../dist/aframe.min.js"></script> <!-- v1.5.0 -->
|
||||
<script src="./../../../dist/xrfragment.aframe.js"></script>
|
||||
|
||||
<!-- important: allow touchevents in AR -->
|
||||
<style type="text/css"> canvas.a-dom-overlay:not(.a-no-style) { padding: 0; pointer-events: auto; }</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<a-scene xr-mode-ui="XRMode: xr" renderer="colorManagement: true; highRefreshRate:true; " light="defaultLightsEnabled: false">
|
||||
<a-entity id="player" wasd-controls="fly:false" look-controls>
|
||||
<a-scene xr-mode-ui="XRMode: xr"
|
||||
renderer="colorManagement: true; highRefreshRate:true;"
|
||||
light="defaultLightsEnabled: false">
|
||||
<a-entity id="player" movement-controls touch-controls wasd-controls="fly:false" look-controls>
|
||||
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
|
||||
<a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
|
||||
<a-entity rotation="-35 0 0" position="0 0.1 0" id="navigator">
|
||||
|
|
|
@ -42,18 +42,30 @@ window.AFRAME.registerComponent('xrf', {
|
|||
aScene.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
aScene.renderer.toneMappingExposure = 1.25;
|
||||
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
||||
|
||||
if( AFRAME.utils.device.isMobile() ){
|
||||
// aScene.setAttribute('webxr',"requiredFeatures: dom-overlay; overlayElement: canvas; referenceSpaceType: local")
|
||||
}
|
||||
|
||||
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR tags in 3D model
|
||||
ARbutton = document.querySelector('.a-enter-ar-button')
|
||||
VRbutton = document.querySelector('.a-enter-vr-button')
|
||||
if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#AR' ) )
|
||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||
aScene.addEventListener('loaded', () => {
|
||||
// this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model
|
||||
ARbutton = document.querySelector('.a-enter-ar-button')
|
||||
VRbutton = document.querySelector('.a-enter-vr-button')
|
||||
if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#-VR' ) )
|
||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||
//if( AFRAME.utils.device.checkARSupport() && VRbutton ){
|
||||
// VRbutton.style.display = 'none'
|
||||
// ARbutton.parentNode.style.right = '20px'
|
||||
//}
|
||||
})
|
||||
|
||||
aScene.addEventListener('enter-vr', () => {
|
||||
let repositionUser = (scale) => () => {
|
||||
// sometimes AFRAME resets the user position to 0,0,0 when entering VR (not sure why)
|
||||
let pos = xrf.frag.pos.last
|
||||
if( pos ){ AFRAME.XRF.camera.position.set(pos.x, pos.y, pos.z) }
|
||||
})
|
||||
if( pos ){ AFRAME.XRF.camera.position.set(pos.x, pos.y*scale, pos.z) }
|
||||
}
|
||||
aScene.addEventListener('enter-vr', repositionUser(1) )
|
||||
aScene.addEventListener('enter-ar', repositionUser(2) )
|
||||
|
||||
xrf.addEventListener('navigateLoaded', (opts) => {
|
||||
setTimeout( () => AFRAME.fade.out(),500)
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* Movement Controls
|
||||
*
|
||||
* @author Don McCurdy <dm@donmccurdy.com>
|
||||
*/
|
||||
|
||||
const COMPONENT_SUFFIX = '-controls';
|
||||
const MAX_DELTA = 0.2; // ms
|
||||
const EPS = 10e-6;
|
||||
const MOVED = 'moved';
|
||||
|
||||
AFRAME.registerComponent('movement-controls', {
|
||||
|
||||
/*******************************************************************
|
||||
* Schema
|
||||
*/
|
||||
|
||||
dependencies: ['rotation'],
|
||||
|
||||
schema: {
|
||||
enabled: { default: true },
|
||||
controls: { default: ['gamepad', 'trackpad', 'keyboard', 'touch'] },
|
||||
speed: { default: 0.3, min: 0 },
|
||||
fly: { default: false },
|
||||
constrainToNavMesh: { default: false },
|
||||
camera: { default: '[movement-controls] [camera]', type: 'selector' }
|
||||
},
|
||||
|
||||
/*******************************************************************
|
||||
* Lifecycle
|
||||
*/
|
||||
|
||||
init: function () {
|
||||
const el = this.el;
|
||||
if (!this.data.camera) {
|
||||
this.data.camera = el.querySelector('[camera]');
|
||||
}
|
||||
this.velocityCtrl = null;
|
||||
|
||||
this.velocity = new THREE.Vector3();
|
||||
this.heading = new THREE.Quaternion();
|
||||
this.eventDetail = {};
|
||||
|
||||
// Navigation
|
||||
this.navGroup = null;
|
||||
this.navNode = null;
|
||||
|
||||
if (el.sceneEl.hasLoaded) {
|
||||
this.injectControls();
|
||||
} else {
|
||||
el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
update: function (prevData) {
|
||||
const el = this.el;
|
||||
const data = this.data;
|
||||
const nav = el.sceneEl.systems.nav;
|
||||
if (el.sceneEl.hasLoaded) {
|
||||
this.injectControls();
|
||||
}
|
||||
if (nav && data.constrainToNavMesh !== prevData.constrainToNavMesh) {
|
||||
data.constrainToNavMesh
|
||||
? nav.addAgent(this)
|
||||
: nav.removeAgent(this);
|
||||
}
|
||||
if (data.enabled !== prevData.enabled) {
|
||||
// Propagate the enabled change to all controls
|
||||
for (let i = 0; i < data.controls.length; i++) {
|
||||
const name = data.controls[i] + COMPONENT_SUFFIX;
|
||||
this.el.setAttribute(name, { enabled: this.data.enabled });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
injectControls: function () {
|
||||
const data = this.data;
|
||||
|
||||
for (let i = 0; i < data.controls.length; i++) {
|
||||
const name = data.controls[i] + COMPONENT_SUFFIX;
|
||||
this.el.setAttribute(name, { enabled: this.data.enabled });
|
||||
}
|
||||
},
|
||||
|
||||
updateNavLocation: function () {
|
||||
this.navGroup = null;
|
||||
this.navNode = null;
|
||||
},
|
||||
|
||||
/*******************************************************************
|
||||
* Tick
|
||||
*/
|
||||
|
||||
tick: (function () {
|
||||
const start = new THREE.Vector3();
|
||||
const end = new THREE.Vector3();
|
||||
const clampedEnd = new THREE.Vector3();
|
||||
|
||||
return function (t, dt) {
|
||||
if (!dt) return;
|
||||
|
||||
const el = this.el;
|
||||
const data = this.data;
|
||||
|
||||
if (!data.enabled) return;
|
||||
|
||||
this.updateVelocityCtrl();
|
||||
const velocityCtrl = this.velocityCtrl;
|
||||
const velocity = this.velocity;
|
||||
|
||||
if (!velocityCtrl) return;
|
||||
|
||||
// Update velocity. If FPS is too low, reset.
|
||||
if (dt / 1000 > MAX_DELTA) {
|
||||
velocity.set(0, 0, 0);
|
||||
} else {
|
||||
this.updateVelocity(dt);
|
||||
}
|
||||
|
||||
if (data.constrainToNavMesh
|
||||
&& velocityCtrl.isNavMeshConstrained !== false) {
|
||||
|
||||
if (velocity.lengthSq() < EPS) return;
|
||||
|
||||
start.copy(el.object3D.position);
|
||||
end
|
||||
.copy(velocity)
|
||||
.multiplyScalar(dt / 1000)
|
||||
.add(start);
|
||||
|
||||
const nav = el.sceneEl.systems.nav;
|
||||
this.navGroup = this.navGroup === null ? nav.getGroup(start) : this.navGroup;
|
||||
this.navNode = this.navNode || nav.getNode(start, this.navGroup);
|
||||
this.navNode = nav.clampStep(start, end, this.navGroup, this.navNode, clampedEnd);
|
||||
el.object3D.position.copy(clampedEnd);
|
||||
} else if (el.hasAttribute('velocity')) {
|
||||
el.setAttribute('velocity', velocity);
|
||||
} else {
|
||||
el.object3D.position.x += velocity.x * dt / 1000;
|
||||
el.object3D.position.y += velocity.y * dt / 1000;
|
||||
el.object3D.position.z += velocity.z * dt / 1000;
|
||||
}
|
||||
|
||||
};
|
||||
}()),
|
||||
|
||||
/*******************************************************************
|
||||
* Movement
|
||||
*/
|
||||
|
||||
updateVelocityCtrl: function () {
|
||||
const data = this.data;
|
||||
if (data.enabled) {
|
||||
for (let i = 0, l = data.controls.length; i < l; i++) {
|
||||
const control = this.el.components[data.controls[i] + COMPONENT_SUFFIX];
|
||||
if (control && control.isVelocityActive()) {
|
||||
this.velocityCtrl = control;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.velocityCtrl = null;
|
||||
}
|
||||
},
|
||||
|
||||
updateVelocity: (function () {
|
||||
const vector2 = new THREE.Vector2();
|
||||
const quaternion = new THREE.Quaternion();
|
||||
|
||||
return function (dt) {
|
||||
let dVelocity;
|
||||
const el = this.el;
|
||||
const control = this.velocityCtrl;
|
||||
const velocity = this.velocity;
|
||||
const data = this.data;
|
||||
|
||||
if (control) {
|
||||
if (control.getVelocityDelta) {
|
||||
dVelocity = control.getVelocityDelta(dt);
|
||||
} else if (control.getVelocity) {
|
||||
velocity.copy(control.getVelocity());
|
||||
return;
|
||||
} else if (control.getPositionDelta) {
|
||||
velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
|
||||
return;
|
||||
} else {
|
||||
throw new Error('Incompatible movement controls: ', control);
|
||||
}
|
||||
}
|
||||
|
||||
if (el.hasAttribute('velocity') && !data.constrainToNavMesh) {
|
||||
velocity.copy(this.el.getAttribute('velocity'));
|
||||
}
|
||||
|
||||
if (dVelocity && data.enabled) {
|
||||
const cameraEl = data.camera;
|
||||
|
||||
// Rotate to heading
|
||||
quaternion.copy(cameraEl.object3D.quaternion);
|
||||
quaternion.premultiply(el.object3D.quaternion);
|
||||
dVelocity.applyQuaternion(quaternion);
|
||||
|
||||
const factor = dVelocity.length();
|
||||
if (data.fly) {
|
||||
velocity.copy(dVelocity);
|
||||
velocity.multiplyScalar(this.data.speed * 16.66667);
|
||||
} else {
|
||||
vector2.set(dVelocity.x, dVelocity.z);
|
||||
vector2.setLength(factor * this.data.speed * 16.66667);
|
||||
velocity.x = vector2.x;
|
||||
velocity.y = 0;
|
||||
velocity.z = vector2.y;
|
||||
}
|
||||
if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
|
||||
this.eventDetail.velocity = velocity;
|
||||
this.el.emit(MOVED, this.eventDetail);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}())
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Touch-to-move-forward controls for mobile.
|
||||
*/
|
||||
AFRAME.registerComponent('touch-controls', {
|
||||
schema: {
|
||||
enabled: { default: true },
|
||||
reverseEnabled: { default: true }
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.dVelocity = new THREE.Vector3();
|
||||
this.bindMethods();
|
||||
this.direction = 0;
|
||||
},
|
||||
|
||||
play: function () {
|
||||
this.addEventListeners();
|
||||
},
|
||||
|
||||
pause: function () {
|
||||
this.removeEventListeners();
|
||||
this.dVelocity.set(0, 0, 0);
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
this.pause();
|
||||
},
|
||||
|
||||
addEventListeners: function () {
|
||||
const sceneEl = this.el.sceneEl;
|
||||
const canvasEl = sceneEl.canvas;
|
||||
|
||||
if (!canvasEl) {
|
||||
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
canvasEl.addEventListener('touchstart', this.onTouchStart);
|
||||
canvasEl.addEventListener('touchend', this.onTouchEnd);
|
||||
const vrModeUI = sceneEl.getAttribute('vr-mode-ui');
|
||||
if (vrModeUI && vrModeUI.cardboardModeEnabled) {
|
||||
sceneEl.addEventListener('enter-vr', this.onEnterVR);
|
||||
}
|
||||
},
|
||||
|
||||
removeEventListeners: function () {
|
||||
const canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
|
||||
if (!canvasEl) { return; }
|
||||
|
||||
canvasEl.removeEventListener('touchstart', this.onTouchStart);
|
||||
canvasEl.removeEventListener('touchend', this.onTouchEnd);
|
||||
this.el.sceneEl.removeEventListener('enter-vr', this.onEnterVR)
|
||||
},
|
||||
|
||||
isVelocityActive: function () {
|
||||
return this.data.enabled && !!this.direction;
|
||||
},
|
||||
|
||||
getVelocityDelta: function () {
|
||||
this.dVelocity.z = this.direction;
|
||||
return this.dVelocity.clone();
|
||||
},
|
||||
|
||||
bindMethods: function () {
|
||||
this.onTouchStart = this.onTouchStart.bind(this);
|
||||
this.onTouchEnd = this.onTouchEnd.bind(this);
|
||||
this.onEnterVR = this.onEnterVR.bind(this);
|
||||
},
|
||||
|
||||
onTouchStart: function (e) {
|
||||
this.direction = 0;
|
||||
if (this.data.reverseEnabled && e.touches ){
|
||||
if( e.touches.length === 3) this.direction = 1;
|
||||
if( e.touches.length === 2) this.direction = -1;
|
||||
}
|
||||
//e.preventDefault();
|
||||
},
|
||||
|
||||
onTouchEnd: function (e) {
|
||||
this.direction = 0;
|
||||
//e.preventDefault();
|
||||
},
|
||||
|
||||
onEnterVR: function () {
|
||||
// This is to make the Cardboard button on Chrome Android working
|
||||
//const xrSession = this.el.sceneEl.xrSession;
|
||||
//if (!xrSession) { return; }
|
||||
//xrSession.addEventListener('selectstart', this.onTouchStart);
|
||||
//xrSession.addEventListener('selectend', this.onTouchEnd);
|
||||
}
|
||||
})
|
|
@ -1,8 +1,9 @@
|
|||
AFRAME.registerComponent('vconsole', {
|
||||
init: function () {
|
||||
//AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
|
||||
return
|
||||
let aScene = AFRAME.scenes[0]
|
||||
|
||||
return
|
||||
document.head.innerHTML += `
|
||||
<style type="text/css">
|
||||
.vc-panel {
|
||||
|
|
|
@ -11,11 +11,12 @@ AFRAME.registerComponent('xrf-gaze',{
|
|||
|
||||
}
|
||||
},
|
||||
setGazer: function(state){
|
||||
setGazer: function(state, fuse){
|
||||
if( !AFRAME.utils.device.isMobile() ) return
|
||||
let cam = document.querySelector("[camera]")
|
||||
if( state ){
|
||||
if( cam.innerHTML.match(/cursor/) ) return; // avoid duplicate calls
|
||||
cam.innerHTML = `<a-entity id="cursor" cursor="fuse: true; fuseTimeout: 1500"
|
||||
cam.innerHTML = `<a-entity id="cursor" cursor="fuse: ${fuse ? 'true': 'false'}; fuseTimeout: 1500"
|
||||
animation__click="property: scale; startEvents: click; easing: easeInCubic; dur: 150; from: 0.1 0.1 0.1; to: 1 1 1"
|
||||
animation__fusing="property: scale; startEvents: fusing; easing: easeInCubic; dur: 1500; from: 1 1 1; to: 0.1 0.1 0.1"
|
||||
animation__mouseleave="property: scale; startEvents: mouseleave; easing: easeInCubic; dur: 500; to: 1 1 1"
|
||||
|
@ -24,27 +25,22 @@ AFRAME.registerComponent('xrf-gaze',{
|
|||
position="0 0 -1"
|
||||
material="color: #BBBBBB; shader: flat">
|
||||
</a-entity>`
|
||||
cam.querySelector('#cursor').setAttribute("geometry","primitive: ring; radiusInner: 0.02; radiusOuter: 0.03")
|
||||
}else{
|
||||
cam.querySelector('#cursor').removeAttribute("geometry")
|
||||
if( document.querySelector('[cursor]') ) {
|
||||
document.querySelector('[cursor]').setAttribute("visible",false)
|
||||
}
|
||||
}
|
||||
},
|
||||
init:function(data){
|
||||
let setVisible = (state) => {
|
||||
if( AFRAME.utils.device.isMobile() ){
|
||||
this.setGazer(state)
|
||||
if( state || xrf.debug ) this.el.setAttribute("geometry","primitive: ring; radiusInner: 0.02; radiusOuter: 0.03")
|
||||
else this.el.removeAttribute("geometry")
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
this.setGazer(true);
|
||||
|
||||
document.querySelector("a-scene").addEventListener('exit-vr', () => setVisible(false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-vr', () => setVisible(true) )
|
||||
document.querySelector("a-scene").addEventListener('exit-ar', () => setVisible(false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-ar', () => setVisible(true) )
|
||||
document.querySelector("a-scene").addEventListener('exit-vr', () => this.setGazer(false,false) )
|
||||
document.querySelector("a-scene").addEventListener('enter-vr', () => this.setGazer(true,true) )
|
||||
document.querySelector("a-scene").addEventListener('enter-ar', () => this.setGazer(true,false) )
|
||||
document.querySelector("a-scene").addEventListener('exit-ar', () => this.setGazer(false,false) )
|
||||
|
||||
let highlightMesh = (state) => (e) => {
|
||||
if( !e.target.object3D ) return
|
||||
|
|
|
@ -149,6 +149,7 @@ window.accessibility = (opts) => new Proxy({
|
|||
})
|
||||
|
||||
document.addEventListener('$menu:ready', (e) => {
|
||||
return
|
||||
try{
|
||||
accessibility = accessibility(e.detail)
|
||||
accessibility.init()
|
||||
|
|
|
@ -90,8 +90,13 @@ window.frontend = (opts) => new Proxy({
|
|||
window.notify('loading '+document.location.search.substr(1))
|
||||
|
||||
setTimeout( () => {
|
||||
window.notify("use WASD-keys and mouse-drag to move around",{timeout:false})
|
||||
xrf.addEventListener('navigate', () => SnackBar() ) // close dialogs when url changes
|
||||
let instructions = AFRAME.utils.device.isMobile()
|
||||
? "hold 2-3 fingers to move forward/backward"
|
||||
: "use WASD-keys and mouse-drag to move around"
|
||||
window.notify(instructions,{timeout:false})
|
||||
xrf.addEventListener('navigate', (opts) => {
|
||||
window.notify('<b class="badge">teleporting</b> to <b>'+opts.url+"</b><br><br>use back/forward browserbutton to undo")
|
||||
}) // close dialogs when url changes
|
||||
},2000 )
|
||||
|
||||
xrf.addEventListener('href', (data) => {
|
||||
|
@ -211,7 +216,6 @@ window.frontend = (opts) => new Proxy({
|
|||
},
|
||||
|
||||
notify(_str,opts){
|
||||
if( window.outerWidth < 800 ) return
|
||||
if( window.accessibility && window.accessibility.enabled ) return $chat.send({message:_str,class:['info']})
|
||||
opts = opts || {status:'info'}
|
||||
opts = Object.assign({ status, timeout:4000 },opts)
|
||||
|
|
|
@ -81,7 +81,7 @@ xrf.frag.src.enableSourcePortation = (opts) => {
|
|||
let frag = {}
|
||||
xrf.Parser.parse("href", url, frag)
|
||||
sphere.userData = scene.userData // allow rich href notifications/hovers
|
||||
sphere.userData.href = url.replace(/(&)?[-][\w-+\.]+(&)?/g,'&') // remove negative selectors to refer to original scene
|
||||
sphere.userData.href = url.replace(/#.*/,'') // remove fragments to refer to original scene
|
||||
sphere.userData.XRF = frag
|
||||
xrf.hashbus.pub.fragment("href", {...opts, mesh:sphere, frag, skipXRWG:true, renderer:xrf.renderer, camera:xrf.camera })
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue