Per-model arm spread against limb/body intersection

Dance clips are authored for the standard mannequin: on chunkier models
the same joint angles drive the arms through the torso (the ninja is
clean because its proportions match). New 'Apertura braccia' slider
(0-35deg, saved per model path): the upper arms get an extra outward
rotation about the world Z axis during clip playback (left +, right -),
solved back into each bone's local frame through the parent rotation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Fable 5 committed 2026-07-31 11:58:17 +02:00
1 parent 26dea77c70
commit a69cac0ebd
5 files changed
+47

No files matched your search

+21
View File
@@ -162,6 +162,7 @@ function glbSend() {
glbAnimFiles.forEach(p => send({ type: 'animAdd', path: p }));
send({ type: 'glbAnims', names: glbClipSel });
send({ type: 'modelBpm', bpm: glbBpm });
spreadSend();
}
function renderGlbClips(names) {
const box = $('#glb-anims');
@@ -215,6 +216,8 @@ function renderGlbModels() {
localStorage.setItem('glb3d', glbPath);
send({ type: 'glb', path: glbPath });
glbLabel(mo.name);
spreadUi();
spreadSend();
renderGlbModels();
});
const del = document.createElement('button');
@@ -231,6 +234,16 @@ function renderGlbModels() {
box.appendChild(row);
});
}
// Arm spread, saved per model path (chunky models need more room).
let glbSpreadMap = {};
try { glbSpreadMap = JSON.parse(localStorage.getItem('glbspread') || '{}'); } catch (e) {}
function spreadFor(p) { return glbSpreadMap[p] || 0; }
function spreadUi() {
const v = spreadFor(glbPath);
$('#glb-spread').value = v;
$('#glb-spread-val').textContent = v + '°';
}
function spreadSend() { send({ type: 'modelSpread', deg: spreadFor(glbPath) }); }
function glbLabel(name) {
$('#glb-name').textContent = name
? '🧊 ' + name + ' — attiva la famiglia “Modello 3D” per vederlo.'
@@ -258,6 +271,13 @@ $('#glb-bg').addEventListener('change', (e) => {
send({ type: 'modelBg', mode: glbBg });
});
renderGlbClips([]);
spreadUi();
$('#glb-spread').addEventListener('input', (e) => {
const v = parseInt(e.target.value, 10) || 0;
if (glbPath) { glbSpreadMap[glbPath] = v; localStorage.setItem('glbspread', JSON.stringify(glbSpreadMap)); }
$('#glb-spread-val').textContent = v + '°';
send({ type: 'modelSpread', deg: v });
});
$('#btn-anim-load').addEventListener('click', () => $('#anim-input').click());
$('#anim-input').addEventListener('change', async (e) => {
for (const f of [...e.target.files]) {
@@ -357,6 +377,7 @@ $('#glb-input').addEventListener('change', async (e) => {
glbSend();
glbLabel(f.name.replace(/\.fbx$/i, '.glb'));
addGlbModel(f.name.replace(/\.fbx$/i, '.glb'), p);
spreadUi();
});
// --- Effect sequence (playlist of effects with auto-cycle) ---