diff --git a/index.html b/index.html index ec859df..890979a 100644 --- a/index.html +++ b/index.html @@ -52,17 +52,226 @@ function listBoundGestures(){ // draw a line between a selector and its instancing // e.g between "#rightHand" and actually the element with this id + +var tips = { + left : {}, + right : {}, +} + +AFRAME.registerComponent('save-on-exit-xr', { + init: function () { + var sceneEl = this.el; + sceneEl.addEventListener('exit-vr', _ => { + saveBoard() // only when exiting VR proper, NOT "multitasking" so added onreleased to enable that workflow too + showBoardFromHash() + }) + } +}); + +function saveBoard(){ + let positions = [] // ordered is preserved as the elements are created in order too (bit risky) + getArrayFromClass("pannels").map( p => { + let v = p.getAttribute("position") + + let o = new THREE.Vector3() + o.x = v.x.toFixed(3) + o.y = v.y.toFixed(3) + o.z = v.z.toFixed(3) + + positions.push( o ) + }) + + let data = {} + data.positions = positions + + window.location.hash = JSON.stringify(data) + // generate URL and append the optional forced 2D overlay parameter + // warning : a URI has a maximum length which might be too short due to text content + // we can rely on the index position of the array ASSUMING that the .json file does not change between both moments +} + +//const billBoardSourceURL = "../content/nlnetproposal.json" +const billBoardSourceURL = "../content/snippets.json" + +function showBoardFromHash(){ // the flattening... not very useful for now, ideally could be brought to a better canvas + let positions = JSON.parse( decodeURI( window.location.hash.substring(1) ) ).positions + + //could already do a preview + let canvas2DpreviewEl = document.createElement('div') + document.body.appendChild( canvas2DpreviewEl ) + canvas2DpreviewEl.style = "zIndex:99; position:absolute; top:0; left:0; width:90%; height:90%; background-color:black;" + + // done again, should be cached instead + fetch(billBoardSourceURL).then( r => r.json() ).then( json => { + let count = json.length // can get too high, thus unreachable, sticking to 1m total + json.map( (l,i) => { + let content = l + // trim very long texts (should evoque the memory of instead, other cluttering) + if (count > 500) content = l.substring(0,500) + "\n..." + let boardEl = document.createElement('span') + boardEl.innerText = content + boardEl.draggable = true + let pos = positions[i] + let x = 500 + pos.x * 1000 + let y = pos.y * 500 - 200 + let z = Math.round( pos.z*100 + 999 ) + boardEl.style = "z-index:"+z+"; position:absolute; top:"+y+"px; left:"+x+"px; color:white; width: 1000px;" + boardEl.className = "boardElement2D" + canvas2DpreviewEl.appendChild( boardEl ) + dragElement( boardEl ) + }) + + // add 2D button on corner to copy data to clipboard + let boardButtonData = document.createElement( "input" ) + boardButtonData.id = "boardbuttondata" + let content = [] + getArrayFromClass("boardElement2D").map( p => content.push( { + zIndex: p.style.zIndex, + x: p.style.left, + y: p.style.top + }) + ) + boardButtonData.style = "z-index:999; position:absolute; top:0px; left:0px; color:black; width: 100px;" + boardButtonData.value = JSON.stringify( content ) + canvas2DpreviewEl.appendChild( boardButtonData ) + }) +} + +function updateBoardButtonData(){ + let boardButtonData = document.getElementById("boardbuttondata") + let content = [] + getArrayFromClass("boardElement2D").map( p => content.push( { + zIndex: p.style.zIndex, + x: p.style.left, + y: p.style.top + }) + ) + boardButtonData.value = JSON.stringify( content ) +} + +function dragElement(elmnt) { // from https://www.w3schools.com/howto/howto_js_draggable.asp + var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; + elmnt.onmousedown = dragMouseDown; + + function dragMouseDown(e) { + e = e || window.event; + e.preventDefault(); + // get the mouse cursor position at startup: + pos3 = e.clientX; + pos4 = e.clientY; + document.onmouseup = closeDragElement; + // call a function whenever the cursor moves: + document.onmousemove = elementDrag; + } + + function elementDrag(e) { + e = e || window.event; + e.preventDefault(); + // calculate the new cursor position: + pos1 = pos3 - e.clientX; + pos2 = pos4 - e.clientY; + pos3 = e.clientX; + pos4 = e.clientY; + // set the element's new position: + elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; + elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; + } + + function closeDragElement() { + // stop moving when mouse button is released: + document.onmouseup = null; + document.onmousemove = null; + // note that this should probably save back to the hash too, otherwise no persistance. + // could also consider saving to JSON or whatever useful format (probably https://jsoncanvas.org ) + updateBoardButtonData() + } +} // done + +AFRAME.registerComponent('billboard-content', { + init: function(){ + let positions = false + if (window.location.hash) + positions = JSON.parse( decodeURI( window.location.hash.substring(1) ) ).positions + // assume JSON is correct... + + let boardEl = document.createElement("a-box") + boardEl.setAttribute("width", 2) + boardEl.setAttribute("depth", .1) + boardEl.setAttribute("height", 1) + boardEl.setAttribute("color", "black") + boardEl.setAttribute("position", "0 1.5 -1") + AFRAME.scenes[0].appendChild( boardEl ) + // bringing content in + fetch(billBoardSourceURL).then( r => r.json() ).then( json => { + let count = json.length // can get too high, thus unreachable, sticking to 1m total + json.map( (l,i) => { + let content = l + // trim very long texts (should evoque the memory of instead, other cluttering) + if (count > 500) content = l.substring(0,500) + "\n..." + // could filter out or highlight when l begins with "TODO:" + let noteEl + if (positions) + noteEl = addNewNote(content, positions[i], scale=".1 .1 .1", null, "pannels") // should add a class for 2D export + else + noteEl = addNewNote(content, "-0.5 "+ (i/count+0.7)+" "+(-0.5-Math.random()), scale=".1 .1 .1", null, "pannels") // should add a class for 2D export + noteEl.setAttribute("max-width", 10) + noteEl.setAttribute("jsonID", i) + noteEl.setAttribute("onreleased", "saveBoard()") + }) + billboarding = true + makeAnchorsVisibleOnTargets() + }) + }, +}) + AFRAME.registerComponent('selector-line', { init: function(){ this.newLine = document.createElement("a-entity") this.newLine.setAttribute("line", "start: 0, 0, 0; end: 0 0 0.01; color: red") AFRAME.scenes[0].appendChild( this.newLine ) this.worldPosition=new THREE.Vector3() + this.hr=new THREE.Vector3() + this.hl=new THREE.Vector3() + + document.querySelector('a-scene').addEventListener('enter-vr', _ => { + // seems there is a slight delay to get children for hands and no hasLoaded event to catch + setTimeout( _ => { + ["left", "right"].map( (side, i) => + [ "index-finger", "middle-finger", "ring-finger", "pinky-finger", "thumb"].map( (part, j) => { + document.querySelector('[hand-tracking-controls="hand: '+side+';"]').object3D.traverse( e => { if (e.name == part+"-tip") tips[side][part] = e }) + addNewNote("#"+side+"_"+part+"_tip", "-1 "+ (j/10+1)+" "+(-1+i/10)) // for surfacing affordances as selectors + }) + ) + }, 500 + ) + // risky bet, maybe hand are occluded when enter VR + } ) + // might also want to "remove" them on leaving VR (works for AR and VR) }, tick: function(){ + let dist = 0 + if (tips.left.thumb) { // assuming we are getting both hands which is not necessarily true + let proximityPairs = [ + { pair : [ tips.left.thumb, tips.right.thumb ], msg : 'thumbs touching-ish', eventName: "touchingThumbs"}, + { pair : [ tips.left["pinky-finger"], tips.right["pinky-finger"] ], msg : 'pinkies touching-ish', eventName: "touchingPinkies" }, + { pair : [ tips.right["pinky-finger"], tips.right["thumb"] ], msg : 'right pinky to thumb touching-ish', eventName: "rightPinkyToThumbTouching" }, + // for the gesture manager those pairs and msg/events directly manipulable as selectors (cf this overall component) + ] + + proximityPairs.map( rule => { + dist = 0 + dist = rule.pair[0].position.distanceTo(rule.pair[1].position) + let threshold = .04 // tricky threshold, assuming with better camera and CV it will improve over time + if ( dist > 0 && dist < threshold) { + console.log( rule.msg ) + AFRAME.scenes[0].emit( rule.eventName ) + } + }) + } + // note that is about RELATIVE position, not absolute position! + // unfortunately here it does not work as it's not the entity itself for the hand that has a position... (cf NAF discussions) let worldPosition = this.worldPosition - document.querySelector( this.el.getAttribute("value") ).object3D.traverse( e => { if (e.name == "wrist") { worldPosition.copy(e.position);e.parent.updateMatrixWorld();e.parent.localToWorld(worldPosition) } @@ -79,7 +288,7 @@ AFRAME.registerComponent('selector-line', { }) - + @@ -100,14 +309,16 @@ AFRAME.registerComponent('selector-line', { - + + + + + - - - + + - - +