How I Collected Wheel Physics at Unigine

Good day.

This time we will talk about the Russian Unigine engine , and how to assemble a simple machine in it. The scheme is not ideal, since I have not figured it out in some things, but at least I will share my experience.



First of all, to understand how the physics of wheels is done, we go to the official documentation and find this example .

By the way, note that this is an example for wheels joint, but for some reason it lies at the address / car_suspension_joints /. You also need to make sure that the documentation does not skip to the old versions (that is, that DOCS (2.11) is open) and mark the “C #” box on top of the article.

Having studied the example, we see that it was written by analogy with the early C ++ solution. That is, everything is created through code. So much for visual programming, we’ve arrived. Well, not everything is so sad, but you can see how all this is done in code. And having run on the links, get a general idea of ​​the wheels joint device and physics settings.
( ). , -, UnigineScript, . LOD'.
Of course, I was expecting a ready-made step-by-step guide on visual assembly in order to get something rolling, costing little blood. As a result, I still had to plunge into it all with my head in order to work out some approximate pipeline. This partially succeeded, but it was far from being understood at all points and questions remain open, for example, whether the casing and wheel nodes should be built into a certain hierarchy, whether the axes of the points are oriented correctly, and so on.

In general, this instruction will help you grasp the basic principle, but in order to understand the intricacies of the settings, you will still have to study the documentation (or maybe they will tell you something useful in the comments).

Frame assembly


1. Create a new project. Choose the C # option. Open the project in the editor.







2. We create the basis of the machine.

To do this, right-click in the scene, choosing Create > Primitive > Box . The result is a Cuboid object . Raise it slightly above the "floor".



3. We create wheels.

Click the mouse again and select Create > Primitive > Cylinder . It will take 4 pieces.



In order not to get confused with the orientation, you can rotate the camera so that -Y glows from above in the cube (that is, we now look at the future machine from behind) and place two cylinders behind Cuboid, two others in front of it. Somehow to rotate and customize them is not necessary. For convenience, different colors can be assigned to the rear and front.

4. Add physics based.

Select Cuboid and on the left, in the Parameters window from the Node tab, switch to Physics . In the Body column, select the Rigid type . After that, scroll down and select the Box item in the opened Shapes column .





We see the appeared ShapeBox. We’ll go into its settings and immediately set our base to the recommended mass ( Mass ) of 64 kilograms.



5. Add physics to the wheels.

In turn, select the cylinders and also put them Rigid in the Physics tab.
They won’t need to create a Shape, because wheel joints work on rakcasts.

6. Tie the wheels to the base.

Time to assign wheel joints. To do this, select Cuboid correctly and scroll down to the Joints column in the Physics tab . In the list, select Wheel and when you click the Add button, the Select a Body window appears. There you select a cylinder, it is highlighted with a white grid in the scene, and press Ok. A WheelJoint binding will appear in the list . Here it is already desirable to rename them (for this you need to click on the line JointWheel and write a new name in the Name column).





In the same way, attach the remaining cylinders to Cuboid. So that in the end on his list of points burned 4 lines.


Without renaming there would be 4 lines of WheelJoint.

Please note that you need to bind from Cuboid, and not from the Physics tab of the cylinders themselves. Although after, if you select a specific cylinder and go to its Physics, then its created joint is also displayed there and you can edit its parameters.

7. Set up the configuration of the bindings.

Again, select Cuboid, find in Physics a list of its points and select one of them. We see that a white corner with squares appeared in the scene inside Cuboid.
In the settings of the joint, we find the graph of the vector Anchor 0 , and adjust the values ​​of its three fields so that this corner is shifted along the X axis, outside Cuboid and slightly lowered along the Z axis.





Then we shift the corner along the Y axis, depending on the binding which wheel is currently selected.

We repeat the procedure with the rest of the points, setting exactly the same shifts along Z and the same, but multidirectional along the other axes.

If you select all the points at once, the overall design will become visible. You can also enable the display of joints in helpers on a special panel above the scene: Helpers> Physics > Joints .


Rear view of the frame with the included helpers

8. We look at what happened.

In the editor there is a button for calculating physics. If you enable it, then a simulation of physics starts (the scripts do not work) and you can check how our Frankenstein behaves.
Before starting, make sure that the enabled checkboxes for Collision and Physics Intersection are checked in the "floor" settings . Otherwise, our creation may completely fail under the floor entirely or with just wheels.



Be careful, before turning on the simulation, it is better to save, and the parameters corrected during its operation will return to their values ​​after disconnecting.

