Block reference #

The drivetrain in Vehicle Physics Pro is designed as a tree of connected Blocks, each one implementing an internal mechanical part of the vehicle. The drivetrain may use any number and combination of connected blocks.

VPP provides blocks for the most common parts: engine, gearbox, differential, etc. The provided Vehicle Controller component internally connects the necessary blocks based on the vehicle configuration (i.e., front-wheel-drive, all-wheel-drive, h-drive, etc).

You may also write custom blocks if your vehicle requires specific parts not provided in VPP. Check out Creating custom blocks for an example of a custom block with source code.

Example of drivetrain in a vehicle controller:

graph RL subgraph Vehicle Controller subgraph Wheels WFL>Wheel Front Left] WFR>Wheel Front Right] WRL>Wheel Rear Left] WRR>Wheel Rear Right] end %% Re-define these here so they appear first WFL>Wheel Front Left] WFR>Wheel Front Right] Eng(Engine + Clutch) Gear[Gearbox] Diff{Differential} Eng-->Gear Gear-->Diff Diff-->WRL Diff-->WRR end

Wheel blocks include Steering, Brakes, and Tire Friction.

Block protocol #

A block inherits from the base class VehiclePhysics.Block. Blocks are hosted by a vehicle controller (host), such as VPVehicleController or any other component derived from VehiclePhysics.VehicleBase.

See Creating Custom Vehicles for example source code of a vehicle controller creating and connecting Blocks.

Input and Output connections #

Blocks have inputs and outputs that connect them. Each connection is a Block.Connection object simulating the physical shaft that connects two blocks in a vehicle's powertrain. For example, the shaft connecting the gearbox with the differential, or the halfshafts connecting the differential with the wheels, are simulated via inputs and outputs.

graph RL subgraph Block T("- Settings
- User Inputs
- States (In)
- Sensors (Out)") BL["#nbsp;
Block Logic
#nbsp;"] end I0(Input 0)-->BL BL-->O0>Output 0] BL-->O1>Output 1] BL-->|...|ON>Output n] O0-->BO0(Downstream Block) O1-->BO1(Downstream Block) ON-->BON(Downstream Block) BI0(Upstream Block)-->I0 classDef InOut fill:#FFF,stroke:#FFF class I0,O0,O1,ON InOut classDef Text fill:#FFD,stroke:#BB4 class T Text classDef NoFill fill:#FFF class BO0,BO1,BON,BI0 NoFill

While it's possible for a Block to have multiple inputs, this is typically only applicable in rare situations. One example is regenerative steering (also double differential steering), a mechanism used to steer a vehicle by shifting power between the left and right sides, commonly employed in tanks.

Block connections and torque flow #

The Block.Connect method connects an input of a block to an output of a different block. For this, the Connect method creates a Block.Connection object and ensures both blocks have access to it. This Connection object is the placeholder for the torque and momentum values that are transmitted upstream and downstream between the connected blocks.

The actual torque and momentum flow is a two-stage process in the Block class, specifically in the methods ComputeStateUpstream and EvaluateTorqueDownstream.

1. ComputeStateUpstream #

graph LR E(Engine) B0[Block 0] B1[Block 1] BN[Block n] DOTS["..."] W>Wheel] W-."Momentum (L)

Inertia (I)

Reaction Torque (Tr)".->BN BN-."L

I

Tr".->DOTS DOTS-."L

I

Tr".->B1 B1-."L

I

Tr".->B0 B0-."L

I

Tr".->E classDef NoBox fill:#FFF,stroke:#FFF class DOTS NoBox

From left to right, each block collects angular momentum (L), inertia (I) and reaction torque (Tr) from the Connection objects at each output. Blocks downstream have already left the values there. The block computes the resulting momentum, inertia, and reaction torque and puts them in the Connection object at the input. The block upstream in the chain will collect them for processing, and so on.

The ending point is the Engine, which has no inputs. It takes momentum, inertia, and reaction torque from its output, processes them, then generates a drive torque and puts it back at the output.

2. EvaluateTorqueDownstream #

