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 assert from 'node:assert/strict';
import test from 'node:test';
import {readFileSync} from 'node:fs';
import {resolve,dirname} from 'node:path';
import {runInNewContext} from 'node:vm';
import ts from 'typescript';
import * as three from 'three';
const cache=new Map();
function load(file){
const path=resolve(file);if(cache.has(path))return cache.get(path).exports;
const module={exports:{}};cache.set(path,module);
runInNewContext(ts.transpileModule(readFileSync(path,'utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText,{module,exports:module.exports,TextEncoder,TextDecoder,require:name=>name==='three'?three:load(resolve(dirname(path),name.replace(/\.js$/,'.ts')))});return module.exports;
}
const {decodeAtlas}=load('src/atlas-data.ts');
const {SkinModel,decodeSkin}=load('src/skin-model.ts'),{decodeSkeleton}=load('src/skeleton-data.ts');
const {draftString,parseDraft,casePayload}=load('src/case-record.ts'),{parseSession,sessionString}=load('src/session-record.ts');
const {profile,makeConcern}=load('src/case.ts');
const bytes=file=>{const b=readFileSync(file);return b.buffer.slice(b.byteOffset,b.byteOffset+b.byteLength);};
const data=decodeSkin(bytes('assets/skin/body-skin.bin')),skeleton=decodeSkeleton(bytes('assets/skeleton/skeleton.bin'));
const muscles=decodeAtlas(bytes('assets/anatomy/muscles.bin'),'bp3d-muscles-1'),organs=decodeAtlas(bytes('assets/anatomy/organs.bin'),'bp3d-organs-1');
const {MeshStandardMaterial,Vector3,Raycaster}=three;
function model(){const m=new SkinModel(data,new MeshStandardMaterial(),skeleton,[muscles,organs]);m.setLayer('skeleton');m.update();return m;}
const {parseFingerPose,describeFingers,digits}=load('src/finger-pose.ts');
const {BodyInspection}=load('src/body-inspection.ts');
const point=(m,name)=>m.bones.find(b=>b.name===name).getWorldPosition(new Vector3());
test('each finger bends toward its own palm, without moving adjacent fingers or its base',()=>{
const m=model();
for(const [side,suffix,sign] of [['left','l',-1],['right','r',1]]) for(const digit of digits){
m.fingers.set({});m.update();
const root=point(m,`${digit}_01_${suffix}`),tip=point(m,`${digit}_03_${suffix}`);
const other=digit==='index'?'middle':'index',neighbor=point(m,`${other}_03_${suffix}`);
m.fingers.set({[`${side}-${digit}`]:[.5,.5,.5]});m.update();
const moved=point(m,`${digit}_03_${suffix}`);
assert.ok(sign*(moved.x-tip.x)>.005,`${side} ${digit} curls inward`);
assert.ok(point(m,`${digit}_01_${suffix}`).distanceTo(root)<1e-8);
assert.ok(point(m,`${other}_03_${suffix}`).distanceTo(neighbor)<1e-8);
m.fingers.set({[`${side}-${digit}`]:[0,.7,0]});m.update();
assert.ok(point(m,`${digit}_03_${suffix}`).distanceTo(tip)>.005);
assert.ok(point(m,`${digit}_01_${suffix}`).distanceTo(root)<1e-8);
}
m.dispose();
});
test('all named hand phalanges follow the correct digit and segment',()=>{
const m=model();
for(const part of skeleton.parts){
const match=part.label.toLowerCase().match(/(proximal|middle|distal) phalanx of (left|right) (thumb|index|middle|ring|little)(?: finger)?$/);
if(!match)continue;
const [,segment,side,finger]=match,digit=finger==='little'?'pinky':finger;
const n=segment==='distal'?3:segment==='middle'?2:digit==='thumb'?2:1;
assert.equal(data.meta.bones[part.bone].name,`${digit}_0${n}_${side==='left'?'l':'r'}`,part.label);
const mesh=m.surfaces.find(s=>s.name===part.id);assert.equal(mesh.parent,m.bones[part.bone]);
}
m.dispose();
});
test('finger surface anchors deform with joints and survive body shape and limb posing',()=>{
const m=model();m.setLayer('skin');
const inspect=new BodyInspection(m);inspect.set('region:left-index');inspect.setVisible('region:body',false);inspect.setVisible('region:left-index',true);
const center=inspect.bounds().getCenter(new Vector3());
const hit=new Raycaster(center.clone().add(new Vector3(0,0,1)),new Vector3(0,0,-1)).intersectObjects(inspect.surfaces,false).find(h=>inspect.accepts(h));assert.ok(hit);
const {mark}=m.mark(hit,.003,1),serialized=JSON.stringify(mark),before=m.anchor(mark).point.clone();
m.fingers.set({'left-index':[.6,.8,.4]});m.update();
assert.ok(m.anchor(mark).point.distanceTo(before)>.015);
assert.ok(inspect.bounds().containsPoint(m.anchor(mark).point));
const pose=JSON.stringify(m.fingers.snapshot());m.joints.get('left-elbow').rotation.x=-1.2;m.shape(190,125,'Female');
assert.equal(JSON.stringify(m.fingers.snapshot()),pose);assert.equal(JSON.stringify(mark),serialized);
assert.ok(inspect.bounds().containsPoint(m.anchor(mark).point));
inspect.clear();m.setLayer('skeleton');assert.ok(inspect.set('region:left-hand'));inspect.setVisible('region:body',false);inspect.setVisible('region:left-hand',true);assert.equal(inspect.surfaces.length,27);
inspect.clear();m.setLayer('muscles');assert.ok(inspect.set('region:right-hand'));assert.ok(inspect.surfaces.length>5);
inspect.dispose();m.dispose();
});
test('finger records are bounded, independent, backwards compatible and included in text/revisions',()=>{
const c=makeConcern(1,'Left hand','standing'),second=makeConcern(2,'Right hand','sitting');
c.fingers={'left-index':[.2,.9,.4]};second.fingers={'right-thumb':[.3,.4,.5]};
const draft=parseDraft(draftString(profile,[c,second]));
assert.equal(JSON.stringify(draft.concerns[0].fingers),JSON.stringify(c.fingers));assert.equal(JSON.stringify(draft.concerns[1].fingers),JSON.stringify(second.fingers));
const session={version:1,draft,view:{page:'map',panel:'body',selectedId:1,pose:'standing',layer:'skin',exampleAttachment:false,newObservation:true,fingers:{'left-pinky':[.5,0,0]}}};
assert.equal(JSON.stringify(parseSession(sessionString(session)).view.fingers),JSON.stringify(session.view.fingers));
assert.equal(parseSession(sessionString(session)).view.newObservation,true);
assert.throws(()=>parseSession(JSON.stringify({...session,view:{...session.view,newObservation:'yes'}})));
const initial=casePayload(profile,[c]);assert.match(initial.concerns[0].illustratedPose,/left index finger bent at base \/ middle \/ tip/);
c.fingers['left-index'][1]=.5;assert.notEqual(initial.bodyMapRevision,casePayload(profile,[c]).bodyMapRevision);
for(const invalid of [null,[],{'left-eye':[0,0,0]},{'left-index':[0,0]},{'left-index':[0,-.1,0]},{'right-thumb':[0,1.1,0]},{'left-pinky':[0,NaN,0]},{'left-ring':['1',0,0]}])assert.throws(()=>parseFingerPose(invalid));
const snapshot=parseFingerPose(c.fingers);snapshot['left-index'][0]=1;assert.equal(c.fingers['left-index'][0],.2);
delete c.fingers;assert.equal(parseDraft(draftString(profile,[c])).concerns[0].fingers,undefined);assert.equal(describeFingers().length,0);
});
test('fingertip IK reaches bent targets on both posed hands without disturbing neighbors',()=>{
const m=model();m.shape(185,92,'Male');m.joints.get('left-arm').rotation.set(.25,.2,-.4);m.joints.get('right-arm').rotation.set(-.4,.15,.2);
for(const side of ['left','right'])for(const digit of digits){
const id=`${side}-${digit}`,other=`${side}-${digit==='index'?'ring':'index'}`;
m.fingers.set({[id]:[.4,.7,.45],[other]:[.2,.3,.2]});m.update();const target=m.fingers.endpoint(id);
m.fingers.set({[other]:[.2,.3,.2]});m.update();const neighbor=m.fingers.endpoint(other),before=m.fingers.endpoint(id).distanceTo(target);
m.fingers.solve(id,target);m.update();
assert.ok(m.fingers.endpoint(id).distanceTo(target)<.002,`${id} reaches target within 2 mm (was ${before})`);
assert.ok(m.fingers.endpoint(other).distanceTo(neighbor)<1e-8,'neighbor stays fixed');
m.fingers.solve(id,new Vector3(100,100,100));m.update();
assert.ok(m.fingers.snapshot()[id].every(n=>Number.isFinite(n)&&n>=0&&n<=1));
const saved=JSON.stringify(m.fingers.snapshot());m.fingers.solve(id,new Vector3(NaN,0,0));assert.equal(JSON.stringify(m.fingers.snapshot()),saved);
}
m.dispose();
});