As we see, the wheels are picked up in the right places, but the structure falls on the surface with the ends of the cylinders.



You can freeze Cuboid itself in place by first enabling the Immovable checkbox on the Physics tab in the Body column.

9. Adjust the axles of the wheels.

Disable physics simulation. Select Cuboid, go to its Physics tab and edit the axes 00 , Axis 10 and Axis 11 for each junction . Please note that in the sum of the fields of each vector take values ​​from 0 to 1 and the editor will try to automatically correct the values ​​if you first put 1 in a new axis without zeroing the vector.

Apparently, I have not yet developed the vectors quite correctly, the helpers show that the left and right parts are looking in the same direction, but they do not give a clear understanding of which axis is where, which makes tuning difficult.

In any case, this layout works less or less. Here I set the axis vectors as follows: Axis 00 ( 0 , 0 , 1 ), Axis 10 ( 1 , 0 , 0 ), Axis 11 ( 0 , 0 , 1 ).



If you start a physics simulation now, then the design should fall on correctly rotated cylinders, which will subsequently spin along the correct axis.

10.Wheel mass and behavior, physical iterations.

Most likely, the structure after falling to the floor now partially fails.

First, let's go into the general settings of physics. We click on the top line of the editor. Windows > Settings and in the Settings tab that opens, we find the Physics column (Runtime / World /). We set at least 5 in the Iterations field . Again we go into the settings of each joint. At the top of the list, everyone has an Iteration field , set 8 . Linear Restitution and Angular Restitution set to 0.1 . Linear from






change -0.15 , Linear To to 0.15 .
At the bottom we affix them a mass of 25 kg in the Wheel Mass field .

When the simulation starts, the physics “machine” still has to partially fail / fail.

Set each Linear Damping to 50 . And set Linear Spring to 10 .
Now, during the simulation, the structure should fall and bounce slightly on the floor. If that doesn't work, then experiment with the Linear Damping and Linear Spring values.
The documentation recommends setting damping 400 and spring 100, but personally, at the same time, the “machine” starts spinning like a helicopter, bounces and flies away.

11. The final setting of the frame.

Save and try to click Play (the button above, next to the inclusion of physical simulation) to walk around the scene in the first person. Most likely the “machine” hung a little in the air, but rolls if it is pushed into the central cube.



Now you can adjust Anchor 0 at the points so that they are approximately equidistant from the center (although not necessary). Then start the simulation and change Linear Spring right during it to find the optimal value at which each wheel normally touches the ground.

After all these manipulations, you should get a “machine” correctly falling to the floor, which rolls slightly when pushing in game mode, where by default you run on a separate controller with a first-person view.



Writing a control script



We have a blank of the “typewriter”, now we will write a control code for it in C #.
To do this, right-click in the window under the scene and select Create > Create C # Component . Enter the name of the script in the window that appears.





Double-click on the appeared script file and the development environment starts.



Select the lines filled with blue in the picture, delete them and instead add the following code to which I came up editing the script proposed in the documentation:

Script snippet
	[ShowInEditor][Parameter(Tooltip = "Left Wheel")]
	private Node targetWheelL = null;
	[ShowInEditor][Parameter(Tooltip = "Right Wheel")]
	private Node targetWheelR = null;

	[ShowInEditor][Parameter(Tooltip = "Left Wheel F")]
	private Node targetFWL = null;
	[ShowInEditor][Parameter(Tooltip = "Right Wheel F")]
	private Node targetFWR = null;

	[ShowInEditor][Parameter(Tooltip = "theCar")]
	private Node targetCar = null;

    private JointWheel my_jointL;
    private JointWheel my_jointR;
    private JointWheel my_jointFL;
    private JointWheel my_jointFR;

	Controls controls = null;
	float angle = 0.0f;
	float velocity = 0.0f;
	float torque = 0.0f;

	private float ifps;

	private void Init()
	{
	my_jointL = targetWheelL.ObjectBodyRigid.GetJoint(0) as JointWheel;
	my_jointR = targetWheelR.ObjectBodyRigid.GetJoint(0) as JointWheel;
	my_jointFL = targetFWL.ObjectBodyRigid.GetJoint(0) as JointWheel;
	my_jointFR = targetFWR.ObjectBodyRigid.GetJoint(0) as JointWheel;

	PlayerPersecutor player = new PlayerPersecutor();
	controls = player.Controls;
	}
	
	private void Update()
	{
			ifps = Game.IFps;

			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;
			}
			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);

			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, -10.0f, 10.0f);

			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;
			}

			my_jointFL.Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
			my_jointFR.Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);

			if (controls.GetState(Controls.STATE_USE) == 1)
			{
				velocity = 0.0f;
					my_jointL.AngularDamping = 20000.0f;
					my_jointR.AngularDamping = 20000.0f;
					my_jointFL.AngularDamping = 20000.0f;
					my_jointFR.AngularDamping = 20000.0f;
			}
			else
			{
					my_jointL.AngularDamping = 0.0f;
					my_jointR.AngularDamping = 0.0f;
					my_jointFL.AngularDamping = 0.0f;
					my_jointFR.AngularDamping = 0.0f;
			}

			if (Input.IsKeyDown(Input.KEY.Q))
			{
				targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.UP*1000f);
			}
			if (Input.IsKeyDown(Input.KEY.E))
			{
				targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.DOWN*1000f);
			}

	}

		private void UpdatePhysics()
		{
			my_jointL.AngularVelocity = velocity;
			my_jointR.AngularVelocity = velocity;
			
			my_jointL.AngularTorque = torque;
			my_jointR.AngularTorque = torque;
		}



