five-on-it / apps/client/src/renderer/models
apps/client/src/renderer/models
The actors' models: the vehicle classes (models/vehicles.glb: one node per class, named by it, its wheels as children), the weapons (models/weapons.glb: one node per weapon, its origin on the grip, its barrel along +z, its muzzle an empty <name>-muzzle, read once into the muzzles and taken off) and the character (models/character.glb), the low-poly meshes assets/blender/models makes, loaded once into asset containers. The files are glTF, right-handed with +z pointing the other way, so a model hangs under a node turned half a circle: game = (−x, y, −z) of the file, the rule the tools use for the kits. A model's frame is its node's own (the chassis centre, the frame its collider is read in, see apps/tools/src/models.ts): where the file puts the node is only the file's layout (the vehicles stand in a row), so a model sits at the origin of its actor. Their PBR materials become the flat materials the rest of the scene is drawn with, the lamps and the signs (the taxi's, the bus's) glowing, the light bars flashing in their livery's pattern (light-bars.ts: two lamps a bar, one material each, shared by every car of the livery), the glass see-through (GLASS_OPACITY: the windscreen light, the other windows darker), so the people seated behind it show. A vehicle's seats are empties under its body (<class>-seat-<n>, with their pose, view and door): they are read into Seats once, in the actor's frame, and taken off the model. Its doors are children (<class>-door-<tag>, their origin on the hinge, with their doorway and swing), read into DoorInfos. A model with a steering wheel (<class>-steering, everything on four wheels or more) turns it about its column as the car steers, and its front wheels (<class>-wheel-fl, -fr) about their hubs (the middle of each tyre) by the angle the simulation gives them; any of its wheels with a flat tyre is drawn squashed onto its rim. A seat whose legs are hidden (inside a car's solid interior) has its figure drawn without them, so no foot pokes out under the body. The character is a hierarchy of parts (figure.ts poses them, clips.ts the clips it plays, loaded here too from models/animations.json); the tip of each fist is read off its forearm once (ActorModels.hand). A figure drawn with a weapon (DrawOptions.weapon) holds it: its grip in the right fist, a one-handed one (a pistol, the tranquilizer, a grenade) along the right forearm, a long gun and the launcher from the right fist toward the left, upright with the torso (weaponFrame); the shot leaves by its muzzle (ActorModels.muzzle). Until the files are loaded (or when one is missing) the actors are wireframe boxes. The dog (models/companions.glb, beside the sweeper's cart) has limbs: its legs, its head and its tail are nodes at their joints (LIMBS), each drawn turned about its joint as DrawOptions.limbs says (dog-view.ts poses them).
A wreck is drawn as the car it was, its paint untouched: it smokes and burns (vehicle-fire.ts). Once it has blown up, its shell is drawn charred (the wrecked of DrawOptions): the paint darkened toward soot and every other material (the glass, the trim, the lamps, which no longer glow) burnt the same way. The dead are drawn as the living, in their own colours.
Two ways to draw a model:
- ActorModels.spawn hangs a tinted clone of its hierarchy under a node: the player's own character on foot (or a ghost), drawn as the scene graph draws anything.
- ActorModels.draw draws it this frame as a thin instance of batches, for every car and everyone else. At load each model is merged (a two-wheeler's wheels and every part that never moves on its own, in the actor's frame; a steered car's wheels, which turn or go flat, and its steering wheel are pieces of their own, each shared by every model whose part has the same shape, so they cost a few meshes in all) into a few meshes: its paint (the
bodymaterial, one mesh per tint sharing the geometry, so a player's colour, a livery's paint, a wreck's scorched paint or a role's shirt are each the same material as a clone's), the rest (one mesh, a sub-mesh per flat material, the light bars' lamps among them) and its glass (one mesh, see-through, drawn after everything opaque); a wreck's rest and glass in the burnt materials, meshes made the first time a model is drawn wrecked. The character is merged part by part, so its figure still poses every limb. A car with a door ajar is drawn otherwise for that moment: its body without its doors (merged the same way) and each door as a piece of its own, swung about its hinge (doorMotion); a closed car costs nothing more. An actor is then a matrix or five in each of its batches: the scene holds a few dozen meshes however many cars and people there are, each drawn in one call, and nothing is cloned or compiled while the game runs. The game culls actors itself (view-rect.ts), so the batches never are.