//
// Copyright (c) 2008-2015 the Urho3D project.
// Copyright (c) 2015 Xamarin Inc
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using AtomicEngine;
namespace FeatureExamples
{
///
/// Vehicle component, responsible for physical movement according to controls.
///
public class Vehicle : CSComponent
{
public const int CtrlForward = 1;
public const int CtrlBack = 2;
public const int CtrlLeft = 4;
public const int CtrlRight = 8;
public const float YawSensitivity = 0.1f;
public const float EnginePower = 10.0f;
public const float DownForce = 10.0f;
public const float MaxWheelAngle = 22.5f;
// Movement controls.
public Controls Controls { get; set; } = new Controls();
// Wheel scene nodes.
Node frontLeft;
Node frontRight;
Node rearLeft;
Node rearRight;
// Steering axle constraints.
Constraint frontLeftAxis;
Constraint frontRightAxis;
// Hull and wheel rigid bodies.
RigidBody hullBody;
RigidBody frontLeftBody;
RigidBody frontRightBody;
RigidBody rearLeftBody;
RigidBody rearRightBody;
// IDs of the wheel scene nodes for serialization.
uint frontLeftId;
uint frontRightId;
uint rearLeftId;
uint rearRightId;
/// Current left/right steering amount (-1 to 1.)
float steering;
public void PhysicsPreStep(float timeStep)
{
float newSteering = 0.0f;
float accelerator = 0.0f;
// Read controls
if (Controls.IsDown(CtrlLeft))
newSteering = -1.0f;
if (Controls.IsDown(CtrlRight))
newSteering = 1.0f;
if (Controls.IsDown(CtrlForward))
accelerator = 1.0f;
if (Controls.IsDown(CtrlBack))
accelerator = -0.5f;
// When steering, wake up the wheel rigidbodies so that their orientation is updated
if (newSteering != 0.0f)
{
frontLeftBody.Activate();
frontRightBody.Activate();
steering = steering * 0.95f + newSteering * 0.05f;
}
else
steering = steering * 0.8f + newSteering * 0.2f;
// Set front wheel angles
Quaternion steeringRot = new Quaternion(0, steering * MaxWheelAngle, 0);
frontLeftAxis.SetOtherAxis(steeringRot * new Vector3(-1f, 0f, 0f));
frontRightAxis.SetOtherAxis(steeringRot * Vector3.UnitX);
Quaternion hullRot = hullBody.Rotation;
if (accelerator != 0.0f)
{
// Torques are applied in world space, so need to take the vehicle & wheel rotation into account
Vector3 torqueVec = new Vector3(EnginePower * accelerator, 0.0f, 0.0f);
frontLeftBody.ApplyTorque(hullRot * steeringRot * torqueVec);
frontRightBody.ApplyTorque(hullRot * steeringRot * torqueVec);
rearLeftBody.ApplyTorque(hullRot * torqueVec);
rearRightBody.ApplyTorque(hullRot * torqueVec);
}
// Apply downforce proportional to velocity
Vector3 localVelocity = Quaternion.Invert(hullRot) * hullBody.LinearVelocity;
hullBody.ApplyForce(hullRot * new Vector3(0f, -1f, 0f) * Math.Abs(localVelocity.Z) * DownForce);
}
public void Init()
{
var cache = GetSubsystem();
// This function is called only from the main program when initially creating the vehicle, not on scene load
var node = Node;
StaticModel hullObject = node.CreateComponent();
hullBody = node.CreateComponent();
CollisionShape hullShape = node.CreateComponent();
node.Scale = new Vector3(1.5f, 1.0f, 3.0f);
hullObject.Model = cache.Get("Models/Box.mdl");
hullObject.SetMaterial(cache.Get("Materials/Stone.xml"));
hullObject.CastShadows = true;
hullShape.SetBox(Vector3.One, Vector3.Zero, Quaternion.Identity);
hullBody.Mass = 4.0f;
hullBody.LinearDamping = 0.2f; // Some air resistance
hullBody.AngularDamping = 0.5f;
hullBody.CollisionLayer = 1;
InitWheel("FrontLeft", new Vector3(-0.6f, -0.4f, 0.3f), out frontLeft, out frontLeftId);
InitWheel("FrontRight", new Vector3(0.6f, -0.4f, 0.3f), out frontRight, out frontRightId);
InitWheel("RearLeft", new Vector3(-0.6f, -0.4f, -0.3f), out rearLeft, out rearLeftId);
InitWheel("RearRight", new Vector3(0.6f, -0.4f, -0.3f), out rearRight, out rearRightId);
GetWheelComponents();
}
void InitWheel(string name, Vector3 offset, out Node wheelNode, out uint wheelNodeId)
{
var cache = GetSubsystem();
// Note: do not parent the wheel to the hull scene node. Instead create it on the root level and let the physics
// constraint keep it together
wheelNode = Scene.CreateChild(name);
wheelNode.Position = Node.LocalToWorld(offset);
wheelNode.Rotation = Node.Rotation * (offset.X >= 0.0 ? new Quaternion(0.0f, 0.0f, -90.0f) : new Quaternion(0.0f, 0.0f, 90.0f));
wheelNode.Scale = new Vector3(0.8f, 0.5f, 0.8f);
// Remember the ID for serialization
wheelNodeId = wheelNode.ID;
StaticModel wheelObject = wheelNode.CreateComponent();
RigidBody wheelBody = wheelNode.CreateComponent();
CollisionShape wheelShape = wheelNode.CreateComponent();
Constraint wheelConstraint = wheelNode.CreateComponent();
wheelObject.Model = (cache.Get("Models/Cylinder.mdl"));
wheelObject.SetMaterial(cache.Get("Materials/Stone.xml"));
wheelObject.CastShadows = true;
wheelShape.SetSphere(1.0f, Vector3.Zero, Quaternion.Identity);
wheelBody.Friction = (1.0f);
wheelBody.Mass = 1.0f;
wheelBody.LinearDamping = 0.2f; // Some air resistance
wheelBody.AngularDamping = 0.75f; // Could also use rolling friction
wheelBody.CollisionLayer = 1;
wheelConstraint.ConstraintType = ConstraintType.CONSTRAINT_HINGE;
wheelConstraint.OtherBody = GetComponent(); // Connect to the hull body
wheelConstraint.SetWorldPosition(wheelNode.Position); // Set constraint's both ends at wheel's location
wheelConstraint.SetAxis(Vector3.UnitY); // Wheel rotates around its local Y-axis
wheelConstraint.SetOtherAxis(offset.X >= 0.0 ? Vector3.UnitX : new Vector3(-1f, 0f, 0f)); // Wheel's hull axis points either left or right
wheelConstraint.LowLimit = new Vector2(-180.0f, 0.0f); // Let the wheel rotate freely around the axis
wheelConstraint.HighLimit = new Vector2(180.0f, 0.0f);
wheelConstraint.DisableCollision = true; // Let the wheel intersect the vehicle hull
}
void GetWheelComponents()
{
frontLeftAxis = frontLeft.GetComponent();
frontRightAxis = frontRight.GetComponent();
frontLeftBody = frontLeft.GetComponent();
frontRightBody = frontRight.GetComponent();
rearLeftBody = rearLeft.GetComponent();
rearRightBody = rearRight.GetComponent();
}
}
}