look ma, theres 3D now
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Three.js + OrbitControls (importmap)</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; overflow: hidden; }
|
||||||
|
canvas { display: block; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Import map tells browser where "three" lives -->
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import * as THREE from "three"
|
||||||
|
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/controls/OrbitControls.js"
|
||||||
|
|
||||||
|
// Scene
|
||||||
|
const scene = new THREE.Scene()
|
||||||
|
scene.background = new THREE.Color(0x202080)
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
const perspCam = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
|
||||||
|
perspCam.position.set(3, 3, 5)
|
||||||
|
|
||||||
|
const aspect = window.innerWidth / window.innerHeight
|
||||||
|
const frustumSize = 10
|
||||||
|
const cam2d = new THREE.OrthographicCamera(
|
||||||
|
-frustumSize * aspect / 2,
|
||||||
|
frustumSize * aspect / 2,
|
||||||
|
frustumSize / 2,
|
||||||
|
-frustumSize / 2,
|
||||||
|
0.1,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
cam2d.position.set(0, 100, 0)
|
||||||
|
|
||||||
|
cam2d.lookAt(0, 0, 0)
|
||||||
|
|
||||||
|
let activeCam = perspCam
|
||||||
|
|
||||||
|
// Renderer
|
||||||
|
const renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
document.body.appendChild(renderer.domElement)
|
||||||
|
|
||||||
|
// Cube
|
||||||
|
const geometry = new THREE.BoxGeometry()
|
||||||
|
const material = new THREE.MeshStandardMaterial({ color: 'red' })
|
||||||
|
const cube = new THREE.Mesh(geometry, material)
|
||||||
|
scene.add(cube)
|
||||||
|
cube.position.x+=2
|
||||||
|
|
||||||
|
|
||||||
|
// Light
|
||||||
|
const light = new THREE.DirectionalLight(0xffffff, 1)
|
||||||
|
light.position.set(5, 5, 5)
|
||||||
|
light.intensity = 2
|
||||||
|
scene.add(light)
|
||||||
|
this.scene.add(new THREE.AmbientLight(0xffffff, 0.4))
|
||||||
|
|
||||||
|
const snowman = new THREE.Group()
|
||||||
|
|
||||||
|
// Bottom sphere
|
||||||
|
const bottom = new THREE.Mesh(
|
||||||
|
new THREE.SphereGeometry(1, 32, 32),
|
||||||
|
new THREE.MeshStandardMaterial({ color: 'white' })
|
||||||
|
)
|
||||||
|
snowman.add(bottom)
|
||||||
|
|
||||||
|
// Middle sphere
|
||||||
|
const middle = new THREE.Mesh(
|
||||||
|
new THREE.SphereGeometry(0.7, 32, 32),
|
||||||
|
new THREE.MeshStandardMaterial({ color: 'white' })
|
||||||
|
)
|
||||||
|
middle.position.y = 1.3
|
||||||
|
snowman.add(middle)
|
||||||
|
|
||||||
|
// Head
|
||||||
|
const head = new THREE.Mesh(
|
||||||
|
new THREE.SphereGeometry(0.5, 32, 32),
|
||||||
|
new THREE.MeshStandardMaterial({ color: 'white' })
|
||||||
|
)
|
||||||
|
head.position.y = 2.3
|
||||||
|
snowman.add(head)
|
||||||
|
scene.add(new THREE.AmbientLight(0xffffff, 0.5) )
|
||||||
|
// Add the group to the scene
|
||||||
|
scene.add(snowman)
|
||||||
|
|
||||||
|
// Optional: expose to console
|
||||||
|
window.snowman = snowman
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
const controls = new OrbitControls(perspCam, renderer.domElement)
|
||||||
|
|
||||||
|
// Resize handler
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
perspCam.aspect = window.innerWidth / window.innerHeight
|
||||||
|
perspCam.updateProjectionMatrix()
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Animation loop
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate)
|
||||||
|
cube.rotation.x += 0.01
|
||||||
|
cube.rotation.y += 0.01
|
||||||
|
controls.update()
|
||||||
|
renderer.render(scene, activeCam)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key.toLowerCase() === 'o') {
|
||||||
|
activeCam = (activeCam === perspCam ? cam2d : perspCam)
|
||||||
|
console.log('Switched to', activeCam.isPerspectiveCamera ? 'Perspective' : 'Orthographic')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
animate()
|
||||||
|
|
||||||
|
window.THREE = THREE
|
||||||
|
window.scene = scene
|
||||||
|
window.renderer = renderer
|
||||||
|
window.cube = cube
|
||||||
|
window.light = light
|
||||||
|
light.intensity = 2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -95,7 +95,7 @@ body[eicapp] {
|
|||||||
bottom: auto;
|
bottom: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: grid;
|
display: block;
|
||||||
grid-template-rows: min-content 1fr;
|
grid-template-rows: min-content 1fr;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
|
|||||||
@@ -16,8 +16,9 @@
|
|||||||
"/helpers/basicDialogs",
|
"/helpers/basicDialogs",
|
||||||
"/helpers/validators",
|
"/helpers/validators",
|
||||||
"/helpers/activeAttributes",
|
"/helpers/activeAttributes",
|
||||||
"/thirdparty/Snaptobus/snap.svg-min",
|
"/thirdparty/Threetobus/three.module",
|
||||||
"/thirdparty/Snaptobus/snaptobus"
|
"/thirdparty/Threetobus/OrbitControls.module",
|
||||||
|
"/thirdparty/Threetobus/threetobus.module"
|
||||||
],
|
],
|
||||||
"assets": {
|
"assets": {
|
||||||
"styles": [
|
"styles": [
|
||||||
|
|||||||
Vendored
+93
@@ -0,0 +1,93 @@
|
|||||||
|
//0. Add you dependencies in your controller.json :
|
||||||
|
// { ...
|
||||||
|
// "controllerDependencies": [
|
||||||
|
// ...
|
||||||
|
// "/thirdparty/Snaptobus/snap.svg-min",
|
||||||
|
// "/thirdparty/Snaptobus/snaptobus"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// 1. Create your sprites :
|
||||||
|
agentTypes = {
|
||||||
|
molecule1:{
|
||||||
|
type: 'circle',
|
||||||
|
attrs: {
|
||||||
|
r: 10,
|
||||||
|
fill: '#BFB',
|
||||||
|
stroke: "#0A0",
|
||||||
|
strokeWidth: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
molecule2:{
|
||||||
|
type: 'circle',
|
||||||
|
attrs: {
|
||||||
|
r: 10,
|
||||||
|
fill: '#BBF',
|
||||||
|
stroke: "#00A",
|
||||||
|
strokeWidth: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. instantiate Snaptobus with the SVG playground selector, the sprites definitions,
|
||||||
|
// and the configuration mapping bus chans, bus event, and events playloads to SNap attributes
|
||||||
|
// You can assign a path in the event payload, or a transformer function like :
|
||||||
|
// fill: {
|
||||||
|
// arguments: [ 'age' ], // What to give from the event as function's params
|
||||||
|
// transformer: i => `rgb(${Math.round(255 * i / 10)},0,${Math.round(255 * (1 - i / 10))})`
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
|
||||||
|
this.snaptobus = new Snaptobus({
|
||||||
|
snap: Snap("svg.stb"),
|
||||||
|
spriteDefs: this.agentTypes,
|
||||||
|
busConfig: [
|
||||||
|
{
|
||||||
|
chan: 'gps:agents', // What to subscribe to
|
||||||
|
events: [ // What to select on this chan
|
||||||
|
{ eventName: 'moving', // which event will trigger a change
|
||||||
|
snaps: [
|
||||||
|
{
|
||||||
|
selector: '#${aid}', // what svg elements do we change ?
|
||||||
|
assign: { // what do we change and with what from the payload ?
|
||||||
|
cx: 'attrs.x',
|
||||||
|
cy: 'attrs.y',
|
||||||
|
},
|
||||||
|
animate: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
//3. Create your sprites
|
||||||
|
this.createAgent('molecule2', 'agent42', 100,100)
|
||||||
|
createAgent(agentType, id, x, y){
|
||||||
|
if(!Object.keys(this.agentTypes).includes(agentType)) return
|
||||||
|
this.agentTypes[agentType]
|
||||||
|
const svgAgent = this.snaptobus.snap[this.agentTypes[agentType].type]().attr(this.agentTypes[agentType].attrs)
|
||||||
|
svgAgent.attr({
|
||||||
|
id: id,
|
||||||
|
cx: x,
|
||||||
|
cy:y,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//4. Send a bus event like : (this is a bmsg)
|
||||||
|
// {
|
||||||
|
// "channel":"gps:agents",
|
||||||
|
// "packet":{
|
||||||
|
// "eventType": "moving",
|
||||||
|
// "payload": {
|
||||||
|
// "aid": "agent42",
|
||||||
|
// "attrs": {
|
||||||
|
// "x": "950",
|
||||||
|
// "y": "650"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "sender": "toto"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
+1417
File diff suppressed because it is too large
Load Diff
+58773
File diff suppressed because one or more lines are too long
+18251
File diff suppressed because one or more lines are too long
+87
@@ -0,0 +1,87 @@
|
|||||||
|
import * as THREE from './three.module.js';
|
||||||
|
import { OrbitControls } from './OrbitControls.module.js';
|
||||||
|
|
||||||
|
export class Threetobus{
|
||||||
|
|
||||||
|
constructor(canvasEl){
|
||||||
|
this.canvasEl = canvasEl
|
||||||
|
}
|
||||||
|
|
||||||
|
init(){
|
||||||
|
// Scene
|
||||||
|
this.scene = new THREE.Scene()
|
||||||
|
//scene.background = new THREE.Color(0x202080)
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
|
||||||
|
this.camera.position.set(3, 3, 5)
|
||||||
|
|
||||||
|
// Renderer
|
||||||
|
this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvasEl })
|
||||||
|
|
||||||
|
//renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
|
||||||
|
// Cube
|
||||||
|
const geometry = new THREE.BoxGeometry()
|
||||||
|
const material = new THREE.MeshStandardMaterial({ color: 'red' })
|
||||||
|
this.cube = new THREE.Mesh(geometry, material)
|
||||||
|
this.scene.add(this.cube)
|
||||||
|
this.cube.position.x+=2
|
||||||
|
|
||||||
|
|
||||||
|
// Light
|
||||||
|
const light = new THREE.DirectionalLight(0xffffff, 1)
|
||||||
|
light.position.set(5, 5, 5)
|
||||||
|
light.intensity = 2
|
||||||
|
this.scene.add(light)
|
||||||
|
this.scene.add(new THREE.AmbientLight(0xffffff, 0.4))
|
||||||
|
|
||||||
|
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
|
||||||
|
this.renderer.render(this.scene, this.camera)
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.camera.aspect = window.innerWidth / window.innerHeight
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
})
|
||||||
|
this.animate()
|
||||||
|
|
||||||
|
this._render()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_resizeRendererToDisplaySize() {
|
||||||
|
const canvas = this.renderer.domElement
|
||||||
|
const width = canvas.clientWidth
|
||||||
|
const height = canvas.clientHeight
|
||||||
|
if (canvas.width !== width || canvas.height !== height) {
|
||||||
|
this.renderer.setSize(width, height, false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_render() {
|
||||||
|
if (this._resizeRendererToDisplaySize()) {
|
||||||
|
this.camera.aspect = this.renderer.domElement.clientWidth / this.renderer.domElement.clientHeight
|
||||||
|
this.camera.updateProjectionMatrix()
|
||||||
|
}
|
||||||
|
this.renderer.render(this.scene, this.camera)
|
||||||
|
requestAnimationFrame(this._render.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
requestAnimationFrame(this.animate.bind(this))
|
||||||
|
this.cube.rotation.x += 0.01
|
||||||
|
this.cube.rotation.y += 0.01
|
||||||
|
this.controls.update()
|
||||||
|
this.renderer.render(this.scene, this.camera)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Make this module available to common JS
|
||||||
|
if(!app.LoadedModules) app.LoadedModules = {}
|
||||||
|
app.LoadedModules.Threetobus = Threetobus
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
height:100%;
|
height:100%;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
|
canvas[data-output="paper43"]{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<article eiccard media class="mainDashboard">
|
<canvas data-output="paper43"></canvas>
|
||||||
<section>
|
|
||||||
<svg class="stb" width="100%" height="100%"></svg>
|
|
||||||
</section>
|
|
||||||
</article>
|
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ class MainDashboardView extends EICDomContent {
|
|||||||
super()
|
super()
|
||||||
Object.assign(this, app.helpers.activeAttributes)
|
Object.assign(this, app.helpers.activeAttributes)
|
||||||
//this.tileMarkup = app.Assets.Store.html['/app/assets/html/mailing/tile.html']
|
//this.tileMarkup = app.Assets.Store.html['/app/assets/html/mailing/tile.html']
|
||||||
this.snap = null
|
|
||||||
this.snaptobus = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DOMContentLoaded(options) {
|
DOMContentLoaded(options) {
|
||||||
@@ -35,38 +33,10 @@ class MainDashboardView extends EICDomContent {
|
|||||||
const components = ui.eicfy(this.el)
|
const components = ui.eicfy(this.el)
|
||||||
this.setupTriggers(components)
|
this.setupTriggers(components)
|
||||||
this.setupRefs(components)
|
this.setupRefs(components)
|
||||||
this.snaptobus = new Snaptobus({
|
|
||||||
snap: Snap("svg.stb"),
|
this.ttb = new app.LoadedModules.Threetobus(this.outputs.paper43)
|
||||||
spriteDefs: this.agentTypes,
|
this.ttb.init()
|
||||||
busConfig: [
|
|
||||||
{
|
|
||||||
chan: 'gps:agents', // What to subscribe to
|
|
||||||
events: [ // What to select on this chan
|
|
||||||
{ eventName: 'moving', // which event will trigger a change
|
|
||||||
snaps: [
|
|
||||||
{
|
|
||||||
selector: '#${aid}', // what svg elements do we change ?
|
|
||||||
assign: { // what do we change and with what from the payload ?
|
|
||||||
cx: 'attrs.x',
|
|
||||||
cy: 'attrs.y',
|
|
||||||
},
|
|
||||||
animate: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
// var agent = this.snap.circle(150, 150, 20);
|
|
||||||
// agent.attr({
|
|
||||||
// id: 'agent42',
|
|
||||||
// fill: "#BFB",
|
|
||||||
// stroke: "#0A0",
|
|
||||||
// strokeWidth: 2
|
|
||||||
// });
|
|
||||||
this.createAgent('molecule2', 'agent42', 100,100)
|
|
||||||
//setTimeout(this.moveit.bind(this), 3000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DOMContentFocused(options) {
|
DOMContentFocused(options) {
|
||||||
@@ -90,22 +60,6 @@ class MainDashboardView extends EICDomContent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// moveit(){
|
|
||||||
// var myCircle = this.snap.select('#agent42')
|
|
||||||
|
|
||||||
// var newx = parseInt(myCircle.attr('cx')) + 600
|
|
||||||
// var newy = parseInt(myCircle.attr('cy')) + 200
|
|
||||||
|
|
||||||
// // animate translate
|
|
||||||
// myCircle.animate(
|
|
||||||
// {
|
|
||||||
// cx: newx,
|
|
||||||
// cy: newy,
|
|
||||||
// },
|
|
||||||
// 1000, // duration in ms
|
|
||||||
// mina.linear // easing
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.registerClass('MainDashboardView', MainDashboardView)
|
app.registerClass('MainDashboardView', MainDashboardView)
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class Sparc {
|
|||||||
console.log('back from checkAuthenticated')
|
console.log('back from checkAuthenticated')
|
||||||
if(this.User.isAuthenticated) {
|
if(this.User.isAuthenticated) {
|
||||||
console.log('authenticated OK')
|
console.log('authenticated OK')
|
||||||
window.onbeforeunload = () => "Do you really want to leave EISMEA application ?";
|
window.onbeforeunload = () => "Do you really want to leave this application ?";
|
||||||
|
|
||||||
// Loading base Classes, who can now use app.config.
|
// Loading base Classes, who can now use app.config.
|
||||||
// If later we do multi-app then one config per app, and baseclass instantiated with its own config.
|
// If later we do multi-app then one config per app, and baseclass instantiated with its own config.
|
||||||
@@ -384,7 +384,7 @@ class Loader {
|
|||||||
static loadScript(src) {
|
static loadScript(src) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement('script')
|
const script = document.createElement('script')
|
||||||
script.type = 'text/javascript'
|
script.type = src.split('?')[0].endsWith('.module.js')? 'module' : 'text/javascript'
|
||||||
script.onload = resolve
|
script.onload = resolve
|
||||||
script.onerror = reject
|
script.onerror = reject
|
||||||
script.src = src
|
script.src = src
|
||||||
@@ -464,7 +464,7 @@ class Loader {
|
|||||||
* Load a list of scripts.
|
* Load a list of scripts.
|
||||||
* @static
|
* @static
|
||||||
* @param {string} basepath - base path (something like /sccripts/)
|
* @param {string} basepath - base path (something like /sccripts/)
|
||||||
* @param {Array<string>} scripts - array of scripts to load
|
* @param {Array<string>} scripts - array of scripts to load. to load a module instead of a JS, just end the filename with '.module.js) like: "mysuperlib.module.js"
|
||||||
* @param {(string|object)} dependencies - Last minute Dependancies :tree object ( like {'scriptName': ['deps1', 'dep2,...]} ) or path to its json file.
|
* @param {(string|object)} dependencies - Last minute Dependancies :tree object ( like {'scriptName': ['deps1', 'dep2,...]} ) or path to its json file.
|
||||||
* @param {function} callback - Called when everything is loaded
|
* @param {function} callback - Called when everything is loaded
|
||||||
* @returns {Promise} A promise resolved whn-en all is loaded
|
* @returns {Promise} A promise resolved whn-en all is loaded
|
||||||
@@ -494,6 +494,7 @@ class Loader {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a list of views.
|
* Load a list of views.
|
||||||
* @static
|
* @static
|
||||||
|
|||||||
Reference in New Issue
Block a user