@ -33,203 +33,15 @@ setTimeout( _ => {
let rot = "0 0 0"
let rot = "0 0 0"
let positions = [ "-1 1 -3.5", "-1 2 -3.5", "0 0 -3.5", "0 1 -3.5", "1 0 -3.5", "-1 0 -3.5" ]
let positions = [ "-1 1 -3.5", "-1 2 -3.5", "0 0 -3.5", "0 1 -3.5", "1 0 -3.5", "-1 0 -3.5" ]
// triangle example, could be based on room boundaries instead
// triangle example, could be based on room boundaries instead
positions.map( pos => {
positions.map( pos => { addFromTemplate(pos, rot, scaffoldingInnerHTMLTemplate) })
}, 2000)
function addFromTemplate(pos="0 0 0", rot="0 0 0", template="< a-box > < / a-box > "){
let el = document.createElement("a-entity")
let el = document.createElement("a-entity")
el.innerHTML = scaffoldingInnerHTMLTemplate
el.innerHTML = t emplate
AFRAME.scenes[0].appendChild(el)
AFRAME.scenes[0].appendChild(el)
el.setAttribute("position", pos)
el.setAttribute("position", pos)
el.setAttribute("rotation", rot)
el.setAttribute("rotation", rot)
})
}, 2000)
var forceXaxis
// setInterval( _ => console.log(forceXaxis), 1000)
var translatingTargets = false
var clearRot
function toggleTranslateTargets(){
translatingTargets = !translatingTargets
let scene = AFRAME.scenes[0].object3D
if (translatingTargets){
let anchor = new THREE.Object3D()
let latest = selectedElements[selectedElements.length-1].element
latest.object3D.add( anchor )
// also inherits rotation, could try cancel it as the opposite of latest rotation
// might be easier to copy the position only every few ms instead
anchor.position.sub( latest.object3D.position )
//targets.map( t => anchor.attach(t.object3D) )
// should attach all BUT the current moving entity!
Array.from(document.querySelectorAll('.mab')).map( t => anchor.attach(t.object3D) )
// they don't move... despite
} else {
clearInterval( clearRot )
Array.from(document.querySelectorAll('.mab')).map( t => scene.attach(t.object3D) )
//targets.map( t => scene.attach(t.object3D) )
// could delete anchor, cleaner
}
}
var attachToPlayer = false
function toggleAttachToSelf(){
attachToPlayer = !attachToPlayer
attachToPlayer ? parent=document.querySelector("#player") : parent=AFRAME.scenes[0]
targets.map( t => parent.object3D.attach(t.object3D) )
}
function checkIntersection(latest, nearby){
//let latest = selectedElements[selectedElements.length-1].element
//let nearby = getClosestTargetElements( latest.getAttribute('position') )
// https://threejs.org/docs/?q=box#api/en/math/Box3.containsBox
// https://threejs.org/docs/?q=box#api/en/math/Box3.expandByObject
let a = new THREE.Box3().expandByObject( latest.object3D ) // consider mesh.geometry.computeBoundingBox() first
let b = new THREE.Box3().expandByObject( nearby.object3D )
console.log(a,b, a.containsBox(b))
// testable as checkIntersection( document.querySelector("[color='yellow']"), document.querySelector("[color='purple']") )
// < a-box scale = ".1 .1 .1" position = ".5 .8 -.3" color = "purple" > < / a-box >
// < a-box scale = ".2 .2 .2" position = ".5 .8 -.3" color = "yellow" > < / a-box >
}
setTimeout( _ => {
let newPostIt = addNewNoteAsPostItNote("jxr console.log(222);", "0 1.2 -.5")
.setAttribute("onreleased", "grammarBasedSnap()")
let otherPostIt = addNewNoteAsPostItNote("jxr console.log(111);", "0 1.4 -.5")
.setAttribute("onreleased", "grammarBasedSnap()")
let postIt = addNewNoteAsPostItNote("hi this is a post-it note.", "0 1.6 -.5")
.setAttribute("onreleased", "runClosestJXR(); grammarBasedSnap()") // dunno how to share the event context back here...
// .setAttribute("onreleased", "snapNext()") // does NOT support multiple instances for now
// see https://aframe.io/docs/1.5.0/core/component.html#multiple
// maybe bind could help
//let cloneMe = addNewNote('jxr clone me from corner', '0 0 .1', '1 1 1', 'cmd')
// should rebind parent...
//setTimeout( _ => { _ => cloneMe.object3D.parent = postIt.object3D }, 1000 )
// should try object3D.attach() instead
//.addEventListener('loaded',
// entityIndexes( document.querySelector("[color='blue']").object3D.children[0] )
}, 1000 )
// e.g document.querySelector("[color='blue']").object3D.children[0]
function entityIndexes(mesh){ // needs a mesh with a geometry, not a group
// could also traverse
let gp = mesh.geometry.attributes.position;
let wPos = [];
for(let i = 0;i < gp.count ; i + + ) {
let p = new THREE.Vector3().fromBufferAttribute(gp, i); // set p from `position`
mesh.localToWorld(p); // p has wordl coords
wPos.push(p);
}
// many are duplicates, i.e a a cube will return 24 indexes (4 per 6 faces), not 8
//let l = [...new Set(wPos)].length; console.log( l )
[...new Set(wPos)].map( p => addNewNote("x", p))
console.log( [...new Set(wPos)].length )
// seems to add the duplicates again
// try to "de-dup" via .distanceTo() below a threshold instead
}
function snapToGrid(gridSize=1){ // default as 1 decimeter
let latest = selectedElements[selectedElements.length-1].element
latest.setAttribute("rotation", "0 0 0")
let pos = latest.getAttribute("position")
pos.multiplyScalar(gridSize*10).round().divideScalar(gridSize*10)
latest.setAttribute("position", pos )
}
// deeper question, making the rules themselves manipulable? JXR?
// So the result of the grammar becomes manipulable, but could you make the rules of the grammar itself visual? Even manipulable?
// could start by visualizing examples first e.g https://writer.com/wp-content/uploads/2024/03/grammar-1.webp
function snapMAB(){
// multibase arithmetic blocks aka MAB cf https://en.wikipedia.org/wiki/Base_ten_block
let latest = selectedElements[selectedElements.length-1].element
let nearby = getClosestTargetElements( latest.getAttribute('position') )
let linked = []
if (nearby.length>0){
latest.setAttribute("rotation", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("rotation") ) )
latest.setAttribute("position", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("position") ) )
latest.object3D.translateX( 1/10 )
linked.push( latest )
linked.push( nearby[0].el )
let overlap = Array.from( document.querySelectorAll(".mab") ).filter( e => e.object3D.position.distanceTo( latest.object3D.position ) < 0.01 & & e ! = latest )
while (overlap.length > 0 ){
latest.object3D.translateX( 1/10 )
linked.push( overlap[0] )
overlap = Array.from( document.querySelectorAll(".mab") ).filter( e => e.object3D.position.distanceTo( latest.object3D.position ) < 0.01 & & e ! = latest )
}
// do something special if it becomes 10, e.g become a single line, removing the "ridges"
if (linked.length > 3)
linked.map( e => Array.from( e.querySelectorAll("a-box") ).setAttribute("color", "orange") )
// also need to go backward too to see if it's the latest added
}
}
function snapRightOf(){
let latest = selectedElements[selectedElements.length-1].element
let nearby = getClosestTargetElements( latest.getAttribute('position') )
if (nearby.length>0){
latest.setAttribute("rotation", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("rotation") ) )
latest.setAttribute("position", AFRAME.utils.coordinates.stringify( nearby[0].el.getAttribute("position") ) )
latest.object3D.translateX( 1/10 )
// somehow... works only the 2nd time, not the 1st?!
}
}
function grammarBasedSnap(){
// verify if snappable, e.g of same type (or not)
// e.g check if both have .getAttribute('value').match(prefix) or not
let latest = selectedElements[selectedElements.length-1].element
let nearby = getClosestTargetElements( latest.getAttribute('position') )
if (nearby.length>0){
let closest = nearby[0].el
let latestTypeJXR = latest.getAttribute('value').match(prefix)
let closestTypeJXR = latest.getAttribute('value').match(prefix)
latest.setAttribute("rotation", AFRAME.utils.coordinates.stringify( closest.getAttribute("rotation") ) )
latest.setAttribute("position", AFRAME.utils.coordinates.stringify( closest.getAttribute("position") ) )
if ( latestTypeJXR & & closestTypeJXR )
latest.object3D.translateX( 1/10 ) // same JXR type, snap close
else
latest.object3D.translateX( 2/10 ) // different types, snap away
// somehow... works only the 2nd time, not the 1st?!
}
}
function cloneTarget(target){
let el = target.cloneNode(true)
if (!el.id)
el.id = "clone_" + crypto.randomUUID()
else
el.id += "_clone_" + crypto.randomUUID()
AFRAME.scenes[0].appendChild(el)
}
function deleteTarget(target){
targets = targets.filter( e => e != target)
target.remove()
}
function runClosestJXR(){
// ideally this would come from event details
let latest = selectedElements[selectedElements.length-1].element
let nearby = getClosestTargetElements( latest.getAttribute('position') )
// if (nearby.length>0){ interpretJXR( nearby[0].el.getAttribute("value") ) }
nearby.map( n => interpretJXR( n.el.getAttribute("value") ) )
}
function notesFromArray(data, generatorName="", field="title", offset=1, step=1/10, depth=-.5 ){
data.slice(0,maxItemsFromSources).map( (n,i) => {
addNewNote( n[field], "0 "+(offset+i*step)+" "+depth, ".1 .1 .1", null, generatorName )
.setAttribute("onreleased","spreadItemsFromCollection('getcsljson', 1.5)")
})
}
function spreadItemsFromCollection( generatorName, offset=1, step=1/10, depth=-.5 ){
getArrayFromClass(generatorName).sort((a,b)=>a.getAttribute('position').y-b.getAttribute('position').y).map( (n,i) => {
n.setAttribute('position', "0 "+(offset+i*step)+" "+depth)
n.setAttribute('rotation', "0 0 0") // could also be based on the average of all items, the first item, last one, etc
// see also snap-on-pinchended component
})
let items = getArrayFromClass(generatorName).sort((b,a)=>a.getAttribute('position').y-b.getAttribute('position').y).map( n => n.getAttribute('value') )
shareLiveEvent('modified list', items)
}
}
AFRAME.registerComponent('onemptypinch', { // changed from ondrop to be coherent with event name
AFRAME.registerComponent('onemptypinch', { // changed from ondrop to be coherent with event name
@ -255,39 +67,6 @@ AFRAME.registerComponent('onemptypinch', { // changed from ondrop to be coherent
}
}
})
})
function onHoveredTeleport(){
// iterate over targets
// see instead of teleportable https://aframe.io/docs/1.5.0/components/cursor.html#configuring-the-cursor-through-the-raycaster-component
Array.from( document.querySelectorAll("[teleporter]") ).map( target => {
if ( target.states.includes( "cursor-hovered" ) ){
target.setAttribute("material", "color", "magenta") // visited
document.getElementById('rig').setAttribute('position', target.getAttribute("position") )
}
})
}
AFRAME.registerComponent('teleporter', {
init: function(){
this.el.setAttribute("opacity", .5)
if (window.location.hash & & document.querySelector(window.location.hash+"[teleporter]"))
document.getElementById('rig').setAttribute('position', document.querySelector(window.location.hash+"[teleporter]").getAttribute("position") )
},
events: {
mouseenter: function (e) { this.el.setAttribute("opacity", .8) },
mouseleave: function (e) { this.el.setAttribute("opacity", .5) },
click: function (e) {
let posTarget = new THREE.Vector3()
this.el.object3D.getWorldPosition( posTarget )
console.log( posTarget)
document.getElementById('rig').setAttribute('position', posTarget)
// seems to work, maybe inteference with others teleporters activated unknowingly, e.g in succession
}
// this.el.getAttribute("position") ) }
// does not get proper world position
// makes it compatible with mouse on desktop ... but also somehow enable the wrist shortcut?!
}
});
AFRAME.registerComponent('scaffolding', {
AFRAME.registerComponent('scaffolding', {
init: function(){
init: function(){
console.log(this.el.innerHTML)
console.log(this.el.innerHTML)
@ -310,15 +89,6 @@ AFRAME.registerComponent('scaffolding', {
}
}
});
});
let page = "Wiki.VirtualRealityInterface";
let pageFromParam = AFRAME.utils.getUrlParameter('page')
if (pageFromParam) page = pageFromParam
setTimeout( _ => {
Array.from( document.querySelectorAll("[value='"+page+"']") ).map( n =>
n.setAttribute("onreleased", "console.log('dropped, should toggle display children,"+n.id+"')"));
Array.from( document.querySelectorAll("[value='"+page+"']>a-sphere") ).map( n => n.setAttribute("color", "purple"))
}, 5000)
< / script >
< / script >
< div style = 'position:fixed;z-index:1; top: 0%; left: 0%; border-bottom: 70px solid transparent; border-left: 70px solid #eee; ' >
< 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/" >
< a href = "https://git.benetou.fr/utopiah/text-code-xr-engine/issues/" >
@ -351,66 +121,21 @@ setTimeout( _ => {
< a-sky hide-on-enter-ar color = "lightgray" > < / a-sky >
< a-sky hide-on-enter-ar color = "lightgray" > < / a-sky >
< a-troika-text anchor = left target value = "instructions : \n--right pinch to move\n--left pinch to execute" position = "0 0.65 -0.2" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left target value = "instructions : \n--right pinch to move\n--left pinch to execute" position = "0 0.65 -0.2" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr onNextPrimaryPinch(deleteTarget)" target position = " -0.3 1.50 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr onNextPrimaryPinch(cloneTarget)" target position = " -0.3 1.60 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr location.reload()" target position = " -0.3 1.30 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr location.reload()" target position = " -0.3 1.30 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr makeAnchorsVisibleOnTargets()" target position = " -0.3 1.20 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "jxr makeAnchorsVisibleOnTargets()" target position = " -0.3 1.20 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-troika-text anchor = left value = "forceXaxis toggling"
< a-plane color = "black" opacity = ".3" position = "1.0095 0.81549 0.3473" rotation = "0 -40 0" material = "" geometry = "" scale = "1.37 1.73 1" > < / a-plane >
onreleased="console.log('run on released');forceXaxis=!forceXaxis"
onpicked="console.log('run on picked');forceXaxis=!forceXaxis"
target position=" -0.3 1.45 0" rotation="0 40 0" scale="0.1 0.1 0.1">< / a-troika-text >
< a-troika-text anchor = left value = "translate targets"
onreleased="toggleTranslateTargets()"
onpicked="toggleTranslateTargets()"
target position=" 1 1.45 -.2" rotation="0 -40 0" scale="0.1 0.1 0.1">< / a-troika-text >
< a-troika-text anchor = left value = "jxr setTimeout( _ => toggleAttachToSelf(), 1000); toggleAttachToSelf()" target position = " -0.3 1.25 0" rotation = "0 40 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
< a-box scale = ".07 .07 .07" class = "mab" target position = ".3 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".07 .07 .07" class = "mab" target position = ".1 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".07 .07 .07" class = "mab" target position = ".5 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".07 .07 .07" class = "mab" target position = "-.5 1.6 -.5" color = "brown" onreleased = "snapMAB()" >
< a-box scale = "1.5 1.5 1" color = "brown" > < / a-box >
< a-box scale = "1 1.5 1.5" color = "brown" > < / a-box >
< a-box scale = "1.5 1 1.5" color = "brown" > < / a-box >
< / a-box >
< a-box scale = ".1 .1 .1" target position = ".5 1.6 -.3" color = "blue" onreleased = "snapToGrid()"
annotation="content:could also show/hide grid with gridHelper on pinch started and hide on release"
>< / a-box >
< a-box scale = ".1 .1 .1" target position = ".5 1.8 -.3" color = "blue" onreleased = "snapToGrid()" > < / a-box >
< a-box scale = ".1 .1 .1" position = ".5 .8 -.3" color = "purple" > < / a-box >
< a-box scale = ".2 .2 .2" position = ".5 .8 -.3" color = "yellow" > < / a-box >
< a-box teleporter height = ".1" class = "teleportable" material = "color: cyan" position = "3.5 0 -3.5" > < / a-box >
< a-box teleporter height = ".1" class = "teleportable" material = "color: cyan" position = "-4 0 4" > < / a-box >
< a-box teleporter height = ".1" class = "teleportable" material = "color: cyan" position = "3 3 4" >
< a-troika-text anchor = left value = "jxr location.reload()" target position = "0 1.30 -.5" rotation = "0 0 0" scale = "0.1 0.1 0.1" > < / a-troika-text >
<!-- works to execute but not to move, should either reparent or take into account the parent offset while moving -->
<!-- see pinchmoved in primary pinch in jxr - core.js as potential solution -->
< / a-box >
< a-box teleporter height = ".1" class = "teleportable" material = "color: cyan" position = "0 0 0" > < / a-box >
< a-box id = "namedteleporter" teleporter height = ".1" class = "teleportable" material = "color: cyan" position = "2 0 2" > < / a-box >
< a-troika-text anchor = left value = "View-source for WebXR" target position = "0.5 1.55 0" rotation = "0 -40 0" scale = "0.3 0.3 0.3" > < / a-troika-text >
< a-troika-text anchor = left value = "Why immersive Web needs visible scaffolding" target position = "0.5 1.45 0" rotation = "0 -40 0" scale = "0.2 0.2 0.2" > < / a-troika-text >
< a-troika-text anchor = left line-height = "1" value = "Lorem ipsum dolor sit amet , consectetur adipiscing elit , sed do eiusmod tempor incididunt ut labore et dolore magna aliqua .
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " target position="0.5 1.25 0" rotation="0 -40 0" scale="0.1 0.1 0.1">< / a-troika-text >
< a-troika-text anchor = left value = "jxr addFromTemplate( '0 1 -1', '', scaffoldingInnerHTMLTemplate)" target position = "0.5 1.05 0" rotation = "0 -40 0" scale = "0.1 0.1 0.1" >
< a-triangle scale = ".5 .5 .5" position = "-.5 0 0" rotation = "0 0 30" > < / a-triangle >
< / a-troika-text >
< a-troika-text anchor = left line-height = "1" value = "Lorem ipsum dolor sit amet , consectetur adipiscing elit , sed do eiusmod tempor incididunt ut labore et dolore magna aliqua .
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " target position="0.5 .85 0" rotation="0 -40 0" scale="0.1 0.1 0.1">< / a-troika-text >
< a-box target teleporter height = ".1" depth = ".1" width = ".1" class = "teleportable" material = "color: red" position = "0 1 -.5" > < / a-box >
< / a-scene >
< / a-scene >
< / body >
< / body >
< / script >
< / script >