GLB skinning + Mixamo retargeting: rigged models mirror the tracked body
The GLB loader now reads skins (JOINTS_0/WEIGHTS_0, inverseBindMatrices, node hierarchy kept instead of baked); a skinned shader variant blends 4 joint matrices per vertex (up to 80 bones). _computeJoints solves FK retargeting: for each mapped bone (mixamorig arms/forearms/up-legs/legs/ neck/spine) an extra local rotation aligns its chain child with the world direction measured between the tracked landmarks — the user's right side drives the character's Left bones (mirror). Interattivo Avatar: when the loaded GLB has a skeleton it drives the rig (bounding box from CPU-skinned samples, pelvis-follow track), else the procedural armoured robot. Validated with a Mixamo Y-Bot: synthetic raised-arm pose reproduced exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
88838792e1
commit
748d036ff6
2 files changed
+293
-30
No files matched your search
@@ -1031,6 +1031,42 @@ class InteractiveSim {
|
||||
// hidden, so the pelvis is synthesized and the robot shows as a bust.
|
||||
const ok = this.modelSim && L && L[11] && L[11].vis > 0.5 && L[12].vis > 0.5;
|
||||
if (!ok) { this._drawField(7, e, audio, canvas, 0); return; }
|
||||
|
||||
// A rigged GLB is loaded: retarget the tracked skeleton onto its bones
|
||||
// (mirror: the user's right side drives the character's Left bones).
|
||||
if (this.modelSim.hasSkin) {
|
||||
const P = (i) => [L[i].x - 0.5, 0.5 - L[i].y, -(L[i].z || 0)*0.6];
|
||||
const vis = (i) => L[i].vis > 0.4;
|
||||
const dir = (a, b) => {
|
||||
const d = [b[0]-a[0], b[1]-a[1], b[2]-a[2]];
|
||||
const l = Math.hypot(d[0], d[1], d[2]) || 1;
|
||||
return [d[0]/l, d[1]/l, d[2]/l];
|
||||
};
|
||||
const T = {};
|
||||
if (vis(12) && vis(14)) T.LeftArm = dir(P(12), P(14));
|
||||
if (vis(14) && vis(16)) T.LeftForeArm = dir(P(14), P(16));
|
||||
if (vis(11) && vis(13)) T.RightArm = dir(P(11), P(13));
|
||||
if (vis(13) && vis(15)) T.RightForeArm = dir(P(13), P(15));
|
||||
const hips2 = vis(23) && vis(24);
|
||||
if (hips2) {
|
||||
if (vis(26)) T.LeftUpLeg = dir(P(24), P(26));
|
||||
if (vis(26) && vis(28)) T.LeftLeg = dir(P(26), P(28));
|
||||
if (vis(25)) T.RightUpLeg = dir(P(23), P(25));
|
||||
if (vis(25) && vis(27)) T.RightLeg = dir(P(25), P(27));
|
||||
}
|
||||
const sh1p = P(11), sh2p = P(12);
|
||||
const neckP = [(sh1p[0]+sh2p[0])/2, (sh1p[1]+sh2p[1])/2, (sh1p[2]+sh2p[2])/2];
|
||||
if (vis(0)) T.Neck = dir(neckP, P(0));
|
||||
let track = [neckP[0]*1.2, 0];
|
||||
if (hips2) {
|
||||
const h1 = P(23), h2 = P(24);
|
||||
const pelvisP = [(h1[0]+h2[0])/2, (h1[1]+h2[1])/2, (h1[2]+h2[2])/2];
|
||||
T.Spine = dir(pelvisP, neckP);
|
||||
track = [pelvisP[0]*1.4, pelvisP[1]*0.8];
|
||||
}
|
||||
this.modelSim.render(timeSec, audio, e, canvas, { skinTargets: T, track });
|
||||
return;
|
||||
}
|
||||
const S = 2.6;
|
||||
const W = (i) => [(L[i].x - 0.5)*S, (0.5 - L[i].y)*S, -(L[i].z || 0)*1.2];
|
||||
const mid = (p, q) => [(p[0]+q[0])/2, (p[1]+q[1])/2, (p[2]+q[2])/2];
|
||||
|
||||
+257
-30
@@ -48,6 +48,47 @@ function trsToMat(t, r, s) {
|
||||
return m;
|
||||
}
|
||||
|
||||
// mat3 (as 9-float, column major) helpers for the retarget solver
|
||||
function m3FromQuat(q) {
|
||||
const [x,y,z,w] = q;
|
||||
return [1-2*(y*y+z*z), 2*(x*y+z*w), 2*(x*z-y*w),
|
||||
2*(x*y-z*w), 1-2*(x*x+z*z), 2*(y*z+x*w),
|
||||
2*(x*z+y*w), 2*(y*z-x*w), 1-2*(x*x+y*y)];
|
||||
}
|
||||
function m3Mul(a, b) {
|
||||
const o = new Array(9);
|
||||
for (let c = 0; c < 3; c++) for (let r = 0; r < 3; r++)
|
||||
o[c*3+r] = a[r]*b[c*3] + a[3+r]*b[c*3+1] + a[6+r]*b[c*3+2];
|
||||
return o;
|
||||
}
|
||||
function m3ApplyT(m, v) { // transpose(m) * v (inverse for pure rotations)
|
||||
return [m[0]*v[0]+m[1]*v[1]+m[2]*v[2], m[3]*v[0]+m[4]*v[1]+m[5]*v[2], m[6]*v[0]+m[7]*v[1]+m[8]*v[2]];
|
||||
}
|
||||
function v3norm(v) { const l = Math.hypot(v[0],v[1],v[2]) || 1; return [v[0]/l, v[1]/l, v[2]/l]; }
|
||||
// rotation taking unit vector a onto unit vector b
|
||||
function m3FromTo(a, b) {
|
||||
const cx = a[1]*b[2]-a[2]*b[1], cy = a[2]*b[0]-a[0]*b[2], cz = a[0]*b[1]-a[1]*b[0];
|
||||
const d = a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
|
||||
const s2 = cx*cx+cy*cy+cz*cz;
|
||||
if (s2 < 1e-12) {
|
||||
if (d > 0) return [1,0,0, 0,1,0, 0,0,1];
|
||||
return [-1,0,0, 0,1,0, 0,0,-1]; // opposite: 180° around Y
|
||||
}
|
||||
const k = (1-d)/s2;
|
||||
return [d+cx*cx*k, cz+cx*cy*k, -cy+cx*cz*k,
|
||||
-cz+cy*cx*k, d+cy*cy*k, cx+cy*cz*k,
|
||||
cy+cz*cx*k, -cx+cz*cy*k, d+cz*cz*k];
|
||||
}
|
||||
// mat4 rotation part with the scale stripped (for solving in world frames)
|
||||
function m4Rot3(m) {
|
||||
const n = (x,y,z) => { const l = Math.hypot(x,y,z) || 1; return [x/l, y/l, z/l]; };
|
||||
const c0 = n(m[0],m[1],m[2]), c1 = n(m[4],m[5],m[6]), c2 = n(m[8],m[9],m[10]);
|
||||
return [c0[0],c0[1],c0[2], c1[0],c1[1],c1[2], c2[0],c2[1],c2[2]];
|
||||
}
|
||||
function m4FromM3T(r, t) {
|
||||
return new Float32Array([r[0],r[1],r[2],0, r[3],r[4],r[5],0, r[6],r[7],r[8],0, t[0],t[1],t[2],1]);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ shaders
|
||||
const MESH_VERT = `#version 300 es
|
||||
in vec3 aPos; in vec3 aNorm; in vec2 aUV;
|
||||
@@ -61,6 +102,24 @@ void main(){
|
||||
vUv = aUV;
|
||||
gl_Position = uProj * uView * w;
|
||||
}`;
|
||||
// skinned variant: vertices follow up to 4 joint matrices
|
||||
const MAXJ = 80;
|
||||
const SKIN_VERT = `#version 300 es
|
||||
in vec3 aPos; in vec3 aNorm; in vec2 aUV; in vec4 aJ; in vec4 aW;
|
||||
uniform mat4 uProj, uView, uModel;
|
||||
uniform mat4 uJoints[${MAXJ}];
|
||||
uniform float uPulse;
|
||||
out vec3 vN; out vec3 vW; out vec2 vUv;
|
||||
void main(){
|
||||
mat4 sk = aW.x*uJoints[int(aJ.x)] + aW.y*uJoints[int(aJ.y)]
|
||||
+ aW.z*uJoints[int(aJ.z)] + aW.w*uJoints[int(aJ.w)];
|
||||
vec4 w = uModel * sk * vec4(aPos + aNorm*uPulse, 1.0);
|
||||
vW = w.xyz;
|
||||
vN = mat3(uModel) * mat3(sk) * aNorm;
|
||||
vUv = aUV;
|
||||
gl_Position = uProj * uView * w;
|
||||
}`;
|
||||
|
||||
const MESH_FRAG = `#version 300 es
|
||||
precision highp float;
|
||||
in vec3 vN; in vec3 vW; in vec2 vUv;
|
||||
@@ -141,7 +200,17 @@ function parseGLB(buf) {
|
||||
}
|
||||
if (!json || !bin) throw new Error('GLB incompleto');
|
||||
|
||||
// world transform per node (baked into the vertices below)
|
||||
// node table (hierarchy kept for skinning) + world transforms
|
||||
const nodesInfo = json.nodes.map((n, i) => ({
|
||||
name: n.name || ('n' + i),
|
||||
t: n.translation || [0, 0, 0],
|
||||
r: n.rotation || [0, 0, 0, 1],
|
||||
s: n.scale || [1, 1, 1],
|
||||
matrix: n.matrix || null,
|
||||
children: n.children || [],
|
||||
parent: -1
|
||||
}));
|
||||
nodesInfo.forEach((n, i) => n.children.forEach(c => { nodesInfo[c].parent = i; }));
|
||||
const worlds = {};
|
||||
const walk = (ni, parent) => {
|
||||
const node = json.nodes[ni];
|
||||
@@ -154,15 +223,65 @@ function parseGLB(buf) {
|
||||
const scene = json.scenes[json.scene || 0];
|
||||
scene.nodes.forEach(n => walk(n, null));
|
||||
|
||||
// skin (first one): joint node indices + inverse bind matrices
|
||||
let skel = null;
|
||||
if (json.skins && json.skins.length) {
|
||||
const sk = json.skins[0];
|
||||
skel = { nodes: nodesInfo, joints: sk.joints.slice(),
|
||||
ibm: readAccessor(json, bin, sk.inverseBindMatrices).data,
|
||||
roots: scene.nodes.slice() };
|
||||
if (skel.joints.length > MAXJ) throw new Error('scheletro con troppe ossa (' + skel.joints.length + ')');
|
||||
}
|
||||
|
||||
const prims = [];
|
||||
let min = [1e9,1e9,1e9], max = [-1e9,-1e9,-1e9];
|
||||
Object.keys(worlds).forEach(niKey => {
|
||||
const ni = parseInt(niKey, 10);
|
||||
const node = json.nodes[ni];
|
||||
if (node.mesh == null) return;
|
||||
const skinned = skel && node.skin != null;
|
||||
const W = worlds[ni];
|
||||
json.meshes[node.mesh].primitives.forEach(p => {
|
||||
if ((p.mode || 4) !== 4 || p.attributes.POSITION == null) return;
|
||||
if (skinned) {
|
||||
// skinned primitive: keep mesh-space vertices, read joints/weights
|
||||
const pos = readAccessor(json, bin, p.attributes.POSITION).data;
|
||||
const nrm = p.attributes.NORMAL != null
|
||||
? readAccessor(json, bin, p.attributes.NORMAL).data : new Float32Array(pos.length);
|
||||
const uv = p.attributes.TEXCOORD_0 != null
|
||||
? readAccessor(json, bin, p.attributes.TEXCOORD_0).data
|
||||
: new Float32Array(pos.length/3*2);
|
||||
const jr = readAccessor(json, bin, p.attributes.JOINTS_0);
|
||||
const joints = Float32Array.from(jr.data);
|
||||
const wr = readAccessor(json, bin, p.attributes.WEIGHTS_0);
|
||||
let weights = Float32Array.from(wr.data);
|
||||
const ct = wr.acc.componentType;
|
||||
if (ct === 5121) weights = weights.map(v => v/255);
|
||||
else if (ct === 5123) weights = weights.map(v => v/65535);
|
||||
let idxData = null, idxType = 0;
|
||||
if (p.indices != null) {
|
||||
const r = readAccessor(json, bin, p.indices);
|
||||
idxData = r.data instanceof Uint32Array || r.data instanceof Uint16Array
|
||||
? r.data : Uint16Array.from(r.data);
|
||||
idxType = idxData instanceof Uint32Array ? 5125 : 5123;
|
||||
}
|
||||
let texBytes = null, baseColor = [0.75, 0.75, 0.8];
|
||||
const mat = p.material != null ? json.materials[p.material] : null;
|
||||
const pbr = mat && mat.pbrMetallicRoughness || {};
|
||||
if (pbr.baseColorFactor) baseColor = pbr.baseColorFactor.slice(0, 3);
|
||||
if (pbr.baseColorTexture && json.textures && json.images) {
|
||||
const tex = json.textures[pbr.baseColorTexture.index];
|
||||
const img = json.images[tex.source];
|
||||
if (img && img.bufferView != null) {
|
||||
const bv2 = json.bufferViews[img.bufferView];
|
||||
texBytes = { bytes: new Uint8Array(bin, bv2.byteOffset || 0, bv2.byteLength),
|
||||
mime: img.mimeType || 'image/png' };
|
||||
}
|
||||
}
|
||||
prims.push({ pos, nrm, uv, idxData, idxType, baseColor, texBytes,
|
||||
skinned: true, joints, weights });
|
||||
return;
|
||||
}
|
||||
const pos = readAccessor(json, bin, p.attributes.POSITION).data;
|
||||
const nrm = p.attributes.NORMAL != null
|
||||
? readAccessor(json, bin, p.attributes.NORMAL).data
|
||||
@@ -211,7 +330,7 @@ function parseGLB(buf) {
|
||||
});
|
||||
});
|
||||
if (!prims.length) throw new Error('nessuna mesh triangolare nel GLB');
|
||||
return { prims, min, max };
|
||||
return { prims, min, max, skel: prims.some(p => p.skinned) ? skel : null };
|
||||
}
|
||||
|
||||
// Generated fallback: a torus knot, so the family shows something before any
|
||||
@@ -269,6 +388,7 @@ class ModelSim {
|
||||
return p;
|
||||
};
|
||||
this.progMesh = prog(MESH_VERT, MESH_FRAG);
|
||||
this.progSkin = prog(SKIN_VERT, MESH_FRAG);
|
||||
this.progBg = prog(BG_VERT, BG_FRAG);
|
||||
const U = (p, n) => gl.getUniformLocation(p, n);
|
||||
this.um = { uProj: U(this.progMesh,'uProj'), uView: U(this.progMesh,'uView'),
|
||||
@@ -278,6 +398,16 @@ class ModelSim {
|
||||
uColB: U(this.progMesh,'uColB'), uCam: U(this.progMesh,'uCam'),
|
||||
uBeat: U(this.progMesh,'uBeat'), uLevel: U(this.progMesh,'uLevel'),
|
||||
uTreble: U(this.progMesh,'uTreble'), uRim: U(this.progMesh,'uRim') };
|
||||
this.us = {};
|
||||
['uProj','uView','uModel','uPulse','uTex','uHasTex','uBase','uColA','uColB',
|
||||
'uCam','uBeat','uLevel','uTreble','uRim','uJoints'].forEach(n => {
|
||||
this.us[n] = U(this.progSkin, n === 'uJoints' ? 'uJoints[0]' : n);
|
||||
});
|
||||
this.aSkin = { pos: gl.getAttribLocation(this.progSkin,'aPos'),
|
||||
nrm: gl.getAttribLocation(this.progSkin,'aNorm'),
|
||||
uv: gl.getAttribLocation(this.progSkin,'aUV'),
|
||||
j: gl.getAttribLocation(this.progSkin,'aJ'),
|
||||
w: gl.getAttribLocation(this.progSkin,'aW') };
|
||||
this.ub = { uColA: U(this.progBg,'uColA'), uColB: U(this.progBg,'uColB'),
|
||||
uRes: U(this.progBg,'uRes'), uT: U(this.progBg,'uT'),
|
||||
uBass: U(this.progBg,'uBass'), uBeat: U(this.progBg,'uBeat') };
|
||||
@@ -306,6 +436,36 @@ class ModelSim {
|
||||
_upload(model) {
|
||||
const gl = this.gl;
|
||||
this._freeMeshes();
|
||||
this.skel = model.skel || null;
|
||||
this.restJoints = null;
|
||||
if (this.skel) {
|
||||
// rest-pose joint matrices, then a CPU-skinned vertex sample for the
|
||||
// bounding box (skinned vertices live in mesh space until deformed)
|
||||
this.restJoints = this._computeJoints(null);
|
||||
const J = this.restJoints;
|
||||
let mn = [1e9,1e9,1e9], mx = [-1e9,-1e9,-1e9];
|
||||
model.prims.forEach(p => {
|
||||
if (!p.skinned) return;
|
||||
const n = p.pos.length/3;
|
||||
const step = Math.max(1, Math.floor(n/800));
|
||||
for (let i = 0; i < n; i += step) {
|
||||
let x = 0, y = 0, z = 0;
|
||||
const px = p.pos[i*3], py = p.pos[i*3+1], pz = p.pos[i*3+2];
|
||||
for (let k = 0; k < 4; k++) {
|
||||
const w = p.weights[i*4+k];
|
||||
if (!w) continue;
|
||||
const o = p.joints[i*4+k]*16;
|
||||
x += w*(J[o]*px + J[o+4]*py + J[o+8]*pz + J[o+12]);
|
||||
y += w*(J[o+1]*px + J[o+5]*py + J[o+9]*pz + J[o+13]);
|
||||
z += w*(J[o+2]*px + J[o+6]*py + J[o+10]*pz + J[o+14]);
|
||||
}
|
||||
if (x < mn[0]) mn[0] = x; if (x > mx[0]) mx[0] = x;
|
||||
if (y < mn[1]) mn[1] = y; if (y > mx[1]) mx[1] = y;
|
||||
if (z < mn[2]) mn[2] = z; if (z > mx[2]) mx[2] = z;
|
||||
}
|
||||
});
|
||||
model.min = mn; model.max = mx;
|
||||
}
|
||||
const c = [(model.min[0]+model.max[0])/2, (model.min[1]+model.max[1])/2, (model.min[2]+model.max[2])/2];
|
||||
this.center = c;
|
||||
this.radius = Math.max(0.001, Math.hypot(model.max[0]-c[0], model.max[1]-c[1], model.max[2]-c[2]));
|
||||
@@ -318,7 +478,10 @@ class ModelSim {
|
||||
};
|
||||
const m = { vboP: mk(p.pos), vboN: mk(p.nrm), vboU: mk(p.uv),
|
||||
count: p.idxData ? p.idxData.length : p.pos.length/3,
|
||||
idxType: p.idxType, ibo: null, tex: null, baseColor: p.baseColor };
|
||||
idxType: p.idxType, ibo: null, tex: null, baseColor: p.baseColor,
|
||||
skinned: !!p.skinned,
|
||||
vboJ: p.skinned ? mk(p.joints) : null,
|
||||
vboW: p.skinned ? mk(p.weights) : null };
|
||||
if (p.idxData) {
|
||||
m.ibo = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.ibo);
|
||||
@@ -345,6 +508,59 @@ class ModelSim {
|
||||
});
|
||||
}
|
||||
|
||||
get hasSkin() { return !!this.skel; }
|
||||
|
||||
// Joint matrices (world * inverseBind) for the whole skeleton. With
|
||||
// `targets` (base bone name -> world-space direction) an extra local
|
||||
// rotation is solved per bone so that its chain child points along the
|
||||
// target — FK retargeting of the tracked body onto the Mixamo rig.
|
||||
_computeJoints(targets) {
|
||||
const sk = this.skel;
|
||||
const worlds = new Array(sk.nodes.length);
|
||||
const base = (name) => name.split(':').pop().split('.').pop();
|
||||
const CHAIN = { LeftArm: 'LeftForeArm', LeftForeArm: 'LeftHand',
|
||||
RightArm: 'RightForeArm', RightForeArm: 'RightHand',
|
||||
LeftUpLeg: 'LeftLeg', LeftLeg: 'LeftFoot',
|
||||
RightUpLeg: 'RightLeg', RightLeg: 'RightFoot',
|
||||
Neck: 'Head', Spine: 'Spine1' };
|
||||
const visit = (ni, parentWorld) => {
|
||||
const n = sk.nodes[ni];
|
||||
let local;
|
||||
if (n.matrix) {
|
||||
local = new Float32Array(n.matrix);
|
||||
} else {
|
||||
let r3 = m3FromQuat(n.r);
|
||||
if (targets) {
|
||||
const tgt = targets[base(n.name)];
|
||||
const childBase = CHAIN[base(n.name)];
|
||||
if (tgt && childBase) {
|
||||
let ci = -1;
|
||||
for (const c of n.children) if (base(sk.nodes[c].name) === childBase) { ci = c; break; }
|
||||
if (ci >= 0) {
|
||||
const cl = v3norm(sk.nodes[ci].t);
|
||||
const pr = parentWorld ? m4Rot3(parentWorld) : [1,0,0, 0,1,0, 0,0,1];
|
||||
const d = v3norm(m3ApplyT(m3Mul(pr, r3), tgt));
|
||||
r3 = m3Mul(r3, m3FromTo(cl, d));
|
||||
}
|
||||
}
|
||||
}
|
||||
const s = n.s;
|
||||
local = m4FromM3T([r3[0]*s[0], r3[1]*s[0], r3[2]*s[0],
|
||||
r3[3]*s[1], r3[4]*s[1], r3[5]*s[1],
|
||||
r3[6]*s[2], r3[7]*s[2], r3[8]*s[2]], n.t);
|
||||
}
|
||||
const world = parentWorld ? m4mul(parentWorld, local) : local;
|
||||
worlds[ni] = world;
|
||||
n.children.forEach(c => visit(c, world));
|
||||
};
|
||||
sk.roots.forEach(r => visit(r, null));
|
||||
const J = sk.joints.length;
|
||||
const out = new Float32Array(J*16);
|
||||
for (let i = 0; i < J; i++)
|
||||
out.set(m4mul(worlds[sk.joints[i]], sk.ibm.subarray(i*16, i*16+16)), i*16);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Load a GLB from an ArrayBuffer (called via the control panel).
|
||||
setModel(buf) {
|
||||
try {
|
||||
@@ -558,59 +774,70 @@ class ModelSim {
|
||||
// model
|
||||
const asp = canvas.width/Math.max(1, canvas.height);
|
||||
const dist = this.radius*2.6;
|
||||
// live body-driven skinning?
|
||||
const skinnedLive = this.skel && pose && pose.skinTargets;
|
||||
const curJoints = this.skel
|
||||
? (skinnedLive ? this._computeJoints(pose.skinTargets) : this.restJoints)
|
||||
: null;
|
||||
// With a live pose the auto-spin slows right down: the person drives it.
|
||||
const yaw = timeSec*0.45*speed*(pose ? 0.12 : 1) + (pose ? pose.yaw : 0);
|
||||
const eye = [Math.sin(timeSec*0.13)*this.radius*0.35,
|
||||
this.radius*(0.25 + 0.15*Math.sin(timeSec*0.09)), dist];
|
||||
const yaw = skinnedLive ? 0
|
||||
: timeSec*0.45*speed*(pose ? 0.12 : 1) + (pose ? pose.yaw : 0);
|
||||
const eye = [Math.sin(timeSec*0.13)*this.radius*(skinnedLive ? 0 : 0.35),
|
||||
this.radius*(0.25 + (skinnedLive ? 0 : 0.15*Math.sin(timeSec*0.09))), dist];
|
||||
const proj = m4persp(0.72, asp, dist*0.05, dist*4.0);
|
||||
const view = m4lookAt(eye, [0, 0, 0]);
|
||||
const scale = 1 + 0.05*bass + 0.07*beat;
|
||||
const sq = pose ? Math.max(0.7, Math.min(1.3, pose.squash || 1)) : 1;
|
||||
const world = pose
|
||||
? [pose.leanX*this.radius*1.6, (pose.hopY || 0)*this.radius, 0]
|
||||
: [0, 0, 0];
|
||||
const sq = pose && !skinnedLive ? Math.max(0.7, Math.min(1.3, pose.squash || 1)) : 1;
|
||||
const world = skinnedLive
|
||||
? [(pose.track ? pose.track[0] : 0)*this.radius, (pose.track ? pose.track[1] : 0)*this.radius, 0]
|
||||
: (pose ? [pose.leanX*this.radius*1.6, (pose.hopY || 0)*this.radius, 0] : [0, 0, 0]);
|
||||
const model = m4mul(m4mul(m4mul(m4trans(world), m4rotY(yaw)),
|
||||
m4scale3(scale/Math.sqrt(sq), scale*sq, scale/Math.sqrt(sq))),
|
||||
m4trans([-this.center[0], -this.center[1], -this.center[2]]));
|
||||
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.depthFunc(gl.LEQUAL);
|
||||
gl.useProgram(this.progMesh);
|
||||
gl.uniformMatrix4fv(this.um.uProj, false, proj);
|
||||
gl.uniformMatrix4fv(this.um.uView, false, view);
|
||||
gl.uniformMatrix4fv(this.um.uModel, false, model);
|
||||
gl.uniform1f(this.um.uPulse, this.radius*0.01*bass);
|
||||
gl.uniform3fv(this.um.uColA, ca);
|
||||
gl.uniform3fv(this.um.uColB, cb);
|
||||
gl.uniform3fv(this.um.uCam, eye);
|
||||
gl.uniform1f(this.um.uBeat, beat);
|
||||
gl.uniform1f(this.um.uLevel, (audio.level || 0)*mix);
|
||||
gl.uniform1f(this.um.uTreble, (audio.treble || 0)*mix);
|
||||
gl.uniform1f(this.um.uRim, pose ? (pose.rim || 0) : 0);
|
||||
for (const m of this.meshes) {
|
||||
const u = m.skinned ? this.us : this.um;
|
||||
const at = m.skinned ? this.aSkin : this.aMesh;
|
||||
gl.useProgram(m.skinned ? this.progSkin : this.progMesh);
|
||||
gl.uniformMatrix4fv(u.uProj, false, proj);
|
||||
gl.uniformMatrix4fv(u.uView, false, view);
|
||||
gl.uniformMatrix4fv(u.uModel, false, model);
|
||||
gl.uniform1f(u.uPulse, m.skinned ? 0 : this.radius*0.01*bass);
|
||||
gl.uniform3fv(u.uColA, ca);
|
||||
gl.uniform3fv(u.uColB, cb);
|
||||
gl.uniform3fv(u.uCam, eye);
|
||||
gl.uniform1f(u.uBeat, beat);
|
||||
gl.uniform1f(u.uLevel, (audio.level || 0)*mix);
|
||||
gl.uniform1f(u.uTreble, (audio.treble || 0)*mix);
|
||||
gl.uniform1f(u.uRim, pose ? (pose.rim || 0) : 0);
|
||||
if (m.skinned) gl.uniformMatrix4fv(u.uJoints, false, curJoints);
|
||||
const bind = (buf, loc, n) => {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||
gl.enableVertexAttribArray(loc);
|
||||
gl.vertexAttribPointer(loc, n, gl.FLOAT, false, 0, 0);
|
||||
};
|
||||
bind(m.vboP, this.aMesh.pos, 3);
|
||||
bind(m.vboN, this.aMesh.nrm, 3);
|
||||
bind(m.vboU, this.aMesh.uv, 2);
|
||||
bind(m.vboP, at.pos, 3);
|
||||
bind(m.vboN, at.nrm, 3);
|
||||
bind(m.vboU, at.uv, 2);
|
||||
if (m.skinned) { bind(m.vboJ, at.j, 4); bind(m.vboW, at.w, 4); }
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, m.tex || null);
|
||||
gl.uniform1i(this.um.uTex, 0);
|
||||
gl.uniform1i(this.um.uHasTex, m.tex ? 1 : 0);
|
||||
gl.uniform3fv(this.um.uBase, m.baseColor);
|
||||
gl.uniform1i(u.uTex, 0);
|
||||
gl.uniform1i(u.uHasTex, m.tex ? 1 : 0);
|
||||
gl.uniform3fv(u.uBase, m.baseColor);
|
||||
if (m.ibo) {
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.ibo);
|
||||
gl.drawElements(gl.TRIANGLES, m.count, m.idxType === 5125 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT, 0);
|
||||
} else {
|
||||
gl.drawArrays(gl.TRIANGLES, 0, m.count);
|
||||
}
|
||||
gl.disableVertexAttribArray(at.nrm);
|
||||
gl.disableVertexAttribArray(at.uv);
|
||||
if (m.skinned) { gl.disableVertexAttribArray(at.j); gl.disableVertexAttribArray(at.w); }
|
||||
}
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
gl.disableVertexAttribArray(this.aMesh.nrm);
|
||||
gl.disableVertexAttribArray(this.aMesh.uv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user