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:

PropertyDefaultMeaning
id, namegenerated id, PersonConsumer identity and display name
seed3Repeatable skin, hair, clothing and build variation
ageGroupadultadult or child; explicitly controls body/head proportions
height1.76 adult / 1.12 childNominal barefoot stature; hats can extend above it
clothingcasualcasual or beach
poseidleInitial pose name
female, skin, hair, shirt, pantsseed-derivedAppearance overrides; adult female and male variants use different shoulder, waist, chest and hip proportions
bodyTypeseed-derivedslim, regular or bigger (the fixture labels slim as Skinny)
build.77, 1, 1.38 adult presetsOptional numeric override in .6..1.7; children apply smaller age proportions
hairStyleseed-derivedbald, short, swept, curls, bob or ponytail
expressionneutralA face-expression preset or numeric control object
rashguard, sunHat, sunglasses, hatColorfalse / seed-derived colorBeach 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.

MethodBehavior
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.

DataRequired representation
CoordinatesMetres, right-handed, +Y up, +Z character front; explicit conversion matrix if the source differs
Render verticesposition float3, normal float3, uv float2; optional tangent float4; consistently wound indexed triangles
Skin dataskinIndex unsigned-short4; skinWeight float4, finite/nonnegative, normalized; retain the four strongest influences and record discarded weight error when reducing a source rig
Source mappingOriginal source vertex index to all render vertices generated at UV/material/normal seams; expand skin weights and morph deltas through this mapping
RigOrdered bone names, parent indices, local rest matrices including rest quaternions, inverse-bind matrices, and semantic joint/effector anchors
MorphsNamed relative position/normal deltas on the render topology; neutral basis and value range; explicit mappings for build/age/sex and face controls
MaterialsUV-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
DeformationOptional skinDQBlend and skinCenter; ordinary authored geometry may omit both. Morphing geometry with center correction must update its centers consistently
LifecycleInstance-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:

MeasurementMedian95th percentile
CPU frame7.00 ms7.30 ms
GPU elapsed time9.82 ms11.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.