Avatar redesign: armoured robot instead of primitive boxes

New primitives (tapered capsule with rounded caps, superellipsoid
rounded box) and a full body redesign: three-segment torso (chest plate,
dark abdomen, pelvis plate) with glowing core and belt light, helmet
with visor/jaw/ears/antenna, shoulder pads, tapered capsule limbs with
elbow/knee joints, hand plates with wrist lights, boots with toe lights.
Two-tone armour tinted by the palette, emissive details in colorB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Fable 5 committed 2026-07-30 20:44:43 +02:00
1 parent a0edb14d1e
commit 88838792e1
2 files changed
+86 -25

No files matched your search

+46 -23
View File
@@ -1042,41 +1042,64 @@ class InteractiveSim {
const hp2 = hipsVis ? W(24) : [neck[0]+shw*0.28, neck[1]-shw*1.25, neck[2]]; const hp2 = hipsVis ? W(24) : [neck[0]+shw*0.28, neck[1]-shw*1.25, neck[2]];
const pelvis = mid(hp1, hp2); const pelvis = mid(hp1, hp2);
const t = shw; const t = shw;
const metal = [0.60, 0.64, 0.72], dark = [0.28, 0.30, 0.36]; // two-tone armour + palette-emissive details
const ca = e.colorA || [0.05, 0, 0.2];
const acc = e.colorB || [0.2, 1, 1]; const acc = e.colorB || [0.2, 1, 1];
const metal = [0.60+ca[0]*0.18, 0.64+ca[1]*0.18, 0.72+ca[2]*0.18];
const dark = [0.15, 0.16, 0.21]; // undersuit / joints
const dark2 = [0.30, 0.32, 0.40]; // secondary plates
const parts = []; const parts = [];
const limb = (a, b, r, col) => parts.push({ a, b, rx: r, rz: r, kind: 'cube', col }); const seg = (a, b, r, col, kind) => parts.push({ a, b, rx: r, rz: r, kind: kind || 'caps', col });
const ball = (a, r, col) => parts.push({ a, b: null, rx: r, kind: 'sphere', col }); const ball = (a, r, col) => parts.push({ a, b: null, rx: r, kind: 'sphere', col });
// torso + chest light const along = (a, b, k) => [a[0]+(b[0]-a[0])*k, a[1]+(b[1]-a[1])*k, a[2]+(b[2]-a[2])*k];
parts.push({ a: neck, b: pelvis, rx: shw*0.52, rz: shw*0.28, kind: 'cube', col: metal }); // --- torso in three segments: chest plate, abdomen, pelvis
ball(mid(neck, mid(neck, pelvis)), t*0.10, acc); const chestC = along(neck, pelvis, 0.30);
// head with visor + antenna parts.push({ a: along(neck, pelvis, 0.02), b: along(neck, pelvis, 0.56),
rx: shw*0.55, rz: t*0.32, kind: 'rbox', col: metal });
parts.push({ a: along(neck, pelvis, 0.52), b: along(neck, pelvis, 0.80),
rx: shw*0.36, rz: t*0.26, kind: 'rbox', col: dark });
parts.push({ a: along(neck, pelvis, 0.78), b: along(neck, pelvis, 1.06),
rx: shw*0.45, rz: t*0.28, kind: 'rbox', col: dark2 });
// glowing chest core + belt light
ball([chestC[0], chestC[1], chestC[2]+t*0.28], t*0.09, acc);
ball([pelvis[0], pelvis[1]+t*0.02, pelvis[2]+t*0.24], t*0.05, acc);
// --- head: neck, helmet, visor, jaw, ears, antenna
const nose = L[0].vis > 0.4 ? W(0) : [neck[0], neck[1]+t*0.55, neck[2]]; const nose = L[0].vis > 0.4 ? W(0) : [neck[0], neck[1]+t*0.55, neck[2]];
const headC = [nose[0], nose[1] + t*0.05, nose[2]]; const headC = [nose[0], nose[1] + t*0.06, nose[2]];
parts.push({ a: headC, b: null, rx: t*0.34, ry: t*0.38, rz: t*0.30, kind: 'cube', col: metal }); seg(neck, headC, t*0.10, dark);
parts.push({ a: [headC[0], headC[1]+t*0.02, headC[2]+t*0.30], b: null, parts.push({ a: headC, b: null, rx: t*0.30, ry: t*0.30, rz: t*0.28, kind: 'rbox', col: metal });
rx: t*0.24, ry: t*0.08, rz: t*0.06, kind: 'cube', col: acc }); parts.push({ a: [headC[0], headC[1]+t*0.03, headC[2]+t*0.26], b: null,
ball([headC[0], headC[1]+t*0.52, headC[2]], t*0.07, acc); rx: t*0.215, ry: t*0.085, rz: t*0.06, kind: 'rbox', col: acc });
// arms (shoulder->elbow->wrist), glowing hands parts.push({ a: [headC[0], headC[1]-t*0.20, headC[2]+t*0.18], b: null,
rx: t*0.16, ry: t*0.075, rz: t*0.10, kind: 'rbox', col: dark2 });
ball([headC[0]-t*0.30, headC[1], headC[2]], t*0.085, dark2);
ball([headC[0]+t*0.30, headC[1], headC[2]], t*0.085, dark2);
seg([headC[0]+t*0.16, headC[1]+t*0.26, headC[2]], [headC[0]+t*0.22, headC[1]+t*0.52, headC[2]], t*0.025, dark2);
ball([headC[0]+t*0.22, headC[1]+t*0.55, headC[2]], t*0.05, acc);
// --- arms: shoulder pads, tapered capsules, elbow, hand plate
const arm = (s, elI, wrI) => { const arm = (s, elI, wrI) => {
ball([s[0], s[1]+t*0.04, s[2]], t*0.19, metal); // shoulder pad
if (L[elI].vis < 0.4 || L[wrI].vis < 0.4) return; if (L[elI].vis < 0.4 || L[wrI].vis < 0.4) return;
const el = W(elI), wr = W(wrI); const el = W(elI), wr = W(wrI);
limb(s, el, t*0.14, metal); seg(s, el, t*0.115, dark); // upper arm
limb(el, wr, t*0.12, metal); ball(el, t*0.10, dark2); // elbow
ball(el, t*0.15, dark); seg(el, wr, t*0.095, metal); // forearm
ball(wr, t*0.17, acc); const hand = along(el, wr, 1.18);
parts.push({ a: wr, b: hand, rx: t*0.095, rz: t*0.05, kind: 'rbox', col: dark2 });
ball(along(el, wr, 1.10), t*0.045, acc); // wrist light
}; };
arm(sh1, 13, 15); arm(sh2, 14, 16); arm(sh1, 13, 15); arm(sh2, 14, 16);
ball(sh1, t*0.16, dark); ball(sh2, t*0.16, dark); // --- legs: tapered capsules, knee, boot with toe
// legs with feet (only when actually visible: seated = bust only)
const leg = (h, knI, anI) => { const leg = (h, knI, anI) => {
if (!hipsVis || L[knI].vis < 0.4 || L[anI].vis < 0.4) return; if (!hipsVis || L[knI].vis < 0.4 || L[anI].vis < 0.4) return;
const kn = W(knI), an = W(anI); const kn = W(knI), an = W(anI);
limb(h, kn, t*0.16, metal); seg(h, kn, t*0.15, metal); // thigh
limb(kn, an, t*0.13, metal); ball(kn, t*0.115, dark2); // knee
ball(kn, t*0.16, dark); seg(kn, an, t*0.115, dark); // shin
parts.push({ a: [an[0], an[1]-t*0.06, an[2]+t*0.12], b: null, parts.push({ a: [an[0], an[1]-t*0.04, an[2]-t*0.02],
rx: t*0.14, ry: t*0.08, rz: t*0.22, kind: 'cube', col: dark }); b: [an[0], an[1]-t*0.09, an[2]+t*0.30], rx: t*0.115, rz: t*0.09,
kind: 'rbox', col: metal }); // boot
ball([an[0], an[1]-t*0.06, an[2]+t*0.30], t*0.04, acc); // toe light
}; };
leg(hp1, 25, 27); leg(hp2, 26, 28); leg(hp1, 25, 27); leg(hp2, 26, 28);
this.modelSim.renderAvatar(timeSec, audio, e, canvas, parts); this.modelSim.renderAvatar(timeSec, audio, e, canvas, parts);
+40 -2
View File
@@ -404,7 +404,45 @@ class ModelSim {
} }
} }
} }
this.prims = { cube, sphere: this._primMesh(SP, SN, SI) }; // tapered capsule (limb): rounded ends, slightly narrower at the top
const CP = [], CN = [], CI = [], SEC = 14, CAP = 5;
const prof = [];
for (let i = 0; i <= CAP; i++) { const a = Math.PI/2*(1 - i/CAP);
prof.push({ r: 0.8*Math.cos(a), y: 0.72 + 0.28*Math.sin(a) }); }
prof.push({ r: 1.0, y: -0.72 });
for (let i = 1; i <= CAP; i++) { const a = Math.PI/2*i/CAP;
prof.push({ r: Math.cos(a), y: -0.72 - 0.28*Math.sin(a) }); }
prof.forEach((p, pi) => {
for (let j = 0; j <= SEC; j++) {
const th = j/SEC*Math.PI*2, x = p.r*Math.cos(th), z = p.r*Math.sin(th);
CP.push(x, p.y, z);
// capsule-style normal: radiate from the nearest axis point
const cy = Math.max(-0.72, Math.min(0.72, p.y));
const nl = Math.hypot(x, p.y-cy, z) || 1;
CN.push(x/nl, (p.y-cy)/nl, z/nl);
if (pi < prof.length-1 && j < SEC) {
const a0 = pi*(SEC+1)+j;
CI.push(a0, a0+SEC+1, a0+1, a0+1, a0+SEC+1, a0+SEC+2);
}
}
});
// rounded box: superellipsoid built from the sphere directions
const RP = [], RN = [], RI = [], RST = 10, RSE = 16, EE = 0.42;
const se = (c) => Math.sign(c)*Math.pow(Math.abs(c), EE);
for (let i = 0; i <= RST; i++) {
const ph = i/RST*Math.PI, y = Math.cos(ph), r = Math.sin(ph);
for (let j = 0; j <= RSE; j++) {
const th = j/RSE*Math.PI*2, x = r*Math.cos(th), z = r*Math.sin(th);
RP.push(se(x), se(y), se(z));
RN.push(x, y, z); // smooth rounded-cube shading
if (i < RST && j < RSE) {
const a0 = i*(RSE+1)+j;
RI.push(a0, a0+RSE+1, a0+1, a0+1, a0+RSE+1, a0+RSE+2);
}
}
}
this.prims = { cube, sphere: this._primMesh(SP, SN, SI),
caps: this._primMesh(CP, CN, CI), rbox: this._primMesh(RP, RN, RI) };
} }
// Matrix placing a unit primitive as a limb from a to b (thickness rx/rz) // Matrix placing a unit primitive as a limb from a to b (thickness rx/rz)
@@ -470,7 +508,7 @@ class ModelSim {
gl.uniform1f(this.um.uRim, 0.15 + 0.3*beat); gl.uniform1f(this.um.uRim, 0.15 + 0.3*beat);
gl.uniform1i(this.um.uHasTex, 0); gl.uniform1i(this.um.uHasTex, 0);
for (const spec of parts) { for (const spec of parts) {
const m = this.prims[spec.kind === 'sphere' ? 'sphere' : 'cube']; const m = this.prims[spec.kind] || this.prims.cube;
gl.uniformMatrix4fv(this.um.uModel, false, this._partMatrix(spec)); gl.uniformMatrix4fv(this.um.uModel, false, this._partMatrix(spec));
gl.uniform3fv(this.um.uBase, spec.col); gl.uniform3fv(this.um.uBase, spec.col);
const bind = (buf, loc, n) => { const bind = (buf, loc, n) => {