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