diff --git a/app/assets/images/P42.png b/app/assets/images/P42.png new file mode 100644 index 0000000..6fa97c1 Binary files /dev/null and b/app/assets/images/P42.png differ diff --git a/app/assets/images/android-chrome-192x192.png b/app/assets/images/android-chrome-192x192.png new file mode 100644 index 0000000..6cbd3b4 Binary files /dev/null and b/app/assets/images/android-chrome-192x192.png differ diff --git a/app/assets/images/android-chrome-512x512.png b/app/assets/images/android-chrome-512x512.png new file mode 100644 index 0000000..497baa7 Binary files /dev/null and b/app/assets/images/android-chrome-512x512.png differ diff --git a/app/assets/images/apple-touch-icon.png b/app/assets/images/apple-touch-icon.png new file mode 100644 index 0000000..dd03b54 Binary files /dev/null and b/app/assets/images/apple-touch-icon.png differ diff --git a/app/assets/images/favicon-16x16.png b/app/assets/images/favicon-16x16.png new file mode 100644 index 0000000..1df3523 Binary files /dev/null and b/app/assets/images/favicon-16x16.png differ diff --git a/app/assets/images/favicon-32x32.png b/app/assets/images/favicon-32x32.png new file mode 100644 index 0000000..6c76bb3 Binary files /dev/null and b/app/assets/images/favicon-32x32.png differ diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 0000000..3ffeb2d Binary files /dev/null and b/app/assets/images/favicon.ico differ diff --git a/app/assets/images/p42.png b/app/assets/images/p42.png new file mode 100644 index 0000000..79ae934 Binary files /dev/null and b/app/assets/images/p42.png differ diff --git a/app/assets/images/p42.xcf b/app/assets/images/p42.xcf new file mode 100644 index 0000000..7808c2f Binary files /dev/null and b/app/assets/images/p42.xcf differ diff --git a/app/assets/json/agents/basic3D.json b/app/assets/json/agents/basic3D.json index 519777f..f1bc933 100644 --- a/app/assets/json/agents/basic3D.json +++ b/app/assets/json/agents/basic3D.json @@ -6,7 +6,7 @@ "children": [ { "type": "Mesh", - "idSuffix": "head", + "childSuffix": "head", "geometry": { "type": "SphereGeometry", "args": [0.3, 16, 16] }, "material": { "type": "MeshStandardMaterial", "color": "blue" }, "position": [0, 0.5, 0] diff --git a/app/assets/json/arena/arenaConfig1.json b/app/assets/json/arena/arenaConfig1.json new file mode 100644 index 0000000..609789d --- /dev/null +++ b/app/assets/json/arena/arenaConfig1.json @@ -0,0 +1,7 @@ +{ + "arenaSize": { + "x": 100, + "y": 100, + "z": 100 + } +} \ No newline at end of file diff --git a/app/assets/json/threetobus/eventsMapping.example.json b/app/assets/json/threetobus/eventsMapping.example.json new file mode 100644 index 0000000..f7ce0f8 --- /dev/null +++ b/app/assets/json/threetobus/eventsMapping.example.json @@ -0,0 +1,56 @@ +[ + { + "chan": "gps:agents", + "events": [ + { + "eventName": "move", + "mappings": [ + { + "id": "aid", + "assign": { + "position.x": "coords.x", + "position.z": "coords.y", + "position.y": "coords.z" + }, + "tween": true, + "tweenDelay": 1000 + } + ] + } + ] + }, + { + "chan": "agent:*", + "events": [ + { + "eventName": "age", + "mappings": [ + { + "id": "aid", + "assign": { + "fill": { + "arguments": ["age"], + "transformer": "rgb(${Math.round(255 * age / 10)},0,${Math.round(255 * (1 - age / 10))})" + } + } + } + ] + }, + { + "eventName": "rotate", + "mappings": [ + { + "id": "aid", + "assign": { + "rotation.x": "facing.x", + "rotation.y": "facing.y", + "rotation.z": "facing.z" + }, + "tween": true, + "tweenDelay": 500 + } + ] + } + ] + } +] diff --git a/app/assets/json/threetobus/eventsMapping.json b/app/assets/json/threetobus/eventsMapping.json index 205f897..84a7b37 100644 --- a/app/assets/json/threetobus/eventsMapping.json +++ b/app/assets/json/threetobus/eventsMapping.json @@ -3,7 +3,7 @@ "chan": "gps:agents", "events": [ { - "eventName": "moving", + "eventName": "move", "mappings": [ { "id": "aid", @@ -16,35 +16,46 @@ "tweenDelay": 1000 } ] - }, + } + ] + }, + { + "chan": "agents:*", + "events": [ { - "eventName": "rotating", + "eventName": "age", "mappings": [ { "id": "aid", "assign": { - "r": "rotangle" + "material.color": "color" + } + } + ] + }, + { + "eventName": "rotate", + "mappings": [ + { + "id": "aid", + "assign": { + "rotation.x": "facing.x", + "rotation.y": "facing.y", + "rotation.z": "facing.z" }, "tween": true, "tweenDelay": 500 } ] - } - ] - }, - { - "chan": "agent:*", - "events": [ + }, { - "eventName": "aging", + "eventName": "polarize", "mappings": [ { "id": "aid", + "child": "head", "assign": { - "fill": { - "arguments": ["age"], - "transformer": "rgb(${Math.round(255 * age / 10)},0,${Math.round(255 * (1 - age / 10))})" - } + "material.color": "color" } } ] diff --git a/app/assets/styles/app.css b/app/assets/styles/app.css index f155873..da3147c 100755 --- a/app/assets/styles/app.css +++ b/app/assets/styles/app.css @@ -87,7 +87,7 @@ body[eicapp] { } [eicapp] .app-workspace .window { position: fixed; - padding: 3px; + padding: 5px; background: var(--app-color-secondary); box-shadow: 0 0 13px rgba(147, 255, 255, 0.55); right: auto; @@ -107,6 +107,7 @@ body[eicapp] { } [eicapp] .app-workspace .window > header { flex: 0 0 auto; + width: 100%; } [eicapp] .app-workspace .window > header h1 { padding: var(--eicui-base-spacing-xs); @@ -127,13 +128,11 @@ body[eicapp] { [eicapp] .app-workspace .window > header .controls button.expand { display: inline-flex; } [eicapp] .app-workspace .window > header .controls button.shrink { display: none; } [eicapp] .app-workspace .window > section { - padding: var(--eicui-base-spacing-2xs) var(--eicui-base-spacing-2xs); cursor: default; - margin: 0; overflow: hidden; transition: all 0.5s; flex: 1 1 auto; - + width: 100%; } [eicapp] .app-workspace .window[device="tablet"] > section { padding: 0; @@ -189,35 +188,35 @@ body[eicapp] { z-index: 2; } [eicapp] .app-workspace .window .handle[data-side="n"] { - top: -5px; left: 5px; right: 5px; height: 10px; + top: 0; left: 6px; right: 6px; height: 5px; cursor: n-resize; } [eicapp] .app-workspace .window .handle[data-side="e"] { - top: 5px; right: -5px; bottom: 5px; width: 10px; + top: 6px; right: 0; bottom: 6px; width: 5px; cursor: e-resize; } [eicapp] .app-workspace .window .handle[data-side="w"] { - top: 5px; left: -5px; bottom: 5px; width: 10px; + top: 6px; left: 0; bottom: 6px; width: 5px; cursor: w-resize; } [eicapp] .app-workspace .window .handle[data-side="nw"] { - top: -5px; left: -5px; width: 10px; height: 10px; - cursor: nw-resize; + top: 0px; left: 0px; width: 6px; height: 6px; + cursor: nw-resize; } [eicapp] .app-workspace .window .handle[data-side="ne"] { - top: -5px; right: -5px; width: 10px; height: 10px; + top: 0; right: 0; width: 6px; height: 6px; cursor: ne-resize; } [eicapp] .app-workspace .window .handle[data-side="s"] { - bottom: -5px; left: 5px; right: 5px; height: 10px; + bottom: 0; left: 6px; right: 6px; height: 5px; cursor: s-resize; } [eicapp] .app-workspace .window .handle[data-side="sw"] { - bottom: -5px; left: -5px; width: 10px; height: 10px; + bottom: -0; left: 0; width: 6px; height: 6px; cursor: sw-resize; } [eicapp] .app-workspace .window .handle[data-side="se"] { - bottom: -5px; right: -5px; width: 10px; height: 10px; + bottom: 0; right: 0; width: 6px; height: 6px; cursor: se-resize; } diff --git a/app/controllers/dashboard/DashboardsController.js b/app/controllers/dashboard/DashboardsController.js index d2608e7..7600957 100644 --- a/app/controllers/dashboard/DashboardsController.js +++ b/app/controllers/dashboard/DashboardsController.js @@ -2,6 +2,7 @@ class DashboardsController extends EICController { constructor(params) { super(params) + this.arenaConfig = app.Assets.Store.json.arenaConfig this.agentDefs = app.Assets.Store.json.agentDefs this.eventsMapping = app.Assets.Store.json.eventsMapping } @@ -16,6 +17,7 @@ class DashboardsController extends EICController { const ttb = new app.LoadedModules.Threetobus({ eventsMapping: this.eventsMapping, + sceneSize: this.arenaConfig.arenaSize, }) ttb.initScene({ axes: true, @@ -24,27 +26,6 @@ class DashboardsController extends EICController { const m1 = ttb.agentFromJSON('agent42', this.agentDefs.molecule1) ttb.scene.add(m1) - // setTimeout(() => { - // ttb.smoothMove({ - // object: m1, - // dX: 5, - // dY:0, - // dZ:0, - // delay: 1500, - // easing: 'Quadratic', - // easingMode: 'InOut', - // }) - // },3000) - - //TODO : side switches - // window.addEventListener('keydown', (e) => { - // if (e.key.toLowerCase() === 'g') { - // ttb.grid.visible = !grid.visible - // } - // if (e.key.toLowerCase() === 'a') { - // ttb.axes.visible = !axes.visible - // } - // }) //TODO: eventsMapping: address child by suffix in assignations diff --git a/app/controllers/dashboard/DashboardsController.json b/app/controllers/dashboard/DashboardsController.json index dde53ac..9fee6a3 100644 --- a/app/controllers/dashboard/DashboardsController.json +++ b/app/controllers/dashboard/DashboardsController.json @@ -28,6 +28,7 @@ { "id":"sapceViewSetting", "name": "sapceViewSetting.html"} ], "json": [ + {"id":"arenaConfig", "name": "arena/arenaConfig1.json"}, {"id":"agentDefs", "name": "agents/basic3D.json"}, {"id":"eventsMapping", "name": "threetobus/eventsMapping.json"} ] diff --git a/app/libs/myUser.js b/app/libs/myUser.js index 0910995..b7f5fcd 100755 --- a/app/libs/myUser.js +++ b/app/libs/myUser.js @@ -107,6 +107,7 @@ class myUser extends app.LoadedClasses.User { } else if(!jsonresp.payload.authenticated){ document.querySelector('div.loginerr').classList.remove('show') document.getElementById('login-dialog').classList.add('show') + document.querySelector('input[name="username"]').focus() document.getElementById('login-dialog').addEventListener('keyup',(event)=>{ if(event.key=='Enter') this.launchLogin(callBack) }) diff --git a/app/thirdparty/Threetobus/threetobus.module.js b/app/thirdparty/Threetobus/threetobus.module.js index 60d539d..a5f2196 100644 --- a/app/thirdparty/Threetobus/threetobus.module.js +++ b/app/thirdparty/Threetobus/threetobus.module.js @@ -7,11 +7,19 @@ export class Threetobus{ constructor(options){ this._curEventsMapping = [] this._stagedEventsMapping = options.eventsMapping + this.sceneSize = options.sceneSize this.commitConfig() this.cameras = {} this.renderers = [] this.tweensRegistry = {} + app.events.addEvent('MessageBus.Connected', this.busReconnect.bind(this), 'threetobus') + + } + + busReconnect(){ + this.commitConfig() // To resubscribe... + //TODO : Not ideal because if we're in the middle of non-commited changes... } get EventsMapping() { return this._stagedEventsMapping } @@ -22,7 +30,6 @@ export class Threetobus{ const chansToAdd = [] const chansToKeep = [] for(const chanObj of this._stagedEventsMapping){ - console.log('staged chan:',chanObj.chan,' current ones:', this._curEventsMapping.map(item => item.chan)) if(this._curEventsMapping.map(item => item.chan).includes(chanObj.chan)) chansToKeep.push(chanObj) else chansToAdd.push(chanObj) } @@ -70,12 +77,15 @@ export class Threetobus{ processBusEvent(eventType, chan, payload, userId, x){ - const chanObj = this._curEventsMapping.find(item => item.chan==chan) - if(!chanObj) return + const chanObj = this._curEventsMapping.find(item => app.MessageBus.chanMatch(chan, item.chan)) + if(!chanObj) { console.warn('Not a configured chan!'); return } const eventObj = chanObj.events.find(item => item.eventName==eventType) - if(!eventObj) return + if(!eventObj) { console.warn('Not a configured event!'); return } for(const mapping of eventObj.mappings){ - const id = this.getValueByPath(payload, mapping.id) + let id = this.getValueByPath(payload, mapping.id) + //TODO Child selection is static in mapping... does it make sense to also have the event select the child ? + // if yes : how to discriminate static value from event-mapping definition ? + if(mapping.child) id += '_'+mapping.child if(id){ const obj3D = this.scene.getObjectByName(id) this.assignFromConfig(payload, mapping, obj3D) @@ -83,28 +93,46 @@ export class Threetobus{ } } - assignFromConfig(payload, mapping, obj3D) { - const toTween = {} + assignFromConfig(payload, mapping, obj3D) { + const tweenProps = { + position: { + props: {}, + method: this.smoothMove.bind(this), + }, + rotation:{ + props: {}, + method: this.smoothRotate.bind(this), + }, + } for (const [path, rule] of Object.entries(mapping.assign)) { let value - if (typeof rule === 'string') { // plain path + if(typeof rule === 'string') { // plain path value= this.getValueByPath(payload, rule) } else if((typeof(rule) == 'object') && (typeof(rule.transformer) == 'function')) { // transformer const fnargs = (rule.arguments || []).map(arg => this.getValueByPath(payload,arg)) value = rule.transformer(...fnargs) } - if (value !== undefined) { - if(mapping.tween && path.startsWith('position.')){ //TODO allow other tweenables - toTween[path.substring(9)] = value - } else { + if(value !== undefined) { + if(mapping.tween){ + if(path.startsWith('position.')){ + tweenProps.position.props[path.substring(9)] = value + } else if(path.startsWith('rotation.')){ + tweenProps.rotation.props[path.substring(9)] = value + } + } else { this.setProp(obj3D, path, value) } - } + } // else console.warn('Could not get value from rule:',rule) } - if(mapping.tween && (Object.keys(toTween).length>0)){ - toTween.object = obj3D - toTween.delay = mapping.tweenDelay - this.smoothMove(toTween) + if(mapping.tween){ + for(const tweenGroup in tweenProps){ + if((Object.keys(tweenProps[tweenGroup].props).length>0) + && (typeof(tweenProps[tweenGroup].method)=='function')){ + tweenProps[tweenGroup].props.object = obj3D + tweenProps[tweenGroup].props.delay = mapping.tweenDelay + tweenProps[tweenGroup].method(tweenProps[tweenGroup].props) + } // else { console.log('avoided tween', tweenGroup)} + } } } @@ -134,12 +162,12 @@ export class Threetobus{ this.scene = new THREE.Scene() if(options.grid){ - this.grid = new THREE.GridHelper(20, 20, 0x8888AA, 0x8888AA) + this.grid = new THREE.GridHelper(this.sceneSize.x, this.sceneSize.y, 0x8888AA, 0x8888AA) this.grid.layers.set(1) this.scene.add(this.grid) } if(options.axes){ - this.axes = new THREE.AxesHelper(5, 5) + this.axes = new THREE.AxesHelper(this.sceneSize.x/2, this.sceneSize.y/2) this.axes.layers.set(2) this.scene.add(this.axes) } @@ -206,12 +234,12 @@ export class Threetobus{ // Recursively add children if(desc.children) { desc.children.forEach(childDesc => { - const childId = (childDesc.idSuffix) ? `${id}_${childDesc.idSuffix}` : '' + const childId = (childDesc.childSuffix) ? `${id}_${childDesc.childSuffix}` : '' obj.add(this.agentFromJSON(childId, childDesc)) }) } obj.name = id - this.tweensRegistry[id] = { 'move': null } + this.tweensRegistry[id] = { 'move': null, 'rotate': null } return obj } @@ -248,7 +276,42 @@ export class Threetobus{ .easing(TWEEN.Easing[options.easing][options.easingMode]) .onComplete(() => this.tweensRegistry[options.object.name]['move']=null) .start() - } + } + + smoothRotate(options){ + // options: object, dX, dY, dZ, delay, easing, easingMode + // Absolute: x,y,z angle, in degrees + // Relative: dx,dy,dz angle in deg + // delay: ms + // easings: Linear, Quadratic, Cubic, Quartic, Quintic, Sinusoidal, Exponential, Circular, Elastic, Back, Bounce + // easingMode: In → starts slow, accelerates towards the end. + // Out → starts fast, decelerates smoothly. + // InOut → slow at both ends, faster in the middle. + options.easing = options.easing ? options.easing : 'Quadratic' + options.easingMode = options.easingMode ? options.easingMode : 'InOut' + + let newX = parseFloat(options.x) + newX = isNaN(newX) ? options.object.rotation.x * Math.PI / 180: newX * Math.PI / 180 + newX += (parseFloat(options.dx) || 0) + + let newY = parseFloat(options.y) + newY = isNaN(newY) ? options.object.rotation.y * Math.PI / 180: newY * Math.PI / 180 + newY += (parseFloat(options.dy) || 0) + + let newZ = parseFloat(options.z) + newZ = isNaN(newZ) ? options.object.rotation.z* Math.PI / 180 : newZ * Math.PI / 180 + newZ += (parseFloat(options.dz) || 0) + + if(this.tweensRegistry[options.object.name]['rotate']) this.tweensRegistry[options.object.name]['rotate'].end() + this.tweensRegistry[options.object.name]['rotate'] = new TWEEN.Tween(options.object.rotation) + .to({ x: newX, + y: newY, + z: newZ, + }, options.delay) + .easing(TWEEN.Easing[options.easing][options.easingMode]) + .onComplete(() => this.tweensRegistry[options.object.name]['rotate']=null) + .start() + } } class RenderingEngine{ @@ -299,4 +362,7 @@ class RenderingEngine{ // Make this module available to common JS if(!app.LoadedModules) app.LoadedModules = {} -app.LoadedModules.Threetobus = Threetobus \ No newline at end of file +app.LoadedModules.Threetobus = Threetobus + + +//TODO resubscribe on connection loss & re-open \ No newline at end of file diff --git a/core/libs/MessageBus.js b/core/libs/MessageBus.js index 6729846..abe1891 100755 --- a/core/libs/MessageBus.js +++ b/core/libs/MessageBus.js @@ -323,7 +323,7 @@ class MessageBus { * Helper method to match a chan with globbing * * @param {string} myChan (no glob) - * @param {string} targetChan (possible glob) + * @param {string} targetChan PATTERN (possible glob) * @returns {boolean} */ chanMatch(myChan, targetChan) { diff --git a/favicon.ico b/favicon.ico index 93e6b24..3ffeb2d 100644 Binary files a/favicon.ico and b/favicon.ico differ