From 0d73a822e8135b3f5c8f46a11eff3410c786a6f3 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Thu, 25 Apr 2024 15:56:29 +0000 Subject: [PATCH] accessibility command plugins --- example/aframe/sandbox/index.html | 3 +- example/assets/index.glb | Bin 8849076 -> 8849496 bytes make | 2 +- src/3rd/js/aframe/index.js | 14 +- src/3rd/js/plugin/frontend/$chat.js | 43 ++++-- src/3rd/js/plugin/frontend/accessibility.js | 30 ++-- .../js/plugin/frontend/chatcommand/href.js | 20 +++ src/3rd/js/plugin/frontend/chatcommand/mud.js | 144 ++++++++++++++++++ src/3rd/js/plugin/frontend/chatcommand/tts.js | 26 ++++ src/3rd/js/plugin/frontend/css.js | 6 +- src/3rd/js/plugin/frontend/frontend.js | 13 +- src/3rd/js/three/util/transcript.js | 11 ++ src/3rd/js/three/xrf/pos.js | 2 +- 13 files changed, 274 insertions(+), 40 deletions(-) create mode 100644 src/3rd/js/plugin/frontend/chatcommand/href.js create mode 100644 src/3rd/js/plugin/frontend/chatcommand/mud.js create mode 100644 src/3rd/js/plugin/frontend/chatcommand/tts.js create mode 100644 src/3rd/js/three/util/transcript.js diff --git a/example/aframe/sandbox/index.html b/example/aframe/sandbox/index.html index 7b4f656..f4093ba 100644 --- a/example/aframe/sandbox/index.html +++ b/example/aframe/sandbox/index.html @@ -5,7 +5,8 @@ - + + diff --git a/example/assets/index.glb b/example/assets/index.glb index 47b51b8826ea93cc7d2c668e506af7bc9a10e926..970009d00edb71ff5ce076caab505a4efe687caf 100644 GIT binary patch delta 4387 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 delta 4181 zcma);dsI~Q7RUYl&Y&pI0Rd6O0o3vec+Sk6Gv|2CM~YrcgUo9$6Hv!T0fRt_Z_E_e za+TWYvTx>t>b`23w;XhMgRgw`kXDwrbxSbQ(q&p|vC7B&{${|yzwTOmVDI04{`POr z8cuKCW~q8vQM~@y=J+6^Ccyo0$FxZYb@() z?jF_Clqc*pADJ}F49ghKG_R4tn0Lvf+%euO>xTx1IuzCJQXDRqsyWQ0O#{Ldr_+!% z+3A#J&EcKA>C@QWg+-pi(>{V%RKpu$~BkQab`RZ(o+?~ zFbi+Qn48; z4=rqBA?C&lSDDkh;=A4~UP-mfcHjU`paKoJygR-RV@x-9T^xc=KE0TXPP{iRZDeR= z&E;>H@=8LAx#0&F)4k0<+{c5EL~)>{cyG{;GUG-nCDpJPbIMQuVX9g9d4l=iwP>!( z-p8-?SNZ!*fr`qj`$Y7v>p84&h`QfcTX3fo0-cN47-akfk3uoCtPW)AH3q|*>j%V`|HMTGu?(&4+LRBfJnDwWuR}dckCQJ2= zUdCjdHq;~W);(-;pjg2kmQHbAHvDceypZ0dkgDHPNY%R)QgxMVhuHwi@iLX7s*q{w zY8FACc-i=L9JZ z!<%35;iYc9!6K4@GTIbRQQ}bc=1rElo!3B38 zVZpxI511p1%6?*RQ&AjGqR1j{qbecEk@)pX9FMKAN-@+P%U95-etZzUXO)u35)UJ! z2>LjaM^Q#RkE2RE52g5iJjJ&mo}bu8cf$Bu`g|XMOQau4o<00Uy6r=He~r=A$WU2@ zUuD%ima;DLa9Zi(FVLZC6luN0Ewu9@??Vr4=g~B&24S6N3ADZj<;vP{^*5?{G93}t z%IiFyY?t{%lvj(SIg^m(-$JSs(oeOB*nEbC()Trd9T{~nY^p`7_* zL+E%tiuG$md!7b_)ptnY)YO1tw;MT%g-IcFt$`1ru}x6@VR|D*wXO-(1~%~s3ZID@ z!Q>rvIBezeR&Yw z$p?|#bqLpk6hVne2wi*#m-k<4??H6u6IQ&<1GMfi8uLHP2SV~cORo>a**3nLHVUb1 z2b@m`sc<`_kRwPRd4j#EgzdG_b|IA> z zlD`Avqi!9LQ2iZzAtZkXaUb(LbY(5Bzx)%NO??qb8-(H=mzuiTP9MH>7OI8c~9r$da^rbygU&-NAD&Ca- zDBK(>wbhESr=-Y$Tk~shD|}0e6lEP~zhw{(v?{a=HIVeGSBjA|%}Gi5h%K#>VmQ7I zfAm19gCeBATi(wYtBX&)>Onc7kivO_2? zJke2D&caR7@c(U#0n>tNaS^*tZHWObmg66hq@V14B=sQ$m}#+{^wB@U01|Z(MY2nyV!>;P5(-m2J;K&~JWjm=ECM;9w1vit3>z#99vy`-^Y8HL5b{Kc+S zK8Oc}Y6MpN2bUBL^WB*Rbh}G~eYccOml|9m&=16e{$Kz|00TiH7zAt} z2_%CQFc=I0L&1IEelQID0SpJJAPuC05kLkCumcBh0u^Y$1$1D5k>HPD6nFrP24lcj zFb+Hj#)AnU155;yd~>({>D3Qu`h?J|eJ}S)wfIWU&nRORnUklJoH;qv*Pi@XWl;I` zyvg7p@GzJHrh-R6CddMhf@$C}Fdh8acRg>0<@xgV3p2pu;0f>~cnUlXW`bwHERYRy zKrWaK=771t4d#LQU;$VNo(0c=Jdh6xfCm(UMW6^2gAyR>F9u6MDOd`Yf#qNYSP52v W)u0Tl0WW~H;6<>`*M4Drj_rR4>+;b6 diff --git a/make b/make index 2a9d3bc..a032498 100755 --- a/make +++ b/make @@ -105,7 +105,7 @@ build(){ cp src/3rd/js/plugin/frontend/\$editor.js dist/xrfragment.plugin.editor.js cp src/3rd/js/plugin/frontend/css.js dist/xrfragment.plugin.frontend.css.js - jscat src/3rd/js/plugin/frontend/{snackbar,accessibility,\$menu,frontend}.js > dist/xrfragment.plugin.frontend.js + jscat src/3rd/js/plugin/frontend/{snackbar,accessibility,\$menu,frontend,chatcommand/*}.js > dist/xrfragment.plugin.frontend.js jscat src/3rd/js/plugin/matrix/{matrix-crdt,matrix}.js > dist/xrfragment.plugin.matrix.js jscat src/3rd/js/plugin/p2p/{trystero-torrent.min,trystero}.js > dist/xrfragment.plugin.p2p.js diff --git a/src/3rd/js/aframe/index.js b/src/3rd/js/aframe/index.js index bd91d91..82f7897 100644 --- a/src/3rd/js/aframe/index.js +++ b/src/3rd/js/aframe/index.js @@ -53,10 +53,16 @@ window.AFRAME.registerComponent('xrf', { VRbutton = document.querySelector('.a-enter-vr-button') if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#-VR' ) ) if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) ) - //if( AFRAME.utils.device.checkARSupport() && VRbutton ){ - // VRbutton.style.display = 'none' - // ARbutton.parentNode.style.right = '20px' - //} + }) + + // (de)active look-controls because of 'rot=' XR Fragment + aScene.addEventListener('loaded', () => { + // this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model + aScene.canvas.addEventListener('mousedown', () => xrf.camera.el.setAttribute("look-controls","") ) + }) + XRF.addEventListener('rot',(e) => { + let lookcontrols = document.querySelector('[look-controls]') + if( lookcontrols ) lookcontrols.removeAttribute("look-controls") }) let repositionUser = (scale) => () => { diff --git a/src/3rd/js/plugin/frontend/$chat.js b/src/3rd/js/plugin/frontend/$chat.js index ba3ff79..5479cc9 100644 --- a/src/3rd/js/plugin/frontend/$chat.js +++ b/src/3rd/js/plugin/frontend/$chat.js @@ -28,6 +28,7 @@ chatComponent = { $messages: el.querySelector("#messages"), $chatline: el.querySelector("#chatline"), $chatbar: el.querySelector("#chatbar"), + $chatsend: el.querySelector("#chatsend"), install(opts){ this.opts = opts @@ -40,6 +41,19 @@ chatComponent = { this.send({message:`Welcome to ${document.location.search.substr(1)}, a 3D scene(file) which simply links to other ones.
You can start a solo offline exploration in XR right away.
Type /help below, or use the arrow- or WASD-keys on your keyboard, and mouse-drag to rotate.
`, class: ["info","guide","multiline"] }) }, + sendInput(value){ + if( value[0] == '#' ) return xrf.navigator.to(value) + let event = value.match(/^[!\/]/) ? "chat.command" : "network.send" + let message = value.replace(/^[!\/]/,'') + let raw = {detail:{message:value, halt:false}} + document.dispatchEvent( new CustomEvent( event, {detail: {message}} ) ) + document.dispatchEvent( new CustomEvent( "chat.input", raw ) ) + if( event == "network.send" && !raw.detail.halt ) this.send({message: value }) + this.$chatline.lastValue = value + this.$chatline.value = '' + if( window.innerHeight < 600 ) this.$chatline.blur() + }, + initListeners(){ let {$chatline} = this @@ -47,12 +61,10 @@ chatComponent = { $chatline.addEventListener('keydown', (e) => { if (e.key == 'Enter' ){ - let event = $chatline.value.match(/^[!\/]/) ? "chat.command" : "network.send" - let message = $chatline.value.replace(/^[!\/]/,'') - document.dispatchEvent( new CustomEvent( event, {detail: {message}} ) ) - if( event == "network.send" ) this.send({message: $chatline.value }) - $chatline.value = '' - if( window.innerHeight < 600 ) $chatline.blur() + this.sendInput($chatline.value) + } + if (e.key == 'ArrowUp' ){ + $chatline.value = $chatline.lastValue || '' } }) @@ -76,11 +88,15 @@ chatComponent = { } }) + this.$chatsend.addEventListener('click', (e) => { + this.sendInput($chatline.value) + }) + }, inform(){ if( !this.inform.informed && (this.inform.informed = true) ){ - window.notify("Connected via P2P. You can now type message which will be visible to others.") + window.notify("You can now type messages in the textfield below.") } }, @@ -263,6 +279,7 @@ chatComponent.css = ` max-width: 500px; */ width:100%; + box-sizing:border-box; align-items: flex-start; position: absolute; transition:1s; @@ -271,7 +288,7 @@ chatComponent.css = ` bottom: 49px; padding: 20px; overflow:hidden; - overflow-y: scroll; + overflow-y: auto; pointer-events:none; transition:1s; z-index: 100; @@ -283,11 +300,14 @@ chatComponent.css = ` pointer-events:all; } #messages *{ + box-sizing:border-box; +/* pointer-events:none; -webkit-user-select:none; -moz-user-select:-moz-none; -ms-user-select:none; user-select:none; +*/ } #messages .msg{ transition:all 1s ease; @@ -320,9 +340,9 @@ chatComponent.css = ` color:#FFF; } #messages .msg.info{ - background: #473f7f; + background: var(--xrf-white); border-radius: 20px; - color: #FFF; + color: var(--xrf-dark-gray); text-align: left; line-height: 19px; } @@ -411,7 +431,8 @@ chatComponent.css = ` .envelope{ margin-right:15px; - max-width:80%; + width:50%; + max-width:700px; } .envelope, diff --git a/src/3rd/js/plugin/frontend/accessibility.js b/src/3rd/js/plugin/frontend/accessibility.js index e93de4a..7d3e10d 100644 --- a/src/3rd/js/plugin/frontend/accessibility.js +++ b/src/3rd/js/plugin/frontend/accessibility.js @@ -4,14 +4,15 @@ window.accessibility = (opts) => new Proxy({ enabled: false, // features - speak_movements: true, - speak_keyboard: true, + speak_teleports: true, + speak_keyboard: false, // audio settings speak_rate: 1, speak_pitch: 1, speak_volume: 1, speak_voice: -1, + speak_voices: 0, toggle(){ this.enabled = !this.enabled }, @@ -33,8 +34,10 @@ window.accessibility = (opts) => new Proxy({ } let speech = window.speechSynthesis let utterance = new SpeechSynthesisUtterance( str ) - if( this.speak_voice != -1) utterance.voice = speech.getVoices()[ this.speak_voice ]; - else{ + this.speak_voices = speech.getVoices().length + if( this.speak_voice != -1 && this.speak_voice < this.speak_voices ){ + utterance.voice = speech.getVoices()[ this.speak_voice ]; + }else{ let voices = speech.getVoices() for(let i = 0; i < voices.length; i++ ){ if( voices[i].lang == navigator.lang ) this.speak_voice = i; @@ -78,6 +81,9 @@ window.accessibility = (opts) => new Proxy({ this.speak( lines.join("."), {override:true,speaksigns:false} ) } }) + document.addEventListener('$chat.send', (opts) => { + if( opts.detail.message ) this.speak( opts.detail.message) + }) }) document.addEventListener('network.send', (e) => { @@ -87,8 +93,7 @@ window.accessibility = (opts) => new Proxy({ }) opts.xrf.addEventListener('pos', (opts) => { - if( this.enabled ){ - $chat.send({message: this.posToMessage(opts) }) + if( this.enabled && this.speak_teleports ){ network.send({message: this.posToMessage(opts), class:["info","guide"]}) } if( opts.frag.pos.string.match(/,/) ){ @@ -100,7 +105,7 @@ window.accessibility = (opts) => new Proxy({ setTimeout( () => this.initCommands(), 200 ) // auto-enable if previously enabled - if( window.localStorage.getItem("accessibility") ){ + if( window.localStorage.getItem("accessibility") === 'true' ){ setTimeout( () => { this.enabled = true this.setFontSize() @@ -111,7 +116,7 @@ window.accessibility = (opts) => new Proxy({ initCommands(){ document.addEventListener('chat.command.help', (e) => { - e.detail.message += `
/fontsize set fontsize (default=14) ` + e.detail.message += `
/fontsize <number> set fontsize (default=14) ` }) document.addEventListener('chat.command', (e) => { @@ -179,10 +184,11 @@ window.accessibility = (opts) => new Proxy({ data[k] = v switch( k ){ case "enabled": { - let message = "accessibility has been"+(v?"boosted":"lowered") + let message = "accessibility mode has been "+(v?"activated":"disabled")+".
Type /help for help." + if( v ) message = "
" + message $('#accessibility.btn').style.filter= v ? 'brightness(1.0)' : 'brightness(0.5)' if( v ) $chat.visible = true - $chat.send({message,class:['info','guide']}) + $chat.send({message,class:['info']}) data.enabled = true data.speak(message) data.enabled = v @@ -212,6 +218,10 @@ document.querySelector('head').innerHTML += ` font-size:24px !important; line-height:40px; } + .accessibility #messages .msg.self { + background:var(--xrf-gray); + color:#FFF; + } .accessibility #messages .msg.info, .accessibility #messages .msg.self { line-height:unset; diff --git a/src/3rd/js/plugin/frontend/chatcommand/href.js b/src/3rd/js/plugin/frontend/chatcommand/href.js new file mode 100644 index 0000000..074ed1c --- /dev/null +++ b/src/3rd/js/plugin/frontend/chatcommand/href.js @@ -0,0 +1,20 @@ +// this allows surfing to a href by typing its node-name + +// help screen +document.addEventListener('chat.command.help', (e) => { + e.detail.message += ` +
<destinationname> surf to a destination + ` +}) + +document.addEventListener('chat.input', (e) => { + + let name = e.detail.message.trim() + xrf.scene.traverse( (n) => { + if( n.userData && n.userData.href && n.userData.href.match(/pos=/) && n.name == name ){ + $chat.send({message:'activating '+n.name, class:['self','info']}) + xrf.navigator.to( n.userData.href ) + } + }) + +}) diff --git a/src/3rd/js/plugin/frontend/chatcommand/mud.js b/src/3rd/js/plugin/frontend/chatcommand/mud.js new file mode 100644 index 0000000..f4e066a --- /dev/null +++ b/src/3rd/js/plugin/frontend/chatcommand/mud.js @@ -0,0 +1,144 @@ +// this allows a more-or-less MUD type interface +// +// + + +// help screen +document.addEventListener('chat.command.help', (e) => { + e.detail.message += ` +
? help screen +
look view scene and destinations +
go [left|right|forward|destination] surf [to destination] +
do [action] list [or perform] action(s) +
rotate <left|right|up|down> rotate camera +
back go to previous portal/link +
forward go to previous portal/link +
#.... execute XR Fragments +
+ ` +}) + +const listExits = (scene) => { + let message = '' + let destinations = {} + scene.traverse( (n) => { + if( n.userData && n.userData.href && n.userData.href.match(/pos=/) ){ + destinations[n.name] = n.userData['aria-label'] || n.userData.href + } + }) + for( let destination in destinations ){ + message += `
${destination} ${destinations[destination]}` + } + if( !message ) message += '
type back to go back' + return message +} + +const listActions = (scene) => { + let message = '' + let destinations = {} + scene.traverse( (n) => { + if( n.userData && n.userData.href && !n.userData.href.match(/pos=/) ){ + destinations[n.name] = n.userData['aria-description'] || n.userData['aria-label'] || n.userData.href + } + }) + for( let destination in destinations ){ + message += `
${destination} ${destinations[destination]}` + } + if( !message ) message += '
no actions found' + return message +} + +document.addEventListener('chat.input', (e) => { + + if( e.detail.message.trim() == '?' ){ + document.dispatchEvent( new CustomEvent( 'chat.command', {detail:{message:"help"}} ) ) + e.detail.halt = true // don't send to other peers + } + + if( e.detail.message.trim() == 'look' ){ + let scene = xrf.frag.pos.last ? xrf.scene.getObjectByName(xrf.frag.pos.last) : xrf.scene + let message = `
${xrf.sceneToTranscript(scene)}

possible destinations in this area:${listExits(scene)}` + e.detail.halt = true // dont print command to screen + $chat.send({message}) + } + + if( e.detail.message.match(/^go($| )/) ){ + if( e.detail.message.trim() == 'go' ){ + $chat.send({message: `all possible destinations:${listExits(xrf.scene)}`}) + }else{ + let destination = e.detail.message.replace(/^go /,'').trim() + if( destination.match(/(left|right|forward|backward)/) ){ + let key = '' + switch( destination){ + case "left": key = 'ArrowLeft'; break; + case "right": key = 'ArrowRight'; break; + case "forward": key = 'ArrowUp'; break; + case "backward": key = 'ArrowDown'; break; + } + if( key ){ + let lookcontrols = document.querySelector('[look-controls]') + if( lookcontrols ) lookcontrols.removeAttribute('look-controls') // workaround to unlock camera + + var wasd = document.querySelector('[wasd-controls]').components['wasd-controls'] + wasd.keys[ key ] = true + wasd.velocity = new THREE.Vector3() + setTimeout( () => delete wasd.keys[ key ], 100 ) + wasd.el.object3D.matrixAutoUpdate = true; + wasd.el.object3D.updateMatrix() + xrf.camera.getCam().updateMatrix() + } + + }else{ + let node + xrf.scene.traverse( (n) => { + if( n.userData && n.userData.href && n.name == destination ) node = n + }) + if( node ) xrf.navigator.to( node.userData.href ) + else $chat.send({message:"type 'look' for possible destinations"}) + } + } + e.detail.halt = true // dont write input to chat + } + + if( e.detail.message.match(/^do($| )/) ){ + if( e.detail.message.trim() == 'do' ){ + $chat.send({message: `all possible actions:${listActions(xrf.scene)}`}) + }else{ + let action = e.detail.message.replace(/^do /,'').trim() + xrf.scene.traverse( (n) => { + if( n.userData && n.userData.href && n.name == action ){ + $chat.send({message:'activating '+n.name, class:['self','info']}) + xrf.navigator.to( n.userData.href ) + } + }) + } + e.detail.halt = true // dont write input to chat + } + + if( e.detail.message.match(/^rotate /) ){ + let dir = e.detail.message.replace(/^rotate /,'').trim() + let y = 0; + let x = 0; + switch(dir){ + case "left": y = 0.3; break; + case "right": y = -0.3; break; + case "up": x = 0.3; break; + case "down": x = -0.3; break; + } + let lookcontrols = document.querySelector('[look-controls]') + if( lookcontrols ) lookcontrols.removeAttribute('look-controls') // workaround to unlock camera + xrf.camera.rotation.y += y + xrf.camera.rotation.x += x + xrf.camera.matrixAutoUpdate = true + e.detail.halt = true // dont write input to chat + } + + if( e.detail.message.trim() == 'back' ){ + window.history.back() + } + + if( e.detail.message.trim() == 'forward' ){ + window.history.forward() + } + +}) diff --git a/src/3rd/js/plugin/frontend/chatcommand/tts.js b/src/3rd/js/plugin/frontend/chatcommand/tts.js new file mode 100644 index 0000000..c1c406e --- /dev/null +++ b/src/3rd/js/plugin/frontend/chatcommand/tts.js @@ -0,0 +1,26 @@ +// this allows surfing to a href by typing its node-name + +// help screen +document.addEventListener('chat.command.help', (e) => { + e.detail.message += ` +
/speak_keyboard <true|false> turn on/off keyboard input TTS +
/speak_teleports <true|false> turn on/off TTS for teleports +
/speak_rate <1> adjust TTS speed +
/speak_pitch <1> adjust TTS pitch +
/speak_volume <1> adjust TTS volume +
/speak_voice <0> select voice (max: ${window.accessibility.speak_voices}) + ` +}) + +document.addEventListener('chat.command', (e) => { + if( !e.detail.message.trim().match(/ /) ) return + let action = e.detail.message.trim().split(" ")[0] + let value = e.detail.message.trim().split(" ")[1] + + if( window.accessibility[action] == undefined ) return + + window.accessibility[action] = value + window.localStorage.setItem(action, value ) + $chat.send({message: `${action} set to ${value}`, class:['info']}) + +}) diff --git a/src/3rd/js/plugin/frontend/css.js b/src/3rd/js/plugin/frontend/css.js index ebdbe19..c38ec23 100644 --- a/src/3rd/js/plugin/frontend/css.js +++ b/src/3rd/js/plugin/frontend/css.js @@ -306,8 +306,8 @@ document.head.innerHTML += ` } #messages .msg .badge{ display:inline; - background: var(--xrf-primary-fg); - color: var(--xrf-dark-gray); + color: var(--xrf-primary-fg); + background: var(--xrf-dark-gray); } .ruler{ @@ -369,7 +369,7 @@ document.head.innerHTML += ` } .transcript{ - max-height:105px; + max-height:132px; width:100%; overflow-y:auto; border: 1px solid var(--xrf-gray); diff --git a/src/3rd/js/plugin/frontend/frontend.js b/src/3rd/js/plugin/frontend/frontend.js index 05746f7..a8da400 100644 --- a/src/3rd/js/plugin/frontend/frontend.js +++ b/src/3rd/js/plugin/frontend/frontend.js @@ -94,9 +94,9 @@ window.frontend = (opts) => new Proxy({ ? "hold 2-3 fingers to move forward/backward" : "use WASD-keys and mouse-drag to move around" window.notify(instructions,{timeout:false}) - xrf.addEventListener('navigate', (opts) => { - let pos = opts.url.replace( document.location.href.replace(/#.*/,''), '') - window.notify('teleporting to '+pos+"

use back/forward browserbutton to undo") + xrf.addEventListener('pos', (opts) => { + let pos = opts.frag.pos.string + window.notify('teleporting to '+pos+"
use back/forward (browserbutton) to undo") }) // close dialogs when url changes },2000 ) @@ -119,13 +119,8 @@ window.frontend = (opts) => new Proxy({ } } } - let transcript = '' let root = data.mesh.portal ? data.mesh.portal.stencilObject : data.mesh - root.traverse( (n) => { - if( n.userData['aria-description'] && n.uuid != data.mesh.uuid ){ - transcript += `#${n.name} ${n.userData['aria-description']}. ` - } - }) + let transcript = xrf.sceneToTranscript(root,data.mesh) if( transcript.length ) html += `
transcript:
${transcript}
` if (hasMeta && !data.mesh.portal ) html += `

Visit embedded scene` diff --git a/src/3rd/js/three/util/transcript.js b/src/3rd/js/three/util/transcript.js new file mode 100644 index 0000000..194d52e --- /dev/null +++ b/src/3rd/js/three/util/transcript.js @@ -0,0 +1,11 @@ +xrf.sceneToTranscript = (scene, ignoreMesh ) => { + let transcript = '' + scene.traverse( (n) => { + let isSRC = false + n.traverseAncestors( (m) => m.userData.src ? isSRC = true : false ) + if( !isSRC && n.userData['aria-description'] && (!ignoreMesh || n.uuid != ignoreMesh.uuid) ){ + transcript += `#${n.name} ${n.userData['aria-description']}. ` + } + }) + return transcript +} diff --git a/src/3rd/js/three/xrf/pos.js b/src/3rd/js/three/xrf/pos.js index 9b5cc5a..189aee8 100644 --- a/src/3rd/js/three/xrf/pos.js +++ b/src/3rd/js/three/xrf/pos.js @@ -19,7 +19,7 @@ xrf.frag.pos = function(v, opts){ if( xrf.debug ) console.log(`#pos.js: setting camera to position ${pos.x},${pos.y},${pos.z}`) - xrf.frag.pos.last = pos // remember + xrf.frag.pos.last = v.string // remember camera.updateMatrixWorld() }