FERRETGame engine over Babylon.js
Ferret

Gearbox: a manual transaxle in the drivetrain

A five-speed transaxle (input shaft, output shaft, final drive, open differential) driven by the Drivetrain of capability M. Everything comes from examples/gearbox/spec/spec.json; when examples/gearbox/build/drivetrain.json exists its measured inertias and node names are used, otherwise the numbers are estimated from the spec.

import { Drivetrain, buildTransaxle } from '@ferret/physics-mechanisms';
const t = buildTransaxle(new Drivetrain(), spec, { dt: drivetrainJson });   // dt is optional
t.setPedal(1);                          // clutch pedal down
t.train.controls.get('gearbox')!.set({ gear: 1 });
t.engine.throttle = 0.5;
t.train.step(1 / 240);
t.state();                              // speeds, torques, power in / out, losses, temperatures

Coupling kind gearbox#

Drivetrain.gearbox(name, input, output, model) adds a coupling that behaves as a clutch whose relation and capacity are set by a model in every sub-step: neutral (free), the cone of a synchroniser (slipping or sticking, capacity T = mu·F·r_m / sin(alpha) on the gear), or the dogs (infinite capacity, omega_out = omega_in / ratio, efficiency of the engaged meshes). A model implements pre(h), post(h), command(cmd) and state(). Gearbox (gearbox.ts) is the manual box. Forward gears turn the output the same way as the input in the model; reverse is negative. The glTF nodes on the output side turn the other way (Shaft_Out has the axis -1, 0, 0; the free gears take nodeAngles()).

The free gears all turn with the input (constant mesh), so their inertia is reflected onto the input shaft (reflectedInertia).

The shift#

machine.set { element: "gearbox", command: { gear: 3 } } (1 to 5, "R", 0 or "N" for neutral; effort scales the force on the lever, default 1).

  1. disengage: the sleeve leaves the engaged gear. It needs the load off the dogs (releaseTorque, 40 N·m); with the clutch closed under power the shift waits and is given up with reason: "load".
  2. gate: the finger crosses to the rail of the wanted sleeve (0.1 of the stroke time).
  3. approach: the sleeve runs to the blocker ring.
  4. sync: the cone drags the gear to the shaft speed while the blocker holds the sleeve back. It passes when the speeds agree (under passSpeed). Slipping heats the cone (temperature) and wears it (wear 0..1, friction falls with wear and above 150 °C). After grindAfter (0.4 s) a machine:slip { grind: true } event is raised; after refuseAfter (1.2 s) the shift is given up (reason: "blocked"), the sleeve goes back to neutral and machine:shift { ok: false } is raised. It never succeeds silently.
  5. pass: the sleeve passes the blocker and the dogs go in; machine:shift { ok: true, time }.

With the clutch released and the speeds matched the whole stroke takes nominal_time_s of the sync entry (0.2 s). Sleeve and fork travel (mm) is in state().sleeves and bound to the Sleeve_*, Fork_*, Rail_* nodes.

Limits and events#

Every gear has the torque at which its teeth give way: the weaker of Lewis bending (sigma_f_allow, form factor from the virtual tooth count) and Hertz flank pressure (sigma_h_allow), at the pitch circle, referred to the input shaft (pairLimit). Beyond it machine:break { element: "Gear_3", node, broken: "Gear_3_Broken" } is raised; the gear is out and cannot be selected again ({ ok: false, reason: "broken" }). The scene replaces node by broken.

Events: machine:break, machine:slip { element, slipping, grind?, gear? }, machine:shift { element, gear, ok, reason?, time? }.

What buildTransaxle creates#

