This page has been translated automatically.
UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Programming
Fundamentals
Setting Up Development Environment
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
Warning! This version of documentation is OUTDATED, as it describes an older SDK version! Please switch to the documentation for the latest SDK version.
Warning! This version of documentation describes an old SDK version which is no longer supported! Please upgrade to the latest SDK version.

Creating a Car with Wheel Joints

A Simple Car with Wheel Joints

A Simple Car with Wheel Joints

This example shows how to:

The basic workflow of creating a simple car with keyboard control is as follows:

  1. Create car geometry: frame and wheels.
  2. Assign rigid bodies and collision shapes to the frame and just a rigid body to all the wheels.
  3. Set up masses for the wheels and the frame.
    Notice
    Do not use real masses (e.g. 2000 kg for the frame and 10 kg for the wheels), as joints may become unstable! It might be better to use 64 kg for the body and 25 kg for each wheel to provide realistic behavior.
  4. Connect the wheels to the frame using wheel joints. Set up joint parameters.
  5. Enable car movement using joint motors and assign movement control to corresponding keyboard keys.

Creating Geometry and Adding Some Physics#

The first thing we are going to address in this tutorial is the geometry of our car. We are going to create a rectangular car body and four wheels. We are also going to add some physical parameters to the geometry. So, we need two functions:

  • The first one to create a box with specified parameters representing the car body:
