From b45c7078dcf512ce15b69a9ee308e79a5ea12d8b Mon Sep 17 00:00:00 2001 From: luciano Date: Fri, 31 Jul 2026 10:59:19 +0200 Subject: [PATCH] Fluid backdrop behind the 3D model New 'Sfondo Modello 3D' selector (Media tab): gradient or any of the five fluid modes. The fluid sim paints the canvas first, then the model renders on top clearing only the depth buffer (overlay mode skips its own backdrop pass). Palette and speed are shared with the preset; persisted and re-sent when the output restarts. Co-Authored-By: Claude Fable 5 --- src/control.html | 9 +++++++++ src/control.js | 12 +++++++++++- src/model3d.js | 14 +++++++++++--- src/output.js | 1 + src/visualizer.js | 10 +++++++++- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/control.html b/src/control.html index 6a08d14..1adf037 100644 --- a/src/control.html +++ b/src/control.html @@ -131,6 +131,15 @@
Il modello ruota a tempo di musica nella famiglia “Modello 3D”.
+ +
diff --git a/src/control.js b/src/control.js index 250f5e5..0bbefeb 100644 --- a/src/control.js +++ b/src/control.js @@ -146,13 +146,23 @@ $('#svg-input').addEventListener('change', (e) => { // --- 3D model (GLB) for the 'Modello 3D' family ----------------------------- // The path persists and is re-sent when the output window (re)starts. let glbPath = localStorage.getItem('glb3d') || ''; -function glbSend() { if (glbPath) send({ type: 'glb', path: glbPath }); } +let glbBg = localStorage.getItem('glbbg') || 'gradient'; +function glbSend() { + if (glbPath) send({ type: 'glb', path: glbPath }); + send({ type: 'modelBg', mode: glbBg }); +} function glbLabel(name) { $('#glb-name').textContent = name ? '🧊 ' + name + ' — attiva la famiglia “Modello 3D” per vederlo.' : 'Il modello ruota a tempo di musica nella famiglia “Modello 3D”.'; } if (glbPath) glbLabel(glbPath.split('/').pop()); +$('#glb-bg').value = glbBg; +$('#glb-bg').addEventListener('change', (e) => { + glbBg = e.target.value; + localStorage.setItem('glbbg', glbBg); + send({ type: 'modelBg', mode: glbBg }); +}); $('#btn-glb-load').addEventListener('click', () => $('#glb-input').click()); $('#glb-input').addEventListener('change', (e) => { const f = e.target.files[0]; diff --git a/src/model3d.js b/src/model3d.js index d4ecac0..f5bb7c8 100644 --- a/src/model3d.js +++ b/src/model3d.js @@ -929,7 +929,9 @@ class ModelSim { // pose (optional, from the camera-interactive mode): { yaw, leanX, hopY, // squash, rim } — extra rotation, sideways lean, jump height (in radii), // vertical squash & stretch, touch-glow 0..1. - render(timeSec, audio, e, canvas, pose) { + // overlay = true: another engine (e.g. the fluid sim) already painted the + // canvas this frame — draw only the model on top (clear depth, no backdrop). + render(timeSec, audio, e, canvas, pose, overlay) { const gl = this.gl; const speed = Math.min(2.5, e.speed || 1); const mix = e.audioMix !== undefined ? e.audioMix : 1; @@ -938,10 +940,15 @@ class ModelSim { gl.viewport(0, 0, canvas.width, canvas.height); gl.disable(gl.BLEND); - gl.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + if (overlay) { + gl.clear(gl.DEPTH_BUFFER_BIT); + } else { + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } // backdrop + if (!overlay) { gl.useProgram(this.progBg); gl.bindBuffer(gl.ARRAY_BUFFER, this.quad); gl.enableVertexAttribArray(this.aBg); @@ -953,6 +960,7 @@ class ModelSim { gl.uniform1f(this.ub.uBass, bass); gl.uniform1f(this.ub.uBeat, beat); gl.drawArrays(gl.TRIANGLES, 0, 3); + } // model const asp = canvas.width/Math.max(1, canvas.height); diff --git a/src/output.js b/src/output.js index 4dd75ca..8661926 100644 --- a/src/output.js +++ b/src/output.js @@ -320,6 +320,7 @@ djv.onControl(async (m) => { djv.report({ type: 'autoVj', on: autoVj }); break; case 'svg': loadCustomTexture(m.dataUrl); break; + case 'modelBg': viz.modelBg = m.mode || 'gradient'; break; case 'glb': try { const bytes = await djv.readFile(m.path); diff --git a/src/visualizer.js b/src/visualizer.js index 940cdfc..684a0f8 100644 --- a/src/visualizer.js +++ b/src/visualizer.js @@ -114,7 +114,15 @@ class Visualizer { if (e.isModel3d && window.ModelSim) { if (!this.model3d) this.model3d = new window.ModelSim(gl); if (this._pendingGlb) { this.model3d.setModel(this._pendingGlb); this._pendingGlb = null; } - this.model3d.render(timeSec, audio, e, this.canvas); + // optional fluid backdrop behind the model (modelBg: ink/fire/ring/...) + const bg = this.modelBg || 'gradient'; + if (bg !== 'gradient' && window.FluidSim) { + if (!this.fluid) this.fluid = new window.FluidSim(gl); + this.fluid.render(timeSec, audio, Object.assign({}, e, { fluidMode: bg }), this.canvas); + this.model3d.render(timeSec, audio, e, this.canvas, null, true); + } else { + this.model3d.render(timeSec, audio, e, this.canvas); + } return; } if (e.isInteractive && window.InteractiveSim) {