SOURCE / PINNED RELEASE
Made of little things.
Body Journal
- Release
- e8049f8da4e2…
- Author-recorded commit
- 61c04bf0e8aa…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6c4554844c7
Archive hash verified: 4369d778da046a8b…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import { BufferGeometry, Float32BufferAttribute, Matrix3, Matrix4, Mesh, MeshStandardMaterial, Quaternion, Triangle, Uint16BufferAttribute, Vector3 } from 'three';
import type { Bone, Group, Intersection } from 'three';
import type { Mark, Point3 } from './case.js';
import { SKELETON_MODEL } from './layers.js';
import type { SkeletonData, SkeletonPart } from './skeleton-data.js';
/** Anatomical meshes, rigidly bound to the existing posing rig. */
export class SkeletonModel {
private material = new MeshStandardMaterial({ color: '#aeb1b8', roughness: .82 });
private items: { mesh: Mesh; info: SkeletonPart }[] = [];
constructor(data: SkeletonData, bones: Bone[], private referenceHeads: Float32Array) {
for (const info of data.parts) {
const geometry = new BufferGeometry();
geometry.setAttribute('position', new Float32BufferAttribute(new Float32Array(data.buffer, data.start + info.positions, info.vertices * 3), 3));
geometry.setIndex(new Uint16BufferAttribute(new Uint16Array(data.buffer, data.start + info.indices, info.triangles * 3), 1));
geometry.computeVertexNormals();
const mesh = new Mesh(geometry, this.material); mesh.name = info.id; mesh.userData.anatomicalPart = info.part; mesh.matrixAutoUpdate = false;
bones[info.bone].add(mesh); this.items.push({ mesh, info });
}
this.visible = false;
}
set visible(value: boolean) { for (const { mesh } of this.items) mesh.visible = value; }
get surfaces(): Mesh[] { return this.items.map(item => item.mesh); }
shape(heads: Float32Array): void {
for (const { mesh, info } of this.items) {
mesh.matrix.identity();
if (info.tail < 0) continue;
const start = new Vector3().fromArray(this.referenceHeads, info.bone * 3);
const ref = new Vector3().fromArray(this.referenceHeads, info.tail * 3).sub(start);
const next = new Vector3().fromArray(heads, info.tail * 3).sub(new Vector3().fromArray(heads, info.bone * 3));
const ratio = next.length() / ref.length(), axis = ref.clone().normalize();
const frame = new Matrix4().makeRotationFromQuaternion(new Quaternion().setFromUnitVectors(new Vector3(0,1,0), axis));
const stretch = frame.clone().multiply(new Matrix4().makeScale(1,ratio,1)).multiply(frame.clone().invert());
mesh.matrix.makeRotationFromQuaternion(new Quaternion().setFromUnitVectors(axis, next.normalize())).multiply(stretch);
}
}
anchor(mark: Mark, owner: Group): { point: Vector3; normal: Vector3 } | undefined {
const surface = mark.surface;
const item = this.items.find(item => item.info.id === surface?.zone);
if (!surface || !item || surface.triangle < 0 || surface.triangle >= item.info.triangles) return;
const { mesh } = item, triangle = this.triangle(mesh, surface.triangle);
const point = triangle.reduce((p,v,i) => p.addScaledVector(v,surface.barycentric[i]), new Vector3());
const normal = Triangle.getNormal(...triangle, new Vector3());
const transform = owner.matrixWorld.clone().invert().multiply(mesh.matrixWorld);
return { point: point.applyMatrix4(transform), normal: normal.applyNormalMatrix(new Matrix3().getNormalMatrix(transform)) };
}
mark(hit: Intersection, size: number, stroke: number, owner: Group): { mark: Mark; label: string } | undefined {
const item = this.items.find(item => item.mesh === hit.object);
if (!item || hit.faceIndex == null) return;
const local = item.mesh.worldToLocal(hit.point.clone()), triangle = this.triangle(item.mesh, hit.faceIndex);
const barycentric = Triangle.getBarycoord(local, ...triangle, new Vector3()); if (!barycentric) return;
const { info } = item;
const mark: Mark = { part: info.part, point: local.clone().add(new Vector3().fromArray(this.referenceHeads,info.bone*3)).toArray() as Point3, normal: [0,0,1], size, stroke,
surface: { model: SKELETON_MODEL, triangle: hit.faceIndex, barycentric: barycentric.toArray() as Point3, uv: [0,0], zone: info.id, label: info.label } };
mark.normal = this.anchor(mark, owner)!.normal.toArray() as Point3;
return { mark, label: info.label };
}
private triangle(mesh: Mesh, index: number): [Vector3, Vector3, Vector3] {
const indices = mesh.geometry.index!, positions = mesh.geometry.getAttribute('position');
return [0,1,2].map(i => new Vector3().fromBufferAttribute(positions,indices.getX(index*3+i))) as [Vector3,Vector3,Vector3];
}
dispose(): void { for (const {mesh} of this.items) {mesh.removeFromParent();mesh.geometry.dispose();} this.material.dispose(); }
}
