Log inGet started
Package

ShaftNetwork.component

Newest version

This package holds more than this page lists. See all of its files in its world.

README.md

ShaftNetwork

A network of rotating parts and the links between them, solved inside the physics step: a drivetrain (engine, clutch, gearbox, differential, wheels), a tank's two tracks off one engine, a winch or a crane's slew on a geared motor, a windmill driving a pump, a conveyor, a propeller on a shaft, a clockwork gear train. Add it to any entity; a vehicle's chassis is the usual place.

Every part and link is solved together and implicitly at the physics tick, together with the tyres and the chassis when the network drives wheels: a locked clutch, a locked differential or a gear mesh holds without ringing at 30 Hz and 60 Hz, and a closed loop of gears creates no energy.

Parts

parts is a list of parts, each with a name that links, set and the state read it by. A name is held once across the network's parts and links, since set reaches either by its name alone. A part turns about one axis with a moment of inertia, and is one of:

  • a free part of its own: a crankshaft, a flywheel, a gearbox shaft, a motor's rotor;
  • a wheel's spin: wheel = wheelEntity (a WheelCollider; an entity or its id, saved with the scene as an entity reference). The wheel spins on its own, with its own inertia or, when it states none, the part's; its motorTorque and brakeTorque still act on its axle. The wheels one network drives stand under one body.
  • a hinge's turn: joint = hingeEntity (the entity carrying a hinge Joint): the joint's body against its connected body, about the joint's axis. The part's inertia is the two bodies' about the hinge (a body swung on a pin away from its centre turns with its own inertia plus its mass times the square of its arm), and the network's torque acts on the joint's body and, opposite, on the connected one. A hinge turns with one part: a second part naming it turns on its own and reads why. Give the hinge stiffness = 0, damping = 0: a hinge with a stiffness or damping and no motor carries a spring back to the angle it started at, which the network's torque then winds against. The step's own forces (the bodies' angular damping, their contacts, other joints) act on the bodies after the network, so a joint part reads its hinge's turn as the network leaves it and the body reads it as the step leaves it, apart by one tick of those forces.
FieldMeaning
inertiaMoment of inertia about its axis (kg·m²). 0 or unset is a light shaft (0.001). A joint part takes its bodies'.
wheel, jointWhat the part turns with (above); neither for a free part.
torqueA constant torque on the part (N·m): an electric motor, a load, a hand crank.
curveA torque source's curve over the part's speed: { {rpm, N·m}, ... } in rising rpm (or { rpm = .., torque = .. } points), interpolated in straight lines and held at the end values past them. An engine's torque curve, a motor map.
throttleThe share of curve the source delivers (0 to 1, or any number: negative runs it backward).
frictionFriction torque holding the part to its frame, always on (N·m): an engine's internal friction, a bearing.
brakeBrake torque holding the part to its frame (N·m).
dampingViscous drag (N·m per rad/s): a pump's or a propeller's load, an engine's pumping loss.

A curve drives its part toward the speed where the curve falls to zero and no further, solved at the speed the part ends the tick with: a curve ending at 0 N·m is a rev limit however coarse the tick.

Links

