@ -73,30 +73,7 @@ new Runtime().module(define2, name => {
< / script >
< / script >
< script >
< script >
var fontColor= "white"
/*
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"
const wikiAsImages = "https://vatelier.benetou.fr/MyDemo/newtooling/wiki_graph.json"
const wikiAsImages = "https://vatelier.benetou.fr/MyDemo/newtooling/wiki_graph.json"
const maxItems = 10
const maxItems = 10
const now = Math.round( +new Date()/1000 ) //ms in JS, seconds in UNIX epoch
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"
+ "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
// could be an array proper completed on each relevant component registration
var heightAdjustableClasses = ["commands-from-external-json"]
var heightAdjustableClasses = ["commands-from-external-json"]
var pinches = [] // position, timestamp, primary vs secondary
const feedbackHUBClearTime = 5000
var dl2p = null // from distanceLastTwoPinches
var selectedElements = [];
// 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.
// 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
// ?inputmode=makeymakey
@ -562,7 +537,6 @@ function plot(equation,variablename="x",scale=5,step=1){
}
}
previousPoint = pos
previousPoint = pos
}
}
// variablename seems unused
}
}
AFRAME.registerComponent('target', {
AFRAME.registerComponent('target', {
@ -667,7 +641,7 @@ AFRAME.registerComponent('screenstack', {
});
});
function getClosestTargetElements( pos, threshold=0.05 ){
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") ) } })
return targets.filter( e => e.getAttribute("visible") == true).map( t => { return { el: t, dist : pos.distanceTo(t.getAttribute("position") ) } })
.filter( t => t.dist < threshold )
.filter( t => t.dist < threshold )
.sort( (a,b) => a.dist > b.dist)
.sort( (a,b) => a.dist > b.dist)
@ -775,6 +749,7 @@ function appendToFeedbackHUD(txt){
function setFeedbackHUD(txt){
function setFeedbackHUD(txt){
document.querySelector("#feedbackhud").setAttribute("value",txt)
document.querySelector("#feedbackhud").setAttribute("value",txt)
setTimeout( _ => document.querySelector("#feedbackhud").setAttribute("value",""), feedbackHUBClearTime)
}
}
function appendToHUD(txt){
function appendToHUD(txt){
@ -789,24 +764,6 @@ function setHUD(txt){
document.querySelector("#typinghud").setAttribute("value",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',{
AFRAME.registerComponent('wristattachsecondary',{
schema: {
schema: {
target: {type: 'selector'},
target: {type: 'selector'},
@ -841,7 +798,6 @@ AFRAME.registerComponent('pinchsecondary', {
init: function () {
init: function () {
this.el.addEventListener('pinchended', function (event) {
this.el.addEventListener('pinchended', function (event) {
selectedElement = getClosestTargetElement( event.detail.position )
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 close enough to a target among a list of potential targets, unselect previous target then select new
if (selectedElement) interpretJXR( selectedElement.getAttribute("value") )
if (selectedElement) interpretJXR( selectedElement.getAttribute("value") )
selectedElement = null
selectedElement = null
@ -916,11 +872,6 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
bbox.man.copy( zeroVector3 )
bbox.man.copy( zeroVector3 )
*/
*/
setTimeout( _ => primaryPinchStarted = false, 200) // delay otherwise still activate on release
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) {
this.el.addEventListener('pinchmoved', function (event) {
// move current target if any
// move current target if any
@ -948,8 +899,21 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
//targets.push( clone )
//targets.push( clone )
//selectedElement = clone
//selectedElement = clone
//setFeedbackHUD( "pinched" ) // works well even close to the HMD, closer than the HUD
selectedElement = getClosestTargetElement( event.detail.position )
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
// 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(){
init: function(){
var el = this.el
var el = this.el
this.el.addEventListener('pressedended', function (event) {
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
// 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
// could become like https://twitter.com/utopiah/status/1264131327269502976
// can include a mini typing game to warm up finger placement
// 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(){
function startSelectionVolume(){
selectionPinchMode = true
selectionPinchMode = true
// see setupBBox in pinchprimary and pinchsecondary
// see setupBBox in pinchprimary and pinchsecondary
@ -1088,7 +1044,7 @@ function parseKeys(status, key){
targets.push( clone )
targets.push( clone )
selectedElement = clone
selectedElement = clone
} else {
} else {
if (txt.match(prefix )) interpretJXR(txt)
if (txt.match(/^jxr / )) interpretJXR(txt)
// check if text starts with jxr, if so, also interpret it.
// check if text starts with jxr, if so, also interpret it.
addNewNote(e.getAttribute("value"))
addNewNote(e.getAttribute("value"))
e.setAttribute("value", "")
e.setAttribute("value", "")
@ -1112,6 +1068,7 @@ AFRAME.registerComponent('hud', {
this.el.appendChild( feedbackHUDel )
this.el.appendChild( feedbackHUDel )
var typingHUDel = document.createElement("a-troika-text")
var typingHUDel = document.createElement("a-troika-text")
typingHUDel.id = "typinghud"
typingHUDel.id = "typinghud"
targets.push(typingHUDel)
typingHUDel.setAttribute("value", startingText)
typingHUDel.setAttribute("value", startingText)
typingHUDel.setAttribute("position", "-0.05 0 -0.2")
typingHUDel.setAttribute("position", "-0.05 0 -0.2")
typingHUDel.setAttribute("scale", "0.05 0.05 0.05")
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 )
newnote.setAttribute("color", userFontColor )
else
else
newnote.setAttribute("color", fontColor )
newnote.setAttribute("color", fontColor )
if (text.match(prefix))
newnote.setAttribute("color", codeFontColor )
newnote.setAttribute("value", text )
newnote.setAttribute("value", text )
//newnote.setAttribute("font", "sw-test/Roboto-msdf.json")
//newnote.setAttribute("font", "sw-test/Roboto-msdf.json")
newnote.setAttribute("position", position)
newnote.setAttribute("position", position)
@ -1232,20 +1187,7 @@ function saveHistoryAsCompoundSnippet(){
addNewNote( commandhistory.map( e => e.uninterpreted ).join("\n") )
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 ){
function parseJXR( code ){
// should make reserved keywords explicit.
var newcode = code
var newcode = code
newcode = newcode.replace("jxr ", "")
newcode = newcode.replace("jxr ", "")
newcode = newcode.replace(/(\d)s (.*)/ ,`setTimeout( _ => { $2 }, $1*1000)`)
newcode = newcode.replace(/(\d)s (.*)/ ,`setTimeout( _ => { $2 }, $1*1000)`)
@ -1259,11 +1201,6 @@ function parseJXR( code ){
newcode = newcode.replace(/obsv ([^\s]+)/ ,`newNoteFromObservableCell('$1')`)
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 =>
// e.g qs a-sphere sa color red =>
// document.querySelector("a-sphere").setAttribute("color", "red")
// document.querySelector("a-sphere").setAttribute("color", "red")
@ -1284,7 +1221,7 @@ function interpretJXR( code ){
appendToHUD( code )
appendToHUD( code )
}
}
}
}
if (!code.match(prefix )) return
if (!code.match(/^jxr / )) return
var uninterpreted = code
var uninterpreted = code
var parseCode = ""
var parseCode = ""
code.split("\n").map( lineOfCode => parseCode += parseJXR( lineOfCode ) + ";" )
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', {
AFRAME.registerComponent('fot', {
init:function(){
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) )
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) )
min = Math.min.apply(null, Array.from( document.querySelectorAll("."+c) ).map( e => e.object3D.position.y) )
pushDownClass(c, userHeight - (max-min)/2 )
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 ) )
setFeedbackHUD( "adjusted height by:" + ( userHeight - (max-min)/2 ) )
} )
} )
}, 100 )
}, 100 )
@ -1517,8 +1480,9 @@ fetch('./templates.json')
]
]
links = []
links = []
//fetch("commands.json").then(res => res.json() ).then(res => {
//fetch("commands.json").then(res => res.json() ).then(res => {
var commandsURL = "https://fabien.benetou.fr/PIMVRdata/CabinCommands?action=source"
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/EngineSequentialTutorialCommands?action=source" // new default
commandsURL = "https://fabien.benetou.fr/PIMVRdata/EngineFOSSXRCommands?action=source" // new default
var src = AFRAME.utils.getUrlParameter('commands-url')
var src = AFRAME.utils.getUrlParameter('commands-url')
if (src & & src != "") commandsURL = src
if (src & & src != "") commandsURL = src
fetch(commandsURL).then(res => res.json() ).then(res => {
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 {
var data = targets.map( e => { return {
localname: e.localName,
localname: e.localName,
src: e.getAttribute("src"),
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
// both might not be ideal directly in the original JSON but could be attachement as URLs
}
}
function remoteSave(){
function remoteSave(configurableURL="https://fabien.benetou.fr/PIMVRdata/CabinData?action="){
fetch(url+'edit', {
// targets could be filtered down, e.g .fossxr only
fetch(configurableURL+'edit', {
method: 'POST',
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: "post=1& author=PIMVR& authpw=edit_password& text="+JSON.stringify( cabin )
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
// could change model opacity based on hand position, fading out when within a (very small here) safe space
< / script >
< / script >
@ -1634,12 +1575,13 @@ function cloneAndDistribute(){
< div id = "observablehq-viewof-offsetExample-ab4c1560" > < / div >
< div id = "observablehq-viewof-offsetExample-ab4c1560" > < / div >
< div id = "observablehq-result_as_html-ab4c1560" > < / div >
< div id = "observablehq-result_as_html-ab4c1560" > < / div >
< / div >
< / div >
< a-scene cursor = "rayOrigin: mouse" raycaster = "objects: [html]; interval:100;" adjust-height-in-vr
< a-scene cursor = "rayOrigin: mouse" raycaster = "objects: [html]; interval:100;"
toolbox disable-components-via-url enable-components-via-url NOcommands-from-external-json >
disable-components-via-url enable-components-via-url >
<!-- screenstack dynamic - view selectionboxonpinches keyboard glossary timeline issues fot
<!-- screenstack dynamic - view selectionboxonpinches keyboard glossary timeline issues fot
networked-scene="serverURL: https://naf.benetou.fr/; adapter: easyrtc; audio: true;"
networked-scene="serverURL: https://naf.benetou.fr/; adapter: easyrtc; audio: true;"
-->
-->
< a-assets >
< 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 = "avatar-template" > < a-cylinder opacity = .3 scale = ".2 1.2 .2" networked-audio-source > < / a-cylinder > < / template >
< template id = "left-hand-default-template" >
< template id = "left-hand-default-template" >
< a-entity networked-hand-controls = "hand:left" > < / a-entity >
< 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-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;"
< 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
<!-- remove for NAF equivalent
< a-entity id = "my-tracked-left-hand" networked-hand-controls = "hand:left" networked = "template:#left-hand-default-template"
< a-entity id = "my-tracked-left-hand" networked-hand-controls = "hand:left" networked = "template:#left-hand-default-template"
pinchsecondary wristattachsecondary="target: #box" >< / a-entity >
pinchsecondary wristattachsecondary="target: #box" >< / a-entity >
< a-entity id = "my-tracked-right-hand" networked-hand-controls = "hand:right" networked = "template:#right-hand-default-template"
< a-entity id = "my-tracked-right-hand" networked-hand-controls = "hand:right" networked = "template:#right-hand-default-template"
pinchprimary >< / a-entity >
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 = "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 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-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-cylinder position = "1 0.75 -3" radius = "0.5" height = "1.5" color = "#FFC65D" > < / a-cylinder >
< / a-entity >
< / 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"
< a-entity layer = "type: quad; src:#bookpageconverted" rotation = "45 0 0" position = "0 5 -5" > < / a-entity >
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"
< 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 >
rotation="0 -45 0" position="1.5 1.4 -.7" scale=".4 .2 .2" >< / a-image >
< a-sky hide-on-enter-ar color = "black" > < / a-sky >
< 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 >
<!-- permanent offline persistent e - ink based, rM2 size, reminder
<!-- 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 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-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 >
< / a-scene >
< / body >
< / body >
< / html >
< / html >