graph RL E(Engine) B0[Block 0] B1[Block 1] BN[Block n] DOTS["..."] W>Wheel] E-."Drive Torque (Td)".->B0 B0-."Td".->B1 B1-."Td".->DOTS DOTS-."Td".->BN BN-."Td".->W classDef NoBox fill:#FFF,stroke:#FFF class DOTS NoBox

From right to left, each block reads the amount of drive torque (Td) left by the block upstream in the Connection object at its input. After processing it, it puts the resulting torque values at the outputs for the blocks downstream to receive.

The ending points are the Wheels, which have no outputs. They receive the final drive torque at their inputs and perform the final tasks:

The tire force is applied to the vehicle's rigidbody. The momentum and the reaction torque are then sent upstream through the input, and the loop repeats.

Public interface #

A block may expose several types of public members following this convention:

Settings

A serializable nested class with the configuration settings of the block. This class may be exposed and serialized at the inspector by the host controller. A public member settings should be declared in the block as well.

For example, the Engine settings that are exposed in the vehicle controller is an instance of the Engine.Settings class and is assigned to the settings member of the Engine block.

Inputs

Values that come from the driver's controls. Examples: the position of the gear lever (Gearbox block) or the position of the throttle pedal (Engine block). Inputs are the values that are typically adjusted by the driver while driving.

In some cases, the block can constrain an input value to prevent it from going out of range. This happens in the manual gearbox, for instance. The gear lever is not allowed to have an invalid value (non-existing gear).

States
States are a special kind of input that must be fed continuously with data coming from the vehicle and other blocks. Blocks use this data for their own logic. For example, the automatic gearbox requires knowing the actual speed of the vehicle to decide whether it is appropriate to engage a gear or not.
Sensors
Sensors expose internal values from the block to the vehicle controller. These values might be used to feed the States of other blocks, take part in the vehicle's logic, or be exposed in the dashboard. Examples: the rpm values of the engine, the currently engaged gear in the gearbox, the torque transmitted by the clutch, etc.

The Vehicle Controller host is responsible for feeding the blocks with the appropriate inputs and states, as well as exposing and using the sensors correctly. These values are typically to be read, exposed and exchanged through the data bus.

Block events #

Initialize #

Define the number of inputs and outputs here by calling Block.SetInputs(n) and Block.SetOutputs(n).

CheckConnections #

Verify whether the connections required for the block to work are established. Example:

public override bool CheckConnections ()
    {
    // First input required
    return inputs[0] != null;
    }

This method is called after Initialize and prior to any integration, so you could also set up internal variables for caching the connections.

PreStep #

Set up the block before each time step. This is the place for reading externally modified inputs and configuring the block accordingly for the upcoming integration step.

Solver.time is linearly coherent in time: you can assume each t will be greater than the value in the previous call. Solver.deltaTime is typically the fixed time step in the host physics engine.

GetState #

Override this for blocks with inertial parts only.

Returns the actual state of the inertial part of the block. This is the state considered "actual": spin rate of a wheel, rpm of an engine...

L   angular momentum of the inertial part
P   linear momentum
Lr  reactive angular momentum

SetSubstepState #

Override this for blocks with inertial parts only.

Apply the provided state to the block for further calculations within this substep. The first substep will typically receive the state already returned in GetState. Other substeps will receive states computed by the solver.

Solver.time and Solver.deltaTime return the timing values for the current substep.

ComputeStateUpstream #

Override this in blocks with inputs (i.e., motors would be excluded as they have outputs only).

Given the L-I-Tr states at the outputs, compute and report the L-I-Tr state at the inputs:

This defines L, I and Tr of the system (and thus the angular velocity along all the shafts).

EvaluateTorqueDownstream #

Given the outTd values received at the inputs, compute the outTd values at the outputs. Use Solver.time and Solver.deltaTime as t and dt for your calculations.

This simulates the actual torque flow along the entire drivetrain.

GetSubstepDerivative #

Override this for blocks with inertial parts only.

