Back to Body Journal
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.

src/skeleton-model.ts
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(); }
}