Modular Vehicles Tutorial 1: Creating a Vehicle Topology
This tutorial includes video content:
In this first module, we will setup the file structure as well as the topology of the drivetrain of an 8x8 land vehicle.
The goal is to fully create a modular vehicle without using templates. This will give you a better understanding of the structure of modular vehicles, allowing you to create complex powertrain systems.
Prerequisites
You need to have Vortex Studio and the Vortex Studio Samples and Demo Scenes installed to be able to follow all steps in this tutorial : https://www.cm-labs.com/downloads/
Creating the vehicle file structure
In your working directory, create a folder named AWD_8x8
Copy the Components folder (C:\CM Labs\Vortex Studio Content <version>\Samples\Vehicle Systems) to this new folder.
Create a Resources folder. We'll use this folder later in the tutorial.
In Vortex Studio, Create an Assembly. Save it in the AWD_8x8 folder and name it AWD_8x8.vxassembly.
Creating the vehicle topology
Open AWD_8x8.vxassembly, created in the previous section.
From the Basics section of the Toolbox, insert Assemblies From Files.
Select Chassis.vxassembly from ...\Components\Chassis
Repeat this process to add all the parts of the vehicle's powertrain:
Engine (Advanced)
Torque Converter (Advanced)
Automatic Transmission
Differentials (7)
Wheels (8)
Rename each wheels to identify their positions on the LAV ("FL" for Front Left, "FML" for Front Middle Left, etc.).
From the Basics section of the Toolbox, insert a Connection Container. Rename it "Chassis Connections"
Insert the Chassis Part Output from the Chassis Attachments VHL in the Chassis Assembly.
For each other component, find their Attachments VHL and insert the Chassis Part Input, connecting it to the Chassis Part Output of the Chassis assembly
From the Basics section of the Toolbox, insert a Connection Container. Rename it "Vehicle Topology"
Insert all Shaft Part Inputs and Parameters from the Engine, Torque Converter, Transmission, Differentials and Wheels Attachments VHL.
Connect Inputs and Parameters following to the topology below:
Insert Shaft RPM and Shaft Speed from the Engine Interface and Torque Converter Coupling Interface.
Insert Input Shaft Speed from the Transmission Interface and Torque Converter Coupling Interface.
Make these connections:
Creating a Wheel Positioner
From the Simulation section of the Toolbox, insert a Dynamics Script.
Close the resulting window and rename it "Wheel Positioner"
In the Code field of the extension, copy the following Python Code.
Wheel Positioner Code
from Vortex import * def on_simulation_start(self): create_input(self, 'Enable', Types.Type_Bool).setDescription("True to update positions") create_parameter(self, 'Wheel Position Front', Types.Type_VxReal).setDescription("Position of front axle") create_parameter(self, 'Wheel Position Front Middle', Types.Type_VxReal).setDescription("Position of front middle axle") create_parameter(self, 'Wheel Position Rear Middle', Types.Type_VxReal).setDescription("Position of rear middle axle") create_parameter(self, 'Wheel Position Rear', Types.Type_VxReal).setDescription("Position of rear axle") create_parameter(self, 'Wheel Spacing', Types.Type_VxReal).setDescription("Distance between left and right wheels") create_parameter(self, 'Wheel Height', Types.Type_VxReal).setDescription("Vertical position of wheels") create_output(self, 'Wheel FL Transform', Types.Type_VxMatrix44) create_output(self, 'Wheel FR Transform', Types.Type_VxMatrix44) create_output(self, 'Wheel FML Transform', Types.Type_VxMatrix44) create_output(self, 'Wheel FMR Transform', Types.Type_VxMatrix44) create_output(self, 'Wheel RML Transform', Types.Type_VxMatrix44) create_output(self, 'Wheel RMR Transform', Types.Type_VxMatrix44) create_output(self, 'Wheel RL Transform', Types.Type_VxMatrix44) create_output(self, 'Wheel RR Transform', Types.Type_VxMatrix44) def paused_update(self): config_update(self) def config_update(self): # Skip update if disabled if not self.inputs.Enable.value: return # Place wheels pos = VxVector3(self.parameters.Wheel_Position_Front.value, self.parameters.Wheel_Spacing.value/2.0, self.parameters.Wheel_Height.value) #Front self.outputs.Wheel_FL_Transform.value = createTranslation(pos) pos.y = -pos.y self.outputs.Wheel_FR_Transform.value = createTranslation(pos) #Front Middle pos.x = self.parameters.Wheel_Position_Front_Middle.value self.outputs.Wheel_FMR_Transform.value = createTranslation(pos) pos.y = -pos.y self.outputs.Wheel_FML_Transform.value = createTranslation(pos) #Rear Middle pos.x = self.parameters.Wheel_Position_Rear_Middle.value self.outputs.Wheel_RML_Transform.value = createTranslation(pos) pos.y = -pos.y self.outputs.Wheel_RMR_Transform.value = createTranslation(pos) #Rear pos.x = self.parameters.Wheel_Position_Rear.value self.outputs.Wheel_RR_Transform.value = createTranslation(pos) pos.y = -pos.y self.outputs.Wheel_RL_Transform.value = createTranslation(pos) def create_output(extension, name, o_type, default_value=None): """Create output field with optional default value, reset on every simulation run.""" if extension.getOutput(name) is None: extension.addOutput(name, o_type) if default_value is not None: extension.getOutput(name).value = default_value return extension.getOutput(name) def create_parameter(extension, name, p_type, default_value=None): """Create parameter field with optional default value set only when the field is created.""" if extension.getParameter(name) is None: field = extension.addParameter(name, p_type) if default_value is not None: field.value = default_value return extension.getParameter(name) def create_input(extension, name, i_type, default_value=None): """Create input field with optional default value set only when the field is created.""" if extension.getInput(name) is None: field = extension.addInput(name, i_type) if default_value is not None: field.value = default_value return extension.getInput(name)
From the Basics section of the Toolbox, insert a Connection Container. Rename it "Wheel Positioner Connections"
Connect the Wheel Transform Outputs from the script to the Local Transform of each wheel assembly.
From the Basics section of the Toolbox, insert a Folder. Rename it "Components". Insert all of the other extensions into this folder.
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Component Configuration".
Drag and drop the Parameters of the Wheel Positioner script into its Parameters section.
Add the Body Attachment Point Parameter from the Chassis Attachments VHL in the Chassis.
Adding Wheel and Suspension Tuning Interface
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Wheel and Suspension Tuning"
Add and group all of the following Inputs from the Wheel Interface VHL from each wheel in the Wheel and Suspension Tuning interface.
Radius
Width
Mass
Suspension Stiffness
Suspension Damping
Type Type
Tire Stiffness
Adding Engine Tuning Interface
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Engine Tuning".
From the Engine Interface VHL, insert the following Inputs and Parameters in the Parameters section of this new VHL.
Torque Scale
Braking Torque Scale
Shaft Mass
Shaft Inertia
Torque Table
Braking Torque Table
Adding Torque Converter Tuning Interface
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Torque Converter Tuning".
From the Coupling Interface VHL, insert the following Inputs and Parameters in the Parameters section of this new VHL.
Stall RPM
Stall Torque
All Parameters.
Adding Transmission Tuning Interface
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Transmission Tuning".
From the Transmission Interface VHL, insert the following Inputs and Parameters in the Parameters section of this new VHL.
All Inputs except Gear Selection, Min Gear Selection, Park, Input Shaft Speed, Throttle.
All Parameters.
Adding Differentials Tuning Interface
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Differentials Tuning"
Add and group all of the following Inputs and Parameters from the Differential Interface VHL from each differential in the Wheel and Differentials Tuning interface.
Gear Ratio
Shaft Mass
Shaft Intertia
Creating an Ackerman Steering Controller
From the Simulation section of the Toolbox, insert a Dynamics Script.
Close the resulting window and rename it "Steering Controller"
In the Code field of the extension, copy the following Python Code.
Steering Controller
from Vortex import * from math import * def on_simulation_start(self): self.Steering_Input = create_input(self, "Steering Input", Types.Type_VxReal) self.Front_Axle_Position = create_input(self, "Front Axle Position", Types.Type_VxReal) self.Front_Middle_Axle_Position = create_input(self, "Front Middle Axle Position", Types.Type_VxReal) self.Rear_Middle_Axle_Position = create_input(self, "Rear Middle Axle Position", Types.Type_VxReal) self.Rear_Axle_Position = create_input(self, "Rear Axle Position", Types.Type_VxReal) self.Width = create_input(self, "Width", Types.Type_VxReal) self.Max_Angle = create_input(self, "Max Angle", Types.Type_VxReal) self.Angle_FL = create_output(self, "Angle FL", Types.Type_VxReal) self.Angle_FR = create_output(self, "Angle FR", Types.Type_VxReal) self.Angle_FML = create_output(self, "Angle FML", Types.Type_VxReal) self.Angle_FMR = create_output(self, "Angle FMR", Types.Type_VxReal) def pre_step(self): steeringInput = clamp(self.Steering_Input.value, -1.0, 1.0) rear_axle_pos = self.Rear_Axle_Position.value + (self.Rear_Middle_Axle_Position.value - self.Rear_Axle_Position.value) angle = steeringInput * self.Max_Angle.value pos_front = self.Front_Axle_Position.value - rear_axle_pos pos_middle = self.Front_Middle_Axle_Position.value - rear_axle_pos pos_lat = self.Width.value / 2.0 self.outputs.Angle_FL.value = compute_angle(pos_front, pos_lat, angle) self.outputs.Angle_FR.value = compute_angle(pos_front, -pos_lat, angle) self.outputs.Angle_FML.value = compute_angle(pos_middle, pos_lat, angle) self.outputs.Angle_FMR.value = compute_angle(pos_middle, -pos_lat, angle) def compute_angle(pos_long, pos_lat, angle): outputAngle = 0.0 tanAngle = tan(angle) if( abs(tanAngle) > 0.0000001 ): radius = pos_long / tanAngle divider = radius + pos_lat if( abs(divider) > 0.0000001 ): outputAngle = atan(pos_long/divider) return outputAngle; def clamp(value, min_value, max_value): """Return a bounded value.""" return min(max(value, min_value), max_value) def create_output(extension, name, o_type, default_value=None): """Create output field with optional default value, reset on every simulation run.""" if extension.getOutput(name) is None: extension.addOutput(name, o_type) if default_value is not None: extension.getOutput(name).value = default_value return extension.getOutput(name) def create_parameter(extension, name, p_type, default_value=None): """Create parameter field with optional default value set only when the field is created.""" if extension.getParameter(name) is None: field = extension.addParameter(name, p_type) if default_value is not None: field.value = default_value return extension.getParameter(name) def create_input(extension, name, i_type, default_value=None): """Create input field with optional default value set only when the field is created.""" if extension.getInput(name) is None: field = extension.addInput(name, i_type) if default_value is not None: field.value = default_value return extension.getInput(name)
Edit the Wheel Positioning VHL.
From the Steering Control script properties, attach the following inputs to the corresponding parameter in the VHL:
Front Axle Position
Front Middle Axle Position
Rear Middle Axle Position
Rear Axle Position
Width
Max Angle (rename it "Max Steering Angle" in the VHL)
From the Basics section of the Toolbox, insert a Connection Container. Rename it "Steering Connections".
Insert all Outputs from the Steering Controller script.
Connect the outputs to the Steering Angle Input of the corresponding Wheel Interface VHL.
Creating a Braking Controller
From the Simulation section of the Toolbox, insert a Dynamics Script.
Close the resulting window and rename it "Braking Controller".
In the Code field of the extension, copy the following Python Code.
Braking Controller
# This script is calculates the front and rear braking torques based on max torque and bias from Vortex import * def on_simulation_start(extension): extension.Brake_Input = create_input(extension, "Brake Input", Types.Type_VxReal, 0) extension.Max_Torque = create_input(extension, "Max Torque", Types.Type_VxReal, 1000) extension.Front_Rear_Bias = create_input(extension, "Front Rear Bias", Types.Type_VxReal, 0.6) extension.Braking_Torque_F = create_output(extension, "Braking Torque F", Types.Type_VxReal) extension.Braking_Torque_R = create_output(extension, "Braking Torque R", Types.Type_VxReal) def pre_step(extension): extension.Braking_Torque_F.value = extension.Max_Torque.value * extension.Brake_Input.value extension.Braking_Torque_R.value = (extension.Max_Torque.value * extension.Brake_Input.value * (1 - extension.Front_Rear_Bias.value) / extension.Front_Rear_Bias.value) def clamp(value, min_value, max_value): """Return a bounded value.""" return min(max(value, min_value), max_value) def create_output(extension, name, o_type, default_value=None): """Create output field with optional default value, reset on every simulation run.""" if extension.getOutput(name) is None: extension.addOutput(name, o_type) if default_value is not None: extension.getOutput(name).value = default_value return extension.getOutput(name) def create_parameter(extension, name, p_type, default_value=None): """Create parameter field with optional default value set only when the field is created.""" if extension.getParameter(name) is None: field = extension.addParameter(name, p_type) if default_value is not None: field.value = default_value return extension.getParameter(name) def create_input(extension, name, i_type, default_value=None): """Create input field with optional default value set only when the field is created.""" if extension.getInput(name) is None: field = extension.addInput(name, i_type) if default_value is not None: field.value = default_value return extension.getInput(name)
From the Basics section of the Toolbox, insert a Connection Container. Rename it "Braking Connections".
Insert all Outputs from the Braking Controller script.
Connect the Braking Torque Input of the front wheels Wheel Interface to Braking Torque F.
Connect the Braking Torque Input of the rear wheels Wheel Interface to Braking Torque R.
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Braking Tuning"
Add the following Inputs from the Braking Controller script:
Max Torque
Front Rear Bias
Adding a Vehicle Control Interface
From the Simulation section of the Toolbox, insert a VHL Interface. Rename it "Vehicle Control".
Expose the following Inputs from the corresponding component:
Engine → Engine Interface
Engine Running
Throttle
Steering Controller
Steering Input
Braking Controller
Brake Input
Automatic Transmission → Transmission Interface
Gear Selection
Park
Throttle (linked to Engine Throttle)
Differentials Tuning
Lock Torque