links is a list of links, each with a name, a kind and the parts it couples by name (a, b, and a differential's input):

KindFieldsMeaning
shafta, bThey turn together, with any torque.
geara, b, ratioa turns ratio times as fast as b, and b takes ratio times the torque: a gear pair, a gearbox's selected gear, a final drive, a belt or chain. ratio = 0 is neutral (nothing coupled); a negative ratio turns b the other way (reverse).
clutcha, b, capacity, engageThey turn together while the torque between them stays within capacity (N·m) times engage (0 to 1, default 1); past it they slip, passing that torque, and lock once their speeds meet. A slipping belt and a torque limiter are clutches too; a fluid coupling, whose torque grows with the difference of its sides' speeds, is a spring with damping alone.
differentialinput, a, b, ratio, locked, preload, bias, dampinginput turns ratio (default 1) times as fast as the mean of a and b, its torque split equally between them: open, the side with less grip spins and the other gets no more torque than it. locked = true holds a and b together. A limited-slip one: preload (N·m) passes that much torque from the faster side to the slower at any input, bias (a torque bias ratio, 1 open, 2 to 5 typical) lets one side take up to bias times the other's torque, and damping (N·m per rad/s) is a viscous coupling between them.
springa, b, stiffness, dampingA torsion spring between them: stiffness (N·m per radian) on the angle wound up between them, damping (N·m per rad/s) on their relative speed. A flexible shaft, a drive's compliance, a clock's mainspring.

Inputs at the tick rate

Every field above is an input. net:set(name, values) writes the fields of one part or link and leaves the rest. These writes are this engine's inputs, laid over the authored parts and links: they reach the next physics step, cost no scene save and no replication, and a driver writes them every frame. Writing parts or links changes the network itself, which is saved with the scene and reaches every session.

local net = car.component.get("ShaftNetwork")
net:set("engine", { throttle = 0.8 })   -- accelerator
net:set("clutch", { engage = 0.4 })     -- clutch pedal
net:set("gear", { ratio = 2.1 })        -- shift; 0 neutral, negative reverse
net:set("diff", { locked = true })      -- diff lock
net:set("rearLeft", { brake = 800 })    -- a steering brake on one track

State

net:state() (or Physics.shaftNetworkState(entityId)) reads the network as of the last physics tick:

  • parts.<name>: speed (rad/s; a wheel part's is its wheel's spin, a joint part's its hinge's turn), rpm, torque (what its curve and torque applied, N·m), brakeTorque (what its friction, brake and drag applied), linkTorque (what the links put on it), throttle and brake (the inputs in effect), and problem, present when the part names a wheel or a joint it cannot turn with (the entity is no WheelCollider, the joint is no hinge, the wheel already belongs to another network, one on an entity spawned before this one's, the hinge already turns with another part) and so turns on its own.
  • links.<name>: kind, ratio, engage, locked (the inputs in effect: a gear indicator reads ratio), torque (N·m the link takes from a, or a differential's from input; positive as a drives the rest), lockTorque (what a differential's lock passes from a to b, or a spring pushes back with), slip (rad/s, a against b, against ratio times b for a gear), slipping (a clutch past its capacity, an open differential or one whose lock is past its capacity), windup (a spring's wound-up angle, radians).
  • settled: whether the tick held every link, friction, brake and drive where its bounds allow: every shaft and gear at its ratio, every differential at the mean of its sides, every clutch and lock holding inside its capacity or slipping at it. It reads false when the links ask for speeds the parts cannot take together (a part shafted to two immovable parts turning at different speeds); the parts then turn as near to every link as they can come.

A tachometer, an engine sound's pitch, a clutch-slip squeal, a gear indicator and a shift logic read these.

Cost

A car's network (an engine, a clutch, a gear and a differential driving two wheels) adds about 0.03 to 0.05 ms to a physics tick on a desktop, about 0.07 ms in a desktop browser and about 0.01 to 0.02 ms in Chrome on a Galaxy Tab S11, measured over eight such cars at 60 Hz and 30 Hz. The profiler reports it under prepare_shaft_networks, update_wheel_colliders and finish_shaft_networks, all three counted in its physics total.

A network costs a tick in proportion to its parts and links in the shapes machines take, a chain, a gear train, a tree of differentials and closed loops of a few gears among them: a part many links meet (a shaft geared to every wheel of a tank) and a chassis under many tyres cost no more than their links do, and cars each driving their own network cost in proportion to their number. A chain of geared parts its friction holds still costs about 2.5 µs a part on a desktop (0.22 ms a tick at 80 parts, in a development build) and about 4.5 µs a part in a desktop browser (0.38 ms at 80 parts). A network finds which of its friction, brakes, clutches and differential locks hold at their limit over its first tick and starts each later tick from where they stood, so its first tick, and a tick in which many of them let go or take hold at once, costs more than the ticks around it.

Recipes

A rear-driven car (the wheels are WheelCollider entities under the chassis):

car.component.add("ShaftNetwork", {
    parts = {
        { name = "engine", inertia = 0.2, friction = 15, damping = 0.02,
          curve = { {1000, 180}, {4500, 260}, {6500, 210}, {7200, 0} } },
        { name = "gearbox", inertia = 0.02 },
        { name = "driveshaft", inertia = 0.05 },
        { name = "rearLeft", wheel = rl }, { name = "rearRight", wheel = rr },
    },
    links = {
        { name = "clutch", kind = "clutch", a = "engine", b = "gearbox", capacity = 450 },
        { name = "gear", kind = "gear", a = "gearbox", b = "driveshaft", ratio = 3.2 },
        { name = "diff", kind = "differential", input = "driveshaft", a = "rearLeft", b = "rearRight", ratio = 3.9 },
    },
})

A four-wheel drive adds a centre differential (input = "driveshaft", a = "frontShaft", b = "rearShaft") with a front and a rear differential below it. A tank drives each track's wheels from one side of a differential and steers with each side's brake.

A winch: a motor geared onto a drum on a hinge.

drum.component.add("Joint", { kind = "hinge", connected = crane.id, axisX = 1, axisY = 0, stiffness = 0, damping = 0 })
drum.component.add("ShaftNetwork", {
    parts = { { name = "motor", inertia = 0.05, torque = 40 }, { name = "drum", joint = drum } },
    links = { { name = "reduction", kind = "gear", a = "motor", b = "drum", ratio = 30 } },
})

A windmill driving a pump: the rotor's hinge part geared onto a pump part whose damping is the pump's load.

mill.component.add("ShaftNetwork", {
    parts = { { name = "rotor", joint = rotor }, { name = "pump", inertia = 0.5, damping = 2 } },
    links = { { name = "gearbox", kind = "gear", a = "pump", b = "rotor", ratio = 8 } },
})

ecs.ShaftNetwork is the native component underneath, with the parts and links as lists of records.