upgraded aframe to v1.6.0 + added check for duplicate movement-controls & touch-controls

This commit is contained in:
Leon van Kammen 2024-12-09 15:42:34 +00:00
parent c3b0636bf7
commit 4bc3d1c520
17 changed files with 19125 additions and 1073 deletions

2
dist/aframe.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,43 @@
/*
* v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:39:00 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:50 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:27 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:37:05 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:36:14 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:35:39 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 03:32:13 PM UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024
* https://xrfragment.org * https://xrfragment.org
@ -3064,10 +3104,6 @@ xrf.frag.pos = function(v, opts){
if( pos.x == undefined ){ if( pos.x == undefined ){
let obj = scene.getObjectByName(v.string) let obj = scene.getObjectByName(v.string)
if( !obj ) return console.warn("#pos="+v.string+" not found") if( !obj ) return console.warn("#pos="+v.string+" not found")
//let worldPos = new THREE.Vector3()
//obj.getWorldPosition(worldPos)
//camera.position.copy(worldPos)
//obj.attach(camera) // instead of add() [keeps camera animations intact]
obj.add(camera) obj.add(camera)
camera.position.set(0,0,0) camera.position.set(0,0,0)
let c = camera.rotation let c = camera.rotation
@ -4834,6 +4870,8 @@ window.AFRAME.registerComponent('xrf', {
}, },
init: async function () { init: async function () {
this.data = Object.values(this.attrValue)[0]
// override this.data when URL has passed (`://....com/?https://foo.com/index.glb` e.g.) // override this.data when URL has passed (`://....com/?https://foo.com/index.glb` e.g.)
if( typeof this.data == "string" ){ if( typeof this.data == "string" ){
let searchIsUri = document.location.search && let searchIsUri = document.location.search &&
@ -5048,216 +5086,219 @@ const MAX_DELTA = 0.2; // ms
const EPS = 10e-6; const EPS = 10e-6;
const MOVED = 'moved'; const MOVED = 'moved';
AFRAME.registerComponent('movement-controls', { if( !AFRAME.components['movement-controls'] ){
/******************************************************************* AFRAME.registerComponent('movement-controls', {
* Schema
*/
dependencies: ['rotation'], /*******************************************************************
* Schema
*/
schema: { dependencies: ['rotation'],
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' }
},
/******************************************************************* schema: {
* Lifecycle 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' }
},
init: function () { /*******************************************************************
const el = this.el; * Lifecycle
if (!this.data.camera) { */
this.data.camera = el.querySelector('[camera]');
}
this.velocityCtrl = null;
this.velocity = new THREE.Vector3(); init: function () {
this.heading = new THREE.Quaternion(); const el = this.el;
this.eventDetail = {}; if (!this.data.camera) {
this.data.camera = el.querySelector('[camera]');
}
this.velocityCtrl = null;
// Navigation this.velocity = new THREE.Vector3();
this.navGroup = null; this.heading = new THREE.Quaternion();
this.navNode = null; this.eventDetail = {};
if (el.sceneEl.hasLoaded) { // Navigation
this.injectControls(); this.navGroup = null;
} else { this.navNode = null;
el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
} 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;
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++) { for (let i = 0; i < data.controls.length; i++) {
const name = data.controls[i] + COMPONENT_SUFFIX; const name = data.controls[i] + COMPONENT_SUFFIX;
this.el.setAttribute(name, { enabled: this.data.enabled }); this.el.setAttribute(name, { enabled: this.data.enabled });
} }
} },
},
injectControls: function () { updateNavLocation: function () {
const data = this.data; this.navGroup = null;
this.navNode = null;
},
for (let i = 0; i < data.controls.length; i++) { /*******************************************************************
const name = data.controls[i] + COMPONENT_SUFFIX; * Tick
this.el.setAttribute(name, { enabled: this.data.enabled }); */
}
},
updateNavLocation: function () { tick: (function () {
this.navGroup = null; const start = new THREE.Vector3();
this.navNode = null; const end = new THREE.Vector3();
}, const clampedEnd = new THREE.Vector3();
/******************************************************************* return function (t, dt) {
* Tick if (!dt) return;
*/
tick: (function () { const el = this.el;
const start = new THREE.Vector3(); const data = this.data;
const end = new THREE.Vector3();
const clampedEnd = new THREE.Vector3();
return function (t, dt) { if (!data.enabled) return;
if (!dt) return;
const el = this.el; this.updateVelocityCtrl();
const data = this.data; const velocityCtrl = this.velocityCtrl;
const velocity = this.velocity;
if (!data.enabled) return; if (!velocityCtrl) return;
this.updateVelocityCtrl(); // Update velocity. If FPS is too low, reset.
const velocityCtrl = this.velocityCtrl; if (dt / 1000 > MAX_DELTA) {
const velocity = this.velocity; velocity.set(0, 0, 0);
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 { } else {
throw new Error('Incompatible movement controls: ', control); this.updateVelocity(dt);
} }
}
if (el.hasAttribute('velocity') && !data.constrainToNavMesh) { if (data.constrainToNavMesh
velocity.copy(this.el.getAttribute('velocity')); && velocityCtrl.isNavMeshConstrained !== false) {
}
if (dVelocity && data.enabled) { if (velocity.lengthSq() < EPS) return;
const cameraEl = data.camera;
// Rotate to heading start.copy(el.object3D.position);
quaternion.copy(cameraEl.object3D.quaternion); end
quaternion.premultiply(el.object3D.quaternion); .copy(velocity)
dVelocity.applyQuaternion(quaternion); .multiplyScalar(dt / 1000)
.add(start);
const factor = dVelocity.length(); const nav = el.sceneEl.systems.nav;
if (data.fly) { this.navGroup = this.navGroup === null ? nav.getGroup(start) : this.navGroup;
velocity.copy(dVelocity); this.navNode = this.navNode || nav.getNode(start, this.navGroup);
velocity.multiplyScalar(this.data.speed * 16.66667); 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 { } else {
vector2.set(dVelocity.x, dVelocity.z); el.object3D.position.x += velocity.x * dt / 1000;
vector2.setLength(factor * this.data.speed * 16.66667); el.object3D.position.y += velocity.y * dt / 1000;
velocity.x = vector2.x; el.object3D.position.z += velocity.z * dt / 1000;
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);
}
}
};
}()) };
}); }()),
/*******************************************************************
* 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);
}
}
};
}())
});
}
AFRAME.components['hand-tracking-controls'].Component.prototype.onModelLoaded = function(onModelLoaded){ AFRAME.components['hand-tracking-controls'].Component.prototype.onModelLoaded = function(onModelLoaded){
return function(e){ return function(e){
onModelLoaded.apply(this); onModelLoaded.apply(this);
@ -5325,94 +5366,98 @@ AFRAME.components['look-controls'].Component.prototype.updateOrientation = funct
/** /**
* Touch-to-move-forward controls for mobile. * Touch-to-move-forward controls for mobile.
*/ */
AFRAME.registerComponent('touch-controls', {
schema: {
enabled: { default: true },
reverseEnabled: { default: true }
},
init: function () { if( !AFRAME.components['touch-controls'] ){
this.dVelocity = new THREE.Vector3();
this.bindMethods();
this.direction = 0;
},
play: function () { AFRAME.registerComponent('touch-controls', {
this.addEventListeners(); schema: {
}, enabled: { default: true },
reverseEnabled: { default: true }
},
pause: function () { init: function () {
this.removeEventListeners(); this.dVelocity = new THREE.Vector3();
this.dVelocity.set(0, 0, 0); this.bindMethods();
}, this.direction = 0;
},
remove: function () { play: function () {
this.pause(); this.addEventListeners();
}, },
addEventListeners: function () { pause: function () {
const sceneEl = this.el.sceneEl; this.removeEventListeners();
const canvasEl = sceneEl.canvas; this.dVelocity.set(0, 0, 0);
},
if (!canvasEl) { remove: function () {
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this)); this.pause();
return; },
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);
} }
})
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', { window.AFRAME.registerComponent('xrf-button', {
schema: { schema: {
label: { label: {

40
dist/xrfragment.js vendored
View File

@ -1,3 +1,43 @@
/*
* v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:39:00 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:50 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:27 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:37:05 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:36:14 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:35:39 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 03:32:13 PM UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024
* https://xrfragment.org * https://xrfragment.org

File diff suppressed because one or more lines are too long

17796
dist/xrfragment.module.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org * https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org * https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org * https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org * https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org * https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org * https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */

View File

@ -1,3 +1,43 @@
/*
* v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:39:00 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:50 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:27 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:37:05 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:36:14 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:35:39 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 03:32:13 PM UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024
* https://xrfragment.org * https://xrfragment.org
@ -3064,10 +3104,6 @@ xrf.frag.pos = function(v, opts){
if( pos.x == undefined ){ if( pos.x == undefined ){
let obj = scene.getObjectByName(v.string) let obj = scene.getObjectByName(v.string)
if( !obj ) return console.warn("#pos="+v.string+" not found") if( !obj ) return console.warn("#pos="+v.string+" not found")
//let worldPos = new THREE.Vector3()
//obj.getWorldPosition(worldPos)
//camera.position.copy(worldPos)
//obj.attach(camera) // instead of add() [keeps camera animations intact]
obj.add(camera) obj.add(camera)
camera.position.set(0,0,0) camera.position.set(0,0,0)
let c = camera.rotation let c = camera.rotation

View File

@ -1,3 +1,43 @@
/*
* v0.5.1 generated at Mon Dec 9 15:41:10 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:39:00 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:50 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:38:27 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:37:05 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:36:14 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 15:35:39 UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Mon Dec 9 03:32:13 PM UTC 2024
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/* /*
* v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024 * v0.5.1 generated at Mon Oct 14 11:39:48 AM CEST 2024
* https://xrfragment.org * https://xrfragment.org
@ -3064,10 +3104,6 @@ xrf.frag.pos = function(v, opts){
if( pos.x == undefined ){ if( pos.x == undefined ){
let obj = scene.getObjectByName(v.string) let obj = scene.getObjectByName(v.string)
if( !obj ) return console.warn("#pos="+v.string+" not found") if( !obj ) return console.warn("#pos="+v.string+" not found")
//let worldPos = new THREE.Vector3()
//obj.getWorldPosition(worldPos)
//camera.position.copy(worldPos)
//obj.attach(camera) // instead of add() [keeps camera animations intact]
obj.add(camera) obj.add(camera)
camera.position.set(0,0,0) camera.position.set(0,0,0)
let c = camera.rotation let c = camera.rotation

View File

@ -5,6 +5,8 @@ window.AFRAME.registerComponent('xrf', {
}, },
init: async function () { init: async function () {
this.data = Object.values(this.attrValue)[0]
// override this.data when URL has passed (`://....com/?https://foo.com/index.glb` e.g.) // override this.data when URL has passed (`://....com/?https://foo.com/index.glb` e.g.)
if( typeof this.data == "string" ){ if( typeof this.data == "string" ){
let searchIsUri = document.location.search && let searchIsUri = document.location.search &&

View File

@ -9,213 +9,216 @@ const MAX_DELTA = 0.2; // ms
const EPS = 10e-6; const EPS = 10e-6;
const MOVED = 'moved'; const MOVED = 'moved';
AFRAME.registerComponent('movement-controls', { if( !AFRAME.components['movement-controls'] ){
/******************************************************************* AFRAME.registerComponent('movement-controls', {
* Schema
*/
dependencies: ['rotation'], /*******************************************************************
* Schema
*/
schema: { dependencies: ['rotation'],
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' }
},
/******************************************************************* schema: {
* Lifecycle 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' }
},
init: function () { /*******************************************************************
const el = this.el; * Lifecycle
if (!this.data.camera) { */
this.data.camera = el.querySelector('[camera]');
}
this.velocityCtrl = null;
this.velocity = new THREE.Vector3(); init: function () {
this.heading = new THREE.Quaternion(); const el = this.el;
this.eventDetail = {}; if (!this.data.camera) {
this.data.camera = el.querySelector('[camera]');
}
this.velocityCtrl = null;
// Navigation this.velocity = new THREE.Vector3();
this.navGroup = null; this.heading = new THREE.Quaternion();
this.navNode = null; this.eventDetail = {};
if (el.sceneEl.hasLoaded) { // Navigation
this.injectControls(); this.navGroup = null;
} else { this.navNode = null;
el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
} 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;
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++) { for (let i = 0; i < data.controls.length; i++) {
const name = data.controls[i] + COMPONENT_SUFFIX; const name = data.controls[i] + COMPONENT_SUFFIX;
this.el.setAttribute(name, { enabled: this.data.enabled }); this.el.setAttribute(name, { enabled: this.data.enabled });
} }
} },
},
injectControls: function () { updateNavLocation: function () {
const data = this.data; this.navGroup = null;
this.navNode = null;
},
for (let i = 0; i < data.controls.length; i++) { /*******************************************************************
const name = data.controls[i] + COMPONENT_SUFFIX; * Tick
this.el.setAttribute(name, { enabled: this.data.enabled }); */
}
},
updateNavLocation: function () { tick: (function () {
this.navGroup = null; const start = new THREE.Vector3();
this.navNode = null; const end = new THREE.Vector3();
}, const clampedEnd = new THREE.Vector3();
/******************************************************************* return function (t, dt) {
* Tick if (!dt) return;
*/
tick: (function () { const el = this.el;
const start = new THREE.Vector3(); const data = this.data;
const end = new THREE.Vector3();
const clampedEnd = new THREE.Vector3();
return function (t, dt) { if (!data.enabled) return;
if (!dt) return;
const el = this.el; this.updateVelocityCtrl();
const data = this.data; const velocityCtrl = this.velocityCtrl;
const velocity = this.velocity;
if (!data.enabled) return; if (!velocityCtrl) return;
this.updateVelocityCtrl(); // Update velocity. If FPS is too low, reset.
const velocityCtrl = this.velocityCtrl; if (dt / 1000 > MAX_DELTA) {
const velocity = this.velocity; velocity.set(0, 0, 0);
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 { } else {
throw new Error('Incompatible movement controls: ', control); this.updateVelocity(dt);
} }
}
if (el.hasAttribute('velocity') && !data.constrainToNavMesh) { if (data.constrainToNavMesh
velocity.copy(this.el.getAttribute('velocity')); && velocityCtrl.isNavMeshConstrained !== false) {
}
if (dVelocity && data.enabled) { if (velocity.lengthSq() < EPS) return;
const cameraEl = data.camera;
// Rotate to heading start.copy(el.object3D.position);
quaternion.copy(cameraEl.object3D.quaternion); end
quaternion.premultiply(el.object3D.quaternion); .copy(velocity)
dVelocity.applyQuaternion(quaternion); .multiplyScalar(dt / 1000)
.add(start);
const factor = dVelocity.length(); const nav = el.sceneEl.systems.nav;
if (data.fly) { this.navGroup = this.navGroup === null ? nav.getGroup(start) : this.navGroup;
velocity.copy(dVelocity); this.navNode = this.navNode || nav.getNode(start, this.navGroup);
velocity.multiplyScalar(this.data.speed * 16.66667); 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 { } else {
vector2.set(dVelocity.x, dVelocity.z); el.object3D.position.x += velocity.x * dt / 1000;
vector2.setLength(factor * this.data.speed * 16.66667); el.object3D.position.y += velocity.y * dt / 1000;
velocity.x = vector2.x; el.object3D.position.z += velocity.z * dt / 1000;
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);
}
}
};
}()) };
}); }()),
/*******************************************************************
* 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);
}
}
};
}())
});
}

View File

@ -1,91 +1,95 @@
/** /**
* Touch-to-move-forward controls for mobile. * Touch-to-move-forward controls for mobile.
*/ */
AFRAME.registerComponent('touch-controls', {
schema: {
enabled: { default: true },
reverseEnabled: { default: true }
},
init: function () { if( !AFRAME.components['touch-controls'] ){
this.dVelocity = new THREE.Vector3();
this.bindMethods();
this.direction = 0;
},
play: function () { AFRAME.registerComponent('touch-controls', {
this.addEventListeners(); schema: {
}, enabled: { default: true },
reverseEnabled: { default: true }
},
pause: function () { init: function () {
this.removeEventListeners(); this.dVelocity = new THREE.Vector3();
this.dVelocity.set(0, 0, 0); this.bindMethods();
}, this.direction = 0;
},
remove: function () { play: function () {
this.pause(); this.addEventListeners();
}, },
addEventListeners: function () { pause: function () {
const sceneEl = this.el.sceneEl; this.removeEventListeners();
const canvasEl = sceneEl.canvas; this.dVelocity.set(0, 0, 0);
},
if (!canvasEl) { remove: function () {
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this)); this.pause();
return; },
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);
} }
})
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);
}
})