<!DOCTYPE html> <html> <title>SpaSca : Spatial Scaffolding</title> <head> <!-- Suggestions? https://git.benetou.fr/utopiah/text-code-xr-engine/issues/ --> <script src='dependencies/aframe.offline.min.js'></script> <script src="dependencies/a-console.js"></script> <script src='dependencies/aframe-troika-text.min.js'></script> <script src='dependencies/webdav.js'></script> <script src='jxr-core.js?1234'></script> <script src='jxr-postitnote.js?13235'></script> </head> <body> <script> /* SpaSca circular menu inspired by https://www.olegfrolov.design/spatialcomputing or the Meta OS menu on empty pinch moving, if (moving duration above threshold 500ms) then show menu nearby position with JXR commands that hides on released, on moving highlight closest snippet rewrite nearby getClosestTargetElements() to be only over a subset or targets, i.e the ones from active menu on release if (nearby one of the created JXR snippets), execute it remove those JXR commands <a-troika-text id="spatial-introspection-test" anchor=left value="console.log('executing from secondary pinch');" onreleased="console.log('run on released')" onpicked="console.log('run on picked')" target position=" -0.3 1.35 0" rotation="0 40 0" scale="0.1 0.1 0.1"> </a-troika-text> */ function cloneTarget(target){ let el = target.cloneNode(true) if (!el.id) el.id = "clone_" + crypto.randomUUID() else el.id += "_clone_" + crypto.randomUUID() AFRAME.scenes[0].appendChild(el) } function deleteTarget(target){ targets = targets.filter( e => e != target) target.remove() } function runClosestJXR(){ // ideally this would come from event details let latest = selectedElements[selectedElements.length-1].element let el = getClosestTargetElement( latest.getAttribute('position') ) if (el) interpretJXR( el.getAttribute("value") ) } // should move to jxr-core.js AFRAME.registerComponent('onemptypinch', { // changed from ondrop to be coherent with event name init: function(){ AFRAME.scenes[0].addEventListener('enter-vr', e => { console.log('entered vr') document.querySelector("[cursor]").setAttribute("visible", "true") document.querySelector("[camera]").setAttribute("cursor", "") }) this.menuTargets = [] this.menuEl = document.createElement("a-box") this.menuEl.setAttribute("scale", ".01 .01 .01") this.menuEl.setAttribute("opacity", 0) this.menuEl.setAttribute("color", "red") this.menuEl.setAttribute("wireframe", "true") this.menuOpacity = 0 AFRAME.scenes[0].appendChild(this.menuEl) let colors =["green", "blue", "orange", "yellow", "brown", "black"].map( (c,i) => { let newEl = document.createElement("a-box") newEl.setAttribute("position", "" + ((i+1)*3) + " 0 0") newEl.setAttribute("opacity", 0) newEl.setAttribute("wireframe", "true") newEl.setAttribute("color", c ) this.menuEl.appendChild( newEl ) this.menuTargets.push( newEl ) }) this.menuTargets.push( this.menuEl ) }, // could support multi events: { emptypinch: function (e) { this.menuEl.setAttribute("position", e.detail.position) // works with AFRAME.scenes[0].emit('emptypinch', {position:"0 0 0"}) let code = this.el.getAttribute('onemptypinch') // if multi, should also look for onreleased__ not just onreleased try { eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt) } catch (error) { console.error(`Evaluation failed with ${error}`); } }, emptypinchmoved: function (e) { let foundElement = getClosestElement( e.detail.position, threshold=0.01, this.menuTargets ) this.menuTargets.map( mt => mt.setAttribute("wireframe", "true") ) if ( foundElement ) foundElement.setAttribute("wireframe", "false") if (this.menuOpacity < 1) { this.menuOpacity += .01 this.menuTargets.map( mt => mt.setAttribute("opacity", this.menuOpacity) ) } let code = this.el.getAttribute('onemptypinchmoved') // if multi, should also look for onreleased__ not just onreleased try { eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt) } catch (error) { console.error(`Evaluation failed with ${error}`); } }, emptypinchreleased: function (e) { // could also check if above this.menuOpacity threshold, e.g not doing below .3 to avoid unexpected action let foundElement = getClosestElement( e.detail.position, threshold=0.02, this.menuTargets ) // does not seem to execute anymore after teleporting, did work before though if ( foundElement ){ document.getElementById("box").setAttribute("color", foundElement.getAttribute("color") ) } this.menuOpacity = 0 this.menuEl.setAttribute("opacity", this.menuOpacity) this.menuTargets.map( mt => mt.setAttribute("opacity", this.menuOpacity) ) let code = this.el.getAttribute('onemptypinchreleased') // if multi, should also look for onreleased__ not just onreleased try { eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt) } catch (error) { console.error(`Evaluation failed with ${error}`); } }, } }) function onHoveredTeleport(){ // iterate over targets // see instead of teleportable https://aframe.io/docs/1.5.0/components/cursor.html#configuring-the-cursor-through-the-raycaster-component Array.from( document.querySelectorAll("[teleporter]") ).map( target => { if ( target.states.includes( "cursor-hovered" ) ){ target.setAttribute("material", "color", "magenta") // visited document.getElementById('rig').setAttribute('position', target.getAttribute("position") ) } }) } AFRAME.registerComponent('teleporter', { init: function(){ this.el.setAttribute("opacity", .5) }, events: { mouseenter: function (e) { this.el.setAttribute("opacity", .8) }, mouseleave: function (e) { this.el.setAttribute("opacity", .5) }, click: function (e) { document.getElementById('rig').setAttribute('position', this.el.getAttribute("position") ) } // makes it compatible with mouse on desktop ... but also somehow enable the wrist shortcut?! } }); </script> <div style='position:fixed;z-index:1; top: 0%; left: 0%; border-bottom: 70px solid transparent; border-left: 70px solid #eee; '> <a href="https://git.benetou.fr/utopiah/text-code-xr-engine/issues/"> <img style='position:fixed;left:10px;' title='code repository' src='gitea_logo.svg'> </a> </div> <a-scene startfunctions onemptypinch="onHoveredTeleport()" > <a-gltf-model hide-on-enter-ar="" id="environment" src="../content/CubeRoom.glb" rotation="0 -90 0" position="0 0 1" scale="" ></a-gltf-model> <!-- Cube Room by Anonymous [CC-BY] via Poly Pizza --> <a-entity id="rig"> <a-entity id="player" networked="template:#avatar-template;attachTemplateToLocal:false;" hud camera look-controls wasd-controls position="0 1.6 0"> <a-entity cursor position="0 0 -1" geometry="primitive: ring; radiusInner: 0.005; radiusOuter: 0.01" material="color: black; shader: flat; opacity:.05;" ></a-entity> </a-entity> <a-entity id="rightHand" pinchprimary hand-tracking-controls="hand: right;"></a-entity> <a-entity id="leftHand" pinchsecondary wristattachsecondary="target: #box" hand-tracking-controls="hand: left;"></a-entity> <a-console position="2 2 0" rotation="0 -45 0" font-size="34" height=1 skip-intro=true></a-console> </a-entity> <a-box pressable start-on-press id="box" scale="0.05 0.05 0.05" color="pink"></a-box> <a-troika-text value="SpaSca : Spatial Scaffolding" anchor="left" outline-width="5%" font="../content/ChakraPetch-Regular.ttf" position="-3 5 -2" scale="3 3 3" rotation="80 0 0" troika-text="outlineWidth: 0.01; strokeColor: #ffffff" material="flatShading: true; blending: additive; emissive: #c061cb"></a-troika-text> <a-sky hide-on-enter-ar color="lightgray"></a-sky> <a-troika-text anchor=left target value="instructions : \n--right pinch to move\n--left pinch to execute" position="0 0.65 -0.2" scale="0.1 0.1 0.1"></a-troika-text> <a-troika-text anchor=left value="jxr onNextPrimaryPinch(deleteTarget)" target position=" -0.3 1.50 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text> <a-troika-text anchor=left value="jxr onNextPrimaryPinch(cloneTarget)" target position=" -0.3 1.60 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text> <a-troika-text anchor=left value="jxr location.reload()" target position=" -0.3 1.30 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text> <a-troika-text anchor=left value="jxr makeAnchorsVisibleOnTargets()" target position=" -0.3 1.20 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text> <a-troika-text anchor=left value="forceXaxis toggling" onreleased="console.log('run on released');forceXaxis=!forceXaxis" onpicked="console.log('run on picked');forceXaxis=!forceXaxis" target position=" -0.3 1.45 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text> <a-troika-text anchor=left value="translate targets" onreleased="toggleTranslateTargets()" onpicked="toggleTranslateTargets()" target position=" 1 1.45 -.2" rotation="0 -40 0" scale="0.1 0.1 0.1"></a-troika-text> <a-troika-text anchor=left value="jxr setTimeout( _ => toggleAttachToSelf(), 1000); toggleAttachToSelf()" target position=" -0.3 1.25 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text> <a-box teleporter height=".1" class="teleportable" material="color: cyan" position="3.5 0 -3.5" ></a-box> <a-box teleporter height=".1" class="teleportable" material="color: cyan" position="-4 0 4" ></a-box> <a-box teleporter height=".1" class="teleportable" material="color: cyan" position="3 3 4" > <a-troika-text anchor=left value="jxr location.reload()" target position="0 1.30 -.5" rotation="0 0 0" scale="0.1 0.1 0.1"></a-troika-text> <!-- works to execute but not to move, should either reparent or take into account the parent offset while moving --> <!-- see pinchmoved in primary pinch in jxr-core.js as potential solution --> </a-box> <a-box teleporter height=".1" class="teleportable" material="color: cyan" position="0 0 0" ></a-box> <a-troika-text id="spatial-introspection-test" anchor=left value="console.log('executing from secondary pinch');" onreleased="console.log('run on released')" onpicked="console.log('run on picked')" target position=" -0.3 1.35 0" rotation="0 40 0" scale="0.1 0.1 0.1"> </a-troika-text> </a-scene> </body> </script> </html>