look ma, theres 3D now

This commit is contained in:
STEINNI
2025-09-25 20:47:58 +00:00
parent 7c6047462f
commit 73e2b5a762
11 changed files with 78778 additions and 61 deletions
+140
View File
@@ -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>
+1 -1
View File
@@ -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": [
+93
View File
@@ -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"
// }
// }
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+87
View File
@@ -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
+5 -5
View File
@@ -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>
+4 -50
View File
@@ -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)
+4 -3
View File
@@ -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