demos relying on bounding boxes on text

This commit is contained in:
Utopiah
2025-09-18 13:30:11 +02:00
parent 8ea968953e
commit e7e42ac39a

View File

@@ -1423,10 +1423,323 @@ setTimeout( _ => {
// spatial scoping
// could show what is affected, and not, on the boundaries
// bounded valume
// internal and external link following
// internal e.g. ToC
// external e.g. supported sources
// ACM OA with PDF
// arXiv
// PmWiki
// bounded volume
// see saved RSS filter as example
// consider as showFile() parameter
// could blend q3_obbox_pinch (where content collide each other) and q3_bbox_pinch (on where to pinch)
// could be easier to add finger tips to obb-collider
// e.g AFRAME.scenes[0].setAttribute("bind-element-to-finger__collider", { hand: 'r_handMeshNode', finger: 'index-finger-tip', target: '#fingertipcollider' } )
// then fingertipcollider as a 2cm2 box with el.setAttribute("obb-collider", true)
// could then listen on that new element for events
// el.addEventListener('obbcollisionstarted', evt => {
// el.addEventListener('obbcollisionended', evt => {
// keyboard move could be done by pinching anywhere within the volume
// AFRAME.scenes[0].addEventListener('emptypinch', evt => { ...containsPoint(evt.detail.position)
// try q3_obbox_grouping with q2_json_collaborations
// group elements from loaded files within cubes
// should be able to show/hide within a cube
// scale down?
// cube in cube, first cube move other cube within it
if (username && username == "q3_alt_webdav") {
let webdavClient = window.WebDAV.createClient( "https://192.168.0.206:8080/dav/",
{ username: "fabien", password: "fabien" })
// CORS failed, PROFIND and other problems, might want to check nginx configuration
// Get directory contents
console.log( webdavClient.getDirectoryContents("/") )
}
if (username && username == "q3_obbox_grouping") {
const debugBoxClass = "debugbox"
window.newParent = function(el){
if (!el.futureParent) return
let parentPos = el.futureParent.getAttribute("position").clone()
el.getAttribute("position").sub(parentPos)
// takes the rotiation of the parent too... can be surprising
el.object3D.parent = el.futureParent.object3D
el.futureParent = null
return el
}
setTimeout( _ => {
let snippetA = addNewNote("text nsippet A", "-.5 1 -.7")
snippetA.id = username + '_snippedA'
let snippetB = addNewNote("text snippet B", "0 1.5 -.7")
let snippetC = addNewNote("snippet C", ".5 1 -.7")
let elements = [snippetA, snippetB, snippetC]
let groupBoxEl = document.createElement("a-box")
groupBoxEl.id = username + '_groupEl'
groupBoxEl.setAttribute("position", "0 1.3 -1")
groupBoxEl.setAttribute("width", .3)
groupBoxEl.setAttribute("height", .3)
groupBoxEl.setAttribute("depth", .3)
groupBoxEl.setAttribute("wireframe", true )
groupBoxEl.setAttribute("color", "pink" )
groupBoxEl.setAttribute("target", "" )
// should also in turn move all associated elements
// could be onpicked/onreleased a la keyboard moving
groupBoxEl.setAttribute("obb-collider", true)
AFRAME.scenes[0].appendChild(groupBoxEl)
let markedElements = []
// for testing only
//setTimeout( _ => q3_obbox_grouping_snippedA.setAttribute("position", q3_obbox_grouping_groupEl.getAttribute("position") ) , 1000)
//setTimeout( _ => q3_obbox_grouping_snippedA.setAttribute("position", "-.1 1.4 -1" ) , 1000)
// works in 3D but not in XR... probably has to happen AFTER being released, not while entering
setTimeout( _ => {
elements.map( el => {
let bbox = new THREE.Box3().setFromObject(el.object3D);
bbox.min.z -= .01
bbox.max.z += .01
// expand a bit forward and backward...
let boxEl = document.createElement("a-box")
boxEl.setAttribute("width", bbox.max.x-bbox.min.x )
boxEl.setAttribute("height", bbox.max.y-bbox.min.y )
boxEl.setAttribute("depth", bbox.max.z-bbox.min.z )
boxEl.setAttribute("position", (bbox.max.x-bbox.min.x)/2*10 + " 0 0" ) // parent has 1/10 scale
boxEl.setAttribute("visible", false )
boxEl.setAttribute("opacity", .3 )
boxEl.setAttribute("scale", "10 10 10" ) // parent has 1/10 scale
boxEl.classList.add(debugBoxClass)
el.appendChild(boxEl)
boxEl.setAttribute("obb-collider", true)
el.addEventListener('obbcollisionstarted', evt => {
console.log( 'obbcollisionstarted', evt.detail ) // specifically evt.detail.withEl
//evt.detail.withEl.parentEl.setAttribute("color", "pink")
// might be the parent instead as here we attached the collider to the box instead
//evt.detail.withEl.setAttribute("color", el.getAttribute("color"))
el.setAttribute("color", evt.detail.withEl.getAttribute("color") )
markedElements.push( el )
// become child of groupBoxEl only after being released
el.futureParent = evt.detail.withEl
el.setAttribute("onreleased", "newParent( selectedElements.at(-1).element )")
el.setAttribute("onpicked", "selectedElements.at(-1).element.object3D.parent = AFRAME.scenes[0].object3D")
// TODO have to leave before being able to parent back...
})
el.addEventListener('obbcollisionended', evt => {
console.log( 'obbcollisionended', evt.detail )
//evt.detail.withEl.parentEl.setAttribute("color", "white")
el.setAttribute("color", "white")
markedElements = markedElements.filter( t => t != el )
el.futureParent = null
})
} )
}, 900 )
//AFRAME.scenes[0].setAttribute('obb-collider','showColliders:true')
}, 500 )
}
if (username && username == "q3_obbox_pinch") {
const debugBoxClass = "debugbox"
setTimeout( _ => {
let snippetA = addNewNote("text nsippet A", "-.5 1 -.7")
let snippetB = addNewNote("text snippet B", "0 1.5 -.7")
let snippetC = addNewNote("snippet C", ".5 1 -.7")
let elements = [snippetA, snippetB, snippetC]
setTimeout( _ => {
elements.map( el => {
let bbox = new THREE.Box3().setFromObject(el.object3D);
bbox.min.z -= .01
bbox.max.z += .01
// expand a bit forward and backward...
let boxEl = document.createElement("a-box")
boxEl.setAttribute("width", bbox.max.x-bbox.min.x )
boxEl.setAttribute("height", bbox.max.y-bbox.min.y )
boxEl.setAttribute("depth", bbox.max.z-bbox.min.z )
boxEl.setAttribute("position", (bbox.max.x-bbox.min.x)/2*10 + " 0 0" ) // parent has 1/10 scale
//boxEl.setAttribute("wireframe", true )
boxEl.setAttribute("visible", false )
boxEl.setAttribute("opacity", .3 )
boxEl.setAttribute("scale", "10 10 10" ) // parent has 1/10 scale
boxEl.classList.add(debugBoxClass)
el.appendChild(boxEl)
boxEl.setAttribute("obb-collider", true)
el.addEventListener('obbcollisionstarted', evt => {
console.log( evt.detail ) // specifically evt.detail.withEl
evt.detail.withEl.parentEl.setAttribute("color", "pink")
// might be the parent instead as here we attached the collider to the box instead
})
el.addEventListener('obbcollisionended', evt => {
console.log( evt.detail )
evt.detail.withEl.parentEl.setAttribute("color", "white")
})
} )
}, 900 )
//AFRAME.scenes[0].setAttribute('obb-collider','showColliders:true')
let el = addNewNote('jxr applyToClass("debugbox", k => k.setAttribute("visible",!k.getAttribute("visible")))')
el.setAttribute("annotation", "content:toggle debug box visibility")
el.setAttribute("rotation", "90 0 0")
jxrhighlight()
}, 500 )
}
if (username && username == "q3_bbox_pinch") {
setTimeout( _ => {
let snippetA = addNewNote("text nsippet A", "-.5 1 -.7")
let snippetB = addNewNote("text snippet B", "0 1.5 -.7")
let snippetC = addNewNote("snippet C", ".5 1 -.7")
let elements = [snippetA, snippetB, snippetC]
let bboxes = []
setTimeout( _ => {
elements.map( el => {
el.removeAttribute("target") // does nothing
targets = targets.filter( t => t != el ) // should update jxr-core accordingly, will do so after HT25
let bbox = new THREE.Box3().setFromObject(el.object3D);
// see instead https://threejs.org/docs/index.html?q=obb#examples/en/math/OBB
// or higher level https://aframe.io/docs/1.7.0/components/obb-collider.html#main
// probably does not work well with a-troika-text or adjusting BB depth
// could try adding a box to it manually after loading then add obb-collider on that invisible box
bbox.min.z -= .01
bbox.max.z += .01
// expand a bit forward and backward...
bboxes.push( {bbox:bbox, element:el })
const helper = new THREE.Box3Helper( bbox, 0xd3e3e5 )
AFRAME.scenes[0].object3D.add( helper )
} )
}, 900 )
AFRAME.scenes[0].addEventListener('emptypinch', evt => {
console.log( evt.detail )
bboxes.filter( b => b.bbox.containsPoint(evt.detail.position) ).map( b => {
b.element.setAttribute("color", "pink")
})
});
}, 500 )
}
// see https://git.benetou.fr/utopiah/spasca-fot-sloan-q3/issues/4
if (username && username == "q3_media_widget") {
// consider
// visible background as affordance to move around (a la brown rootEl cube in PDF unpacked)
// seek via
// onpicked : seek based on distance to initial position, showing current time and total time)
// distance 0 = seek 0, 1m (probably way too far for most people but easier to test) = 100%
// should be canceable too... so maybe under threshold (e.g. <.05m) do not even seek at all?
// onreleased : reset command position to initial position then play()
const testFileName = "Kaijuu.mp4"
setTimeout( _ => {
manuscript.setAttribute("visible", false)
let videoEl = document.createElement("a-video")
videoEl.setAttribute("width", 2 )
//videoEl.setAttribute("src", testFileName)
videoEl.setAttribute("src", "#videosrc")
videoEl.setAttribute("position", "0 1.5 -1")
AFRAME.scenes[0].appendChild(videoEl)
let el = document.createElement("a-assets")
AFRAME.scenes[0].appendChild(el)
let elAsset = document.createElement("video")
elAsset.id="videosrc"
elAsset.src=testFileName
el.appendChild(elAsset)
let widgetPlayEl = addNewNote("jxr document.getElementById('" + elAsset.id + "').play()", "0 2 -1")
// does not work, might have to rely on component instead or play the src target
widgetPlayEl.setAttribute("annotation", "content:play video")
widgetPlayEl.setAttribute("rotation", "90 0 0")
let widgetPauseEl = addNewNote("jxr document.getElementById('" + elAsset.id + "').pause()", "-0.5 2 -1")
widgetPauseEl.setAttribute("annotation", "content:pause video")
widgetPauseEl.setAttribute("rotation", "90 0 0")
let widgetSeekEl = addNewNote("jxr document.getElementById('" + elAsset.id + "')", "-1 2 -1") // different way to interaction than play/pause
widgetSeekEl.setAttribute("onpicked", "window.seeking=true")
widgetSeekEl.setAttribute("onreleased", "window.seeking=false;let el=selectedElements.at(-1).element; el.setAttribute('position','-1 2 -1'); el.setAttribute('rotation','90 0 0')")
widgetSeekEl.setAttribute("annotation", "content:seek video")
widgetSeekEl.setAttribute("rotation", "90 0 0")
// could be positioned below the media, typically where the timeline is
jxrhighlight()
}, 800) // should delay until ready
window.seeking = false
setInterval( _ => {
if (!window.seeking) return
let elAsset = document.getElementById("videosrc")
let pos = new THREE.Vector3().copy({x:-1, y:2, z:-1})
let dist = selectedElements.at(-1).element.getAttribute('position').distanceTo(pos)
console.log( elAsset.duration , dist, elAsset.duration / (dist/10) )
// elAsset.fastSeek( elAsset.duration / (dist/10) ) // get .duration but not get fastSeek(), Chromium limitation
elAsset.currentTime = elAsset.duration / dist
}, 100 )
}
// see https://git.benetou.fr/utopiah/spasca-fot-sloan-q3/issues/3
if (username && username == "q3_pdf_hidden_text") {
// use pageAsTextViaXML() from filters/pdf_unpacked.xml.js but do not add new note
const testFileName = "3648188.3675128"
const testFileNamePDF = testFileName+ ".pdf"
const testFileNameXML = "/saved/pdfxml/" + testFileName+ ".xml"
const testFileNamePng = "/saved/pdfxml/" + testFileName + ".image-0.png"
// needs a manual convert (to image) from 3648188.3675128.pdf
// convert -density 150 3648188.3675128.pdf 3648188.3675128.image.png
setTimeout( _ => {
manuscript.setAttribute("visible", false)
let elImg = document.createElement("a-image")
elImg.setAttribute("height", 29.7/21 ) // cm size of A4 for approximate ratio
// display image
elImg.setAttribute("src", testFileNamePng)
elImg.setAttribute("position", "0.5 0.5 0") // requires small offset but might be related to scaling
let rootEl = interactableOverlay( testFileNameXML )
rootEl.appendChild(elImg)
}, 800) // should delay until ready
// get coordinates from XML page from saved/pdfxml/3603163.3609031.xml
function interactableOverlay(src){
const scalingFactor = 1/1000*1.141// 1/1000
if (!src) return
let rootEl = document.createElement("a-entity")
rootEl.id = "test_page_from_" + username
rootEl.setAttribute("position", "-.5 1 -.5") // requires small offset but might be related to
AFRAME.scenes[0].appendChild(rootEl)
let sheetEl = document.createElement("a-box")
sheetEl.setAttribute("height", 29.7/21 ) // cm size of A4 for approximate ratio
sheetEl.setAttribute("depth", .01 )
sheetEl.setAttribute("position", "0.5 0.5 -0.0051")
rootEl.appendChild(sheetEl)
let page = 0
fetch( src ).then( r => r.text() ).then( txt => {
const parser = new DOMParser();
let doc = parser.parseFromString(txt, "application/xml")
Array.from( doc.children[0].children[page].querySelectorAll("text") ).map( (l,n) => {
let el = document.createElement("a-entity")
el.setAttribute("target", "")
el.setAttribute("scale", ".1 .1 .1") // used to make target sphere smaller
el.id = "highlightimagefromxml_"+n
let x = (l.attributes.left.value*scalingFactor) - .02
let y = (1-l.attributes.top.value*scalingFactor) + .18
let z = 0
el.setAttribute("position", ""+x+" "+ y+ " " +z)
el.setAttribute("onpicked", "let el = addNewNote('"+l.textContent+"', '0 0 0', '1 1 1'); el.setAttribute('color', 'black'); el.setAttribute('outline-width', 0); el.object3D.parent = selectedElements.at(-1).element.object3D")
// instead append to the current element, break AFrame readability but quick to test
el.setAttribute("onreleased", "console.log('"+l.textContent+"')")
// 'live-selector-line'
rootEl.appendChild(el)
})
setTimeout( _ => { makeAnchorsVisibleOnTargets() }, 500)
setTimeout( _ => { Array.from( test_page_from_q3_pdf_hidden_text.querySelectorAll("a-sphere") ).map( el => el.setAttribute("color","#bbb")) }, 700)
// should scale down
})
return rootEl
}
}
if (username && username == "q3_code_editing") {
//showFile( "demo_hypertext2025.js" ) // javascript filter not existing, could consider txt.js instead
file = "demo_hypertext2025.js"