Must return the derivatives that affected the inertial part's state in this substep based on the state already provided by SetSubstepState and the block's inputs and logic.

Use Solver.time and Solver.deltaTime as t and dt for your calculations.

Typically the derivative has already been calculated in EvaluateTorqueDownstream for providing the torque at the outputs.

T   derivative of the angular momentum L (torque)
F   derivative of the linear momentum P (force)
Tr  derivative of the reactive angular momentum (reaction torque)

SetState #

Override this for blocks with inertial parts only.

Receive the new integrated values for this integration interval Solver.deltaTime.

L, P, Lr:   Integrated magnitudes.

These values must be returned at the next GetState event.

Here Solver.time contains the next step's value. Methods GetState and SetState are invoked like this:

- GetState (time, deltaTime)
-   [ substep ]
- SetState (time + deltaTime, deltaTime)

Scripting reference #

namespace VehiclePhysics
{
    public class Block
}

Nested classes #

    // Definition for a torque connection (shaft) between two blocks

    public class Connection
        {
        public float L = 0.0f;              // State: Angular momentum
        public float I = 0.0f;              // State: Inertia
        public float Tr = 0.0f;             // Reaction torque

        public float outTd = 0.0f;          // Derivative: Drive torque

        // Connected blocks and their corresponding slots

        public Block input;
        public int inputSlot;
        public Block output;
        public int outputSlot;
        }

    // Definition of State and Derivative for inertial parts and wheels

    public struct State
        {
        public float L;         // Angular momentum
        public Vector2 P;       // Linear momentum
        public float Lr;        // Reactive angular momentum
        }

    public struct Derivative
        {
        public float T;         // Torque is the derivative of angular momentum     dL/dt = T
        public Vector2 F;       // Force is the derivative of linear momentum       dP/dt = F
        public float Tr;        // Reaction torque is the derivative of Lr          dLr/dt= Tr
        }

Properties #

    // Utility
    // Angular velocity in radians/s = 2pi * frequency in revs/second = 2pi * f / 60 in revs/min

    public static float RpmToW = (2.0f * Mathf.PI) / 60.0f;
    public static float WToRpm = 60.0f / (2.0f * Mathf.PI);

Events #

    // Torque calculation
    // =============================================================================================
    //
    // Blocks without inertial parts:
    // ------------------------------
    //
    // Torque transmitters/multipliers with negligible inertia such as shafts, gears,
    // differentials...
    //
    //      Initialize
    //      CheckConnections
    //
    //      PreStep                         Set up the block before the step is computed
    //      ComputeStateUpstream            |- Integration substep
    //      EvaluateTorqueDownstream        |
    //
    // Blocks with inertial parts:
    // ---------------------------
    //
    // Contain a significant inertial element, such as flywheel or wheels.
    //
    //      Initialize
    //      CheckConnections
    //
    //      PreStep                     Set up the block before the step is computed
    //      GetState                    Retrieve actual state of the inertial part
    //        SetSubstepState           --|
    //        ComputeStateUpstream        |-- Integration substep
    //        EvaluateTorqueDownstream    |
    //        GetSubstepDerivative      --|
    //      SetState                    Set the new integrated state to the inertial part

    protected virtual void Initialize ()
    public virtual bool CheckConnections ()
    public virtual void PreStep ()
    public virtual void GetState (ref State S)
    public virtual void SetSubstepState (State S)
    public virtual void ComputeStateUpstream ()
    public virtual void EvaluateTorqueDownstream ()
    public virtual void GetSubstepDerivative (ref Derivative D)
    public virtual void SetState (State S)

Methods #

    // Initialization

    protected void SetInputs (int count)
    protected void SetOutputs (int count)

    // Connection management

    public static bool Connect (Block inputUnit, int inputSlot, Block outputUnit, int outputSlot)
    public static bool Connect (Block inputUnit, Block outputUnit)
    public static bool Connect (Block inputUnit, Block outputUnit, int outputSlot)
    public bool DisconnectInput (int inputSlot)
    public bool DisconnectOutput (int outputSlot)