added docs

This commit is contained in:
Leon van Kammen 2023-05-22 13:56:33 +02:00
parent 2b480c04f5
commit 955baeb2f4

View file

@ -1,26 +1,44 @@
/**
* navigation, portals & mutations
*
* | fragment | type | scope | example value |
* |-|-|-|-|
* |`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
*
* 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.
* 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){
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;
@ -38,7 +56,7 @@ xrf.frag.href = function(v, opts){
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;
@ -47,21 +65,20 @@ xrf.frag.href = function(v, opts){
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 let portalArea = 1 // 2 meter
mesh.clicked = true
let portalArea = 1 // 1 meter
const meshWorldPosition = new THREE.Vector3(); const meshWorldPosition = new THREE.Vector3();
meshWorldPosition.setFromMatrixPosition(mesh.matrixWorld); meshWorldPosition.setFromMatrixPosition(mesh.matrixWorld);
@ -72,35 +89,41 @@ 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!
setTimeout( () => mesh.clicked = false, 200 ) // prevent double clicks xrf.navigator.to(v.string) // ok let's surf to HREF!
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 add mesh (because we're inside a recursive traverse)
setTimeout( (mesh) => { setTimeout( (mesh) => {
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
}, 20, mesh ) }, 20, mesh )
} }
/**
* > 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)
*/