Portable people, pets and birds
objects/people is an independently copyable ES-module package. Its imports stay within local base.js, models.js, skinning.js and face-builder.js; it has no image downloads, runtime AI or DOM dependency. Model geometry, skeletons and woven bump textures are generated by this package. Instantiate it with the namespace exported by a separately copied engine:
import * as api from './engine/index.js';
import {Person} from './objects/people/index.js';
const person = new Person(api, {ageGroup:'child', height:1.16, clothing:'beach', seed:38});
scene.add(person.root);
person.setPosition(0, terrain.heightAt(0,0), 0);
person.setPose('dig').update(dt, time);
The injected API requires object protocol 1, THREE, ObjectState and random. Other destructured model services are retained for the source adapter but not required by the current character builders. No object-folder imports refer to a sibling package or an engine disk location. Units are metres and radians; people face local +Z. The root transform is the ground anchor for standing people and the support anchor for reclining people. Applications position the root above their own terrain.
Shared interface
Person, Bird and Pet expose root, properties, position, rotation, quaternion, scale, setPosition(x,y,z), update(dt,time), getBounds(), serialize(), restore(data) and idempotent dispose(). getBounds() includes the current skinned pose. Serialization contains constructor properties and root transforms; restore applies the transforms to an already-created compatible object. To reconstruct appearance, instantiate the class with data.properties before calling restore(data).
Each instance owns its geometry, skeleton, materials and textures. Disposal removes its root and disposes those resources. Do not use an instance after disposal. Parent transforms should have uniform scale for anatomical hand IK.
Person
Constructor properties:
| Property | Default | Meaning |
|---|---|---|
id, name | generated id, Person | Consumer identity and display name |
seed | 3 | Repeatable skin, hair, clothing and build variation |
ageGroup | adult | adult or child; explicitly controls body/head proportions |
height | 1.76 adult / 1.12 child | Nominal barefoot stature; hats can extend above it |
clothing | casual | casual or beach |
pose | idle | Initial pose name |
female, skin, hair, shirt, pants | seed-derived | Appearance overrides; adult female and male variants use different shoulder, waist, chest and hip proportions |
bodyType | seed-derived | slim, regular or bigger (the fixture labels slim as Skinny) |
build | .77, 1, 1.38 adult presets | Optional numeric override in .6..1.7; children apply smaller age proportions |
hairStyle | seed-derived | bald, short, swept, curls, bob or ponytail |
expression | neutral | A face-expression preset or numeric control object |
rashguard, sunHat, sunglasses, hatColor | false / seed-derived color | Beach outfit options |
Beach clothing uses swim shorts; children and female-presenting variants have a short-sleeve top, and adults can request a rash guard. Children preserve independent seed variation and do not inherit elder facial accessories. Faces have shaped jaws, eyes, brows, nose, mouth, ears, hair and optional hats/glasses. The feet use a low profile with narrower heels and toes. Torso, limbs and fingers are skinned. The separately owned expressive face is attached to the head bone; its skull, nose and jaw form a continuous surface. The attachment offset is [0,.055,0], giving a nominal local head center of 1.590 m before stature scaling. The neck tapers beneath the jaw.
| Method | Behavior |
|---|---|
setPose(name,{phase=0,...options}) | Selects idle, walk, run, wave, chat, seated, pet, feed, ready, volley, throw, catch, dig, cheer, armsOut, overhead, recline, sunbathe, kneel, sunbatheGround, or prone; invalid names throw |
walkTo(position,{speed=1.1,terrain=null}) | Moves the ground anchor toward a world destination at metres/second and samples supplied terrain; accepts Vector3, array or {x,y,z} |
moveTo(position,options), stop() | Alias for walkTo; cancels a destination |
setGaze({yaw=0,pitch=0}), lookAt(worldPosition) | Sets local head direction or derives it from a world point |
getHandPosition('left'|'right') | Returns the world-space wrist joint position |
reach(side,worldTarget,amount=1,{palmDown=false}) | Two-bone arm IK; apply after update; unreachable targets are clamped to anatomical arm length |
setHeight(metres) | Updates nominal height and uniform model scale |
setBodyType(type) | Rebuilds the body as slim, regular or bigger, retaining transforms and pose |
setAppearance(options) | Rebuilds owned model resources with changed appearance; previous resources are disposed |
setExpression(nameOrObject,intensity=1) | Forwards neutral/happy/sad/focused/surprised/talking or numeric controls to the face |
setHair(style), blink() | Changes the owned hairstyle or triggers a blink |
update(dt,time) | Applies movement, activity pose, head direction and joint animation; records the actual appliedPose and returns the object |
kneel folds the legs with knees and dorsal feet on the support plane. sunbatheGround lies face-up and prone lies face-down; these accept supportHeight (default .022 metres), optional footOffset, and prone accepts headTurn in radians (default .95). Their resting limb counter-bends keep a low hand from lifting the torso during grounding. In authored actors, the complete deformed body participates in floor support. recline and legacy sunbathe retain their chair/support behavior.
phase is clamped to 0–1. volley supports jump in metres (default .23). recline/sunbathe accept supportHeight, footOffset and backAngle; SunLounger.getReclinePose() supplies compatible settings. The reclining torso bends at the pelvis while legs counter-rotate, supporting a raised chair back. walkTo turns the body along the route; gait cadence follows distance. Ground-following routes assume the object parent is at world identity; gaze and hand IK accept world coordinates. General route planning, obstacle avoidance, contact events and game choreography are application responsibilities.
Bird and Pet
Bird(api,{seed=4,species='gull',name='Bird'}) supports gull, pigeon and sparrow appearances and setActivity(name) with fly, soar, approach, takeoff, perch, peck or walk. It articulates wings, primary feathers, legs, head and tail. The application supplies flight-path transforms.
Pet(api,{seed=1,species='dog',name='Pet'}) supplies dog or cat appearance and setActivity(name) with idle, walk, sit, sniff or petted. It articulates legs, paws, spine/head and tail. The application supplies its path.
legacyPeople(api) exposes the harvested builder adapter for compatibility. Its createHuman(seed,{legacyFace:true}) option retains the old face builder; the normal path uses the new face module. New consumers should use the classes above, whose lifecycle follows the shared object protocol.
Current limits and verification
These are procedurally modeled, articulated characters, not scanned people. Facial expressions and finger contacts are approximate. IK targets wrist joints; fingertip skin is not a collision solver. The beach application uses deterministic ballistic arcs and timed contact poses, not a competitive volleyball AI.
The dedicated tests/beach-object.html exercises object construction, avatar height/age variation, finite bounds, ball/hand synchronization across cycle boundaries, net clearance, terrain anchors, locomotion and repeated disposal. Its rendered scene is available for separate visual inspection; numerical assertions alone do not establish visual realism.
Body topology and visual inspection
The torso and both upper arms now form one indexed surface. Side openings in the torso connect directly to the deltoid/arm loft, sharing vertices, smooth normals and blended skin weights. There are no separate shoulder-ball meshes or exposed caps at the arm pivots. Torso radii interpolate smoothly between anatomical sections. The pelvis and trouser legs form a connected surface with a crotch bridge; waistband color is embedded in that surface. The torso waist and trousers use matching pelvis-based deformation, avoiding the previous jagged overlapping waistband.
The builder records actor.mesh.userData.surfaceAudit: the torso/arm surface has one connected component, zero boundary edges and zero non-manifold edges. The pelvis/leg surface has one connected component and only its intended 38-edge waist opening. This checks connection topology; visual inspection is still required to assess deformation and contour.
The earlier body review in the confirmed Chrome instance inspected clothed and shirtless adults with arms lowered, horizontal, overhead and at actual volleyball contact. Skinny, regular and bigger overhead variants were inspected separately. Front and three-quarter views show continuous shoulder attachment, clean waists, narrower feet and shortened tapered necks. The raised-sleeve skin slivers from the intermediate build were corrected by keeping the proximal forearm inside the sleeve. The meshes remain stylized procedural people; this evidence does not establish photorealism. The fixture passes 106 assertions, retaining ball contact, locomotion, bounds, terrain and lifecycle checks alongside the topology checks.
The fixture's Shoulder audit, Shoulder pose and Body size controls reproduce these comparisons in one tab. Face-module implementation and expression controls are documented separately in docs/objects/FACE.md.
Anatomy and deformation refinement
The current body uses smoother forearm/calf sections, a connected palm/thumb surface, a lofted heel/arch/forefoot, and chest/ribcage relief in the torso surface. Skin and cloth share one body material, with per-vertex surface classification controlling roughness and bump strength. Hands and feet remain simplified; finger skin does not simulate contact compression.
skinning.js exports installSkinDeformer(api, mesh). It installs dual-quaternion rotation/translation blending on a bound THREE.SkinnedMesh, matching CPU applyBoneTransform and custom depth/distance materials. The controller is available as mesh.skinDeformer, with update(), dispose() and uniforms. Updates follow bone matrix updates. Ordinary rigid/uniformly scaled rigs use quaternion blending; negative or nonuniform scale selects the existing Three matrix implementation exactly. Morph-target chunks remain before skinning.
Shoulder-ring vertices additionally carry their rest cross-section center. Their centers move with matrix-weighted bone transforms while the ring offsets rotate with the normalized quaternion blend. This avoids the long underarm lobes seen with unrestricted dual-quaternion translation, while preserving more cross-section volume than plain matrix interpolation. A monotonically descending loft follows the upper-arm bone axis. Clavicle motion shares arm elevation; the previous total arm orientation is restored before interpolating the next pose, preventing cumulative compensation from lowering an animated raised arm.
The deformation attributes are skinDQBlend (float, 0 for matrix positions/normals, 1 for quaternion) and optional skinCenter (float4: rest center XYZ, W=1 enables ring-center translation). Missing attributes are initialized to full quaternion blending with no center correction. Color, directional/point shadow, and CPU position queries use the same blend. Lighting normals use the blended rotation; this is an approximation of the deforming surface differential.
Current close Chrome checks covered clothed/shirtless adults in lowered, horizontal and overhead poses, front/three-quarter/side views, slim/regular/bigger builds, and moving wave/volley/crouch poses. The large projecting underarm lobes were corrected. Extreme elevation still exposes a shallow angular armpit crease; the torso silhouette, wrist transition, and sculpted hair remain recognizably procedural. These are actionable visual limits, and this pass does not establish the requested near-photorealistic acceptance level.
Additional source verification exercised 42 pose/build/sex combinations with finite bounds; 190,127 rigid-influence vertex comparisons agreed with Three matrix skinning to 0.00000450 m in a translated/rotated actor. Nonuniform-scale fallback matched the original matrix implementation, and owned body, face, texture, skeleton-support, and shadow resources disposed exactly once. The browser fixture retains all contact, locomotion, terrain, topology and lifecycle assertions. Direct render inspection is separate from these numerical results.
Authored mesh adapter contract
A future authored body can replace the source geometry without replacing the public Person properties, pose selection, reach/gaze methods, transforms or serialization. This is an integration contract, not an implemented asset importer.
| Data | Required representation |
|---|---|
| Coordinates | Metres, right-handed, +Y up, +Z character front; explicit conversion matrix if the source differs |
| Render vertices | position float3, normal float3, uv float2; optional tangent float4; consistently wound indexed triangles |
| Skin data | skinIndex unsigned-short4; skinWeight float4, finite/nonnegative, normalized; retain the four strongest influences and record discarded weight error when reducing a source rig |
| Source mapping | Original source vertex index to all render vertices generated at UV/material/normal seams; expand skin weights and morph deltas through this mapping |
| Rig | Ordered bone names, parent indices, local rest matrices including rest quaternions, inverse-bind matrices, and semantic joint/effector anchors |
| Morphs | Named relative position/normal deltas on the render topology; neutral basis and value range; explicit mappings for build/age/sex and face controls |
| Materials | UV-matched base-color/albedo maps in sRGB; normal/roughness/metalness/AO maps as linear data; tangent handedness and normal-map convention; local files with recorded provenance/license |
| Deformation | Optional skinDQBlend and skinCenter; ordinary authored geometry may omit both. Morphing geometry with center correction must update its centers consistently |
| Lifecycle | Instance-owned or reference-counted geometry/material/texture resources, explicit disposal, and no imports outside the object package |
The canonical actor returned to Person contains mesh (bound SkinnedMesh), bones (ordered Bone array), root, spine, chest, neck, head (indices), and names.left/right with clavicle, upper, elbow, wrist, thigh, knee, ankle, fingers[], thumb. It provides animate(time,speed,action,gaze), reachHand(side,target,amount,palmDown), optional updateShoulders(), optional face, and description. The factory also supplies disposal. An authored face can either provide the current face controller methods or map those methods to source morph targets.
The current procedural driver uses zero-delta rest anchors at pelvis 0.900 m, spine 1.050 m, chest 1.300 m, neck 1.435 m, head 1.535 m; upper arm 1.340 m, elbow 1.095 m, wrist 0.870 m, knee 0.490 m, ankle 0.110 m. Left/right widths depend on age/sex/build. Person stature scales this canonical geometry by height / 1.78. An authored rig therefore needs either a canonical driver skeleton with retargeting through its source rest transforms, or a replacement pose driver that supplies equivalent semantic outputs and source-specific body/leg/stature metrics. Applying the current Euler deltas directly to an unrelated rig is not supported. Preserve wrist-target and chair/ground-contact checks after any retargeting.
Full beach measurement
On 2026-09-07, tests/beach-object.html?revision=anatomy14-final measured the actual 16-person beach, including 16 center-corrected bodies, 15 non-neutral faces, and all three body builds. The confirmed Chrome viewport was 1835 × 1010 CSS pixels at renderer pixel ratio 1.25, high quality with PostFX. After 30 warm-up frames, 120 measured frames gave:
| Measurement | Median | 95th percentile |
|---|---|---|
| CPU frame | 7.00 ms | 7.30 ms |
| GPU elapsed time | 9.82 ms | 11.83 ms |
All 120 GPU timer-query samples were valid (EXT_disjoint_timer_query_webgl2). Median total work was 906 render calls and 4,561,598 rendered triangles, including multipass rendering. This is a local workload measurement, not a device-independent frame-rate guarantee. An earlier concurrent-browser run measured 26.8 ms median CPU; the difference is not evidence of an isolated optimization. The repeatable measurement control is Measure full16-person beach.
The same final scene was viewed at ball/hand contact, children/sand play, and reclining sunbathers. Contact poses remained synchronized, the reclining bodies stayed on their supports, and the inspected views showed no browser shader/runtime warnings or errors. The fixture passed 106 assertions, including the new repeated-wave shoulder-drift regression.
Optional authored anatomy
objects/people/authored/index.js now supplies an optional AuthoredPerson with local verified CC0 mesh/rig/weight/shape data. The default entry remains procedural. See AUTHORED_PERSON.md for preload, complete interface, source-data deformation, child build limits, and current validation status. The permanent close inspection is tests/authored-person.html.