Shafts flywheel, Shaft_In, Shaft_Out, Diff_Carrier, Halfshaft_L/R (each wheel carries its inertia plus half the vehicle mass reflected to the tyre); couplings clutch (friction, 1.4 x design torque, command = engagement), gearbox, final_drive, differential, brake_L/R; road load (rolling and drag) on the wheels; an engine (torque curve scaled to the design torque, rev limiter at the spec's limit, engine braking, idle governor). Controls for machine.set: gearbox, clutch, throttle, brake.

state() returns speeds (rpm), torques, power in / out / loss of the box, losses by element, temperatures (oil, cones), energy and its balance.

Tests#

packages/physics-mechanisms/test/gearbox.test.ts: exact ratios (0.5 %), torque T_in · ratio · eta (1 %), shift time 0.15 to 0.3 s, refused / grinding shift without the clutch, energy balance over a 60 s drive cycle, gear break within 10 % of the Lewis / Hertz limit.

The demo#

examples/gearbox/demo is a small app with no engine behind the box: the engine is a speed you set (slider or Up / Down, 0 to 6500 rpm), the clutch an on / off switch (Space), the gears are shifted by hand (N, 1 to 5, R; Left / Right or the digit keys) a brake is a slider (hold B: the wheels must nearly stop before reverse goes in, as in a car), and the housing's opacity is a slider (Esc steps half / solid / hidden). It starts in 1st at 700 rpm with the clutch engaged, and the clutch is the weak link by design, so a dump at a big speed difference slips instead of stripping a gear. A shift with the clutch engaged is refused by the box, and the HUD says so. It runs on OS Beroun (beroun/gearbox, with the decimated model) and in a browser (node tools/build.mjs gearbox; ?lite=1 forces the light model). It is also on the front page, as a recorded run and live.

A run through the demo: the housing fades out and back, the engine speeds up in 1st, a shift with the clutch engaged is refused, then the clutch is released and the gears are changed.

Oil, heat, sound, wear (stage 5)#

gearbox.life (GearboxLife, gearbox-life.ts); everything is in machine.state under models.gearbox.life (and Transaxle.state()).

  • Oil. Level as a fraction of the spec's oil_level_z (machine.set { element: "gearbox", command: { oil: 0.5 } }; the Oil node scales with it). Viscosity nu(T) = nu40 · exp(-0.032 (T - 40)). Splash loss of each gear is ½ ρ Cd b · depth · v³ times (nu/nu40)^0.2, where depth comes from the gear's tip radius and shaft height against the oil level: with the spec's level only the crown dips. It is a torque on the shaft that carries the gear (so its energy is in the balance) and heat into the oil. Cold oil also makes every sleeve stroke longer (coldFactor).
  • Heat. A lumped network (ThermalNet): oil, housing, six bearings, the six gear meshes, the crown and the three cones. Mesh losses go 60/40 to the gear and the oil, final drive and differential losses likewise, bearing friction (M = mu · P · d / 2, scaled by viscosity) to each bearing, cone slip to the cone, churn to the oil. Bearings and gears conduct to oil and housing, the housing convects to the air (10 + 6 √v W/m²K over 0.8 m²; life.opt.airSpeed is the vehicle speed in the builder).
  • Sound state (life.state().audio): whine per loaded mesh (the engaged pair and the final drive) with f = z · n / 60, three harmonics, a level that grows with load and with pitting; rattle (rate, intensity, impacts) from free gears moving in their backlash when the input speed pulses (engine.ripple gives the firing pulses); grind while the blocker is fought; events is a queue (life.drain()): clunk (dogs in, final drive taking up), grind (start and end), rattle.
  • Wear and failure. Pitting: damage per mesh Σ revs / N_f, N_f = 5e7 (sigma_lim / sigma_H)^8.5 with the Hertz pressure of the load; at 1 the event machine:wear { mode: "pitting" } fires, the gear whines louder, loses 1 % efficiency and breaks at 70 % of its limit. Bearings: L10 = 1e6 (C/P)^p revolutions (p = 3 ball, 10/3 roller) from the gear forces; damage revs / L10, at 1 machine:wear { mode: "bearing" }, friction ×5. A gear past its Lewis/Hertz limit raises machine:break { mode: "stripped" }, a shaft past its shear limit machine:break { mode: "sheared" }. Wear sheds metal into the oil (oil.metalPpm). fatigueSpeedup makes a life test run faster than real time.

The model, the package and the renders (stages 3, 6.1, 7)#

The unit is generated, not drawn: examples/gearbox/spec/spec.json gives every number, build_parts.py turns it into 141 solids, check_assembly.py proves them (no clash, every gear pair meshes with 0.08 mm backlash, all six engagements work, ratios read back from the solids), and Blender builds the glTF models. One command, python examples/gearbox/checks/check_all.py, runs the whole chain of checks and fails on a regression; examples/gearbox/README.md has the rebuild steps, how to change a ratio and how to add a gear.

File (examples/gearbox/assets/)What
gearbox_lod1.glbthe default model for a weak GPU (the console, phones): about 118k triangles, 6 MB
gearbox.glbfull detail for a close view: about 349k triangles, 21 MB (the budget is 600k and 25 MB)
drivetrain.jsonthe contract with the machine file: node names, axes, inertias, ratios, synchroniser data

Node names are the contract: one node per moving group (Shaft_In, Shaft_Out, Gear_1..Gear_5, Gear_R, Idler_R, Sleeve_*, Fork_*, Diff_Carrier, Halfshaft_L/R, Housing, Oil); a rotating node has its origin on its axis of rotation and local X as the axis (tests/unit/gearbox-model-contract.test.ts holds it to 0.01 mm). Every group carries an explode vector in the glTF extras for an exploded view without a new model.

Presentation renders come from the same models: blender -b -P examples/gearbox/render/render.py writes stills (closed, cutaway, exploded, three close-ups; 3840x2160, a 1920x1080 web copy and a transparent variant), turntables of the closed and the cutaway unit, the exploded-view animation and the shift 1 to 2 animation (1920x1080, 30 fps, MP4 and a poster each) to examples/gearbox/renders/. Scenes are data in render/scenes.json; the shift animation takes its speeds from the ratios in the spec. examples/gearbox/checks/check_renders.py checks that the set is complete, has the expected sizes and frame counts, and that the turntables loop.