:

TheVehicle.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "ca695c8787d5703a22a6c2516a3c177cddf38cab")]
public class TheVehicle : Component
{

, , . , .

[ShowInEditor][Parameter(Tooltip = "Left Wheel")]
private Node targetWheelL = null;
[ShowInEditor][Parameter(Tooltip = "Right Wheel")]
private Node targetWheelR = null;

[ShowInEditor][Parameter(Tooltip = "Left Wheel F")]
private Node targetFWL = null;
[ShowInEditor][Parameter(Tooltip = "Right Wheel F")]
private Node targetFWR = null;

[ShowInEditor][Parameter(Tooltip = "theCar")]
private Node targetCar = null;

. tooltip , .

private JointWheel my_jointL;
private JointWheel my_jointR;
private JointWheel my_jointFL;
private JointWheel my_jointFR;

.

Controls controls = null;

float angle = 0.0f;
float velocity = 0.0f;
float torque = 0.0f;

private float ifps;

.

private void Init()
{
my_jointL = targetWheelL.ObjectBodyRigid.GetJoint(0) as JointWheel;
my_jointR = targetWheelR.ObjectBodyRigid.GetJoint(0) as JointWheel;
my_jointFL = targetFWL.ObjectBodyRigid.GetJoint(0) as JointWheel;
my_jointFR = targetFWR.ObjectBodyRigid.GetJoint(0) as JointWheel;

, JointWheel.
, , ( ).

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

, PlayerPresecutor, Controls player .

private void Update()
{
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, -10.0f, 10.0f);//      30  (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;
			}

Update, , .

my_jointFL.Axis10 = MathLib.RotateZ(angle_0).GetColumn3(0);
my_jointFR.Axis10 = MathLib.RotateZ(angle_1).GetColumn3(0);

// enabling or disabling a brake
if (controls.GetState(Controls.STATE_USE) == 1)
{
velocity = 0.0f;
my_jointL.AngularDamping = 20000.0f;
my_jointR.AngularDamping = 20000.0f;
my_jointFL.AngularDamping = 20000.0f;
my_jointFR.AngularDamping = 20000.0f;
}
else
{
my_jointL.AngularDamping = 0.0f;
my_jointR.AngularDamping = 0.0f;
my_jointFL.AngularDamping = 0.0f;
my_jointFR.AngularDamping = 0.0f;
}

, , . , 4 . , , 10 .

if (Input.IsKeyDown(Input.KEY.Q))
{
targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.UP*1000f);
}

if (Input.IsKeyDown(Input.KEY.E))
{
targetCar.ObjectBodyRigid.AddLinearImpulse(vec3.DOWN*1000f);
}

}

Update , . , — «this.» .

private void UpdatePhysics()
{
// set angular velocity for rear joints
my_jointL.AngularVelocity = velocity;
my_jointR.AngularVelocity = velocity;

// set torque for rear joints
my_jointL.AngularTorque = torque;
my_jointR.AngularTorque = torque;

}

}

Update Physics , velocity torque.


After that, save the script file and return to the editor window. Unigine will check the program for errors, and if everything is fine, then a green rectangle should appear at the bottom right with the notification Project build successful.

Now select Cuboid, add the component field to it and drag our script there. A few fields appear that need to be filled. In the Target Wheel L field, gently drag the left rear wheel (that is, the Cylinder denoting it), Target Whell R - the right rear wheel, Target FWL - the front left, Target FWR - the front right, and Cuboid itself in the Target Car field.


To add a component, we find this field in the Cuboid properties and click on it. An


