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) {