ShaftNetwork.component
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(aWheelCollider; an entity or its id, saved with the scene as an entity reference). The wheel spins on its own, with its owninertiaor, when it states none, the part's; itsmotorTorqueandbrakeTorquestill act on its axle. The wheels one network drives stand under one body. - a hinge's turn:
joint = hingeEntity(the entity carrying a hingeJoint): 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 hingestiffness = 0, damping = 0: a hinge with astiffnessordampingand 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.
| Field | Meaning |
|---|---|
inertia | Moment of inertia about its axis (kg·m²). 0 or unset is a light shaft (0.001). A joint part takes its bodies'. |
wheel, joint | What the part turns with (above); neither for a free part. |
torque | A constant torque on the part (N·m): an electric motor, a load, a hand crank. |
curve | A 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. |
throttle | The share of curve the source delivers (0 to 1, or any number: negative runs it backward). |
friction | Friction torque holding the part to its frame, always on (N·m): an engine's internal friction, a bearing. |
brake | Brake torque holding the part to its frame (N·m). |
damping | Viscous 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):
| Kind | Fields | Meaning |
|---|---|---|
shaft | a, b | They turn together, with any torque. |
gear | a, b, ratio | a 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). |
clutch | a, b, capacity, engage | They 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. |
differential | input, a, b, ratio, locked, preload, bias, damping | input 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. |
spring | a, b, stiffness, damping | A 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 itscurveandtorqueapplied, N·m),brakeTorque(what its friction, brake and drag applied),linkTorque(what the links put on it),throttleandbrake(the inputs in effect), andproblem, present when the part names a wheel or a joint it cannot turn with (the entity is noWheelCollider, 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 readsratio),torque(N·m the link takes froma, or a differential's frominput; positive asadrives the rest),lockTorque(what a differential's lock passes fromatob, or a spring pushes back with),slip(rad/s,aagainstb, againstratiotimesbfor 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 readsfalsewhen 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.