empty line will appear where you can drag the script file (or find the path to it through the folder icon)


Fill in the fields

Run Play - now the machine began to move. True, simultaneously with the character on whose behalf you are now looking at the world.

Her control is as follows: W - rolls forward, S - backward, A and D - turn the front wheels left and right, respectively. An impulse is given up to Q , the machine, as it were, jumps. On E , on the contrary, the impulse moves down.

You may notice that the machine falls slightly slowly, like on the moon. To improve this moment there are two options. The documentation suggested making changes to the AppWorldLogic.cs file. But we don’t need this, since I carried out the initialization there in the typewriter script. Except a few lines that rule global physics.

The fact is that you can independently enter these settings and change them. To do this, click on the top line of the editor. Windows> Settings, in the window that opens, find the column Physics and set the value for Frozen Linear Velocity to 0.1, for Frozen Angular Velocity also 0.1, and change the lowest Gravity vector from (0,0, -9,8) to (0,0, - 19.6).

The same can be done in the code by adding the following lines to Init:

Physics.Gravity = new vec3(0.0f, 0.0f, -9.8f * 2.0f);
Physics.FrozenLinearVelocity = 0.1f;
Physics.FrozenAngularVelocity = 0.1f;

If you start Play now, then the “machine” will probably deteriorate again and begin to fail. To fix this, try spinning a Linear Spring at each wheel. Raise twice for a start. When you set up, you will notice that gravity during jumps is stronger (though I set the jump momentum to pretty high by default).

Now that you’ve already gotten wet while setting up this moody cart, it’s time to drop the shackles of firts_person_controller and look at what is happening from the side of the machine itself, with a third-person camera.
Downloaded disable first_person_controller (click on the box next to his name so that it becomes empty).
Create a camera. To do this, right-click on the scene and select Create> Camera> Presecutor. The PlayerPresecutor camera itself and its dummy target Presecutor_Target will appear.
The dummy can be turned off. We click on the camera itself, be sure to check the Main Player checkbox in its properties (so as not to get a black screen at startup). In the Target Node field we find our Cuboid in the drop-down list.

Save and click Play.

Now you can fully ride. True, the camera can fly quite far. To fix this, you need to adjust its limits. We return to the editor and set in the camera properties, for example, Min Distance 10 and Max Distance 30.

Neungers in the Unigine engine


Having learned to collect approximate wheel physics, I made a test scene with a bio-mechos model from my Nevanger project. Here's what it looks like at the moment:



Screenshots







The main problems at the moment are some instability of the design. Which is again due to the fact that I have not yet thoroughly understood all the aspects and nuances of the settings. Sometimes the machine stumbles about something and starts to rotate fiercely. Reducing Linear Damping doesn’t seem to help much, twisting the other parameters as well, although I haven’t twisted or configured something yet, but it’s not comprehensive.

Another bug - there are situations with total stuck in the floor, when the front wheels stick up, and the rear under the landscape. Or punching through the landscape itself at high speed, followed by flying away under it. All this, apparently, is corrected by setting tolerances and iterations, but so far has not found the optimal values.

In general, on a fairly flat surface, the movement is already more adequate. It’s not clear how well Wheel Joints behave on an uneven surface ideally, as I understood in the video demos of the engine for cars they used outdated (seemingly), but more physically “honest” Suspension Joints (by the way, I didn’t try to assemble a machine on them, maybe , everything is somewhat easier there). Do Wheel Joints take into account the friction parameters of the landscape, or they are only interested in whether the physics flag is turned on.

General impressions of the engine


As for working with the editor, what happened most reminded me of the process of working with CryEngine. Although in some places it’s more convenient. But it’s like someone, in the same CryEngine it was possible to model directly in the editor. This is not relevant for me, because for this I use Blender, where you have orders of magnitude more control over the model, and for someone it may be important.

In the picture below, a list of materials is visible on the left. Meshes are painted through a group of mesh based materials, from which you can clone your material options. Painting terrane with a texture is already more difficult, but using masks to the full - you can mix textures, height maps, and so on, if you understand the device. For the same water, there is its own separate group of materials, it is added literally in one click and looks pretty good.

On the right is the hierarchy of scene objects and the properties window. It’s a little inconvenient to manipulate numerical values ​​on the fly - you need to get into the icon with small arrows. Some sliders are initially misleading, because they do not show the entire possible range of the parameter, but, say, only the interval from 0 to 1. The flight speed of the camera is regulated in the field with a running man on top, by default it somehow moves slowly, judging by the sensations . Perhaps this is more convenient when you work with models on a real scale.


All Articles