Skip to content

Forces and coupling

Motors, solenoids, servos, thrusters and brakes all join electricity and motion. In CRUMB the circuit logic stays purely electrical, the component logic handles the physics, and the two pass plain numbers through UserData.

A motor shows the whole pattern. Each half does its work in a set hook, and two numbers cross between them in UserData: the winding’s current one way and the shaft’s speed the other.

Three rules make this stable and repeatable:

  1. Publish from PreStep, never from Step. Step runs on every solve pass, and the early passes carry trial values that can be absurd. The physics side reads the mailbox on its own thread, so it must only ever see a settled value. Publishing the previous tick’s current costs one tick, which is nothing at microsecond steps.
  2. Couple in PhysicsUpdate, never Update. Feedback at frame rate is too slow for a light rotor, and the loop diverges.
  3. Keep each constant on its own side. The back-EMF constant Ke belongs to the winding, so pass it the raw spin rate. The torque constant Kt belongs to the component logic. Don’t apply Ke on both sides.

The motor example is a complete part built this way.

A mesh that carries a part body has .Rigidbody, a RigidbodyWrapper: Rendering.Wheel.Rigidbody for a mesh named Wheel. All values are SI, in world space unless they say otherwise.

Pushing

  • ApplyTorque(Vector3 axisTorque) in N·m, or ApplyTorque(double) about the part’s hinge.
  • ApplyForce(Vector3 force) in N, at the part’s centre of mass. A thruster or a solenoid is just a force.
  • ApplyJointFriction(double nm) adds breakaway friction to the part’s joint, for a brake, a clutch or gearbox losses.

Applied values are sustained. CRUMB re-applies the last torque or force every physics step until you change it, so set it when it changes and set it to zero to stop.

Reading motion

  • AngularVelocity and LinearVelocity.
  • SpinRate (rad/s) and HingeAngle (rad, multi-turn) about the hinge. These are the reads for back-EMF, encoders and feedback pots.
  • HingeAxis, the hinge axis in world space.

Reading loads

  • JointForce, JointForceLocal and JointTorque: what the part’s joint carried over the last step, such as a load cell or torque sensor reading.
  • ContactForce, ContactNormalForce, ContactTorque, ContactPoint, ContactArea, FrictionUse and InContact: what touching the world did to the part, for force sensors, bump switches and grip sensing.
  • Weight: the body’s mass times the gravity it feels.

Inertia through a gearbox. Set ReflectedInertia (and optionally ReflectedInertiaGearRatio) once in Initialize for a geared drive. CRUMB gives it a real rotor, so a light geared part stays stable without you tuning its mass.

A body’s physics config can make its contact surface soft: a rubber layer on a rigid backing, a solid material, or a hollow body. A soft surface squashes, spreads its load, grips and returns a known share of an impact. Use the material’s own figures (modulus or Shore hardness, resilience, friction) rather than values tuned to make a demo work.

Parts welded together act as one rigid body, and each part keeps its own mass, centre of mass and colliders. Forces still act where each part is, so a thruster on a welded wing turns the whole body as a real one would.

A sensor that responds to force, such as a force-sensing resistor, reads the force in component logic and passes the circuit a resistance, never a force:

public override void PhysicsUpdate(double delta)
{
double force = Rendering.Sensor.Rigidbody.ContactNormalForce; // N, over the last physics step
Circuit.UserData[0] = OhmsFromCurve(force); // the circuit only ever sees ohms
}

The circuit logic latches UserData[0] in PreStep and stamps StampConductanceDynamic(a, b, 1.0 / Math.Max(1.0, ohms)) in Step.