Compare commits

..

5 Commits

  1. 62
      README.md
  2. 214
      index.html
  3. 1
      submit.html

@ -8,69 +8,7 @@ You can test it live at https://fabien.benetou.fr/pub/home/future_of_text_demo/e
See also the AR version https://video.benetou.fr/w/x7HeEBF9HGfdVyf7vWsKsQ
Note that the master branch is never the most up to date branch. Instead see https://git.benetou.fr/utopiah/text-code-xr-engine/branches for an overview. To explore branches in VR see https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/branches.html
In order to have a better view of the different features accross branches see https://mastodon.pirateparty.be/web/@utopiah and https://twitter.com/utopiah/ which act as a kind of live documentation of the process.
![Manipulation prevew image](https://fabien.benetou.fr/pub/home/future_of_text_demo/content/primitives_manipulation.gif)
![Preview image](https://fabien.benetou.fr/pub/home/future_of_text_demo/content/engine-preview.png)
First communicated on https://twitter.com/utopiah/status/1531188862415929344 as way to work on (WebXR) code during a flight.
## Minimalist example
See for details the [minimalist-template branch](https://git.benetou.fr/utopiah/text-code-xr-engine/src/branch/minimalist-template/index.html) and [deployment issue](https://git.benetou.fr/utopiah/text-code-xr-engine/issues/72).
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>JXR minimalist template</title>
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-troika-text/dist/aframe-troika-text.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/kylebakerio/a-console@1.0.2/a-console.js"></script>
<script src="https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/jxr.js"></script>
<!-- use to define targets and left/right pinch interactions, respectively execute code and move targets -->
</head>
<body>
<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="https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/gitea_logo.svg">
</a>
</div>
<button id="mainbutton" style="display:none; z-index: 1; position: absolute; width:50%; margin: auto; text-align:center; top:45%; left:30%; height:30%;" onclick="startExperience()">Start the experience (hand tracking recommended)</button>
<a-scene>
<a-entity id="rig">
<a-entity id="player"
hud camera look-controls wasd-controls waistattach="target: .movebypinch" position="0 1.6 0"></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-entity>
<a-troika-text value="SpaSca : Spatial Scaffolding" anchor="left" outline-width="5%" font="https://fabien.benetou.fr/pub/home/future_of_text_demo/content/ChakraPetch-Regular.ttf" position="-5.26197 6.54224 -1.81284"
scale="4 4 5" rotation="90 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="black"></a-sky>
<a-entity hide-on-enter-ar="" id="environmentsky" class="hidableenvironment" ></a-entity>
<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 target id="locationreload" value="jxr location.reload()" position="0 1.20 -0.1" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left target id="makeAnchorsVisibleOnTargets" value="jxr makeAnchorsVisibleOnTargets()" position="0 1.05 -0.1" scale="0.1 0.1 0.1"></a-troika-text>
<a-console position="0 1.1 -0.8" rotation="-45 0 0" font-size="34" height="0.5" skip-intro="true"></a-console>
</a-scene>
</body>
</html>
```

@ -73,30 +73,7 @@ new Runtime().module(define2, name => {
</script>
<script>
/*
motion to data
- integer, e.g distance from beginning to end
- curve, sampling N points between beginning and end
being able to use that in jxr commands, with example related to positioning entities
see https://git.benetou.fr/utopiah/text-code-xr-engine/issues/52#issuecomment-229
warning that selectedElement will get overwritten once executing a command by pinching
consequently in addition to have a history of executed commands
there should be a history of selected elements
and maybe their changed position states
*/
// motivated by https://git.benetou.fr/utopiah/text-code-xr-engine/issues/63
var reservedKeywords = ["selectedElement", "lastPointSketch ", "commandhistory", "groupSelection", "targets", "observe", "sa", "qs"]
// see generated file reserved-keywords for more yet not sufficient, see instead parseJXR()
// should also include some documentation
const prefix = /^jxr /
const codeFontColor = "lightgrey"
const fontColor= "white"
var fontColor= "white"
const wikiAsImages = "https://vatelier.benetou.fr/MyDemo/newtooling/wiki_graph.json"
const maxItems = 10
const now = Math.round( +new Date()/1000 ) //ms in JS, seconds in UNIX epoch
@ -139,9 +116,7 @@ var generators = "line-link-entities link screenstack dynamic-view selectionboxo
+ "commands-from-external-json glossary timeline issues web-url background-via-url observableui hidableenvironmentfot fot"
// could be an array proper completed on each relevant component registration
var heightAdjustableClasses = ["commands-from-external-json"]
var pinches = [] // position, timestamp, primary vs secondary
var dl2p = null // from distanceLastTwoPinches
var selectedElements = [];
const feedbackHUBClearTime = 5000
// could add a dedicated MakeyMakey mode with a fixed camera, e.g bird eye view, and an action based on some physical input that others, thanks to NAF, could see or even use.
// ?inputmode=makeymakey
@ -562,7 +537,6 @@ function plot(equation,variablename="x",scale=5,step=1){
}
previousPoint = pos
}
// variablename seems unused
}
AFRAME.registerComponent('target', {
@ -667,7 +641,7 @@ AFRAME.registerComponent('screenstack', {
});
function getClosestTargetElements( pos, threshold=0.05 ){
// TODO Bbox intersects rather than position
// TODO Bbox intersects rather than position, especially as most people try to pick from center rather than beginning
return targets.filter( e => e.getAttribute("visible") == true).map( t => { return { el: t, dist : pos.distanceTo(t.getAttribute("position") ) } })
.filter( t => t.dist < threshold )
.sort( (a,b) => a.dist > b.dist)
@ -775,6 +749,7 @@ function appendToFeedbackHUD(txt){
function setFeedbackHUD(txt){
document.querySelector("#feedbackhud").setAttribute("value",txt)
setTimeout( _ => document.querySelector("#feedbackhud").setAttribute("value",""), feedbackHUBClearTime)
}
function appendToHUD(txt){
@ -789,24 +764,6 @@ function setHUD(txt){
document.querySelector("#typinghud").setAttribute("value",txt)
}
AFRAME.registerComponent('waistattach',{
schema: {
target: {type: 'selectorAll'},
},
init: function () {
var el = this.el
this.worldPosition=new THREE.Vector3();
},
tick: function () {
var worldPosition=this.worldPosition;
worldPosition.copy(this.el.object3D.position);this.el.object3D.parent.updateMatrixWorld();this.el.object3D.parent.localToWorld(worldPosition)
Array.from( this.data.target ).map( t => {
t.object3D.position.x = worldPosition.x
t.object3D.position.z = worldPosition.z
})
},
});
AFRAME.registerComponent('wristattachsecondary',{
schema: {
target: {type: 'selector'},
@ -841,7 +798,6 @@ AFRAME.registerComponent('pinchsecondary', {
init: function () {
this.el.addEventListener('pinchended', function (event) {
selectedElement = getClosestTargetElement( event.detail.position )
selectedElements.push({element:selectedElement, timestamp:Date.now(), primary:false})
// if close enough to a target among a list of potential targets, unselect previous target then select new
if (selectedElement) interpretJXR( selectedElement.getAttribute("value") )
selectedElement = null
@ -916,11 +872,6 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
bbox.man.copy( zeroVector3 )
*/
setTimeout( _ => primaryPinchStarted = false, 200) // delay otherwise still activate on release
var newPinchPos = new THREE.Vector3()
newPinchPos.copy(event.detail.position )
pinches.push({position:newPinchPos, timestamp:Date.now(), primary:true})
dl2p = distanceLastTwoPinches()
});
this.el.addEventListener('pinchmoved', function (event) {
// move current target if any
@ -948,8 +899,21 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
//targets.push( clone )
//selectedElement = clone
//setFeedbackHUD( "pinched" ) // works well even close to the HMD, closer than the HUD
selectedElement = getClosestTargetElement( event.detail.position )
selectedElements.push({element:selectedElement, timestamp:Date.now(), primary:true})
if (selectedElement) { // never works with #typinghud
setFeedbackHUD( "selectedElement:"+selectedElement.getAttribute("value") )
// somehow never happens trying to get text from HUD
if (selectedElement.id == "typinghud"){
setFeedbackHUD( "cloned typinghud" )
var clone = selectedElement.cloneNode()
clone.removeAttribute("id")
clone.className += "typinghud"
AFRAME.scenes[0].appendChild( clone )
targets.push( clone )
selectedElement = clone
}
}
// if close enough to a target among a list of potential targets, unselect previous target then select new
});
},
@ -996,7 +960,7 @@ AFRAME.registerComponent('start-on-press', {
init: function(){
var el = this.el
this.el.addEventListener('pressedended', function (event) {
if (!primaryPinchStarted && wristShortcut.match(prefix)) interpretJXR(wristShortcut)
if (!primaryPinchStarted && wristShortcut.match(/^jxr /)) interpretJXR(wristShortcut)
// other action could possibly based on position relative to zones instead, i.e a list of bbox/functions pairs
})
}
@ -1005,14 +969,6 @@ AFRAME.registerComponent('start-on-press', {
// could become like https://twitter.com/utopiah/status/1264131327269502976
// can include a mini typing game to warm up finger placement
function distanceLastTwoPinches(){
let dist = null
if (pinches.length>1){
dist = pinches[pinches.length-1].position.distanceTo( pinches[pinches.length-2].position )
}
return dist
}
function startSelectionVolume(){
selectionPinchMode = true
// see setupBBox in pinchprimary and pinchsecondary
@ -1088,7 +1044,7 @@ function parseKeys(status, key){
targets.push( clone )
selectedElement = clone
} else {
if (txt.match(prefix)) interpretJXR(txt)
if (txt.match(/^jxr /)) interpretJXR(txt)
// check if text starts with jxr, if so, also interpret it.
addNewNote(e.getAttribute("value"))
e.setAttribute("value", "")
@ -1112,6 +1068,7 @@ AFRAME.registerComponent('hud', {
this.el.appendChild( feedbackHUDel )
var typingHUDel = document.createElement("a-troika-text")
typingHUDel.id = "typinghud"
targets.push(typingHUDel)
typingHUDel.setAttribute("value", startingText)
typingHUDel.setAttribute("position", "-0.05 0 -0.2")
typingHUDel.setAttribute("scale", "0.05 0.05 0.05")
@ -1141,8 +1098,6 @@ function addNewNote( text, position=`-0.2 1.1 -0.1`, scale= "0.1 0.1 0.1", id=nu
newnote.setAttribute("color", userFontColor )
else
newnote.setAttribute("color", fontColor )
if (text.match(prefix))
newnote.setAttribute("color", codeFontColor )
newnote.setAttribute("value", text )
//newnote.setAttribute("font", "sw-test/Roboto-msdf.json")
newnote.setAttribute("position", position)
@ -1232,20 +1187,7 @@ function saveHistoryAsCompoundSnippet(){
addNewNote( commandhistory.map( e => e.uninterpreted ).join("\n") )
}
function bindVariableValueToNewNote(variableName){
// from observe jxr keyword
const idName = "bindVariableValueToNewNote"+variableName
addNewNote( variableName + ":" + eval(variableName), `-0.15 1.4 -0.1`, "0.1 0.1 0.1", idName, "observers", "true" )
// could add to the HUD instead and have a list of these
return setInterval( _ => {
const value = variableName+";"+eval(variableName)
// not ideal for DOM elements, could have shortcuts for at least a-text with properties, e.g value or position
document.getElementById(idName).setAttribute("value", value)
}, 100 )
}
function parseJXR( code ){
// should make reserved keywords explicit.
var newcode = code
newcode = newcode.replace("jxr ", "")
newcode = newcode.replace(/(\d)s (.*)/ ,`setTimeout( _ => { $2 }, $1*1000)`)
@ -1259,11 +1201,6 @@ function parseJXR( code ){
newcode = newcode.replace(/obsv ([^\s]+)/ ,`newNoteFromObservableCell('$1')`)
// TODO
//<a-text target value="jxr observe selectedElement" position="0 1.25 -0.2" scale="0.1 0.1 0.1"></a-text>
newcode = newcode.replace(/observe ([^\s]+)/,`bindVariableValueToNewNote('$1')`)
// could proxy instead... but for now, the quick and dirty way :
// e.g qs a-sphere sa color red =>
// document.querySelector("a-sphere").setAttribute("color", "red")
@ -1284,7 +1221,7 @@ function interpretJXR( code ){
appendToHUD( code )
}
}
if (!code.match(prefix)) return
if (!code.match(/^jxr /)) return
var uninterpreted = code
var parseCode = ""
code.split("\n").map( lineOfCode => parseCode += parseJXR( lineOfCode ) + ";" )
@ -1390,6 +1327,31 @@ AFRAME.registerComponent('glossary', {
},
});
AFRAME.registerComponent('fossxr', {
init:function(){
let generatorName = this.attrName
fetch("https://fabien.benetou.fr/Testing/FOSSXR2022?action=source#" + Date.now()).then(res => res.text() ).then(res => {
res.split("\n").filter(e => (e.match(/^\* /))).slice(0,maxItemsFromSources).map( (n,i) => {
found = added.find((str) => str === n)
if (typeof found === 'undefined'){
added.push(n)
addNewNote( n, "-.1 "+(1+i/10)+" -.5", ".1 .1 .1", null, generatorName )
}
})
})
}
});
function saveBackList(){
data = Array.from( document.querySelectorAll(".fossxr") ).sort((a, b) => a.object3D.position.y - b.object3D.position.y ).map( e => e.getAttribute("value")).join("%0a")
configurableURL="https://fabien.benetou.fr/PIMVRdata/FOSSXR?action="
fetch(configurableURL+'edit', {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: "post=1&author=PIMVR&authpw=edit_password&text="+JSON.stringify( data )
}).then(res => res).then(res => setFeedbackHUD("saved remotely"+ res))
}
AFRAME.registerComponent('fot', {
init:function(){
},
@ -1479,6 +1441,7 @@ AFRAME.registerComponent('adjust-height-in-vr', {
max = Math.max.apply(null, Array.from( document.querySelectorAll("."+c) ).map( e => e.object3D.position.y) )
min = Math.min.apply(null, Array.from( document.querySelectorAll("."+c) ).map( e => e.object3D.position.y) )
pushDownClass(c, userHeight - (max-min)/2 )
// to adjust, works well while seated but now on floor or standing up
setFeedbackHUD( "adjusted height by:" + ( userHeight - (max-min)/2 ) )
} )
}, 100 )
@ -1517,8 +1480,9 @@ fetch('./templates.json')
]
links = []
//fetch("commands.json").then(res => res.json() ).then(res => {
var commandsURL = "https://fabien.benetou.fr/PIMVRdata/CabinCommands?action=source"
commandsURL = "https://fabien.benetou.fr/PIMVRdata/EngineSequentialTutorialCommands?action=source" // new default
var commandsURL = "https://fabien.benetou.fr/PIMVRdata/CabinCommands?action=source" // should become a global parameter
// commandsURL = "https://fabien.benetou.fr/PIMVRdata/EngineSequentialTutorialCommands?action=source" // new default
commandsURL = "https://fabien.benetou.fr/PIMVRdata/EngineFOSSXRCommands?action=source" // new default
var src = AFRAME.utils.getUrlParameter('commands-url')
if (src && src != "") commandsURL = src
fetch(commandsURL).then(res => res.json() ).then(res => {
@ -1538,7 +1502,7 @@ fetch('./templates.json')
},
});
function save(){
function save(classname=null){
var data = targets.map( e => { return {
localname: e.localName,
src: e.getAttribute("src"),
@ -1578,8 +1542,9 @@ function remoteLoad(){
// both might not be ideal directly in the original JSON but could be attachement as URLs
}
function remoteSave(){
fetch(url+'edit', {
function remoteSave(configurableURL="https://fabien.benetou.fr/PIMVRdata/CabinData?action="){
// targets could be filtered down, e.g .fossxr only
fetch(configurableURL+'edit', {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: "post=1&author=PIMVR&authpw=edit_password&text="+JSON.stringify( cabin )
@ -1603,30 +1568,6 @@ function switchSide(){
}
}
function cloneAndDistribute(){
el = document.querySelector("a-box[src]") // page
// trying instead to rely on previously selected matching element and dl2p
// lack visual feedback to show what is indeed lastly selected or the distance found
//el = selectedElements[selectedElements.length-2] // not current command
times = Math.floor(dl2p*10) // also assume it's been done properly
if (times < 2) times = 7
offset = .5
for (var i = 0; i < times ; i++) { // equivalent of Blender array modifier
let newEl = el.cloneNode()
AFRAME.scenes[0].appendChild(newEl) // takes time...
setTimeout( setZ, 100, {el: newEl, z: -1-i*offset} )
newEl.addEventListener('hasLoaded', function (event) {
//this.object3D.position.z = i*offset
console.log("loaded") // doesnt seem to happen
})
}
function setZ(params){
params.el.object3D.position.z = params.z
}
}
// could change model opacity based on hand position, fading out when within a (very small here) safe space
</script>
@ -1634,12 +1575,13 @@ function cloneAndDistribute(){
<div id="observablehq-viewof-offsetExample-ab4c1560"></div>
<div id="observablehq-result_as_html-ab4c1560"></div>
</div>
<a-scene cursor="rayOrigin: mouse" raycaster="objects: [html]; interval:100;" adjust-height-in-vr
toolbox disable-components-via-url enable-components-via-url NOcommands-from-external-json >
<a-scene cursor="rayOrigin: mouse" raycaster="objects: [html]; interval:100;"
disable-components-via-url enable-components-via-url >
<!-- screenstack dynamic-view selectionboxonpinches keyboard glossary timeline issues fot
networked-scene="serverURL: https://naf.benetou.fr/; adapter: easyrtc; audio: true;"
-->
<a-assets>
<img id="bookpageconverted" crossOrigin="anonymous" src="/pub/home/moby_p10.epub.pdf.inverted.jpg">
<template id="avatar-template"> <a-cylinder opacity=.3 scale=".2 1.2 .2" networked-audio-source></a-cylinder> </template>
<template id="left-hand-default-template">
<a-entity networked-hand-controls="hand:left"></a-entity>
@ -1654,22 +1596,14 @@ function cloneAndDistribute(){
<a-video position="0 2 -2" src="https://video.benetou.fr/videos/embed/91634fb7-116e-43a1-a4e7-144dd92da17c"></a-video>
-->
<a-entity id="player" networked="template:#avatar-template;attachTemplateToLocal:false;"
hud camera look-controls wasd-controls waistattach="target: .movebypinch" position="0 1.6 0"></a-entity>
hud camera look-controls wasd-controls position="0 1.6 0"></a-entity>
<!-- remove for NAF equivalent
<a-entity id="my-tracked-left-hand" networked-hand-controls="hand:left" networked="template:#left-hand-default-template"
pinchsecondary wristattachsecondary="target: #box" ></a-entity>
<a-entity id="my-tracked-right-hand" networked-hand-controls="hand:right" networked="template:#right-hand-default-template"
pinchprimary ></a-entity>
<a-entity class=movebypinch >
<a-text target value="jxr document.getElementById('player').object3D.position.z++" position="0 1.15 0.1" rotation="-30 0 0" scale="0.1 0.1 0.1"></a-text>
<a-text target value="jxr player.object3D.position.z--" position="0 1.15 -0.1" rotation="-30 0 0" scale="0.1 0.1 0.1"></a-text>
<a-text target value="jxr player.object3D.position.x++" position="-0.3 1.15 0" rotation="-30 0 0" scale="0.1 0.1 0.1"></a-text>
<a-text target value="jxr player.object3D.position.x--" position="0.3 1.15 0" rotation="-30 0 0" scale="0.1 0.1 0.1"></a-text>
</a-entity>
-->
<!-- works on desktop via interpretJXR() but not in VR by trying to pinch... disabled for now for demo clarity-->
<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>
@ -1681,37 +1615,17 @@ function cloneAndDistribute(){
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
</a-entity>
<a-image id=background background-via-url visible=false position="0 1.5 -1.02" scale="2 1 1" src=""></a-image>
<a-image visible=false class=mural-instructions src="../content/future_of_text_symposium/mappinghypertext_typesofdiagrams2.png"
rotation="0 -45 0" position="1.5 1.7 -.7" scale=".4 .2 .2" ></a-image>
<a-image visible=false class=mural-instructions src="../content/future_of_text_symposium/mappinghypertext_typesofdiagrams1.png"
rotation="0 -45 0" position="1.5 1.4 -.7" scale=".4 .2 .2" ></a-image>
<a-image visible=false class=mural-instructions src="../content/future_of_text_symposium/mappinghypertext_semanticanalysisofdiagrams.png"
rotation="0 45 0" position="-1.5 1.7 -.7" scale=".4 .2 .2" ></a-image>
<a-image visible=false class=mural-instructions src="../content/future_of_text_symposium/mappinghypertext_mappingfusion.png"
rotation="0 45 0" position="-1.5 1.4 -.7" scale=".4 .2 .2" ></a-image>
<!-- visual reminders of shortcuts, a poster on the far left/right of keyboard shortcuts -->
<!-- assets CabanaAndCurtains.glb Pond.glb TempleOfLife.glb JapaneseRoom.glb -->
<a-entity hide-on-enter-ar id="environment" class="hidableenvironment" gltf-model="url(../content/Pond.glb)" scale="80 80 80" position="0 0.2 0.15" rotation="0 -90 0"></a-entity>
<a-entity hide-on-enter-ar class="hidableenvironment" gltf-model="url(../content/CabanaAndCurtains.glb)" scale=".010 .010 .010" position="0 0.2 0.15" rotation="0 0 0"></a-entity>
<a-entity light="type: ambient; color: #BBB; intensity: 0.6"></a-entity>
<a-entity light="type: directional; color: #FFF; intensity: 0.6" position="-0.5 1 1"></a-entity>
<a-sky hide-on-enter-ar color="#add8e6"></a-sky>
<a-entity layer="type: quad; src:#bookpageconverted" rotation="45 0 0" position="0 5 -5"></a-entity>
<a-troika-text anchor="left" target="" id="locationreload" value="jxr location.reload()" position="0 1.20 -0.1" scale="0.1 0.1 0.1" troika-text="" class="collidable"></a-troika-text>
<a-sky hide-on-enter-ar color="black"></a-sky>
<!-- permanent offline persistent e-ink based, rM2 size, reminder
<a-plane position="0 2 -2" scale="4 4 4" mirror></a-plane>
<a-plane position="0 1 -1" scale="0.21 0.15 1" rotation="-30 0 0" wireframe="true"></a-plane>
-->
<a-text target value="instructions : pinch twice for distance then select element then execute cloneAndDistribute() " position="0 1.65 -0.2" scale="0.1 0.1 0.1"></a-text>
<a-text target value="jxr AFRAME.scenes[0].components.inspector.openInspector()" position="0 1.25 -0.2" scale="0.1 0.1 0.1"></a-text>
<a-text target value="jxr observe selectedElement" position="0 1.15 -0.2" scale="0.1 0.1 0.1"></a-text>
<a-text target value="jxr observe dl2p" position="0 1.35 -0.2" scale="0.1 0.1 0.1"></a-text>
<a-text target value="jxr cloneAndDistribute()" position="0 1.45 -0.2" scale="0.1 0.1 0.1"></a-text>
<a-box target position="-0.1 1.2 -0.3" scale=".1 1 0.01" rotation="0 45 0"
src="https://vatelier.benetou.fr/MyDemo/newtooling/textures/fabien.benetou.fr_Analysis_LibrarianMoveWalls.png"></a-box>
</a-scene>
</body>
</html>

@ -28,7 +28,6 @@ Documentation as :
<li><a href=https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/>live result</a> (ideally in VR) and</li>
<li>open-source <a href=https://git.benetou.fr/utopiah/text-code-xr-engine/issues>code repository</a> of the code and to make your own suggestions via issues. </li>
<li><a href=qrcode.png>QRcode</a> of this page to share with others also on mobile.</li>
<li><a href=https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/?background=../content/future_of_text_symposium/HORN-2001-FutureOfHumanCognomeCL.png&fontcolor=lightgray>background</a> as URL parameter. Feel free to use your own content.</li>
</ul>
<script>
function replaceWithThisText(element){

Loading…
Cancel
Save