Source code (C#)
/// function, creating a car body having a specified size, color and transformation with a body and a shape
ObjectMeshDynamic createBody(String name, vec3 size, float mass, vec4 color, Mat4 transform)
{
	// creating geometry and setting up its parameters (name, material and transformation)
	ObjectMeshDynamic OMD = Primitives.CreateBox(size);
	OMD.WorldTransform = transform;
	OMD.SetMaterial("mesh_base", "*");
	OMD.SetMaterialParameterFloat4("albedo_color", color, 0);
	OMD.Name = name;

	// enabling collision detection for the body
	OMD.SetCollision(true, 0);

	// adding physics, i.e. a rigid body and a box shape with specified mass
	BodyRigid body = new BodyRigid(OMD);

	body.AddShape(new ShapeBox(size), MathLib.Translate(new vec3(0.0f)));
	OMD.Body.GetShape(0).Mass = mass;

	return OMD;
}
  • The second one to create a cylinder with specified parameters representing a wheel:
Source code (C#)
/// function, creating a wheel having a specified size, color and transformation with a body
ObjectMeshDynamic createWheel(Node frame, String name, float radius, float height, Mat4 transform)
{
	// creating geometry and setting up its parameters (name, material and transformation)
	ObjectMeshDynamic OMD = Primitives.CreateCylinder(radius, height, 1, 32);
	frame.AddChild(OMD);
	OMD.Transform = transform;
	OMD.SetMaterial("mesh_base", "*");
	OMD.SetMaterialParameterFloat4("albedo_color", new vec4(0.1f, 0.1f, 0.1f, 1.0f), 0);
	OMD.Name = name;

	// enabling collision detection for the wheel
	OMD.SetCollision(true, 0);

	// adding a rigid body 
	BodyRigid body = new BodyRigid(OMD);

	return OMD;
}

Now, using these functions we can create our car. We are going to use DynamicMesh objects for the frame and four wheels. For a proper mass balance let us set frame mass equal to 64 kg and the mass of each wheel - to 25 kg.

Source code (C#)
// defining DynamicMesh objects for the frame and four wheels
ObjectMeshDynamic car_frame;
ObjectMeshDynamic[] wheels = new ObjectMeshDynamic[4];

// creating car frame
car_frame = createBody("car_frame", new vec3(frame_width, frame_length, frame_height), 64.0f, new vec4(1.0f, 0.1f, 0.1f, 1.0f), transform);
	
// creating car wheels
// front left wheel
wheels[0] = createWheel(car_frame, "car_wheel_f_l", wheel_radius, wheel_width, new Mat4(MathLib.Translate(-(frame_width + wheel_width) / 2 - delta, frame_length / 2 - wheel_radius, -frame_height / 2) * MathLib.RotateY(90.0f)));

// front right wheel
wheels[1] = createWheel(car_frame, "car_wheel_f_r", wheel_radius, wheel_width, new Mat4(MathLib.Translate((frame_width + wheel_width) / 2 + delta, frame_length / 2 - wheel_radius, -frame_height / 2) * MathLib.RotateY(90.0f)));
	
// rear left wheel
wheels[2] = createWheel(car_frame, "car_wheel_r_l", wheel_radius, wheel_width, new Mat4(MathLib.Translate(-(frame_width + wheel_width) / 2 - delta, -0.25f * frame_length, -frame_height / 2) * MathLib.RotateY(90.0f)));
	
// rear right wheel
wheels[3] = createWheel(car_frame, "car_wheel_r_r", wheel_radius, wheel_width, new Mat4(MathLib.Translate((frame_width + wheel_width) / 2 + delta, -0.25f * frame_length, -frame_height / 2) * MathLib.RotateY(90.0f)));

Let's also add the following lines at the top of our source file to ensure proper type conversion depending on the selected precision type:

Source code (C#)
#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
using Vec4 = Unigine.dvec4;
using Mat4 = Unigine.dmat4;
#else
	using Vec3 = Unigine.vec3;
	using Vec4 = Unigine.vec4;
	using Mat4 = Unigine.mat4;
#endif

Adding and Setting Up Joints#

Now that we have the frame and four wheels we can attach each wheel to the frame with a wheel joint.

To create a wheel joint we call the JointWheel() constructor and then set necessary parameters:

  1. Rigid body of the car frame
  2. Rigid body of the wheel
  3. Anchor point coordinates (this point is determined by the position(translation) of the wheel relative to the frame)
  4. Coordinates of suspension axis (a vertical axis along which a wheel moves vertically and rotates when steering)
  5. Coordinates of wheel spindle axis (a horizontal around which a wheel rotates when moving forward or backward)
Source code (C#)
for (int i = 0; i < 4; i++)
{
	wheel_joints[i] = new JointSuspension(car_frame.Body, wheels[i].Body);
	
	// calculating axes and anchor coordinates
	Mat4 wheel_t = wheel_body.Object.Transform;
	Vec3 anchor0 = wheel_t.Translate;
	Vec3 anchor1 = Vec3.ZERO;
	vec3 axis00 = vec3.UP;
	vec3 axis10 = vec3.RIGHT;
	vec3 axis11 = wheel_t.GetRotate() * axis10;

	// setting up joint's anchor and axes
	wheel_joints[i].Anchor0 = anchor0;
	wheel_joints[i].Anchor1 = anchor1;

	wheel_joints[i].Axis00 = axis00;
	wheel_joints[i].Axis10 = axis10;
	wheel_joints[i].Axis11 = axis11;

	// wheel parameters
	wheel_joints[i].WheelRadius = wradius;
	wheel_joints[i].WheelMass = wmass;
	
	// setting restitution parameters
	wheel_joints[i].LinearRestitution = 0.1f;
	wheel_joints[i].AngularRestitution = 0.1f;

	// setting linear damping and spring rigidity
	wheel_joints[i].LinearDamping = 400.0f;
	wheel_joints[i].LinearSpring = 100.0f;

	// setting lower and upper suspension ride limits [-0.15; 0.15] and target distance
	wheel_joints[i].LinearLimitFrom = -0.15f;
	wheel_joints[i].LinearLimitTo = 0.15f;
	wheel_joints[i].LinearDistance = 0.0f;

	// setting number of iterations
	wheel_joints[i].NumIterations = 8;
}

Using Joint Motors#

So, we have a car, let us make it move now.

To move the car forward or backward we are going to use joint motors of the rear wheels (2 and 3):

  • setting a positive velocity value will move our car forward.
  • setting a negative velocity value will move our car backward.
See the following code:
Source code (C#)
wheel_joints[2].AngularVelocity = velocity;
wheel_joints[3].AngularVelocity = velocity;
		
wheel_joints[2].AngularTorque = torque;
wheel_joints[3].AngularTorque = torque;

To steer the car left or right we are going to change Axis10 coordinates of the front wheels (0 and 1) using setAxis10() method:

Source code (C#)
wheel_joints[0].Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
wheel_joints[1].Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);

To stop the car we are going to set a high angular damping value for all wheels via setAngularDamping() method:

Source code (C#)
for (int i = 0; i < 4; i++)
	wheel_joints[i].AngularDamping = 20000.0f;

Adding Keyboard Controls#

To add keyboard control we are going to make a handler for the keys that we need. The handler must be put to the world script update() function to be called for each frame. We are going to update car physics (joint motors) in the updatePhysics() method.

Source code (C#)
bool Update()
{	
	// forward and backward movement by setting joint motor's velocity and torque
	if ((controls.GetState(Controls.STATE_FORWARD) == 1) || (controls.GetState(Controls.STATE_TURN_UP) == 1))
	{
		// TODO: increase velocity by delta
		// set desired torque
	} 
	else if ((controls.GetState(Controls.STATE_BACKWARD) == 1) || (controls.GetState(Controls.STATE_TURN_DOWN) == 1))
	{
		// TODO: decrease velocity by delta
		// set desired torque
	} 
	else {
		// TODO: decrease velocity gradually
	}
	
	// clamp velocity value
	 velocity = MathLib.Clamp(velocity, -90.0f, 90.0f);
	
	// steering left and right by changing Axis01 for front wheel joints
	if ((controls.GetState(Controls.STATE_MOVE_LEFT) == 1) || (controls.GetState(Controls.STATE_TURN_LEFT) == 1))
	{
		// TODO: increase steering angle by some delta
	} 
	else if ((controls.GetState(Controls.STATE_MOVE_RIGHT) == 1)|| (controls.GetState(Controls.STATE_TURN_RIGHT) == 1))
	{
		// TODO: decrease steering angle
	} 
	else {
		// TODO: return steering angle to zero value gradually
	}

	// clamp steering angle value
	angle = MathLib.Clamp(angle,-30.0f,30.0f);
		
	// TODO: calculate steering angles for front joints (angle_0 and angle_1) 

	// set new Axis10 coordinates for front joints
	wheel_joints[0].Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
	wheel_joints[1].Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);
		
	if (controls.GetState(Controls.STATE_USE) == 1) 
	{
		// TODO: set a very high angular damping value for all joints
		// set velocity to zero
	} 
	else 
	{
		// TODO: set angular damping value to zero for all joints
	}
	
	return true;
}
		
bool UpdatePhysics()
{
	// set angular velocity for rear joints
	wheel_joints[2].AngularVelocity = velocity;
	wheel_joints[3].AngularVelocity = velocity;

	// set torque for rear joints		
	wheel_joints[2].AngularTorque = torque;
	wheel_joints[3].AngularTorque = torque;
	
	return true;
}

Putting it All Together#

In this section let us sum up all described above and create a new class for our car. The final code for our tutorial will be as follows:

Add a new empty Car.cs file to the project, and insert the following code:

Source code (C#)
// Car.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Unigine;

#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
using Vec4 = Unigine.dvec4;
using Mat4 = Unigine.dmat4;
#else
	using Vec3 = Unigine.vec3;
	using Vec4 = Unigine.vec4;
	using Mat4 = Unigine.mat4;
#endif

namespace UnigineApp
{
	class Car
	{
		// car parameters
		float frame_width;
		float frame_height;
		float frame_length;
		float wheel_radius;
		float wheel_width;

		// movement parameters
		float angle = 0.0f;
		float velocity = 0.0f;
		float torque = 0.0f;

		// car elements
		ObjectMeshDynamic car_frame;
		ObjectMeshDynamic[] wheels = new ObjectMeshDynamic[4];
		JointWheel[] wheel_joints = new JointWheel[4];
		Controls controls;

		// function setting up bodies for a wheel joint
		void setBodies(JointWheel joint, BodyRigid car_body, BodyRigid wheel_body)
		{
			joint.Body0 =car_body;
			joint.Body1 =wheel_body;

			Mat4 wheel_t = wheel_body.Object.Transform;

			Vec3 anchor0 = wheel_t.Translate;
			Vec3 anchor1 = Vec3.ZERO;

			vec3 axis00 = vec3.UP;
			vec3 axis10 = vec3.RIGHT;
			vec3 axis11 = (wheel_t.GetRotate() * axis10);

			// setting up joint's anchor and axes
			joint.Anchor0 = anchor0;
			joint.Anchor1 = anchor1;

			joint.Axis00 = axis00;
			joint.Axis10 = axis10;
			joint.Axis11 = axis11;
		}
		/// function, creating a car body having a specified size, color and transformation with a body and a shape
		ObjectMeshDynamic createBody(String name, vec3 size, float mass, vec4 color, Mat4 transform)
		{
			// creating geometry and setting up its parameters (name, material and transformation)
			ObjectMeshDynamic OMD = Primitives.CreateBox(size);
			OMD.WorldTransform = transform;
			OMD.SetMaterial("mesh_base", "*");
			OMD.SetMaterialParameterFloat4("albedo_color", color, 0);
			OMD.Name = name;

			// enabling collision detection for the body
			OMD.SetCollision(true, 0);

			// adding physics, i.e. a rigid body and a box shape with specified mass
			BodyRigid body = new BodyRigid(OMD);

			body.AddShape(new ShapeBox(size), MathLib.Translate(new vec3(0.0f)));
			OMD.Body.GetShape(0).Mass = mass;

			return OMD;
		}

		/// function, creating a wheel having a specified size, color and transformation with a body
		ObjectMeshDynamic createWheel(Node frame, String name, float radius, float height, Mat4 transform)
		{
			// creating geometry and setting up its parameters (name, material and transformation)
			ObjectMeshDynamic OMD = Primitives.CreateCylinder(radius, height, 1, 32);
			frame.AddChild(OMD);
			OMD.Transform = transform;
			OMD.SetMaterial("mesh_base", "*");
			OMD.SetMaterialParameterFloat4("albedo_color", new vec4(0.1f, 0.1f, 0.1f, 1.0f), 0);
			OMD.Name = name;

			// enabling collision detection for the wheel
			OMD.SetCollision(true, 0);

			// adding a rigid body 
			BodyRigid body = new BodyRigid(OMD);

			return OMD;
		}

		/// Initializing a car with specified frame and wheel parameters
		public bool Init(float blength, float bwidth, float bheight, float wradius, float wwidth, float wmass, Mat4 transform)
		{
			frame_width = bwidth;
			frame_height = bheight;
			frame_length = blength;
			wheel_radius = wradius;
			wheel_width = wwidth;
			float delta = 0.2f;

			car_frame = createBody("car_frame", new vec3(frame_width, frame_length, frame_height), 64.0f, new vec4(1.0f, 0.1f, 0.1f, 1.0f), transform);

			// initialization of wheels
			wheels[0] = createWheel(car_frame, "car_wheel_f_l", wheel_radius, wheel_width, new Mat4(MathLib.Translate(-(frame_width + wheel_width) / 2 - delta, frame_length / 2 - wheel_radius, -frame_height / 2) * MathLib.RotateY(90.0f)));
			wheels[1] = createWheel(car_frame, "car_wheel_f_r", wheel_radius, wheel_width, new Mat4(MathLib.Translate((frame_width + wheel_width) / 2 + delta, frame_length / 2 - wheel_radius, -frame_height / 2) * MathLib.RotateY(90.0f)));
			wheels[2] = createWheel(car_frame, "car_wheel_r_l", wheel_radius, wheel_width, new Mat4(MathLib.Translate(-(frame_width + wheel_width) / 2 - delta, -0.25f * frame_length, -frame_height / 2) * MathLib.RotateY(90.0f)));
			wheels[3] = createWheel(car_frame, "car_wheel_r_r", wheel_radius, wheel_width, new Mat4(MathLib.Translate((frame_width + wheel_width) / 2 + delta, -0.25f * frame_length, -frame_height / 2) * MathLib.RotateY(90.0f)));

			// initialization of wheel joints
			for (int i = 0; i < 4; i++)
			{
				wheel_joints[i] = new JointWheel();
				// setting bodies and wheel parameters
				setBodies(wheel_joints[i], car_frame.BodyRigid, wheels[i].BodyRigid);
				wheel_joints[i].WheelRadius = wradius;
				wheel_joints[i].WheelMass = wmass;
				
				// setting restitution parameters
				wheel_joints[i].LinearRestitution = 0.1f;
				wheel_joints[i].AngularRestitution = 0.1f;

				// setting linear damping and spring rigidity
				wheel_joints[i].LinearDamping = 400.0f;
				wheel_joints[i].LinearSpring = 100.0f;

				// setting lower and upper suspension ride limits [-0.15; 0.15]
				wheel_joints[i].LinearLimitFrom = -0.15f;
				wheel_joints[i].LinearLimitTo = 0.15f;
				wheel_joints[i].LinearDistance = 0.0f;

				// setting number of iterations
				wheel_joints[i].NumIterations = 8;
			}

			// setting up player and controls
			PlayerPersecutor player = new PlayerPersecutor();

			player.Fixed = true;
			player.Target = car_frame;
			player.MinDistance = 6.0f;
			player.MaxDistance = 11.0f;
			player.Position = new Vec3(0.0f, -10.0f, 6.0f);
			controls = player.Controls;
			player.Controlled = false;
			Game.Player = player;
			Game.Enabled = true;

			return true;
		}

		/// method updating current car state with a keyboard control handler
		public bool Update()
		{
			
			float ifps = Game.IFps;

			// forward and backward movement by setting joint motor's velocity and torque
			if ((controls.GetState(Controls.STATE_FORWARD) == 1) || (controls.GetState(Controls.STATE_TURN_UP) == 1))
			{
				velocity = MathLib.Max(velocity, 0.0f);
				velocity += ifps * 50.0f;
				torque = 5.0f;
			}//Input.IsKeyDown(Input.KEY.DOWN)
			else if ((controls.GetState(Controls.STATE_BACKWARD) == 1) || (controls.GetState(Controls.STATE_TURN_DOWN) == 1))
			{
				velocity = MathLib.Min(velocity, 0.0f);
				velocity -= ifps * 50.0f;
				torque = 5.0f;
			}
			else
			{
				velocity *= MathLib.Exp(-ifps);
			}
			velocity = MathLib.Clamp(velocity, -90.0f, 90.0f);

			// steering left and right by changing Axis01 for front wheel joints
			if ((controls.GetState(Controls.STATE_MOVE_LEFT) == 1) || (controls.GetState(Controls.STATE_TURN_LEFT) == 1))
				angle += ifps * 100.0f;
			else if ((controls.GetState(Controls.STATE_MOVE_RIGHT) == 1) || (controls.GetState(Controls.STATE_TURN_RIGHT) == 1))
				angle -= ifps * 100.0f;
			else
			{
				if (MathLib.Abs(angle) < 0.25f) angle = 0.0f;
				else angle -= MathLib.Sign(angle) * ifps * 45.0f;
			}
			angle = MathLib.Clamp(angle, -30.0f, 30.0f);

			// calculating steering angles for front joints (angle_0 and angle_1)
			float base_a = 3.3f;
			float width = 3.0f;
			float angle_0 = angle;
			float angle_1 = angle;
			if (MathLib.Abs(angle) > MathLib.EPSILON)
			{
				float radius = base_a / MathLib.Tan(angle * MathLib.DEG2RAD);
				angle_0 = MathLib.Atan(base_a / (radius + width / 2.0f)) * MathLib.RAD2DEG;
				angle_1 = MathLib.Atan(base_a / (radius - width / 2.0f)) * MathLib.RAD2DEG;
			}

			wheel_joints[0].Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
			wheel_joints[1].Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);

			// enabling or disabling a brake
			if (controls.GetState(Controls.STATE_USE) == 1)
			{
				velocity = 0.0f;
				for (int i = 0; i < 4; i++)
					wheel_joints[i].AngularDamping = 20000.0f;
			}
			else
			{
				for (int i = 0; i < 4; i++)
					wheel_joints[i].AngularDamping = 0.0f;
			}

			return true;
		}

		/// method updating car physics
		public bool UpdatePhysics()
		{
			// set angular velocity for rear joints
			wheel_joints[2].AngularVelocity = velocity;
			wheel_joints[3].AngularVelocity = velocity;

			// set torque for rear joints
			wheel_joints[2].AngularTorque = torque;
			wheel_joints[3].AngularTorque = torque;

			return true;
		}
	}
}

Insert the following code into the AppWorldLogic.cs file.

Source code (C#)
// AppWorldLogic.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Unigine;

namespace UnigineApp
{

	class AppWorldLogic : WorldLogic
	{

		Car car;

		/* .. */

		public override bool Init()
		{
			// setting up physics parameters
			Physics.Gravity = new vec3(0.0f, 0.0f, -9.8f * 2.0f);
			Physics.FrozenLinearVelocity = 0.1f;
			Physics.FrozenAngularVelocity = 0.1f;
			
			// enabling collision for the ground
			Node ground = World.GetNodeByName("ground_plane");
			if (ground)
			{
				(ground as Unigine.Object).SetCollision(true, 0);
				(ground as Unigine.Object).SetPhysicsIntersection(true, 0);
			}
			
			// creating and initializing our car
			car = new Car();
			car.Init(4.0f, 2.0f, 0.5f, 0.5f, 0.5f, 25.0f, MathLib.Translate(new dvec3(0.0f, 0.0f, 1.0f)));

			return true;
		}

		// start of the main loop
		public override bool Update()
		{
			//updating our car
			car.Update();

			return true;
		}
		
		public override bool UpdatePhysics()
		{
			// updating car physics
			car.UpdatePhysics();

			return true;
		}
		/* .. */
	
	}
}
Last update: 2020-06-09
Build: ()