@ -73,7 +73,30 @@ 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
@ -116,6 +139,9 @@ 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
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
@ -536,6 +562,7 @@ function plot(equation,variablename="x",scale=5,step=1){
}
}
previousPoint = pos
previousPoint = pos
}
}
// variablename seems unused
}
}
AFRAME.registerComponent('target', {
AFRAME.registerComponent('target', {
@ -762,6 +789,24 @@ 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'},
@ -796,6 +841,7 @@ 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
@ -870,6 +916,11 @@ 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
@ -898,6 +949,7 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
//selectedElement = clone
//selectedElement = clone
selectedElement = getClosestTargetElement( event.detail.position )
selectedElement = getClosestTargetElement( event.detail.position )
selectedElements.push({element:selectedElement, timestamp:Date.now(), primary:true})
// 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
});
});
},
},
@ -944,7 +996,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(/^jxr / )) interpretJXR(wristShortcut)
if (!primaryPinchStarted & & wristShortcut.match(prefix )) 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
})
})
}
}
@ -953,6 +1005,14 @@ 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
@ -1028,7 +1088,7 @@ function parseKeys(status, key){
targets.push( clone )
targets.push( clone )
selectedElement = clone
selectedElement = clone
} else {
} else {
if (txt.match(/^jxr / )) interpretJXR(txt)
if (txt.match(prefix )) 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", "")
@ -1081,6 +1141,8 @@ 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)
@ -1170,7 +1232,20 @@ 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)`)
@ -1184,6 +1259,11 @@ 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")
@ -1204,7 +1284,7 @@ function interpretJXR( code ){
appendToHUD( code )
appendToHUD( code )
}
}
}
}
if (!code.match(/^jxr / )) return
if (!code.match(prefix )) 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 ) + ";" )
@ -1523,6 +1603,30 @@ 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 >
@ -1531,7 +1635,7 @@ function switchSide(){
< 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;" adjust-height-in-vr
toolbox disable-components-via-url enable-components-via-url commands-from-external-json >
toolbox disable-components-via-url enable-components-via-url NO commands-from-external-json >
<!-- 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;"
-->
-->
@ -1550,14 +1654,22 @@ function switchSide(){
< 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 position="0 1.6 0">< / a-entity >
hud camera look-controls wasd-controls waistattach="target: .movebypinch" 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 >
@ -1592,6 +1704,14 @@ function switchSide(){
< 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 >