splitting with some explorations onreleased/onpinched

core-vs-extras
Fabien Benetou 8 months ago
parent a3042794db
commit ffc8494062
  1. 230
      index.html
  2. 42
      jxr-core.js
  3. 218
      jxr-extras.js
  4. 3772
      jxr.js

@ -7,12 +7,184 @@
<script src="dependencies/a-console.js"></script> <script src="dependencies/a-console.js"></script>
<script src='dependencies/aframe-troika-text.min.js'></script> <script src='dependencies/aframe-troika-text.min.js'></script>
<script src='dependencies/webdav.js'></script> <script src='dependencies/webdav.js'></script>
<script src='jxr-core.js?132345'></script> <script src='jxr-core.js?123'></script>
<script src='jxr-extras.js?123456'></script> <script src='jxr-postitnote.js?13235'></script>
</head> </head>
<body> <body>
<script> <script>
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 ){ function notesFromArray(data, generatorName="", field="title", offset=1, step=1/10, depth=-.5 ){
data.slice(0,maxItemsFromSources).map( (n,i) => { data.slice(0,maxItemsFromSources).map( (n,i) => {
@ -31,6 +203,16 @@ function spreadItemsFromCollection( generatorName, offset=1, step=1/10, depth=-.
shareLiveEvent('modified list', items) shareLiveEvent('modified list', items)
} }
let page = "Wiki.VirtualRealityInterface";
// should do then only once graph loaded instead, should emit event
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/">
@ -57,9 +239,53 @@ function spreadItemsFromCollection( generatorName, offset=1, step=1/10, depth=-.
<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="forceXaxis toggling"
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-console position="2 2 0" rotation="0 -45 0" font-size="34" height=1 skip-intro=true></a-console> <a-console position="2 2 0" rotation="0 -45 0" font-size="34" height=1 skip-intro=true></a-console>
<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-scene> </a-scene>
</body> </body>
</script> </script>

@ -38,7 +38,7 @@ AFRAME.registerComponent('target', {
function getClosestTargetElements( pos, threshold=0.05 ){ function getClosestTargetElements( pos, threshold=0.05 ){
// TODO Bbox intersects rather than position // TODO Bbox intersects rather than position
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 && t.dist > 0 )
.sort( (a,b) => a.dist > b.dist) .sort( (a,b) => a.dist > b.dist)
} }
@ -161,8 +161,7 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
} }
if (selectedElement){ if (selectedElement){
let content = selectedElement.getAttribute("value") let content = selectedElement.getAttribute("value")
selectedElements.push({element:selectedElement, timestamp:Date.now(), primary:true}) selectedElement.emit('released', {element:selectedElement, timestamp:Date.now(), primary:true})
selectedElement.emit('released')
} }
// unselect current target if any // unselect current target if any
selectedElement = null; selectedElement = null;
@ -211,7 +210,10 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
//selectedElement = clone //selectedElement = clone
selectedElement = getClosestTargetElement( event.detail.position ) selectedElement = getClosestTargetElement( event.detail.position )
if (selectedElement) selectedElement.emit("picked") if (selectedElement) {
selectedElements.push({element:selectedElement, timestamp:Date.now(), primary:true})
selectedElement.emit("picked")
}
// is it truly world position? See https://github.com/aframevr/aframe/issues/5182 // is it truly world position? See https://github.com/aframevr/aframe/issues/5182
// setFeedbackHUD( AFRAME.utils.coordinates.stringify( event.detail.position ) ) // setFeedbackHUD( AFRAME.utils.coordinates.stringify( event.detail.position ) )
// 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
@ -224,11 +226,28 @@ AFRAME.registerComponent('pinchprimary', { // currently only 1 hand, the right o
// avoiding setOnDropFromAttribute() as it is not idiosyncratic and creates timing issues // avoiding setOnDropFromAttribute() as it is not idiosyncratic and creates timing issues
AFRAME.registerComponent('onreleased', { // changed from ondrop to be coherent with event name AFRAME.registerComponent('onreleased', { // changed from ondrop to be coherent with event name
// could support multi
events: { events: {
released: function (e) { released: function (e) {
let code = this.el.getAttribute('onreleased') let code = this.el.getAttribute('onreleased')
// if multi, should also look for onreleased__ not just onreleased
try {
eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt)
} catch (error) {
console.error(`Evaluation failed with ${error}`);
}
}
}
})
AFRAME.registerComponent('onpicked', {
// could support multi
events: {
picked: function (e) {
let code = this.el.getAttribute('onpicked')
// if multi, should also look for onreleased__ not just onreleased
try { try {
eval( code ) eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt)
} catch (error) { } catch (error) {
console.error(`Evaluation failed with ${error}`); console.error(`Evaluation failed with ${error}`);
} }
@ -236,6 +255,19 @@ AFRAME.registerComponent('onreleased', { // changed from ondrop to be coherent w
} }
}) })
function onNextPrimaryPinch(callback){
// could add an optional filter, e.g only on specific ID or class
// e.g function onNextPrimaryPinch(callback, filteringSelector){}
let lastPrimary = selectedElements.filter( e => e.primary ).length
let checkForNewPinches = setInterval( _ => {
if (selectedElements.filter( e => e.primary ).length > lastPrimary){
let latest = selectedElements[selectedElements.length-1].element
if (latest) callback(latest)
clearInterval(checkForNewPinches)
}
}, 50) // relatively cheap check, filtering on small array
}
function distanceLastTwoPinches(){ function distanceLastTwoPinches(){
let dist = null let dist = null
if (pinches.length>1){ if (pinches.length>1){

@ -1,3 +1,221 @@
console.log('jxr extras to gradually add, by default could refer to branches instead then add as components proper') console.log('jxr extras to gradually add, by default could refer to branches instead then add as components proper')
console.log('arguably utils and additional interactions beyond pinche could be extras rather than core') console.log('arguably utils and additional interactions beyond pinche could be extras rather than core')
console.log('extra could also be empty...') console.log('extra could also be empty...')
// from https://glitch.com/edit/#!/zen-pim?path=graph-cyto-headless.html
// see also https://observablehq.com/@utopiah/d3-pim-graph
// motivated by https://these.arthurperret.fr/chapitre-4.html#cosmographe-et-cosmoscope
const cytoJson = "https://vatelier.benetou.fr/MyDemo/newtooling/wiki_graph_cyto.json"
var cy
var jsonLoaded
fetch(cytoJson)
.then(function (response) {
return response.json();
})
.then(function (json) {
// take nodes from wiki.Nodes then construct an array in elements for cytoscape format
// or directly from JSON http://js.cytoscape.org/#notation/elements-json
jsonLoaded = json
startCytoWithData(json)
})
.catch(function (err) {
console.log(err);
});
// should instead directly use https://vatelier.net/MyDemo/newtooling/wiki_graph_cyto.json
// generated from https://vatelier.net/MyDemo/newtooling/build_graph_cytograph.js
// cf instead http://js.cytoscape.org/demos/tokyo-railways/tokyo-railways.json more detail
// with details on how to load the JSON
function runCytoAnalysis(){
console.log('----------------- network analysis started --------------')
if (!cy.nodes('[id = "Analysis.Analysis"]').length) {
console.warn('Wrong dataset, cancelling')
return
}
// example of queries
var aStar = cy.elements().aStar({ root: '[id = "Analysis.Analysis"]', goal: '[id = "Analysis.CostsAndBenefitsOfSocietalMembership"]' })
if ( aStar.path){
console.log("Path from Analysis.Analysis to Analysis.CostsAndBenefitsOfSocietalMembership", aStar.path.edges() , aStar.path.nodes() )
aStar.path.select()
}
console.log( cy.nodes('[id = "Analysis.Analysis"]').neighborhood() )
before = Date.now()
var bc = cy.elements().bc()
after = Date.now()
console.log("took ", after-before, "ms to run.")
console.log( 'bc of j: ' + bc.betweenness('[id = "Analysis.Analysis"]') )
console.log( cy.nodes('[id = "Analysis.CostsAndBenefitsOfSocietalMembership"]').neighborhood() )
//no need to run cy.elements().bc() again. It's done once for the whole graph.
console.log( 'bc of j: ' + bc.betweenness('[id = "Analysis.CostsAndBenefitsOfSocietalMembership"]') )
console.log('----------------- network analysis done -----------------')
}
function startCytoWithData(json){
cy = cytoscape({
headless:true,
elements: json.elements
})
//runCytoAnalysis() //quite demanding, skipped for now.
let defaults = {
name: 'euler',
springLength: edge => 80,
springCoeff: edge => 0.0008,
mass: node => 4,
gravity: -1.2,
pull: 0.001,
theta: 0.666,
dragCoeff: 0.02,
movementThreshold: 1,
timeStep: 20,
refresh: 10,
animate: true,
animationDuration: undefined,
animationEasing: undefined,
maxIterations: 1000,
maxSimulationTime: 4000,
ungrabifyWhileSimulating: false,
fit: true,
padding: 30,
// Constrain layout bounds with one of
// - { x1, y1, x2, y2 }
// - { x1, y1, w, h }
// - undefined / null : Unconstrained
boundingBox: undefined,
// Layout event callbacks; equivalent to `layout.one('layoutready', callback)` for example
ready: function(){ console.log("graph ready", cy.json()) }, // on layoutready
stop: stableGraph(), // on layoutstop
randomize: false
};
// disabled for tests
//cy.layout( defaults ).run(); // too demanding for the entire graph, should limit to a subset
}
function stableGraph(){
var exportableJSON = cy.json()
console.log( 'exportable JSON', exportableJSON );
// not actually stable!
// still could be used as a form of caching BUT... would take into account new nodes added since
var node = "ReadingNotes.ApocalypticAI"
if (!cy.nodes('[id = "'+node+'"]').length) {
console.warn('Wrong dataset, cancelling')
return
}
console.log('should add/update AFrame nodes e.g', cy.elements('[id = "'+node+'"]').position())
console.log('could add all the nodes then their links with proper attributes in order to do select() after')
// using e.g. cy.nodes().forEach( n => console.log(n.data(), n.position() ) )
// cy.edges().forEach( e => console.log(e.data(), e.sourceEndpoint(), e.targetEndpoint() ))
// warning, very costly!
// run on entire wiki though whereas previous D3 instance limited to 10 pages and their targets
}
let edges_to_display = []
function displayLeafs(graph, graphEl, rootId, rootEl, depth=3){
console.log( graph[rootId].Id )
if (depth<1) return
graph[rootId].Targets.map( l => {
console.log( "-", graph[l].Id )
let x = addNodeFromGraph(graph[l].Id, "" + (Math.random()*2) + " " + (Math.random()*2) + " -" + (Math.random()*2) )
// layout could be done with Cytoscape but somehow seems I can't use it properly, in headless mode or not.
x.setAttribute('update-links-on-pinchended', true)
x.setAttribute('toggle-links-on-left-pinchended', true)
console.log("linking:", rootEl, x)
edges_to_display.push({graphel:graphEl, source:rootEl, target:x})
displayLeafs( graph, graphEl, graph[l].Id, x, --depth)
})
}
AFRAME.registerComponent('toggle-links-on-left-pinchended', {
init: function(){
let graphEl = document.querySelector("#graphroot")
let el = this.el
this.el.addEventListener('lreleased', function (event) {
// if it has children (...how knowing that we are not using the hierarchy?)
// delete them, including links (should be recursive too)
// if not, displayLeafs( mynodes, graphEl, root.Id, rootEl, 1)
// assumes not for now
displayLeafs( wikiStructure, graphEl, el.getAttribute("value"), el, 1)
setTimeout( _ => { edges_to_display.map( i => addEdgeBetweenNodesFromGraph( i.graphel, i.source, i.target ))}, 2000 )
})
}
})
AFRAME.registerComponent('update-links-on-pinchended', {
init: function(){
let rootEl = document.querySelector("#graphroot")
let el = this.el
this.el.addEventListener('released', function (event) {
Object.keys( rootEl.components ) // get all links
.filter( i => i.indexOf(el.id) > -1 ) // keeps links related to the moved node
.map( i => { // for each link
let newpos = AFRAME.utils.coordinates.stringify( el.getAttribute("position") )
let [src,tgt] = i.replace("line__","").split("__to__");
srcpos = AFRAME.utils.coordinates.stringify( rootEl.getAttribute(i).start )
tgtpos = AFRAME.utils.coordinates.stringify( rootEl.getAttribute(i).end )
if (src==el.id){
rootEl.setAttribute(i, { start: newpos, end: tgtpos })
} else {
rootEl.setAttribute(i, { start: srcpos, end: newpos })
}
})
})
}
})
let nodes = []
let edges = []
let wikiStructure = null
fetch('https://vatelier.benetou.fr/MyDemo/newtooling/wiki_graph.json').then( r => r.json() ).then( r => displayGraph(r.Nodes) );
// alternatively there is the issue component that could also display linked issues
function displayGraph(mynodes){
wikiStructure = mynodes
let startingNode = "Wiki.VirtualRealityInterface"
let root = mynodes[startingNode]
let graphEl = document.createElement("a-entity")
graphEl.id = "graphroot"
AFRAME.scenes[0].appendChild( graphEl )
let node_names = Object.keys( mynodes )
let rootEl = addNodeFromGraph(root.Id, "" + (Math.random()*2-1) + " " + (Math.random()+1) + " -" + (Math.random()*2-0.5) )
rootEl.setAttribute('update-links-on-pinchended', true)
displayLeafs( mynodes, graphEl, root.Id, rootEl, 2)
setTimeout( _ => { edges_to_display.map( i => addEdgeBetweenNodesFromGraph( i.graphel, i.source, i.target ))}, 2000 )
// quite unreliable
// should listen to an event instead to insure that nodes are all created before
}
function addNodeFromGraph(name, position="0 0 0"){
// add sphere, with its name, make it a target
// define what "it" is knowing we can't move a children with an offset
// consequently parenting should be done by the text
let el = addNewNote(name, position, ".1 .1 .1", "node_" +crypto.randomUUID(), "node_from_graph")
let sphereEl = document.createElement("a-sphere")
sphereEl.setAttribute("radius", .1)
sphereEl.setAttribute("segments-height", 4)
sphereEl.setAttribute("segments-width", 4)
sphereEl.setAttribute("wireframe", true)
sphereEl.setAttribute("position", "0 -.1 0")
el.appendChild(sphereEl)
// position shouldn't have to be offset
return el
}
function addEdgeBetweenNodesFromGraph(graphEl, a, b){
// a.setAttribute( "line-link-entities", {source: a.id, target: b.id} ) doesn't seem work, back to basics for now
graphEl.setAttribute("line__"+a.id+"__to__"+b.id, {
start: AFRAME.utils.coordinates.stringify( a.getAttribute("position") ) ,
end: AFRAME.utils.coordinates.stringify( b.getAttribute("position") )
})
// not that this doesn't take into account the parent node moving
}

3772
jxr.js

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save