Spaces:
Running on T4
Running on T4
File size: 10,161 Bytes
a4f8eb3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | /**
* Skeleton visualization with capsule bones and sphere joints
* Based on HumanML3D skeleton structure (22 joints)
*/
class Skeleton3D {
constructor(scene) {
this.scene = scene;
this.joints = [];
this.bones = [];
// Trail settings
this.trailPoints = []; // Store root positions
this.maxTrailPoints = 200; // Maximum trail length
this.trailLine = null;
this.trailGeometry = null;
this.trailMaterial = null;
// HumanML3D skeleton chains (from render_skeleton.py)
this.chains = [
[0, 2, 5, 8, 11], // Chain 0: spine
[0, 1, 4, 7, 10], // Chain 1: left leg
[0, 3, 6, 9, 12, 15], // Chain 2: right leg + torso
[9, 14, 17, 19, 21], // Chain 3: left arm
[9, 13, 16, 18, 20], // Chain 4: right arm
];
// Convert chains to bone connections
this.boneConnections = [];
for (const chain of this.chains) {
for (let i = 0; i < chain.length - 1; i++) {
this.boneConnections.push([chain[i], chain[i + 1]]);
}
}
// Bone and joint sizes - stick figure style (thin and small)
this.boneRadius = 0.015; // Thin cylinder
this.jointSize = 0.03; // Small sphere
// Colors from render_skeleton.py
this.chainColors = [
0xFEB21A, // orange (chain 0 - spine)
0x00AAFF, // cyan (chain 1 - left leg)
0x134686, // aquamarine (chain 2 - right leg)
0xFFB600, // amber (chain 3 - left arm)
0x00D47E, // aquamarine (chain 4 - right arm)
];
// Joint color: teal (0, 128, 157)
this.jointMaterial = new THREE.MeshStandardMaterial({
color: 0x00809D, // teal
metalness: 0.2,
roughness: 0.5,
});
// Will create multiple bone materials for different chains
this.boneMaterials = this.chainColors.map(color =>
new THREE.MeshStandardMaterial({
color: color,
metalness: 0.2,
roughness: 0.5,
})
);
this.initSkeleton();
this.initTrail();
}
initTrail() {
// Create trail line with gradient opacity
this.trailGeometry = new THREE.BufferGeometry();
// Initialize with empty arrays
const positions = new Float32Array(this.maxTrailPoints * 3);
const colors = new Float32Array(this.maxTrailPoints * 4); // RGBA
this.trailGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
this.trailGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 4));
this.trailMaterial = new THREE.LineBasicMaterial({
vertexColors: true,
transparent: true,
opacity: 1.0,
linewidth: 2,
});
this.trailLine = new THREE.Line(this.trailGeometry, this.trailMaterial);
this.trailLine.frustumCulled = false;
this.scene.add(this.trailLine);
}
initSkeleton() {
// Create 22 joint spheres - uniform small spheres
const jointGeometry = new THREE.SphereGeometry(this.jointSize, 16, 16);
for (let i = 0; i < 22; i++) {
const joint = new THREE.Mesh(jointGeometry, this.jointMaterial);
joint.castShadow = true;
joint.receiveShadow = true;
this.joints.push(joint);
this.scene.add(joint);
}
// Create bone cylinders - different color for each chain
let boneIndex = 0;
for (let chainIdx = 0; chainIdx < this.chains.length; chainIdx++) {
const chain = this.chains[chainIdx];
const material = this.boneMaterials[chainIdx];
for (let i = 0; i < chain.length - 1; i++) {
const bone = this.createBone(material);
this.bones.push(bone);
this.scene.add(bone);
boneIndex++;
}
}
}
createBone(material) {
// Create a simple thin cylinder with specified material
const geometry = new THREE.CylinderGeometry(this.boneRadius, this.boneRadius, 1, 8);
const bone = new THREE.Mesh(geometry, material);
bone.castShadow = true;
bone.receiveShadow = true;
return bone;
}
updatePose(jointPositions) {
/**
* Update skeleton with new joint positions
* jointPositions: array of shape [22, 3]
*/
if (!jointPositions || jointPositions.length !== 22) {
console.error('Invalid joint positions:', jointPositions);
return;
}
// Update joint positions
for (let i = 0; i < 22; i++) {
const pos = jointPositions[i];
this.joints[i].position.set(pos[0], pos[1], pos[2]);
}
// Update bone positions and orientations
for (let i = 0; i < this.boneConnections.length; i++) {
const [startIdx, endIdx] = this.boneConnections[i];
const startPos = new THREE.Vector3(
jointPositions[startIdx][0],
jointPositions[startIdx][1],
jointPositions[startIdx][2]
);
const endPos = new THREE.Vector3(
jointPositions[endIdx][0],
jointPositions[endIdx][1],
jointPositions[endIdx][2]
);
this.updateBone(this.bones[i], startPos, endPos);
}
// Update trail with root position (joint 0)
this.updateTrail(jointPositions[0]);
}
updateTrail(rootPos) {
// Add new point to trail (project to ground y=0.01)
const trailPoint = {
x: rootPos[0],
y: 0.01, // Slightly above ground to avoid z-fighting
z: rootPos[2]
};
// Only add if moved significantly (avoid duplicate points)
if (this.trailPoints.length === 0) {
this.trailPoints.push(trailPoint);
} else {
const lastPoint = this.trailPoints[this.trailPoints.length - 1];
const dist = Math.sqrt(
Math.pow(trailPoint.x - lastPoint.x, 2) +
Math.pow(trailPoint.z - lastPoint.z, 2)
);
if (dist > 0.02) { // Minimum distance threshold
this.trailPoints.push(trailPoint);
}
}
// Limit trail length
if (this.trailPoints.length > this.maxTrailPoints) {
this.trailPoints.shift();
}
// Update geometry
const positions = this.trailGeometry.attributes.position.array;
const colors = this.trailGeometry.attributes.color.array;
const numPoints = this.trailPoints.length;
for (let i = 0; i < this.maxTrailPoints; i++) {
if (i < numPoints) {
const point = this.trailPoints[i];
positions[i * 3] = point.x;
positions[i * 3 + 1] = point.y;
positions[i * 3 + 2] = point.z;
// Gradient: older points (lower index) are more transparent
const alpha = i / (numPoints - 1); // 0 (oldest) to 1 (newest)
const opacity = Math.pow(alpha, 1.5) * 0.8; // Fade out older points
// Use cyan color (matching joint color)
colors[i * 4] = 0.0; // R
colors[i * 4 + 1] = 0.67; // G (cyan)
colors[i * 4 + 2] = 0.85; // B
colors[i * 4 + 3] = opacity; // A
} else {
// Hide unused vertices
positions[i * 3] = 0;
positions[i * 3 + 1] = 0;
positions[i * 3 + 2] = 0;
colors[i * 4 + 3] = 0;
}
}
this.trailGeometry.attributes.position.needsUpdate = true;
this.trailGeometry.attributes.color.needsUpdate = true;
this.trailGeometry.setDrawRange(0, numPoints);
}
clearTrail() {
this.trailPoints = [];
this.trailGeometry.setDrawRange(0, 0);
}
updateBone(bone, startPos, endPos) {
/**
* Update a bone's position, rotation, and scale to connect two joints
*/
const direction = new THREE.Vector3().subVectors(endPos, startPos);
const length = direction.length();
if (length < 0.001) return;
// Position bone at midpoint
const midpoint = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5);
bone.position.copy(midpoint);
// Scale bone to match distance
bone.scale.y = length;
// Rotate bone to point from start to end
bone.quaternion.setFromUnitVectors(
new THREE.Vector3(0, 1, 0),
direction.normalize()
);
}
setVisible(visible) {
this.joints.forEach(joint => joint.visible = visible);
this.bones.forEach(bone => bone.visible = visible);
if (this.trailLine) this.trailLine.visible = visible;
}
dispose() {
// Clean up resources
this.joints.forEach(joint => {
this.scene.remove(joint);
joint.geometry.dispose();
});
this.bones.forEach(bone => {
bone.children.forEach(child => {
if (child.geometry) child.geometry.dispose();
});
this.scene.remove(bone);
});
if (this.trailLine) {
this.scene.remove(this.trailLine);
this.trailGeometry.dispose();
this.trailMaterial.dispose();
}
this.jointMaterial.dispose();
this.boneMaterials.forEach(mat => mat.dispose());
}
}
// Export for use in main.js
window.Skeleton3D = Skeleton3D;
|