diff --git a/com/app.js b/com/app.js
index 3383b6d..4127e10 100644
--- a/com/app.js
+++ b/com/app.js
@@ -148,7 +148,7 @@ AFRAME.AComponent.prototype.initComponent = function(initComponent){
AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
- return function(){
+ let setupApp = function(){
updateProperties.apply(this,arguments)
if( !this.data || !this.data.uri || this.isApp ) return // only deal with apps (once)
@@ -179,7 +179,7 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
createReactiveDOMElement: () => {
this.el.dom = document.createElement('div')
this.el.dom.className = this.parseAppURI(this.data.uri).component
- this.el.dom.innerHTML = this.dom.html(this)
+ this.el.dom.innerHTML = this.com.dom.html(this)
this.el.dom.style.display = 'none'
this.data = reactify( this.dom.el, this.el )
this.dom.events.map( (e) => this.el.dom.addEventListener(e, (ev) => this.el.emit(e,ev) ) )
@@ -257,6 +257,9 @@ AFRAME.AComponent.prototype.updateProperties = function(updateProperties){
this.isApp = true
this.el.app = this
}
+ return function(){
+ try{ setupApp.apply(this,arguments) }catch(e){ console.error(e) }
+ }
}( AFRAME.AComponent.prototype.updateProperties)
document.head.innerHTML += `
diff --git a/com/control/hand-menu-button.js b/com/control/hand-menu-button.js
deleted file mode 100644
index 80e56c4..0000000
--- a/com/control/hand-menu-button.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/* global AFRAME, THREE */
-AFRAME.registerComponent('hand-menu-button', {
- schema: {
- src: {default: ''},
- srcHover: {default: ''},
- mixin: {default: ''}
- },
-
- init: function () {
- this.onWatchButtonHovered = this.onWatchButtonHovered.bind(this);
- this.onAnimationComplete = this.onAnimationComplete.bind(this);
- this.onCollisionStarted = this.onCollisionStarted.bind(this);
- this.onCollisionEnded = this.onCollisionEnded.bind(this);
- this.onAnimationBegin = this.onAnimationBegin.bind(this);
- this.onPinchEnded = this.onPinchEnded.bind(this);
-
- this.el.addEventListener('obbcollisionstarted', this.onCollisionStarted);
- this.el.addEventListener('obbcollisionended', this.onCollisionEnded);
- this.el.object3D.renderOrder = 1000;
-
- this.menuEl = this.el.parentEl;
- this.handMenuEl = this.el.sceneEl.querySelector('[hand-menu]');
-
- this.menuEl.addEventListener('animationbegin', this.onAnimationBegin);
- this.menuEl.addEventListener('animationcomplete', this.onAnimationComplete);
- },
-
- onAnimationBegin: function (evt) {
- // To prevent menu activations while animation is running.
- if (evt.detail.name === 'animation__open') { this.menuOpen = false; }
- },
-
- onAnimationComplete: function (evt) {
- if (evt.detail.name === 'animation__open') { this.menuOpen = true; }
- if (evt.detail.name === 'animation__close') { this.menuOpen = false; }
- },
-
- onCollisionStarted: function (evt) {
- var withEl = evt.detail.withEl;
- if (this.handMenuEl === withEl ||
- !withEl.components['hand-tracking-controls']) { return; }
- if (!this.menuOpen) { return; }
- this.handHoveringEl = withEl;
- this.el.emit('watchbuttonhoverstarted');
- },
-
- onCollisionEnded: function (evt) {
- var withEl = evt.detail.withEl;
- if (this.handMenuEl === withEl ||
- !withEl.components['hand-tracking-controls']) { return; }
- this.disableHover();
- this.handHoveringEl = undefined;
- this.el.emit('watchbuttonhoverended');
- },
-
- enableHover: function () {
- this.handHoveringEl.addEventListener('pinchended', this.onPinchEnded);
- this.el.setAttribute('material', 'src', this.data.srcHover);
- },
-
- disableHover: function () {
- if (!this.handHoveringEl) { return; }
- this.handHoveringEl.removeEventListener('pinchended', this.onPinchEnded);
- this.el.setAttribute('material', 'src', this.data.src);
- },
-
- onPinchEnded: (function () {
- var spawnPosition = new THREE.Vector3(0, 1, 0);
- return function () {
- var cubeEl;
- var newEntityEl;
- if (!this.menuOpen) { return; }
- this.menuOpen = false;
- if (!this.handHoveringEl || !this.data.mixin) { return; }
- // Spawn shape a little above the menu.
- spawnPosition.set(0, 1, 0);
- // Apply rotation of the menu.
- spawnPosition.applyQuaternion(this.el.parentEl.object3D.quaternion);
- // 20cm above the menu.
- spawnPosition.normalize().multiplyScalar(0.2);
- spawnPosition.add(this.el.parentEl.object3D.position);
-
- newEntityEl = document.createElement('a-entity');
- newEntityEl.setAttribute('mixin', this.data.mixin);
- newEntityEl.setAttribute('position', spawnPosition);
- this.el.sceneEl.appendChild(newEntityEl);
- this.handHoveringEl.removeEventListener('pinchended', this.onPinchEnded);
- };
- })(),
-
- onWatchButtonHovered: function (evt) {
- if (evt.target === this.el || !this.handHoveringEl) { return; }
- this.disableHover();
- this.handHoveringEl = undefined;
- }
-});
-
-/*
- User's hand can collide with multiple buttons simultaneously but only want one in a hovered state.
- This system keeps track of all the collided buttons, keeping just the closest to the hand in a hovered state.
-*/
-AFRAME.registerSystem('hand-menu-button', {
- init: function () {
- this.onWatchButtonHovered = this.onWatchButtonHovered.bind(this);
- this.el.parentEl.addEventListener('watchbuttonhoverended', this.onWatchButtonHovered);
- this.el.parentEl.addEventListener('watchbuttonhoverstarted', this.onWatchButtonHovered);
- this.hoveredButtonEls = [];
- },
-
- tick: function () {
- var buttonWorldPosition = new THREE.Vector3();
- var thumbPosition;
- var smallestDistance = 1000000;
- var currentDistance;
- var closestButtonEl;
- if (this.hoveredButtonEls.length < 2) { return; }
- thumbPosition = this.hoveredButtonEls[0].components['hand-menu-button'].handHoveringEl.components['obb-collider'].trackedObject3D.position;
- for (var i = 0; i < this.hoveredButtonEls.length; ++i) {
- this.hoveredButtonEls[i].object3D.getWorldPosition(buttonWorldPosition);
- currentDistance = buttonWorldPosition.distanceTo(thumbPosition);
- if (currentDistance < smallestDistance) {
- closestButtonEl = this.hoveredButtonEls[i];
- smallestDistance = currentDistance;
- }
- }
-
- if (this.hoveredButtonEl === closestButtonEl) { return; }
-
- this.hoveredButtonEl = closestButtonEl;
-
- for (i = 0; i < this.hoveredButtonEls.length; ++i) {
- if (!this.hoveredButtonEls[i].components['hand-menu-button'].handHoveringEl) { continue; }
- if (this.hoveredButtonEls[i] === closestButtonEl) {
- this.hoveredButtonEls[i].components['hand-menu-button'].enableHover();
- continue;
- }
- this.hoveredButtonEls[i].components['hand-menu-button'].disableHover();
- }
- },
-
- onWatchButtonHovered: function (evt) {
- this.buttonEls = this.el.sceneEl.querySelectorAll('[hand-menu-button]');
- this.hoveredButtonEls = [];
- for (var i = 0; i < this.buttonEls.length; ++i) {
- if (!this.buttonEls[i].components['hand-menu-button'].handHoveringEl) { continue; }
- this.hoveredButtonEls.push(this.buttonEls[i]);
- }
- if (this.hoveredButtonEls.length === 1) {
- this.hoveredButtonEl = this.hoveredButtonEls[0];
- this.hoveredButtonEls[0].components['hand-menu-button'].enableHover();
- }
- }
-});
diff --git a/com/control/hand-menu.js b/com/control/hand-menu.js
deleted file mode 100644
index 987cd79..0000000
--- a/com/control/hand-menu.js
+++ /dev/null
@@ -1,198 +0,0 @@
-/* global AFRAME, THREE */
-AFRAME.registerComponent('hand-menu', {
- schema: {
- location: {default: 'palm', oneOf: ['palm']}
- },
-
- menuHTML: /* syntax: html */ `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
-
- init: function () {
- this.onCollisionStarted = this.onCollisionStarted.bind(this);
- this.onCollisionEnded = this.onCollisionEnded.bind(this);
- this.onSceneLoaded = this.onSceneLoaded.bind(this);
- this.onEnterVR = this.onEnterVR.bind(this);
-
- this.throttledOnPinchEvent = AFRAME.utils.throttle(this.throttledOnPinchEvent, 50, this);
-
- //this.el.sceneEl.addEventListener('loaded', this.onSceneLoaded);
- this.el.sceneEl.addEventListener('enter-vr', this.onEnterVR);
- },
-
- onEnterVR: function () {
- this.onSceneLoaded()
- this.setupMenu();
- },
-
- onSceneLoaded: function () {
- var handEls = this.el.sceneEl.querySelectorAll('[hand-tracking-controls]');
- for (var i = 0; i < handEls.length; i++) {
- if (handEls[i] === this.el) { continue; }
- this.handElement = handEls[i];
- }
- },
-
- setupMenu: function () {
- var template = document.createElement('template');
- template.innerHTML = this.menuHTML;
- this.menuEl = template.content.children[0];
- this.el.sceneEl.appendChild(this.menuEl);
-
- if (this.data.location === 'palm') {
- this.setupPalm();
- }
- },
-
- setupPalm: function () {
- var el = this.openMenuEl = document.createElement('a-entity');
- el.setAttribute('geometry', 'primitive: circle; radius: 0.025');
- el.setAttribute('material', 'side: double; src: #palmButton; shader: flat');
- el.setAttribute('rotation', '90 0 180');
- el.setAttribute('position', '0 -0.035 -0.07');
- el.setAttribute('obb-collider', '');
- el.addEventListener('obbcollisionstarted', this.onCollisionStarted);
- el.addEventListener('obbcollisionended', this.onCollisionEnded);
- this.el.appendChild(el);
- },
-
- throttledOnPinchEvent: function (evt) {
- if (evt.type === 'pinchstarted') { this.onPinchStarted(evt); }
- if (evt.type === 'pinchended') { this.onPinchEnded(evt); }
- },
-
- onCollisionStarted: function (evt) {
- var withEl = evt.detail.withEl;
- if (this.handElement !== withEl) { return; }
- withEl.addEventListener('pinchstarted', this.throttledOnPinchEvent);
- withEl.addEventListener('pinchended', this.throttledOnPinchEvent);
- this.handHoveringEl = withEl;
- this.updateUI();
- },
-
- onCollisionEnded: function (evt) {
- var withEl = evt.detail.withEl;
- if (this.handElement !== withEl) { return; }
- withEl.removeEventListener('pinchstarted', this.throttledOnPinchEvent);
- if (!this.opened) {
- withEl.removeEventListener('pinchended', this.throttledOnPinchEvent);
- }
- this.handHoveringEl = undefined;
- this.updateUI();
- },
-
- updateUI: function () {
- var palmButtonImage;
- if (this.data.location === 'palm') {
- palmButtonImage = this.handHoveringEl ? '#palmButtonHover' : '#palmButton';
- debugger
- this.openMenuEl.setAttribute('material', 'src', palmButtonImage);
- return;
- }
- },
-
- onPinchStarted: (function () {
- var auxMatrix = new THREE.Matrix4();
- var auxQuaternion = new THREE.Quaternion();
- return function (evt) {
- if (!this.handHoveringEl || this.opened) { return; }
- this.opened = true;
- this.menuEl.object3D.position.copy(evt.detail.position);
- this.menuEl.emit('open');
- function lookAtVector (sourcePoint, destPoint) {
- return auxQuaternion.setFromRotationMatrix(
- auxMatrix.identity()
- .lookAt(sourcePoint, destPoint, new THREE.Vector3(0, 1, 0)));
- }
-
- var cameraEl = this.el.sceneEl.querySelector('[camera]');
- var rotationQuaternion = lookAtVector(this.menuEl.object3D.position, cameraEl.object3D.position);
- this.menuEl.object3D.quaternion.copy(rotationQuaternion);
- this.pinchedEl = this.handHoveringEl;
- if (this.data.location === 'palm') { this.openMenuEl.object3D.visible = false; }
- };
- })(),
-
- onPinchEnded: function (evt) {
- if (!this.pinchedEl) { return; }
- this.opened = false;
- this.menuEl.emit('close');
- this.pinchedEl = undefined;
- this.openMenuEl.object3D.visible = true;
- },
-
- lookAtCamera: (function () {
- var auxVector = new THREE.Vector3();
- var auxObject3D = new THREE.Object3D();
- return function (el) {
- var cameraEl = this.el.sceneEl.querySelector('[camera]');
- auxVector.subVectors(cameraEl.object3D.position, el.object3D.position).add(el.object3D.position);
- el.object3D.lookAt(auxVector);
- el.object3D.rotation.z = 0;
- };
- })()
-});
-
-/*
-
-Watch style UI that work both in VR and AR with @aframevr in one line of
-
-Try now on @Meta Quest Browser
-
-https://a-watch.glitch.me/
-
-Just 400 lines of code: https://glitch.com/edit/#!/a-watch
-
-Watch-style intuitive but easy to occlude hands ⌚️
-Palm- style less familiar but more robust ✋
-
-Enjoy! Wanna see more of this? sponsor me on @github
-
-https://github.com/sponsors/dmarcos
-
-*/
diff --git a/com/dom.js b/com/dom.js
new file mode 100644
index 0000000..2b6cd53
--- /dev/null
+++ b/com/dom.js
@@ -0,0 +1,127 @@
+/*
+ * ## dom
+ *
+ * instances reactive DOM component from AFRAME component's `dom` metadata
+ *
+ * ```html
+ *
+ *
+ *
+ * ```
+ *
+ * | property | type | example |
+ * |--------------|--------------------|----------------------------------------------------------------------------------------|
+ * | `com` | `array` of strings | |
+ *
+ * | event | target | info |
+ * |--------------|-------------------------------------------------------------------------------------------------------------|
+ * | `DOMready` | self | fired when dom component (`this.dom`) is created |
+ */
+
+AFRAME.registerComponent('dom',{
+
+ init: function(){
+ Object.values(this.el.components)
+ .map( (c) => {
+ if( c.dom && c.attrName != "dom"){
+ this.dom = c.dom
+ this.com = c
+ }
+ })
+ if( !this.dom ) return console.warn('dom.js did not find a .dom object inside components')
+
+ this
+ .ensureOverlay()
+ .addCSS()
+ .createReactiveDOMElement()
+ .scaleDOMvsXR()
+ .triggerKeyboardForInputs()
+ .setupListeners()
+
+ document.querySelector('#overlay').appendChild(this.el.dom)
+ this.el.emit('DOMready',{el: this.el.dom})
+ },
+
+ ensureOverlay(){
+ // ensure overlay
+ let overlay = document.querySelector('#overlay')
+ if( !overlay ){
+ overlay = document.createElement('div')
+ overlay.id = "overlay"
+ document.body.appendChild(overlay)
+ document.querySelector("a-scene").setAttribute("webxr","overlayElement:#overlay")
+ }
+ return this
+ },
+
+ reactify: function(el,data){
+ return new Proxy(data, {
+ get(me,k,v) {
+ return me[k]
+ },
+ set(me,k,v){
+ me[k] = v
+ el.emit(k,{el,k,v})
+ }
+ })
+ },
+
+ // creates el.dom (the 2D DOM object)
+ createReactiveDOMElement: function(){
+ this.el.dom = document.createElement('div')
+ this.el.dom.innerHTML = this.dom.html(this)
+ this.el.dom.className = this.dom.attrName
+ this.com.data = this.reactify( this.el, this.com.data )
+ if( this.dom.events ) this.dom.events.map( (e) => this.el.dom.addEventListener(e, (ev) => this.el.emit(e,ev) ) )
+ this.el.dom = this.el.dom.children[0]
+ return this
+ },
+
+ addCSS: function(){
+ if( this.dom.css && !document.head.querySelector(`style#${this.attrName}`) ){
+ document.head.innerHTML += ``
+ }
+ return this
+ },
+
+ scaleDOMvsXR: function(){
+ if( this.dom.scale ) this.el.setAttribute('scale',`${this.dom.scale} ${this.dom.scale} ${this.dom.scale}`)
+ return this
+ },
+
+ setupListeners: function(){
+ this.el.sceneEl.addEventListener('apps:2D', () => this.el.setAttribute('visible', false) )
+ this.el.sceneEl.addEventListener('apps:XR', () => {
+ this.el.setAttribute('visible', true)
+ this.el.setAttribute("html",`html:#${this.el.uid}; cursor:#cursor`)
+ })
+ return this
+ },
+
+ triggerKeyboardForInputs: function(){
+ // https://developer.oculus.com/documentation/web/webxr-keyboard ;
+ [...this.el.dom.querySelectorAll('[type=text]')].map( (input) => {
+ let triggerKeyboard = function(){
+ this.focus()
+ console.log("focus")
+ }
+ input.addEventListener('click', triggerKeyboard )
+ })
+ return this
+ }
+
+})
diff --git a/app/example/helloworld-html.js b/com/example/helloworld-html.js
similarity index 100%
rename from app/example/helloworld-html.js
rename to com/example/helloworld-html.js
diff --git a/app/example/helloworld-htmlform.js b/com/example/helloworld-htmlform.js
similarity index 100%
rename from app/example/helloworld-htmlform.js
rename to com/example/helloworld-htmlform.js
diff --git a/app/example/helloworld-iframe.js b/com/example/helloworld-iframe.js
similarity index 100%
rename from app/example/helloworld-iframe.js
rename to com/example/helloworld-iframe.js
diff --git a/app/example/helloworld-window.js b/com/example/helloworld-window.js
similarity index 78%
rename from app/example/helloworld-window.js
rename to com/example/helloworld-window.js
index 8a103b8..2f1a353 100644
--- a/app/example/helloworld-window.js
+++ b/com/example/helloworld-window.js
@@ -3,12 +3,10 @@ AFRAME.registerComponent('helloworld-window', {
foo: { type:"string"}
},
- init: function(){},
+ dependencies: ['dom'],
- requires:{
- html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
- winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
- winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", //
+ init: function(){
+ this.el.object3D.visible = false
},
dom: {
@@ -23,9 +21,6 @@ AFRAME.registerComponent('helloworld-window', {
events:{
- // component events
- html: function( ){ console.log("html-mesh requirement mounted") },
-
// combined AFRAME+DOM reactive events
click: function(e){ }, //
keydown: function(e){ }, //
@@ -33,14 +28,16 @@ AFRAME.registerComponent('helloworld-window', {
// reactive events for this.data updates
myvalue: function(e){ this.el.dom.querySelector('b').innerText = this.data.myvalue },
- ready: function( ){
+ launcher: async function(){
this.el.dom.style.display = 'none'
- console.log("this.el.dom has been added to DOM")
+ await AFRAME.utils.require({
+ dom: "./com/dom.js", // interpret .dom object
+ html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
+ winboxjs: "https://unpkg.com/winbox@0.2.82/dist/winbox.bundle.min.js", // deadsimple windows: https://nextapps-de.github.io/winbox
+ winboxcss: "https://unpkg.com/winbox@0.2.82/dist/css/winbox.min.css", //
+ })
this.data.myvalue = 1
setInterval( () => this.data.myvalue++, 100 )
- },
-
- launcher: function(){
console.log("this.el.dom has been added to DOM")
new WinBox("Hello World",{
width: 250,
@@ -58,8 +55,8 @@ AFRAME.registerComponent('helloworld-window', {
},
manifest: { // HTML5 manifest to identify app to xrsh
- "short_name": "Hello world",
- "name": "Hello world",
+ "short_name": "Hello world window",
+ "name": "Hello world window",
"icons": [
{
"src": "https://css.gg/browser.svg",
diff --git a/app/example/helloworld.js b/com/example/helloworld.js
similarity index 85%
rename from app/example/helloworld.js
rename to com/example/helloworld.js
index 4c9ede9..7c0f930 100644
--- a/app/example/helloworld.js
+++ b/com/example/helloworld.js
@@ -1,8 +1,11 @@
AFRAME.registerComponent('helloworld', {
schema: {
- foo: { type:"string"}
+ wireframe: { type:"boolean", "default":false},
+ foo: {type:"string","default":"foo"}
},
+ dependencies:['dom'],
+
init: function () {
this.el.object3D.visible = false
@@ -12,22 +15,15 @@ AFRAME.registerComponent('helloworld', {
`
-
- this.interval = setInterval( () => this.data.wireframe = !this.data.wireframe, 500 )
- },
-
- requires:{
- // somecomponent: "https://unpkg.com/some-aframe-component/mycom.min.js"
},
events:{
- // component events
- somecomponent: function( ){ console.log("component requirement mounted") },
- ready: function(e){ console.log("requires are loaded") },
-
launcher: function(e){
this.el.object3D.visible = !this.el.object3D.visible
+ this.interval = setInterval( () => {
+ this.data.wireframe = !this.data.wireframe
+ }, 500 )
},
// reactive this.data value demo
diff --git a/app/launcher.js b/com/launcher.js
similarity index 54%
rename from app/launcher.js
rename to com/launcher.js
index 72d166c..5da96f0 100644
--- a/app/launcher.js
+++ b/com/launcher.js
@@ -18,30 +18,29 @@
AFRAME.registerComponent('launcher', {
schema: {
- foo: { type:"string"}
+ attach: { type:"selector"}
},
- init: function () {
+ dependencies:['dom'],
+
+ init: async function () {
this.data.apps = []
- AFRAME.scenes.map( (scene) => {
- scene.addEventListener('app:ready', (e) => this.render(e.detail) )
+ await AFRAME.utils.require({
+ html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
+ dom: "./com/dom.js",
+ svgfile: "https://7dir.github.io/aframe-svgfile-component/aframe-svgfile-component.min.js",
})
- },
- requires:{
- html: "https://unpkg.com/aframe-htmlmesh@2.1.0/build/aframe-html.js", // html to AFRAME
- 'hand-menu': "./com/control/hand-menu.js",
- 'hand-menu-button': "./com/control/hand-menu-button.js",
+ this.el.setAttribute("dom","")
+ this.render()
+ this.el.sceneEl.addEventListener('enter-vr', (e) => this.render() )
},
dom: {
scale: 3,
events: ['click'],
- html: (me) => `
-
-
`,
-
+ html: (me) => ``,
css: (me) => `#iconmenu {
z-index: 1000;
display: flex;
@@ -102,47 +101,67 @@ AFRAME.registerComponent('launcher', {
events:{
- // combined AFRAME+DOM reactive events
- click: function(e){
- console.dir(e)
- }, //
-
-
- ready: function( ){
- this.el.dom.children[0].id = this.el.uid // important hint for html-mesh
- document.querySelector('#left-hand').setAttribute('hand-menu','')
- },
-
},
- render: function(app){
- clearTimeout(this.timeout)
- this.timeout = setTimeout( () => {
- const drawButton = (app) => {
- if( !app.manifest ) return
- console.log(app.data.uri+" "+app.data.order)
- let btn = app.btn = document.createElement('button')
- if( app.manifest.icons?.length > 0){
- let img = document.createElement('img')
- img.src = app.manifest.icons[0].src
- img.title = app.manifest.name + ": " + app.manifest.description
- btn.appendChild(img)
- }else btn.innerText = app.manifest.short_name
- btn.addEventListener('click', () => {
- app.el.emit('launcher',app)
- app.el.sceneEl.emit('launched',app)
- })
- this.el.dom.querySelector('#iconmenu').appendChild(btn)
+ render: async function(){
+ if( !this.el.dom ) return // too early (dom.js component not ready)
+
+ let inVR = this.sceneEl && this.sceneEl.renderer.xr.isPresenting
+ let items = [...this.el.children]
+ let requires = []
+ let i = 0
+ let colors = [
+ '#4C73FE',
+ '#554CFE',
+ '#864CFE',
+ '#B44CFE',
+ '#E24CFE',
+ '#FE4CD3'
+ ]
+
+ const add2D = (launchCom,el,manifest,aentity) => {
+ let btn = document.createElement('button')
+ btn.innerHTML = `${ manifest?.icons?.length > 0
+ ? ``
+ : `${manifest.short_name}`
+ }`
+ btn.addEventListener('click', () => el.emit('launcher',{}) )
+ this.el.dom.appendChild(btn)
+ }
+
+ const add3D = (launchCom,el,manifest) => {
+ let aentity = document.createElement('a-entity')
+ let atext = document.createElement('a-entity')
+ aentity.setAttribute("mixin","menuitem")
+ aentity.setAttribute("position",`${i++ * 0.2} 0 0`)
+ if( !aentity.getAttribute("material")){
+ aentity.setAttribute('material',`side: double; color: ${colors[ i % colors.length]}`)
}
+ atext.setAttribute("text",`value: ${manifest.short_name}; align: baseline; anchor: align; align:center; wrapCount:7`)
+ atext.setAttribute("scale","0.1 0.1 0.1")
+ atext.setAttribute("position","0 0 0.0")
+ aentity.appendChild(atext)
+ this.el.appendChild(aentity)
+ return aentity
+ }
- const drawCategory = (app,c) => app.manifest &&
- app.manifest.short_name != "launcher" &&
- app.manifest.category == c ? drawButton(app) : false
+ // finally render them!
+ this.el.dom.innerHTML = '' // clear
+ this.system.components.map( (c) => {
+ const launchComponentKey = c.getAttributeNames().pop()
+ const launchCom = c.components[ launchComponentKey ]
+ if( !launchCom ) return console.warn(`could not find component '${launchComponentKey}' (forgot to include script-tag?)`)
+ const manifest = launchCom.manifest
+ if( manifest ){
+ add2D(launchCom,c,manifest, add3D(launchCom,c,manifest) )
+ }
+ })
- AFRAME.app.foreach( (app) => drawCategory(app,undefined) )
- AFRAME.app.foreach( (app) => drawCategory(app,"system") )
+ if( this.data.attach ){
+ this.el.object3D.visible = inVR ? true : false
+ // if( inVR ) this.data.attach.appendChild(this.el)
+ }
- },100)
},
manifest: { // HTML5 manifest to identify app to xrsh
@@ -199,3 +218,37 @@ in above's case "\nHelloworld application\n" will qualify as header.
});
+
+AFRAME.registerSystem('launcher',{
+
+ init: function(){
+ this.components = []
+ // observer HTML changes in
+ observer = new MutationObserver( (a,b) => this.getLaunchables(a,b) )
+ observer.observe( this.sceneEl, {characterData: false, childList: true, attributes: false});
+ },
+
+ getLaunchables: function(mutationsList,observer){
+ let searchEvent = 'launcher'
+ let els = [...this.sceneEl.getElementsByTagName("*")]
+
+ this.components = els.filter( (el) => {
+ let hasEvent = false
+ if( el.components ){
+ for( let i in el.components ){
+ if( el.components[i].events && el.components[i].events[searchEvent] ){
+ hasEvent = true
+ }
+ }
+ }
+ return hasEvent ? el : null
+ })
+ this.updateLauncher()
+ },
+
+ updateLauncher: function(){
+ let launcher = document.querySelector('[launcher]')
+ if( launcher ) launcher.components['launcher'].render()
+ }
+
+})
diff --git a/com/osbutton.js b/com/osbutton.js
deleted file mode 100644
index 32b6440..0000000
--- a/com/osbutton.js
+++ /dev/null
@@ -1,86 +0,0 @@
-AFRAME.registerComponent('osbutton',{
-
- data:{
- width: {type: 'number', default: 0.4},
- height: {type: 'number', default: 0.2},
- depth: {type: 'number', default: 0.06},
- color: {type: 'color', default: 'blue'},
- distance: {type: 'number'},
- label: {type: 'string'}
- },
-
- init: function(){
- this
- .createBox()
- .setupDistanceTick()
- },
-
- setupDistanceTick: function(){
- // we throttle by distance, to support scenes with loads of clickable objects (far away)
- if( !this.data.distance ) this.data.distance = 0.9
- this.distance = -1
- this.worldPosition = new THREE.Vector3()
- this.posCam = new THREE.Vector3()
- this.tick = this.throttleByDistance( () => this.showSource() )
- },
-
- createBox: function(){
- let geometry = this.geometry = new THREE.BoxGeometry(this.data.width, this.data.height, this.data.depth);
- this.material = new THREE.MeshStandardMaterial({color: this.data.color });
- this.mesh = new THREE.Mesh(this.geometry, this.material);
- this.scaleChildToButton(this.el.object3D, this.mesh)
- this.el.object3D.add(this.mesh)
- return this
- },
-
- throttleByDistance: function(f){
- return function(){
- if( this.distance < 0 ) return f() // first call
- if( !f.tid ){
- let x = this.distance
- let y = x*(x*0.05)*1000 // parabolic curve
- f.tid = setTimeout( function(){
- f.tid = null
- f()
- }, y )
- }
- }
- },
-
- showSource: function(){
- this.el.sceneEl.camera.getWorldPosition(this.posCam)
- this.el.object3D.getWorldPosition(this.worldPosition)
- this.distance = this.posCam.distanceTo(this.worldPosition)
-
- if( this.distance < this.data.distance ){
- this.material.side = THREE.BackSide
- }else{
- this.material.side = THREE.FrontSide
- }
- },
-
- scaleChildToButton: function(scene, mesh ){
- let cleanScene = scene.clone()
- let remove = []
- const notVisible = (n) => !n.visible || (n.material && !n.material.visible)
- cleanScene.traverse( (n) => notVisible(n) && n.children.length == 0 && (remove.push(n)) )
- remove.map( (n) => n.removeFromParent() )
- let restrictTo3DBoundingBox = mesh.geometry
- if( restrictTo3DBoundingBox ){
- // normalize instanced objectsize to boundingbox
- let sizeFrom = new THREE.Vector3()
- let sizeTo = new THREE.Vector3()
- let empty = new THREE.Object3D()
- new THREE.Box3().setFromObject(mesh).getSize(sizeTo)
- new THREE.Box3().setFromObject(cleanScene).getSize(sizeFrom)
- let ratio = sizeFrom.divide(sizeTo)
- scene.children.map( (c) => {
- if( c.uuid != mesh.uuid ){
- c.scale.multiplyScalar( 1.0 / Math.max(ratio.x, ratio.y, ratio.z))
- }
- })
- }
-
- },
-
-})
diff --git a/app/paste.js b/com/paste.js
similarity index 100%
rename from app/paste.js
rename to com/paste.js
diff --git a/app/save.js b/com/save.js
similarity index 92%
rename from app/save.js
rename to com/save.js
index 5067f68..445fcca 100644
--- a/app/save.js
+++ b/com/save.js
@@ -9,21 +9,12 @@ AFRAME.registerComponent('save', {
//this.el.innerHTML = ` `
},
- requires:{
- // somecomponent: "https://unpkg.com/some-aframe-component/mycom.min.js"
- },
-
events:{
- // component events
- somecomponent: function( ){ console.log("component requirement mounted") },
- ready: function(e){ console.log("requires are loaded") },
-
launcher: function(e){
this.save()
},
-
},
save: function(){
diff --git a/app/xrfragments.js b/com/xrfragments.js
similarity index 68%
rename from app/xrfragments.js
rename to com/xrfragments.js
index 2aae5ce..733a934 100644
--- a/app/xrfragments.js
+++ b/com/xrfragments.js
@@ -3,30 +3,33 @@ AFRAME.registerComponent('xrfragments', {
url: { type:"string"}
},
- init: function () {
- },
+ dependencies:['dom'],
- requires:{
- xrfragments: "https://xrfragment.org/dist/xrfragment.aframe.js",
+ init: function () {
},
events:{
- // requires are loaded
- ready: function(e){
- this.el.setAttribute("xrf","https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
-
- let ARbutton = document.querySelector('.a-enter-ar-button')
- if( ARbutton ){
- ARbutton.addEventListener('click', () => {
- AFRAME.XRF.reset()
- })
- }
- },
-
- launcher: function(){
+ launcher: async function(){
let url = prompt('enter URL to glb/fbx/json/obj/usdz asset', 'https://xrfragment.org/index.glb')
- if( url ) AFRAME.XRF.navigator.to(url)
+ if( !url ) return
+ await AFRAME.utils.require({
+ xrfragments: "https://xrfragment.org/dist/xrfragment.aframe.js",
+ })
+
+ // remove objects which are marked to be removed from scene (with noxrf)
+ let els = [...document.querySelectorAll('[noxrf]') ]
+ els.map( (el) => el.remove() )
+
+ if( !this.el.getAttribute("xrf") ){
+ this.el.setAttribute("xrf", url )
+ let ARbutton = document.querySelector('.a-enter-ar-button')
+ if( ARbutton ){
+ ARbutton.addEventListener('click', () => {
+ AFRAME.XRF.reset()
+ })
+ }
+ }else AFRAME.XRF.navigator.to(url)
}
},