From a456cd8264dce55e7c509c30492c827bed241c2d Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Tue, 12 Mar 2024 11:32:59 +0100 Subject: [PATCH] work in progress [might break] --- example/aframe/sandbox/index.html | 4 +- src/3rd/js/aframe/build/three.module.js | 3 +- src/3rd/js/aframe/index.js | 3 +- src/3rd/js/aframe/pressable.js | 3 +- src/3rd/js/plugin/frontend/$editor.js | 126 ++++++++++++++++++++++++ src/3rd/js/plugin/frontend/.js | 40 ++++++++ src/3rd/js/plugin/frontend/css.js | 41 ++++++++ src/3rd/js/plugin/frontend/frontend.js | 9 +- src/3rd/js/three/util/interactive.js | 16 ++- 9 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 src/3rd/js/plugin/frontend/$editor.js create mode 100644 src/3rd/js/plugin/frontend/.js diff --git a/example/aframe/sandbox/index.html b/example/aframe/sandbox/index.html index 9b7556a..b3fb796 100644 --- a/example/aframe/sandbox/index.html +++ b/example/aframe/sandbox/index.html @@ -18,13 +18,13 @@ light="defaultLightsEnabled: false"> - + - + diff --git a/src/3rd/js/aframe/build/three.module.js b/src/3rd/js/aframe/build/three.module.js index d5683db..11c6835 100644 --- a/src/3rd/js/aframe/build/three.module.js +++ b/src/3rd/js/aframe/build/three.module.js @@ -10,7 +10,7 @@ import { ColladaLoader } from 'super-three/examples/jsm/loaders/ColladaLoader'; import { MTLLoader } from 'super-three/examples/jsm/loaders/MTLLoader'; import * as BufferGeometryUtils from 'super-three/examples/jsm/utils/BufferGeometryUtils'; import { LightProbeGenerator } from 'super-three/examples/jsm/lights/LightProbeGenerator'; -//import {Text} from 'troika-three-text' +import { TransformControls } from 'super-three/examples/jsm/controls/TransformControls.js'; var THREE = window.THREE = SUPER_THREE; @@ -27,6 +27,7 @@ THREE.ColladaLoader = ColladaLoader; THREE.OBB = OBB; THREE.BufferGeometryUtils = BufferGeometryUtils; THREE.LightProbeGenerator = LightProbeGenerator; +THREE.TransformControls = TransformControls; //THREE.Text = Text export default THREE; diff --git a/src/3rd/js/aframe/index.js b/src/3rd/js/aframe/index.js index 1d3c232..bd91d91 100644 --- a/src/3rd/js/aframe/index.js +++ b/src/3rd/js/aframe/index.js @@ -100,7 +100,7 @@ window.AFRAME.registerComponent('xrf', { } // give headset users way to debug without a cumbersome usb-tapdance - if( xrf.debug || document.location.hostname.match(/^(localhost|[1-9])/) && !aScene.getAttribute("vconsole") ){ + if( document.location.hostname.match(/^(localhost|[1-9])/) && !aScene.getAttribute("vconsole") ){ aScene.setAttribute('vconsole','') } @@ -146,7 +146,6 @@ window.AFRAME.registerComponent('xrf', { el.addEventListener("click", clickHandler ) el.addEventListener("mouseenter", mesh.userData.XRF.href.selected(true) ) el.addEventListener("mouseleave", mesh.userData.XRF.href.selected(false) ) - el.addEventListener("pressedstarted", clickHandler ) $('a-scene').appendChild(el) } createEl(mesh) diff --git a/src/3rd/js/aframe/pressable.js b/src/3rd/js/aframe/pressable.js index aed7825..85d8675 100644 --- a/src/3rd/js/aframe/pressable.js +++ b/src/3rd/js/aframe/pressable.js @@ -45,7 +45,7 @@ AFRAME.registerComponent('pressable', { handEl.object3D.getWorldPosition( this.fingerWorldPosition ) this.fingerWorldPosition.add( indexTipPosition ) - this.raycaster.far = 0.05 + this.raycaster.far = this.data.pressDistance // Create a direction vector (doesnt matter because it is supershort for 'touch' purposes) const direction = new THREE.Vector3(1.0,0,0); this.raycaster.set(this.fingerWorldPosition, direction) @@ -59,6 +59,7 @@ AFRAME.registerComponent('pressable', { if (intersects.length ){ if( !this.pressed ){ this.el.emit('pressedstarted'); + this.el.emit('click'); this.pressed = setTimeout( () => { this.el.emit('pressedended'); this.pressed = null diff --git a/src/3rd/js/plugin/frontend/$editor.js b/src/3rd/js/plugin/frontend/$editor.js new file mode 100644 index 0000000..a6d8c73 --- /dev/null +++ b/src/3rd/js/plugin/frontend/$editor.js @@ -0,0 +1,126 @@ +// reactive component for displaying the menu +$editor = (el,opts) => new Proxy({ + + html: ` +
+ +
+ + `, + + enabled: false, + helper: null, + selected: null, + + init(opts){ + el.innerHTML = this.html + window.frontend.el.querySelector('#topbar').appendChild(el); + el.querySelector('.edit-btn').addEventListener('click', () => $editor.enabled = true ) + return this + }, + + editNode(){ + if( !this.enabled ) return console.log("not editing") + console.log("click!") + $editor.enabled = false // disable selections + this.enableHref(this.selected,true) // re-enable hrefs + notify(`${this.selected.name}
${this.getMetaData(this.selected)}`) + notify(`XR Fragment: #${this.selected.name}

