added docs + compiled sources
This commit is contained in:
parent
955baeb2f4
commit
a428150b0a
17 changed files with 4138 additions and 546 deletions
170
dist/xrfragment.aframe.js
vendored
170
dist/xrfragment.aframe.js
vendored
|
|
@ -852,6 +852,30 @@ xrf.add = (object) => {
|
||||||
object.isXRF = true // mark for easy deletion when replacing scene
|
object.isXRF = true // mark for easy deletion when replacing scene
|
||||||
xrf.scene.add(object)
|
xrf.scene.add(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EVENTS
|
||||||
|
*/
|
||||||
|
|
||||||
|
xrf.addEventListener = function(eventName, callback) {
|
||||||
|
if( !this._listeners ) this._listeners = []
|
||||||
|
if (!this._listeners[eventName]) {
|
||||||
|
// create a new array for this event name if it doesn't exist yet
|
||||||
|
this._listeners[eventName] = [];
|
||||||
|
}
|
||||||
|
// add the callback to the listeners array for this event name
|
||||||
|
this._listeners[eventName].push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
xrf.emit = function(eventName, data) {
|
||||||
|
if( !this._listeners ) this._listeners = []
|
||||||
|
var callbacks = this._listeners[eventName]
|
||||||
|
if (callbacks) {
|
||||||
|
for (var i = 0; i < callbacks.length; i++) {
|
||||||
|
callbacks[i](data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
xrf.navigator = {}
|
xrf.navigator = {}
|
||||||
|
|
||||||
xrf.navigator.to = (url,event) => {
|
xrf.navigator.to = (url,event) => {
|
||||||
|
|
@ -934,62 +958,67 @@ xrf.frag.env = function(v, opts){
|
||||||
xrf.frag.href = function(v, opts){
|
xrf.frag.href = function(v, opts){
|
||||||
let { mesh, model, camera, scene, renderer, THREE} = opts
|
let { mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
|
|
||||||
const world = { pos: new THREE.Vector3(), scale: new THREE.Vector3() }
|
const world = {
|
||||||
|
pos: new THREE.Vector3(),
|
||||||
|
scale: new THREE.Vector3(),
|
||||||
|
quat: new THREE.Quaternion()
|
||||||
|
}
|
||||||
mesh.getWorldPosition(world.pos)
|
mesh.getWorldPosition(world.pos)
|
||||||
mesh.getWorldScale(world.scale)
|
mesh.getWorldScale(world.scale)
|
||||||
|
mesh.getWorldQuaternion(world.quat);
|
||||||
mesh.position.copy(world.pos)
|
mesh.position.copy(world.pos)
|
||||||
mesh.scale.copy(world.scale)
|
mesh.scale.copy(world.scale)
|
||||||
|
mesh.setRotationFromQuaternion(world.quat);
|
||||||
|
|
||||||
// convert texture if needed
|
// detect equirectangular image
|
||||||
let texture = mesh.material.map
|
let texture = mesh.material.map
|
||||||
if( texture && texture.source.data.height == texture.source.data.width/2 ){
|
if( texture && texture.source.data.height == texture.source.data.width/2 ){
|
||||||
// assume equirectangular image
|
|
||||||
texture.mapping = THREE.ClampToEdgeWrapping
|
texture.mapping = THREE.ClampToEdgeWrapping
|
||||||
texture.needsUpdate = true
|
texture.needsUpdate = true
|
||||||
}
|
|
||||||
|
|
||||||
// poor man's equi-portal
|
// poor man's equi-portal
|
||||||
mesh.material = new THREE.ShaderMaterial( {
|
mesh.material = new THREE.ShaderMaterial( {
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
pano: { value: texture },
|
pano: { value: texture },
|
||||||
highlight: { value: false },
|
selected: { value: false },
|
||||||
},
|
},
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
vec3 portalPosition;
|
vec3 portalPosition;
|
||||||
varying vec3 vWorldPosition;
|
varying vec3 vWorldPosition;
|
||||||
varying float vDistanceToCenter;
|
varying float vDistanceToCenter;
|
||||||
varying float vDistance;
|
varying float vDistance;
|
||||||
void main() {
|
void main() {
|
||||||
vDistanceToCenter = clamp(length(position - vec3(0.0, 0.0, 0.0)), 0.0, 1.0);
|
vDistanceToCenter = clamp(length(position - vec3(0.0, 0.0, 0.0)), 0.0, 1.0);
|
||||||
portalPosition = (modelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
portalPosition = (modelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
||||||
vDistance = length(portalPosition - cameraPosition);
|
vDistance = length(portalPosition - cameraPosition);
|
||||||
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
fragmentShader: `
|
fragmentShader: `
|
||||||
#define RECIPROCAL_PI2 0.15915494
|
#define RECIPROCAL_PI2 0.15915494
|
||||||
uniform sampler2D pano;
|
uniform sampler2D pano;
|
||||||
uniform bool highlight;
|
uniform bool selected;
|
||||||
varying float vDistanceToCenter;
|
varying float vDistanceToCenter;
|
||||||
varying float vDistance;
|
varying float vDistance;
|
||||||
varying vec3 vWorldPosition;
|
varying vec3 vWorldPosition;
|
||||||
void main() {
|
void main() {
|
||||||
vec3 direction = normalize(vWorldPosition - cameraPosition);
|
vec3 direction = normalize(vWorldPosition - cameraPosition);
|
||||||
vec2 sampleUV;
|
vec2 sampleUV;
|
||||||
sampleUV.y = -clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
|
sampleUV.y = -clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
|
||||||
sampleUV.x = atan(direction.z, -direction.x) * -RECIPROCAL_PI2;
|
sampleUV.x = atan(direction.z, -direction.x) * -RECIPROCAL_PI2;
|
||||||
sampleUV.x += 0.33; // adjust focus to AFRAME's $('a-scene').components.screenshot.capture()
|
sampleUV.x += 0.33; // adjust focus to AFRAME's $('a-scene').components.screenshot.capture()
|
||||||
vec4 color = texture2D(pano, sampleUV);
|
vec4 color = texture2D(pano, sampleUV);
|
||||||
// Convert color to grayscale (lazy lite approach to not having to match tonemapping/shaderstacking of THREE.js)
|
// Convert color to grayscale (lazy lite approach to not having to match tonemapping/shaderstacking of THREE.js)
|
||||||
float luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
float luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
||||||
vec4 grayscale_color = highlight ? color : vec4(vec3(luminance) + vec3(0.33), color.a);
|
vec4 grayscale_color = selected ? color : vec4(vec3(luminance) + vec3(0.33), color.a);
|
||||||
gl_FragColor = grayscale_color;
|
gl_FragColor = grayscale_color;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
mesh.material.needsUpdate = true
|
mesh.material.needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
let teleport = mesh.userData.XRF.href.exec = (e) => {
|
let teleport = mesh.userData.XRF.href.exec = (e) => {
|
||||||
if( mesh.clicked ) return
|
if( mesh.clicked ) return
|
||||||
|
|
@ -1005,31 +1034,31 @@ xrf.frag.href = function(v, opts){
|
||||||
cameraDirection.multiplyScalar(portalArea); // move away from portal
|
cameraDirection.multiplyScalar(portalArea); // move away from portal
|
||||||
const newPos = meshWorldPosition.clone().add(cameraDirection);
|
const newPos = meshWorldPosition.clone().add(cameraDirection);
|
||||||
|
|
||||||
const positionInFrontOfPortal = () => {
|
|
||||||
camera.position.copy(newPos);
|
|
||||||
camera.lookAt(meshWorldPosition);
|
|
||||||
|
|
||||||
if( renderer.xr.isPresenting && xrf.baseReferenceSpace ){ // WebXR VR/AR roomscale reposition
|
|
||||||
const offsetPosition = { x: -newPos.x, y: 0, z: -newPos.z, w: 1 };
|
|
||||||
const offsetRotation = new THREE.Quaternion();
|
|
||||||
const transform = new XRRigidTransform( offsetPosition, offsetRotation );
|
|
||||||
const teleportSpaceOffset = xrf.baseReferenceSpace.getOffsetReferenceSpace( transform );
|
|
||||||
xrf.renderer.xr.setReferenceSpace( teleportSpaceOffset );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const distance = camera.position.distanceTo(newPos);
|
const distance = camera.position.distanceTo(newPos);
|
||||||
if( renderer.xr.isPresenting && distance > portalArea ) positionInFrontOfPortal()
|
if( renderer.xr.isPresenting && distance > portalArea ) return // too far away
|
||||||
else xrf.navigator.to(v.string) // ok let's surf to HREF!
|
|
||||||
|
xrf.navigator.to(v.string) // ok let's surf to HREF!
|
||||||
|
|
||||||
setTimeout( () => mesh.clicked = false, 200 ) // prevent double clicks
|
setTimeout( () => mesh.clicked = false, 200 ) // prevent double clicks
|
||||||
|
xrf.emit('href',{click:true,mesh,xrf:v})
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !opts.frag.q ){
|
let selected = (state) => () => {
|
||||||
|
if( mesh.selected == state ) return // nothing changed
|
||||||
|
if( mesh.material.uniforms ) mesh.material.uniforms.selected.value = state
|
||||||
|
else mesh.material.color.r = mesh.material.color.g = mesh.material.color.b = state ? 2.0 : 1.0
|
||||||
|
// update mouse cursor
|
||||||
|
if( !renderer.domElement.lastCursor )
|
||||||
|
renderer.domElement.lastCursor = renderer.domElement.style.cursor
|
||||||
|
renderer.domElement.style.cursor = state ? 'pointer' : renderer.domElement.lastCursor
|
||||||
|
xrf.emit('href',{selected:state,mesh,xrf:v})
|
||||||
|
mesh.selected = state
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !opts.frag.q ){ // query means an action
|
||||||
mesh.addEventListener('click', teleport )
|
mesh.addEventListener('click', teleport )
|
||||||
mesh.addEventListener('mousemove', () => mesh.material.uniforms.highlight.value = true )
|
mesh.addEventListener('mousemove', selected(true) )
|
||||||
mesh.addEventListener('nocollide', () => mesh.material.uniforms.highlight.value = false )
|
mesh.addEventListener('nocollide', selected(false) )
|
||||||
}
|
}
|
||||||
|
|
||||||
// lazy remove mesh (because we're inside a traverse)
|
// lazy remove mesh (because we're inside a traverse)
|
||||||
|
|
@ -1060,9 +1089,12 @@ xrf.frag.q = function(v, opts){
|
||||||
}
|
}
|
||||||
xrf.frag.rot = function(v, opts){
|
xrf.frag.rot = function(v, opts){
|
||||||
let { mesh, model, camera, scene, renderer, THREE} = opts
|
let { mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
camera.rotation.x = v.x * Math.PI / 180;
|
console.log(" └ setting camera rotation to "+v.string)
|
||||||
camera.rotation.y = v.y * Math.PI / 180;
|
camera.rotation.set(
|
||||||
camera.rotation.z = v.z * Math.PI / 180;
|
v.x * Math.PI / 180,
|
||||||
|
v.y * Math.PI / 180,
|
||||||
|
v.z * Math.PI / 180
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// *TODO* use webgl instancing
|
// *TODO* use webgl instancing
|
||||||
|
|
||||||
|
|
@ -1135,7 +1167,7 @@ window.AFRAME.registerComponent('xrf', {
|
||||||
|
|
||||||
// override the camera-related XR Fragments so the camera-rig is affected
|
// override the camera-related XR Fragments so the camera-rig is affected
|
||||||
let camOverride = (xrf,v,opts) => {
|
let camOverride = (xrf,v,opts) => {
|
||||||
opts.camera = $('[camera]').object3D //parentElement.object3D
|
opts.camera = $('[camera]').object3D.parent
|
||||||
xrf(v,opts)
|
xrf(v,opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
168
dist/xrfragment.three.js
vendored
168
dist/xrfragment.three.js
vendored
|
|
@ -852,6 +852,30 @@ xrf.add = (object) => {
|
||||||
object.isXRF = true // mark for easy deletion when replacing scene
|
object.isXRF = true // mark for easy deletion when replacing scene
|
||||||
xrf.scene.add(object)
|
xrf.scene.add(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EVENTS
|
||||||
|
*/
|
||||||
|
|
||||||
|
xrf.addEventListener = function(eventName, callback) {
|
||||||
|
if( !this._listeners ) this._listeners = []
|
||||||
|
if (!this._listeners[eventName]) {
|
||||||
|
// create a new array for this event name if it doesn't exist yet
|
||||||
|
this._listeners[eventName] = [];
|
||||||
|
}
|
||||||
|
// add the callback to the listeners array for this event name
|
||||||
|
this._listeners[eventName].push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
xrf.emit = function(eventName, data) {
|
||||||
|
if( !this._listeners ) this._listeners = []
|
||||||
|
var callbacks = this._listeners[eventName]
|
||||||
|
if (callbacks) {
|
||||||
|
for (var i = 0; i < callbacks.length; i++) {
|
||||||
|
callbacks[i](data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
xrf.navigator = {}
|
xrf.navigator = {}
|
||||||
|
|
||||||
xrf.navigator.to = (url,event) => {
|
xrf.navigator.to = (url,event) => {
|
||||||
|
|
@ -934,62 +958,67 @@ xrf.frag.env = function(v, opts){
|
||||||
xrf.frag.href = function(v, opts){
|
xrf.frag.href = function(v, opts){
|
||||||
let { mesh, model, camera, scene, renderer, THREE} = opts
|
let { mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
|
|
||||||
const world = { pos: new THREE.Vector3(), scale: new THREE.Vector3() }
|
const world = {
|
||||||
|
pos: new THREE.Vector3(),
|
||||||
|
scale: new THREE.Vector3(),
|
||||||
|
quat: new THREE.Quaternion()
|
||||||
|
}
|
||||||
mesh.getWorldPosition(world.pos)
|
mesh.getWorldPosition(world.pos)
|
||||||
mesh.getWorldScale(world.scale)
|
mesh.getWorldScale(world.scale)
|
||||||
|
mesh.getWorldQuaternion(world.quat);
|
||||||
mesh.position.copy(world.pos)
|
mesh.position.copy(world.pos)
|
||||||
mesh.scale.copy(world.scale)
|
mesh.scale.copy(world.scale)
|
||||||
|
mesh.setRotationFromQuaternion(world.quat);
|
||||||
|
|
||||||
// convert texture if needed
|
// detect equirectangular image
|
||||||
let texture = mesh.material.map
|
let texture = mesh.material.map
|
||||||
if( texture && texture.source.data.height == texture.source.data.width/2 ){
|
if( texture && texture.source.data.height == texture.source.data.width/2 ){
|
||||||
// assume equirectangular image
|
|
||||||
texture.mapping = THREE.ClampToEdgeWrapping
|
texture.mapping = THREE.ClampToEdgeWrapping
|
||||||
texture.needsUpdate = true
|
texture.needsUpdate = true
|
||||||
}
|
|
||||||
|
|
||||||
// poor man's equi-portal
|
// poor man's equi-portal
|
||||||
mesh.material = new THREE.ShaderMaterial( {
|
mesh.material = new THREE.ShaderMaterial( {
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
pano: { value: texture },
|
pano: { value: texture },
|
||||||
highlight: { value: false },
|
selected: { value: false },
|
||||||
},
|
},
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
vec3 portalPosition;
|
vec3 portalPosition;
|
||||||
varying vec3 vWorldPosition;
|
varying vec3 vWorldPosition;
|
||||||
varying float vDistanceToCenter;
|
varying float vDistanceToCenter;
|
||||||
varying float vDistance;
|
varying float vDistance;
|
||||||
void main() {
|
void main() {
|
||||||
vDistanceToCenter = clamp(length(position - vec3(0.0, 0.0, 0.0)), 0.0, 1.0);
|
vDistanceToCenter = clamp(length(position - vec3(0.0, 0.0, 0.0)), 0.0, 1.0);
|
||||||
portalPosition = (modelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
portalPosition = (modelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
||||||
vDistance = length(portalPosition - cameraPosition);
|
vDistance = length(portalPosition - cameraPosition);
|
||||||
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
fragmentShader: `
|
fragmentShader: `
|
||||||
#define RECIPROCAL_PI2 0.15915494
|
#define RECIPROCAL_PI2 0.15915494
|
||||||
uniform sampler2D pano;
|
uniform sampler2D pano;
|
||||||
uniform bool highlight;
|
uniform bool selected;
|
||||||
varying float vDistanceToCenter;
|
varying float vDistanceToCenter;
|
||||||
varying float vDistance;
|
varying float vDistance;
|
||||||
varying vec3 vWorldPosition;
|
varying vec3 vWorldPosition;
|
||||||
void main() {
|
void main() {
|
||||||
vec3 direction = normalize(vWorldPosition - cameraPosition);
|
vec3 direction = normalize(vWorldPosition - cameraPosition);
|
||||||
vec2 sampleUV;
|
vec2 sampleUV;
|
||||||
sampleUV.y = -clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
|
sampleUV.y = -clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
|
||||||
sampleUV.x = atan(direction.z, -direction.x) * -RECIPROCAL_PI2;
|
sampleUV.x = atan(direction.z, -direction.x) * -RECIPROCAL_PI2;
|
||||||
sampleUV.x += 0.33; // adjust focus to AFRAME's $('a-scene').components.screenshot.capture()
|
sampleUV.x += 0.33; // adjust focus to AFRAME's $('a-scene').components.screenshot.capture()
|
||||||
vec4 color = texture2D(pano, sampleUV);
|
vec4 color = texture2D(pano, sampleUV);
|
||||||
// Convert color to grayscale (lazy lite approach to not having to match tonemapping/shaderstacking of THREE.js)
|
// Convert color to grayscale (lazy lite approach to not having to match tonemapping/shaderstacking of THREE.js)
|
||||||
float luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
float luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
||||||
vec4 grayscale_color = highlight ? color : vec4(vec3(luminance) + vec3(0.33), color.a);
|
vec4 grayscale_color = selected ? color : vec4(vec3(luminance) + vec3(0.33), color.a);
|
||||||
gl_FragColor = grayscale_color;
|
gl_FragColor = grayscale_color;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
mesh.material.needsUpdate = true
|
mesh.material.needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
let teleport = mesh.userData.XRF.href.exec = (e) => {
|
let teleport = mesh.userData.XRF.href.exec = (e) => {
|
||||||
if( mesh.clicked ) return
|
if( mesh.clicked ) return
|
||||||
|
|
@ -1005,31 +1034,31 @@ xrf.frag.href = function(v, opts){
|
||||||
cameraDirection.multiplyScalar(portalArea); // move away from portal
|
cameraDirection.multiplyScalar(portalArea); // move away from portal
|
||||||
const newPos = meshWorldPosition.clone().add(cameraDirection);
|
const newPos = meshWorldPosition.clone().add(cameraDirection);
|
||||||
|
|
||||||
const positionInFrontOfPortal = () => {
|
|
||||||
camera.position.copy(newPos);
|
|
||||||
camera.lookAt(meshWorldPosition);
|
|
||||||
|
|
||||||
if( renderer.xr.isPresenting && xrf.baseReferenceSpace ){ // WebXR VR/AR roomscale reposition
|
|
||||||
const offsetPosition = { x: -newPos.x, y: 0, z: -newPos.z, w: 1 };
|
|
||||||
const offsetRotation = new THREE.Quaternion();
|
|
||||||
const transform = new XRRigidTransform( offsetPosition, offsetRotation );
|
|
||||||
const teleportSpaceOffset = xrf.baseReferenceSpace.getOffsetReferenceSpace( transform );
|
|
||||||
xrf.renderer.xr.setReferenceSpace( teleportSpaceOffset );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const distance = camera.position.distanceTo(newPos);
|
const distance = camera.position.distanceTo(newPos);
|
||||||
if( renderer.xr.isPresenting && distance > portalArea ) positionInFrontOfPortal()
|
if( renderer.xr.isPresenting && distance > portalArea ) return // too far away
|
||||||
else xrf.navigator.to(v.string) // ok let's surf to HREF!
|
|
||||||
|
xrf.navigator.to(v.string) // ok let's surf to HREF!
|
||||||
|
|
||||||
setTimeout( () => mesh.clicked = false, 200 ) // prevent double clicks
|
setTimeout( () => mesh.clicked = false, 200 ) // prevent double clicks
|
||||||
|
xrf.emit('href',{click:true,mesh,xrf:v})
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !opts.frag.q ){
|
let selected = (state) => () => {
|
||||||
|
if( mesh.selected == state ) return // nothing changed
|
||||||
|
if( mesh.material.uniforms ) mesh.material.uniforms.selected.value = state
|
||||||
|
else mesh.material.color.r = mesh.material.color.g = mesh.material.color.b = state ? 2.0 : 1.0
|
||||||
|
// update mouse cursor
|
||||||
|
if( !renderer.domElement.lastCursor )
|
||||||
|
renderer.domElement.lastCursor = renderer.domElement.style.cursor
|
||||||
|
renderer.domElement.style.cursor = state ? 'pointer' : renderer.domElement.lastCursor
|
||||||
|
xrf.emit('href',{selected:state,mesh,xrf:v})
|
||||||
|
mesh.selected = state
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !opts.frag.q ){ // query means an action
|
||||||
mesh.addEventListener('click', teleport )
|
mesh.addEventListener('click', teleport )
|
||||||
mesh.addEventListener('mousemove', () => mesh.material.uniforms.highlight.value = true )
|
mesh.addEventListener('mousemove', selected(true) )
|
||||||
mesh.addEventListener('nocollide', () => mesh.material.uniforms.highlight.value = false )
|
mesh.addEventListener('nocollide', selected(false) )
|
||||||
}
|
}
|
||||||
|
|
||||||
// lazy remove mesh (because we're inside a traverse)
|
// lazy remove mesh (because we're inside a traverse)
|
||||||
|
|
@ -1060,9 +1089,12 @@ xrf.frag.q = function(v, opts){
|
||||||
}
|
}
|
||||||
xrf.frag.rot = function(v, opts){
|
xrf.frag.rot = function(v, opts){
|
||||||
let { mesh, model, camera, scene, renderer, THREE} = opts
|
let { mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
camera.rotation.x = v.x * Math.PI / 180;
|
console.log(" └ setting camera rotation to "+v.string)
|
||||||
camera.rotation.y = v.y * Math.PI / 180;
|
camera.rotation.set(
|
||||||
camera.rotation.z = v.z * Math.PI / 180;
|
v.x * Math.PI / 180,
|
||||||
|
v.y * Math.PI / 180,
|
||||||
|
v.z * Math.PI / 180
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// *TODO* use webgl instancing
|
// *TODO* use webgl instancing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@
|
||||||
<title>AFRAME - xrfragment sandbox</title>
|
<title>AFRAME - xrfragment sandbox</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
<link rel="stylesheet" href="./../../assets/axist.min.css" />
|
<link rel="stylesheet" href="./../../assets/css/axist.min.css" />
|
||||||
<link type="text/css" rel="stylesheet" href="./../../assets/style.css"/>
|
<link type="text/css" rel="stylesheet" href="./../../assets/css/style.css"/>
|
||||||
<script async src="./../../assets/alpine.min.js" defer></script>
|
<script async src="./../../assets/js/alpine.min.js" defer></script>
|
||||||
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls/dist/aframe-blink-controls.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/aframe-blink-controls/dist/aframe-blink-controls.min.js"></script>
|
||||||
<script src="./../../../dist/xrfragment.aframe.js"></script>
|
<script src="./../../../dist/xrfragment.aframe.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="overlay" x-data="{ urls: ['#pos=0,1.6,15&rot=0,360,0'] }">
|
<div id="overlay">
|
||||||
<img src="./../../assets/logo.png" class="logo"/>
|
<img src="./../../assets/logo.png" class="logo"/>
|
||||||
<input type="submit" value="load 3D asset"></input>
|
<input type="submit" value="load 3D asset"></input>
|
||||||
<input type="text" id="uri" value="" onchange="AFRAME.XRF.navigator.to( $('#uri').value )"/>
|
<input type="text" id="uri" value="" onchange="AFRAME.XRF.navigator.to( $('#uri').value )"/>
|
||||||
|
|
@ -22,18 +22,22 @@
|
||||||
<textarea style="display:none"></textarea>
|
<textarea style="display:none"></textarea>
|
||||||
<a-scene light="defaultLightsEnabled: false">
|
<a-scene light="defaultLightsEnabled: false">
|
||||||
<a-entity id="player" >
|
<a-entity id="player" >
|
||||||
<a-entity camera position="0 1.6 15" wasd-controls id="camera"></a-entity>
|
<a-entity camera="fov:90" position="0 1.6 0" wasd-controls id="camera"></a-entity>
|
||||||
<a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.collidable;far:5500" oculus-touch-controls="hand: left" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor"></a-entity>
|
<a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.collidable;far:5500" oculus-touch-controls="hand: left" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor"></a-entity>
|
||||||
<a-entity id="right-hand" laser-controls="hand: right" raycaster="objects:.collidable;far:5500" oculus-touch-controls="hand: right" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor"></a-entity>
|
<a-entity id="right-hand" laser-controls="hand: right" raycaster="objects:.collidable;far:5500" oculus-touch-controls="hand: right" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor"></a-entity>
|
||||||
</a-entity>
|
</a-entity>
|
||||||
<a-entity id="home" xrf="example3.gltf#pos=1,2,3"></a-entity>
|
<a-entity id="home" xrf="example3.gltf#pos=0,0,0"></a-entity>
|
||||||
<a-entity id="floor" xrf-get="floor"></a-entity>
|
<a-entity id="floor" xrf-get="floor"></a-entity>
|
||||||
|
|
||||||
</a-scene>
|
</a-scene>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { loadFile, setupConsole, setupUrlBar } from './../../assets/utils.js';
|
import { loadFile, setupConsole, setupUrlBar, notify } from './../../assets/js/utils.js';
|
||||||
window.$ = (s) => document.querySelector(s)
|
window.$ = (s) => document.querySelector(s)
|
||||||
|
window.notify = notify(window)
|
||||||
|
console.log = ( (log) => function(str){
|
||||||
|
if( String(str).match(/(camera)/) ) window.notify(str)
|
||||||
|
log(str)
|
||||||
|
})(console.log)
|
||||||
|
|
||||||
if( document.location.search.length > 2 )
|
if( document.location.search.length > 2 )
|
||||||
$('#home').setAttribute('xrf', document.location.search.substr(1)+document.location.hash )
|
$('#home').setAttribute('xrf', document.location.search.substr(1)+document.location.hash )
|
||||||
|
|
@ -47,6 +51,10 @@
|
||||||
// add screenshot component with camera to capture proper equirects
|
// add screenshot component with camera to capture proper equirects
|
||||||
$('a-scene').setAttribute("screenshot",{camera: "[camera]",width: 4096*2, height:2048*2})
|
$('a-scene').setAttribute("screenshot",{camera: "[camera]",width: 4096*2, height:2048*2})
|
||||||
|
|
||||||
|
setTimeout( () => window.notify("use arrow-keys and mouse-drag to move around",{timeout:4000}),2000 )
|
||||||
|
|
||||||
|
window.AFRAME.XRF.addEventListener('href', (data) => data.selected ? window.notify(`href: ${data.xrf.string}`) : false )
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -132,3 +132,105 @@ html.a-fullscreen a#source{
|
||||||
left:auto !important;
|
left:auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* notifications */
|
||||||
|
.js-snackbar-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 0px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width:100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
z-index:1001;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar-container * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__wrapper {
|
||||||
|
--color-c: #555;
|
||||||
|
--color-a: #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.js-snackbar__wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
height: auto;
|
||||||
|
margin: 5px 0;
|
||||||
|
transition: all ease .5s;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 0 0 4px 0 #0007;
|
||||||
|
left: 20px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar {
|
||||||
|
display: inline-flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--color-c);
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: var(--color-a);
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__close,
|
||||||
|
.js-snackbar__status,
|
||||||
|
.js-snackbar__message {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__message {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__status {
|
||||||
|
display: none;
|
||||||
|
width: 15px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 3px 0 0 3px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__status.js-snackbar--success,
|
||||||
|
.js-snackbar__status.js-snackbar--warning,
|
||||||
|
.js-snackbar__status.js-snackbar--danger,
|
||||||
|
.js-snackbar__status.js-snackbar--info {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__status.js-snackbar--success {
|
||||||
|
background-color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__status.js-snackbar--warning {
|
||||||
|
background-color: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__status.js-snackbar--danger {
|
||||||
|
background-color: #ff6060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__status.js-snackbar--info {
|
||||||
|
background-color: #CCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__close {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.js-snackbar__close:hover {
|
||||||
|
background-color: #4443;
|
||||||
|
}
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
267
example/assets/js/utils.js
Normal file
267
example/assets/js/utils.js
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
|
||||||
|
// contentLoaders = {".gltf" : () => .....} and so on
|
||||||
|
|
||||||
|
export function loadFile(contentLoaders, multiple){
|
||||||
|
return () => {
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.multiple = multiple;
|
||||||
|
input.accept = Object.keys(contentLoaders).join(",");
|
||||||
|
input.onchange = () => {
|
||||||
|
let files = Array.from(input.files);
|
||||||
|
let file = files.slice ? files[0] : files
|
||||||
|
for( var i in contentLoaders ){
|
||||||
|
let r = new RegExp('\\'+i+'$')
|
||||||
|
if( file.name.match(r) ) return contentLoaders[i](file)
|
||||||
|
}
|
||||||
|
alert(file.name+" is not supported")
|
||||||
|
};
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupConsole(el){
|
||||||
|
if( !el ) return setTimeout( () => setupConsole( $('.lil-gui') ),200 )
|
||||||
|
let $console = document.createElement('textarea')
|
||||||
|
$console.style.position = 'absolute'
|
||||||
|
$console.style.display = 'block'
|
||||||
|
$console.style.zIndex = 2000;
|
||||||
|
$console.style.background = "transparent !important"
|
||||||
|
$console.style.pointerEvents = 'none'
|
||||||
|
$console.style.top = '70px'
|
||||||
|
$console.style.padding = '10px'
|
||||||
|
$console.style.margin = '10px'
|
||||||
|
$console.style.background = '#000'
|
||||||
|
$console.style.left = $console.style.right = $console.style.bottom = 0;
|
||||||
|
$console.style.color = '#A6F';
|
||||||
|
$console.style.fontSize = '10px';
|
||||||
|
$console.style.fontFamily = 'Courier'
|
||||||
|
$console.style.border = '0'
|
||||||
|
$console.innerHTML = "XRFRAGMENT CONSOLE OUTPUT:\n"
|
||||||
|
|
||||||
|
el.appendChild($console)
|
||||||
|
|
||||||
|
console.log = ( (log) => function(){
|
||||||
|
let str = ([...arguments]).join(" ")
|
||||||
|
let s = new Date().toISOString().substr(11).substr(0,8) + " " + str.replace(/.*[0-9]: /,"")
|
||||||
|
log(s)
|
||||||
|
let lines = String($console.innerHTML + "\n"+s).split("\n")
|
||||||
|
while( lines.length > 200 ) lines.shift()
|
||||||
|
$console.innerHTML = lines.join("\n")
|
||||||
|
$console.scrollTop = $console.scrollHeight;
|
||||||
|
})(console.log.bind(console))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupUrlBar(el){
|
||||||
|
|
||||||
|
var isIframe = (window === window.parent || window.opener) ? false : true;
|
||||||
|
if( isIframe ){
|
||||||
|
// show internal URL bar to test XR fragments interactively
|
||||||
|
el.style.display = 'block'
|
||||||
|
let nav = window.AFRAME.XRF.navigator
|
||||||
|
|
||||||
|
AFRAME.XRF.navigator.to = ((to) => (url,e) => {
|
||||||
|
to(url,e)
|
||||||
|
reflectUrl(url)
|
||||||
|
})(AFRAME.XRF.navigator.to)
|
||||||
|
|
||||||
|
const reflectUrl = (url) => el.value = url || document.location.search.substr(1) + document.location.hash
|
||||||
|
reflectUrl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SnackBar(userOptions) {
|
||||||
|
var snackbar = this || (window.snackbar = {});
|
||||||
|
var _Interval;
|
||||||
|
var _Message;
|
||||||
|
var _Element;
|
||||||
|
var _Container;
|
||||||
|
|
||||||
|
var _OptionDefaults = {
|
||||||
|
message: "Operation performed successfully.",
|
||||||
|
dismissible: true,
|
||||||
|
timeout: 5000,
|
||||||
|
status: ""
|
||||||
|
}
|
||||||
|
var _Options = _OptionDefaults;
|
||||||
|
|
||||||
|
function _Create() {
|
||||||
|
_Container = document.getElementsByClassName("js-snackbar-container")[0];
|
||||||
|
|
||||||
|
if (!_Container) {
|
||||||
|
// need to create a new container for notifications
|
||||||
|
_Container = document.createElement("div");
|
||||||
|
_Container.classList.add("js-snackbar-container");
|
||||||
|
|
||||||
|
document.body.appendChild(_Container);
|
||||||
|
}
|
||||||
|
_Element = document.createElement("div");
|
||||||
|
_Element.classList.add("js-snackbar__wrapper");
|
||||||
|
|
||||||
|
let innerSnack = document.createElement("div");
|
||||||
|
innerSnack.classList.add("js-snackbar", "js-snackbar--show");
|
||||||
|
|
||||||
|
if (_Options.status) {
|
||||||
|
_Options.status = _Options.status.toLowerCase().trim();
|
||||||
|
|
||||||
|
let status = document.createElement("span");
|
||||||
|
status.classList.add("js-snackbar__status");
|
||||||
|
|
||||||
|
|
||||||
|
if (_Options.status === "success" || _Options.status === "green") {
|
||||||
|
status.classList.add("js-snackbar--success");
|
||||||
|
}
|
||||||
|
else if (_Options.status === "warning" || _Options.status === "alert" || _Options.status === "orange") {
|
||||||
|
status.classList.add("js-snackbar--warning");
|
||||||
|
}
|
||||||
|
else if (_Options.status === "danger" || _Options.status === "error" || _Options.status === "red") {
|
||||||
|
status.classList.add("js-snackbar--danger");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
status.classList.add("js-snackbar--info");
|
||||||
|
}
|
||||||
|
|
||||||
|
innerSnack.appendChild(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
_Message = document.createElement("span");
|
||||||
|
_Message.classList.add("js-snackbar__message");
|
||||||
|
_Message.textContent = _Options.message;
|
||||||
|
|
||||||
|
innerSnack.appendChild(_Message);
|
||||||
|
|
||||||
|
if (_Options.dismissible) {
|
||||||
|
let closeBtn = document.createElement("span");
|
||||||
|
closeBtn.classList.add("js-snackbar__close");
|
||||||
|
closeBtn.innerText = "\u00D7";
|
||||||
|
|
||||||
|
closeBtn.onclick = snackbar.Close;
|
||||||
|
|
||||||
|
innerSnack.appendChild(closeBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
_Element.style.height = "0px";
|
||||||
|
_Element.style.opacity = "0";
|
||||||
|
_Element.style.marginTop = "0px";
|
||||||
|
_Element.style.marginBottom = "0px";
|
||||||
|
|
||||||
|
_Element.appendChild(innerSnack);
|
||||||
|
_Container.appendChild(_Element);
|
||||||
|
|
||||||
|
if (_Options.timeout !== false) {
|
||||||
|
_Interval = setTimeout(snackbar.Close, _Options.timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ConfigureDefaults = function() {
|
||||||
|
// if no options given, revert to default
|
||||||
|
if (userOptions === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userOptions.message !== undefined) {
|
||||||
|
_Options.message = userOptions.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userOptions.dismissible !== undefined) {
|
||||||
|
if (typeof (userOptions.dismissible) === "string") {
|
||||||
|
_Options.dismissible = (userOptions.dismissible === "true");
|
||||||
|
}
|
||||||
|
else if (typeof (userOptions.dismissible) === "boolean") {
|
||||||
|
_Options.dismissible = userOptions.dismissible;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.debug("Invalid option provided for 'dismissable' [" + userOptions.dismissible + "] is of type " + (typeof userOptions.dismissible));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (userOptions.timeout !== undefined) {
|
||||||
|
if (typeof (userOptions.timeout) === "boolean" && userOptions.timeout === false) {
|
||||||
|
_Options.timeout = false;
|
||||||
|
}
|
||||||
|
else if (typeof (userOptions.timeout) === "string") {
|
||||||
|
_Options.timeout = parseInt(userOptions.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof (userOptions.timeout) === "number") {
|
||||||
|
if (userOptions.timeout === Infinity) {
|
||||||
|
_Options.timeout = false;
|
||||||
|
}
|
||||||
|
else if (userOptions.timeout >= 0) {
|
||||||
|
_Options.timeout = userOptions.timeout;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.debug("Invalid timeout entered. Must be greater than or equal to 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_Options.timeout = userOptions.timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userOptions.status !== undefined) {
|
||||||
|
_Options.status = userOptions.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snackbar.Open = function() {
|
||||||
|
let contentHeight = _Element.firstElementChild.scrollHeight; // get the height of the content
|
||||||
|
|
||||||
|
_Element.style.height = contentHeight + "px";
|
||||||
|
_Element.style.opacity = 1;
|
||||||
|
_Element.style.marginTop = "5px";
|
||||||
|
_Element.style.marginBottom = "5px";
|
||||||
|
|
||||||
|
_Element.addEventListener("transitioned", function() {
|
||||||
|
_Element.removeEventListener("transitioned", arguments.callee);
|
||||||
|
_Element.style.height = null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
snackbar.Close = function () {
|
||||||
|
if (_Interval)
|
||||||
|
clearInterval(_Interval);
|
||||||
|
|
||||||
|
let snackbarHeight = _Element.scrollHeight; // get the auto height as a px value
|
||||||
|
let snackbarTransitions = _Element.style.transition;
|
||||||
|
_Element.style.transition = "";
|
||||||
|
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
_Element.style.height = snackbarHeight + "px"; // set the auto height to the px height
|
||||||
|
_Element.style.opacity = 1;
|
||||||
|
_Element.style.marginTop = "0px";
|
||||||
|
_Element.style.marginBottom = "0px";
|
||||||
|
_Element.style.transition = snackbarTransitions
|
||||||
|
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
_Element.style.height = "0px";
|
||||||
|
_Element.style.opacity = 0;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
_Container.removeChild(_Element);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
_ConfigureDefaults();
|
||||||
|
_Create();
|
||||||
|
snackbar.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function notify(scope){
|
||||||
|
return function notify(str,opts){
|
||||||
|
str = String(str)
|
||||||
|
opts = opts || {}
|
||||||
|
if( !opts.status ){
|
||||||
|
opts.status = "info"
|
||||||
|
if( str.match(/error/g) ) opts.status = "danger"
|
||||||
|
if( str.match(/warning/g) ) opts.status = "warning"
|
||||||
|
}
|
||||||
|
opts = Object.assign({ message: str , status, timeout:2000 },opts)
|
||||||
|
SnackBar( opts )
|
||||||
|
}
|
||||||
|
}
|
||||||
2153
example/assets/src_selfreference.gltf
Normal file
2153
example/assets/src_selfreference.gltf
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,70 +0,0 @@
|
||||||
|
|
||||||
// contentLoaders = {".gltf" : () => .....} and so on
|
|
||||||
|
|
||||||
export function loadFile(contentLoaders, multiple){
|
|
||||||
return () => {
|
|
||||||
let input = document.createElement('input');
|
|
||||||
input.type = 'file';
|
|
||||||
input.multiple = multiple;
|
|
||||||
input.accept = Object.keys(contentLoaders).join(",");
|
|
||||||
input.onchange = () => {
|
|
||||||
let files = Array.from(input.files);
|
|
||||||
let file = files.slice ? files[0] : files
|
|
||||||
for( var i in contentLoaders ){
|
|
||||||
let r = new RegExp('\\'+i+'$')
|
|
||||||
if( file.name.match(r) ) return contentLoaders[i](file)
|
|
||||||
}
|
|
||||||
alert(file.name+" is not supported")
|
|
||||||
};
|
|
||||||
input.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setupConsole(el){
|
|
||||||
if( !el ) return setTimeout( () => setupConsole( $('.lil-gui') ),200 )
|
|
||||||
let $console = document.createElement('textarea')
|
|
||||||
$console.style.position = 'absolute'
|
|
||||||
$console.style.display = 'block'
|
|
||||||
$console.style.zIndex = 2000;
|
|
||||||
$console.style.background = "transparent !important"
|
|
||||||
$console.style.pointerEvents = 'none'
|
|
||||||
$console.style.top = '70px'
|
|
||||||
$console.style.padding = '10px'
|
|
||||||
$console.style.margin = '10px'
|
|
||||||
$console.style.background = '#000'
|
|
||||||
$console.style.left = $console.style.right = $console.style.bottom = 0;
|
|
||||||
$console.style.color = '#A6F';
|
|
||||||
$console.style.fontSize = '10px';
|
|
||||||
$console.style.fontFamily = 'Courier'
|
|
||||||
$console.style.border = '0'
|
|
||||||
$console.innerHTML = "XRFRAGMENT CONSOLE OUTPUT:\n"
|
|
||||||
|
|
||||||
el.appendChild($console)
|
|
||||||
|
|
||||||
console.log = ( (log) => function(){
|
|
||||||
let s = new Date().toISOString().substr(11).substr(0,8) + " " + ([...arguments]).join(" ").replace(/.*[0-9]: /,"")
|
|
||||||
log(s)
|
|
||||||
let lines = String($console.innerHTML + "\n"+s).split("\n")
|
|
||||||
while( lines.length > 200 ) lines.shift()
|
|
||||||
$console.innerHTML = lines.join("\n")
|
|
||||||
$console.scrollTop = $console.scrollHeight;
|
|
||||||
})(console.log.bind(console))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setupUrlBar(el){
|
|
||||||
|
|
||||||
var isIframe = (window === window.parent || window.opener) ? false : true;
|
|
||||||
if( isIframe ){
|
|
||||||
// show internal URL bar to test XR fragments interactively
|
|
||||||
el.style.display = 'block'
|
|
||||||
let nav = window.AFRAME.XRF.navigator
|
|
||||||
|
|
||||||
AFRAME.XRF.navigator.to = ((to) => (url,e) => {
|
|
||||||
to(url,e)
|
|
||||||
reflectUrl(url)
|
|
||||||
})(AFRAME.XRF.navigator.to)
|
|
||||||
|
|
||||||
const reflectUrl = (url) => el.value = url || document.location.search.substr(1) + document.location.hash
|
|
||||||
reflectUrl()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="./assets/axist.min.css" />
|
<link rel="stylesheet" href="./assets/css/axist.min.css" />
|
||||||
<link rel="stylesheet" href="./assets/style.css" />
|
<link rel="stylesheet" href="./assets/css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="./../dist/xrfragment.js"></script>
|
<script src="./../dist/xrfragment.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
<title>THREE.js - xrfragment sandbox</title>
|
<title>THREE.js - xrfragment sandbox</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
<link rel="stylesheet" href="./../../assets/axist.min.css" />
|
<link rel="stylesheet" href="./../../assets/css/axist.min.css" />
|
||||||
<link type="text/css" rel="stylesheet" href="./../../assets/style.css"/>
|
<link type="text/css" rel="stylesheet" href="./../../assets/css/style.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="overlay" x-data="{ urls: ['#pos=0,1.6,15','#pos=0,1.6,15&rot=0,360,0'] }">
|
<div id="overlay" x-data="{ urls: ['#pos=0,1.6,15','#pos=0,1.6,15&rot=0,360,0'] }">
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
import xrfragment from './../../../dist/xrfragment.three.js';
|
import xrfragment from './../../../dist/xrfragment.three.js';
|
||||||
|
|
||||||
import { loadFile, setupConsole, setupUrlBar } from './../../assets/utils.js';
|
import { loadFile, setupConsole, setupUrlBar, notify } from './../../assets/js/utils.js';
|
||||||
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
|
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
|
||||||
import { Lensflare, LensflareElement } from 'three/addons/objects/Lensflare.js';
|
import { Lensflare, LensflareElement } from 'three/addons/objects/Lensflare.js';
|
||||||
import { BoxLineGeometry } from 'three/addons/geometries/BoxLineGeometry.js';
|
import { BoxLineGeometry } from 'three/addons/geometries/BoxLineGeometry.js';
|
||||||
|
|
|
||||||
13
index.html
13
index.html
File diff suppressed because one or more lines are too long
|
|
@ -31,7 +31,7 @@ window.AFRAME.registerComponent('xrf', {
|
||||||
|
|
||||||
// override the camera-related XR Fragments so the camera-rig is affected
|
// override the camera-related XR Fragments so the camera-rig is affected
|
||||||
let camOverride = (xrf,v,opts) => {
|
let camOverride = (xrf,v,opts) => {
|
||||||
opts.camera = $('[camera]').object3D //parentElement.object3D
|
opts.camera = $('[camera]').object3D.parent
|
||||||
xrf(v,opts)
|
xrf(v,opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,3 +125,27 @@ xrf.add = (object) => {
|
||||||
object.isXRF = true // mark for easy deletion when replacing scene
|
object.isXRF = true // mark for easy deletion when replacing scene
|
||||||
xrf.scene.add(object)
|
xrf.scene.add(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EVENTS
|
||||||
|
*/
|
||||||
|
|
||||||
|
xrf.addEventListener = function(eventName, callback) {
|
||||||
|
if( !this._listeners ) this._listeners = []
|
||||||
|
if (!this._listeners[eventName]) {
|
||||||
|
// create a new array for this event name if it doesn't exist yet
|
||||||
|
this._listeners[eventName] = [];
|
||||||
|
}
|
||||||
|
// add the callback to the listeners array for this event name
|
||||||
|
this._listeners[eventName].push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
xrf.emit = function(eventName, data) {
|
||||||
|
if( !this._listeners ) this._listeners = []
|
||||||
|
var callbacks = this._listeners[eventName]
|
||||||
|
if (callbacks) {
|
||||||
|
for (var i = 0; i < callbacks.length; i++) {
|
||||||
|
callbacks[i](data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,17 @@
|
||||||
* navigation, portals & mutations
|
* navigation, portals & mutations
|
||||||
*
|
*
|
||||||
* | fragment | type | scope | example value |
|
* | fragment | type | scope | example value |
|
||||||
* |-|-|-|-|
|
* |`href`| string (uri or [predefined view](#predefined_view )) | 🔒 |`#pos=1,1,0`<br>`#pos=1,1,0&rot=90,0,0`<br>`#pos=pyramid`<br>`#pos=lastvisit\|pyramid`<br>`://somefile.gltf#pos=1,1,0`<br> |
|
||||||
* |`href`| (uri) string | 🔒 |`#pos=1,1,0`<br>`#pos=1,1,0&rot=90,0,0`<br>`#pos=pyramid`<br>`#pos=lastvisit\|pyramid`<Br>`://somefile.gltf#pos=1,1,0`<br> |
|
|
||||||
*
|
*
|
||||||
* ### spec 1.0
|
* [img[xrfragment.jpg]]
|
||||||
|
*
|
||||||
|
* !!!spec 1.0
|
||||||
|
*
|
||||||
|
* 1. a ''external''- or ''file URI'' fully replaces the current scene and assumes `pos=0,0,0&rot=0,0,0` by default (unless specified)
|
||||||
*
|
*
|
||||||
* 1. a **external**- or **file URI** fully replaces the current scene and assumes `pos=0,0,0&rot=0,0,0` by default (unless specified)
|
|
||||||
* 2. navigation should not happen when queries (`q=`) are present in local url: queries will apply (`pos=`, `rot=` e.g.) to the targeted object(s) instead.
|
* 2. navigation should not happen when queries (`q=`) are present in local url: queries will apply (`pos=`, `rot=` e.g.) to the targeted object(s) instead.
|
||||||
* 3. navigation should not happen immediately when user is more than 2 meter away from the portal/object containing the href (to prevent accidental navigation e.g.)
|
*
|
||||||
|
* 3. navigation should not happen ''immediately'' when user is more than 2 meter away from the portal/object containing the href (to prevent accidental navigation e.g.)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
xrf.frag.href = function(v, opts){
|
xrf.frag.href = function(v, opts){
|
||||||
|
|
@ -122,8 +125,8 @@ xrf.frag.href = function(v, opts){
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* > above was abducted from [this](https://i.imgur.com/E3En0gJ.png) and [this](https://i.imgur.com/lpnTz3A.png) survey result
|
* > above was abducted from [[this|https://i.imgur.com/E3En0gJ.png]] and [[this|https://i.imgur.com/lpnTz3A.png]] survey result
|
||||||
*
|
*
|
||||||
* [» source example](https://github.com/coderofsalvation/xrfragment/blob/main/src/three/xrf/pos.js)<br>
|
* [[» discussion|https://github.com/coderofsalvation/xrfragment/issues/1]]<br>
|
||||||
* [» discussion](https://github.com/coderofsalvation/xrfragment/issues/1)
|
* [[» implementation example|https://github.com/coderofsalvation/xrfragment/blob/main/src/three/xrf/pos.js]]<br>
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
xrf.frag.rot = function(v, opts){
|
xrf.frag.rot = function(v, opts){
|
||||||
let { mesh, model, camera, scene, renderer, THREE} = opts
|
let { mesh, model, camera, scene, renderer, THREE} = opts
|
||||||
camera.rotation.x = v.x * Math.PI / 180;
|
console.log(" └ setting camera rotation to "+v.string)
|
||||||
camera.rotation.y = v.y * Math.PI / 180;
|
camera.rotation.set(
|
||||||
camera.rotation.z = v.z * Math.PI / 180;
|
v.x * Math.PI / 180,
|
||||||
|
v.y * Math.PI / 180,
|
||||||
|
v.z * Math.PI / 180
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue