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 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) ![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. 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>
<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>

@ -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><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>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=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> </ul>
<script> <script>
function replaceWithThisText(element){ function replaceWithThisText(element){

Loading…
Cancel
Save