${this.getMetaData(this.selected)}`) + }, + + initEdit(scene){ + AFRAME.scenes[0].addEventListener('click', () => this.editNode() ) + scene.traverse( (n) => { + let highlight = (n) => (e) => { + console.log(n.name) + if( this.selected ) this.enableHref(this.selected,true) // re-enable href of previous selection + if( this.helper){ + if( this.helper.selected == n.uuid ) return // already selected + xrf.scene.remove(this.helper) + } + if( !this.enabled ) return // do nothing + + this.selected = n + this.helper = new THREE.BoxHelper( n, 0xFF00FF ) + this.helper.material.linewidth = 5 + this.helper.material.color = xrf.focusLine.material.color + this.helper.selected = n.uuid + xrf.scene.add(this.helper) + + let div = document.createElement('div') + notify(`XR Fragment: #${n.name}

${this.getMetaData(this.selected)}`) + + this.enableHref(n,false) // prevent clicks from doing their usual teleporting/executions + } + if( n.geometry ) n.addEventListener('mousemove', n.highlightOnMouseMove = highlight(n) ) + }) + console.log("inited scene") + }, + + getMetaData(n){ + return `href: ${n.userData.href}
src: ${n.userData.src}
tag: ${n.userData.tag}` + }, + + enableHref(n, state){ + if( n.userData.XRF && n.userData.XRF.href && n.userData.XRF.href.exec ){ + let exec = n.userData.XRF.href.exec + if( !state && !exec.bak ){ + exec.bak = exec + n.userData.XRF.href.exec = function(){} + } + if( state && exec.bak ){ + n.userData.XRF.href.exec = exec.bak + } + } + } + +}, +{ + + get(me,k,v){ return me[k] }, + + set(me,k,v){ + me[k] = v + switch( k ){ + + case "enabled":{ + if( v ){ + notify("click an object to reveal XR Fragment metadata") + xrf.interactive.raycastAll = true + if( !xrf.scene.initEdit ) me.initEdit(xrf.scene) + }else{ + console.log("idsabled") + xrf.scene.traverse( (n) => { + me.enableHref(n,true) + if( n.highlightOnMouseMove ){ + n.removeEventListener( 'mousemove', n.highlightOnMouseMove ) + } + }) + me.helper.remove() + console.log("removed events") + } + break; + } + } + }, + +}) + +// reactify component! +document.addEventListener('frontend:ready', (e) => { + window.$editor = $editor( document.createElement('div') ).init(e.detail) +}) diff --git a/src/3rd/js/plugin/frontend/.js b/src/3rd/js/plugin/frontend/.js new file mode 100644 index 0000000..144e055 --- /dev/null +++ b/src/3rd/js/plugin/frontend/.js @@ -0,0 +1,40 @@ +window.$editor = (opts) => new Proxy({ + opts, + html: ` + + `, + + enabled: false, + + toggle(){ this.enabled = !this.enabled }, + + init(){ + el.innerHTML = this.html + document.body.appendChild(el); + }, + +}, +{ + // auto-trigger events on changes + get(data,k,receiver){ return data[k] }, + set(data,k,v){ + data[k] = v + switch( k ){ + case "enabled": { + data.enabled = v + } + } +}) + +document.addEventListener('$menu:ready', (e) => { + return + try{ + $editor = $editor( document.createElement('div'), {} ) + $editor.init() + }catch(e){console.error(e)} +}) diff --git a/src/3rd/js/plugin/frontend/css.js b/src/3rd/js/plugin/frontend/css.js index 4d66c37..4f803ba 100644 --- a/src/3rd/js/plugin/frontend/css.js +++ b/src/3rd/js/plugin/frontend/css.js @@ -781,5 +781,46 @@ document.head.innerHTML += ` bottom: 8px; background: currentColor } + + .gg-pen { + box-sizing: border-box; + position: relative; + display: block; + transform: rotate(-45deg) scale(var(--ggs,1)); + width: 14px; + height: 4px; + border-right: 2px solid transparent; + box-shadow: + 0 0 0 2px, + inset -2px 0 0; + border-top-right-radius: 1px; + border-bottom-right-radius: 1px; + margin-right: -2px + } + .gg-pen::after, + .gg-pen::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute + } + .gg-pen::before { + background: currentColor; + border-left: 0; + right: -6px; + width: 3px; + height: 4px; + border-radius: 1px; + top: 0 + } + .gg-pen::after { + width: 8px; + height: 7px; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-right: 7px solid; + left: -11px; + top: -2px + } ` diff --git a/src/3rd/js/plugin/frontend/frontend.js b/src/3rd/js/plugin/frontend/frontend.js index 0e71799..9300013 100644 --- a/src/3rd/js/plugin/frontend/frontend.js +++ b/src/3rd/js/plugin/frontend/frontend.js @@ -178,9 +178,14 @@ window.frontend = (opts) => new Proxy({ let isChatMsg = e.target.closest('.msg') let isChatLine = e.target.id == 'chatline' let isChatEmptySpace = e.target.id == 'messages' - let isUI = e.target.closest('.ui') + let isUI = e.target.closest('.ui') || + e.target.closest('.btn') || + e.target.closest('button') || + e.target.closest('textarea') || + e.target.closest('input') || + e.target.closest('a') //console.dir({class: e.target.className, id: e.target.id, isChatMsg,isChatLine,isChatEmptySpace,isUI, tagName: e.target.tagName}) - if( isUI || e.target.tagName.match(/^(BUTTON|TEXTAREA|INPUT|A)/) || e.target.className.match(/(btn)/) ) return + if( isUI ) return if( show ){ $chat.visible = true }else{ diff --git a/src/3rd/js/three/util/interactive.js b/src/3rd/js/three/util/interactive.js index d1dc3d0..80299f2 100644 --- a/src/3rd/js/three/util/interactive.js +++ b/src/3rd/js/three/util/interactive.js @@ -1,4 +1,4 @@ -// wrapper to survive in/outside modules +// wrapper to collect interactive raycastable objects xrf.interactiveGroup = function(THREE,renderer,camera){ @@ -26,6 +26,8 @@ xrf.interactiveGroup = function(THREE,renderer,camera){ const scope = this; scope.objects = [] + scope.raycastAll = false + const raycaster = new Raycaster(); const tempMatrix = new Matrix4(); @@ -44,7 +46,8 @@ xrf.interactiveGroup = function(THREE,renderer,camera){ raycaster.setFromCamera( _pointer, camera ); - const intersects = raycaster.intersectObjects( scope.objects, false ); + let objects = scope.raycastAll ? xrf.scene.children : scope.objects + const intersects = raycaster.intersectObjects( objects, scope.raycastAll ); if ( intersects.length > 0 ) { @@ -54,7 +57,7 @@ xrf.interactiveGroup = function(THREE,renderer,camera){ const uv = intersection.uv; _event.type = event.type; - _event.data.set( uv.x, 1 - uv.y ); + if( uv ) _event.data.set( uv.x, 1 - uv.y ); object.dispatchEvent( _event ); }else{ @@ -93,7 +96,8 @@ xrf.interactiveGroup = function(THREE,renderer,camera){ raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); - const intersections = raycaster.intersectObjects( scope.objects, false ); + let objects = scope.raycastAll ? xrf.scene.children : scope.objects + const intersections = raycaster.intersectObjects( objects, scope.raycastAll ); if ( intersections.length > 0 ) { @@ -105,7 +109,7 @@ xrf.interactiveGroup = function(THREE,renderer,camera){ const uv = intersection.uv; _event.type = eventsMapper[ event.type ]; - _event.data.set( uv.x, 1 - uv.y ); + if( uv ) _event.data.set( uv.x, 1 - uv.y ); object.dispatchEvent( _event ); @@ -132,6 +136,8 @@ xrf.interactiveGroup = function(THREE,renderer,camera){ } + // we create our own add to avoid unnecessary unparenting of buffergeometries from + // their 3D model (which breaks animations) add(obj, unparent){ if( unparent ) Group.prototype.add.call( this, obj ) this.objects.push(obj)