Browse Source

Added vehicle sample.

Lasse Öörni 12 years ago
parent
commit
26901cdc4c

+ 30 - 5
Source/Samples/18_CharacterDemo/Character.cpp

@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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.
+//
+
 #include "AnimationController.h"
 #include "AnimationController.h"
 #include "Character.h"
 #include "Character.h"
 #include "MemoryBuffer.h"
 #include "MemoryBuffer.h"
@@ -17,9 +39,12 @@ Character::Character(Context* context) :
 
 
 void Character::OnNodeSet(Node* node)
 void Character::OnNodeSet(Node* node)
 {
 {
-    // Component has been inserted into its scene node. Subscribe to events now
-    SubscribeToEvent(node, E_NODECOLLISION, HANDLER(Character, HandleNodeCollision));
-    SubscribeToEvent(GetScene()->GetComponent<PhysicsWorld>(), E_PHYSICSPRESTEP, HANDLER(Character, HandleFixedUpdate));
+    if (node)
+    {
+        // Component has been inserted into its scene node. Subscribe to events now
+        SubscribeToEvent(node, E_NODECOLLISION, HANDLER(Character, HandleNodeCollision));
+        SubscribeToEvent(GetScene()->GetComponent<PhysicsWorld>(), E_PHYSICSPRESTEP, HANDLER(Character, HandleFixedUpdate));
+    }
 }
 }
 
 
 void Character::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
 void Character::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
@@ -71,9 +96,9 @@ void Character::HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
     // Velocity on the XZ plane
     // Velocity on the XZ plane
     Vector3 planeVelocity(velocity.x_, 0.0f, velocity.z_);
     Vector3 planeVelocity(velocity.x_, 0.0f, velocity.z_);
     
     
-    if (controls_.IsDown(CTRL_UP))
+    if (controls_.IsDown(CTRL_FORWARD))
         moveDir += Vector3::FORWARD;
         moveDir += Vector3::FORWARD;
-    if (controls_.IsDown(CTRL_DOWN))
+    if (controls_.IsDown(CTRL_BACK))
         moveDir += Vector3::BACK;
         moveDir += Vector3::BACK;
     if (controls_.IsDown(CTRL_LEFT))
     if (controls_.IsDown(CTRL_LEFT))
         moveDir += Vector3::LEFT;
         moveDir += Vector3::LEFT;

+ 7 - 7
Source/Samples/18_CharacterDemo/Character.h

@@ -27,13 +27,11 @@
 
 
 using namespace Urho3D;
 using namespace Urho3D;
 
 
-const int CTRL_UP = 1;
-const int CTRL_DOWN = 2;
+const int CTRL_FORWARD = 1;
+const int CTRL_BACK = 2;
 const int CTRL_LEFT = 4;
 const int CTRL_LEFT = 4;
 const int CTRL_RIGHT = 8;
 const int CTRL_RIGHT = 8;
-const int CTRL_FIRE = 16;
-const int CTRL_JUMP = 32;
-const int CTRL_ALL = 63;
+const int CTRL_JUMP = 16;
 
 
 const float MOVE_FORCE = 0.8f;
 const float MOVE_FORCE = 0.8f;
 const float INAIR_MOVE_FORCE = 0.02f;
 const float INAIR_MOVE_FORCE = 0.02f;
@@ -54,13 +52,15 @@ public:
     /// Handle node being assigned.
     /// Handle node being assigned.
     virtual void OnNodeSet(Node* node);
     virtual void OnNodeSet(Node* node);
     
     
+    /// Movement controls.
+    Controls controls_;
+    
+private:
     /// Handle physics collision event.
     /// Handle physics collision event.
     void HandleNodeCollision(StringHash eventType, VariantMap& eventData);
     void HandleNodeCollision(StringHash eventType, VariantMap& eventData);
     /// Handle physics world update event.
     /// Handle physics world update event.
     void HandleFixedUpdate(StringHash eventType, VariantMap& eventData);
     void HandleFixedUpdate(StringHash eventType, VariantMap& eventData);
     
     
-    /// Movement controls.
-    Controls controls_;
     /// Grounded flag for movement.
     /// Grounded flag for movement.
     bool onGround_;
     bool onGround_;
     /// Jump flag.
     /// Jump flag.

+ 26 - 3
Source/Samples/18_CharacterDemo/CharacterDemo.cpp

@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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.
+//
+
 #include "AnimatedModel.h"
 #include "AnimatedModel.h"
 #include "AnimationController.h"
 #include "AnimationController.h"
 #include "Camera.h"
 #include "Camera.h"
@@ -71,6 +93,7 @@ void CharacterDemo::CreateScene()
     // Create camera and define viewport. Camera does not necessarily have to belong to the scene
     // Create camera and define viewport. Camera does not necessarily have to belong to the scene
     cameraNode_ = new Node(context_);
     cameraNode_ = new Node(context_);
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     Camera* camera = cameraNode_->CreateComponent<Camera>();
+    camera->SetFarClip(300.0f);
     GetSubsystem<Renderer>()->SetViewport(0, new Viewport(context_, scene_, camera));
     GetSubsystem<Renderer>()->SetViewport(0, new Viewport(context_, scene_, camera));
     
     
     // Create static scene content. First create a zone for ambient lighting and fog control
     // Create static scene content. First create a zone for ambient lighting and fog control
@@ -230,8 +253,8 @@ void CharacterDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
         // Get movement controls and assign them to the character logic component. If UI has a focused element, clear controls
         // Get movement controls and assign them to the character logic component. If UI has a focused element, clear controls
         if (!ui->GetFocusElement())
         if (!ui->GetFocusElement())
         {
         {
-            character_->controls_.Set(CTRL_UP, input->GetKeyDown('W'));
-            character_->controls_.Set(CTRL_DOWN, input->GetKeyDown('S'));
+            character_->controls_.Set(CTRL_FORWARD, input->GetKeyDown('W'));
+            character_->controls_.Set(CTRL_BACK, input->GetKeyDown('S'));
             character_->controls_.Set(CTRL_LEFT, input->GetKeyDown('A'));
             character_->controls_.Set(CTRL_LEFT, input->GetKeyDown('A'));
             character_->controls_.Set(CTRL_RIGHT, input->GetKeyDown('D'));
             character_->controls_.Set(CTRL_RIGHT, input->GetKeyDown('D'));
             character_->controls_.Set(CTRL_JUMP, input->GetKeyDown(KEY_SPACE));
             character_->controls_.Set(CTRL_JUMP, input->GetKeyDown(KEY_SPACE));
@@ -247,7 +270,7 @@ void CharacterDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
                 firstPerson_ = !firstPerson_;
                 firstPerson_ = !firstPerson_;
         }
         }
         else
         else
-            character_->controls_.Set(CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
+            character_->controls_.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
         
         
         // Set rotation already here so that it's updated every rendering frame instead of every physics frame
         // Set rotation already here so that it's updated every rendering frame instead of every physics frame
         character_->GetNode()->SetRotation(Quaternion(character_->controls_.yaw_, Vector3::UP));
         character_->GetNode()->SetRotation(Quaternion(character_->controls_.yaw_, Vector3::UP));

+ 8 - 8
Source/Samples/18_CharacterDemo/CharacterDemo.h

@@ -33,12 +33,12 @@ namespace Urho3D
 using namespace Urho3D;
 using namespace Urho3D;
 
 
 class Character;
 class Character;
-
-/// Moving character example.
-/// This sample demonstrates:
-///     - Controlling a humanoid character through physics;
-///     - Driving animations using the AnimationController component;
-///     - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera
+
+/// Moving character example.
+/// This sample demonstrates:
+///     - Controlling a humanoid character through physics;
+///     - Driving animations using the AnimationController component;
+///     - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera
 ///       clipping into scenery
 ///       clipping into scenery
 class CharacterDemo : public Sample
 class CharacterDemo : public Sample
 {
 {
@@ -56,7 +56,7 @@ private:
     void CreateScene();
     void CreateScene();
     /// Create controllable character.
     /// Create controllable character.
     void CreateCharacter();
     void CreateCharacter();
-    /// Construct an instruction text to the UI.
+    /// Construct an instruction text to the UI.
     void CreateInstructions();
     void CreateInstructions();
     /// Subscribe to necessary events.
     /// Subscribe to necessary events.
     void SubscribeToEvents();
     void SubscribeToEvents();
@@ -69,7 +69,7 @@ private:
     SharedPtr<Scene> scene_;
     SharedPtr<Scene> scene_;
     /// Camera scene node.
     /// Camera scene node.
     SharedPtr<Node> cameraNode_;
     SharedPtr<Node> cameraNode_;
-    /// The controllable character.
+    /// The controllable character component.
     WeakPtr<Character> character_;
     WeakPtr<Character> character_;
     /// First person camera flag.
     /// First person camera flag.
     bool firstPerson_;
     bool firstPerson_;

+ 32 - 0
Source/Samples/19_VehicleDemo/CMakeLists.txt

@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2008-2013 the Urho3D project.
+#
+# 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.
+#
+
+# Define target name
+set (TARGET_NAME 19_VehicleDemo)
+
+# Define source files
+file (GLOB CPP_FILES *.cpp)
+file (GLOB H_FILES *.h)
+set (SOURCE_FILES ${CPP_FILES} ${H_FILES} ${COMMON_SAMPLE_H_FILES})
+
+# Setup target with resource copying
+setup_main_executable ()

+ 161 - 0
Source/Samples/19_VehicleDemo/Vehicle.cpp

@@ -0,0 +1,161 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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.
+//
+
+#include "CollisionShape.h"
+#include "Constraint.h"
+#include "Material.h"
+#include "Model.h"
+#include "PhysicsEvents.h"
+#include "PhysicsWorld.h"
+#include "ResourceCache.h"
+#include "RigidBody.h"
+#include "Scene.h"
+#include "StaticModel.h"
+#include "Vehicle.h"
+
+Vehicle::Vehicle(Context* context) :
+    Component(context),
+    steering_(0.0f)
+{
+}
+
+void Vehicle::OnNodeSet(Node* node)
+{
+    if (node)
+        SubscribeToEvent(GetScene()->GetComponent<PhysicsWorld>(), E_PHYSICSPRESTEP, HANDLER(Vehicle, HandleFixedUpdate));
+}
+
+void Vehicle::Init()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    
+    StaticModel* hullObject = node_->CreateComponent<StaticModel>();
+    hullBody_ = node_->CreateComponent<RigidBody>();
+    CollisionShape* hullShape = node_->CreateComponent<CollisionShape>();
+
+    node_->SetScale(Vector3(1.5f, 1.0f, 3.0f));
+    hullObject->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
+    hullObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
+    hullObject->SetCastShadows(true);
+    hullShape->SetBox(Vector3::ONE);
+    hullBody_->SetMass(4.0f);
+    hullBody_->SetLinearDamping(0.2f); // Some air resistance
+    hullBody_->SetAngularDamping(0.5f);
+    hullBody_->SetCollisionLayer(1);
+
+    frontLeft_ = InitWheel("FrontLeft", Vector3(-0.6f, -0.4f, 0.3f));
+    frontRight_ = InitWheel("FrontRight", Vector3(0.6f, -0.4f, 0.3f));
+    rearLeft_ = InitWheel("RearLeft", Vector3(-0.6f, -0.4f, -0.3f));
+    rearRight_ = InitWheel("RearRight", Vector3(0.6f, -0.4f, -0.3f));
+    
+    frontLeftAxis_ = frontLeft_->GetComponent<Constraint>();
+    frontRightAxis_ = frontRight_->GetComponent<Constraint>();
+    frontLeftBody_ = frontLeft_->GetComponent<RigidBody>();
+    frontRightBody_ = frontRight_->GetComponent<RigidBody>();
+    rearLeftBody_ = rearLeft_->GetComponent<RigidBody>();
+    rearRightBody_ = rearRight_->GetComponent<RigidBody>();
+}
+
+Node* Vehicle::InitWheel(const String& name, const Vector3& offset)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    
+    // 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
+    Node* wheelNode = GetScene()->CreateChild(name);
+    wheelNode->SetPosition(node_->LocalToWorld(offset));
+    wheelNode->SetRotation(node_->GetWorldRotation() * (offset.x_ >= 0.0 ? Quaternion(0.0f, 0.0f, -90.0f) :
+        Quaternion(0.0f, 0.0f, 90.0f)));
+    wheelNode->SetScale(Vector3(0.8f, 0.5f, 0.8f));
+
+    StaticModel* wheelObject = wheelNode->CreateComponent<StaticModel>();
+    RigidBody* wheelBody = wheelNode->CreateComponent<RigidBody>();
+    CollisionShape* wheelShape = wheelNode->CreateComponent<CollisionShape>();
+    Constraint* wheelConstraint = wheelNode->CreateComponent<Constraint>();
+
+    wheelObject->SetModel(cache->GetResource<Model>("Models/Cylinder.mdl"));
+    wheelObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
+    wheelObject->SetCastShadows(true);
+    wheelShape->SetSphere(1.0f);
+    wheelBody->SetFriction(1.0f);
+    wheelBody->SetMass(1.0f);
+    wheelBody->SetLinearDamping(0.2f); // Some air resistance
+    wheelBody->SetAngularDamping(0.75f); // Could also use rolling friction
+    wheelBody->SetCollisionLayer(1);
+    wheelConstraint->SetConstraintType(CONSTRAINT_HINGE);
+    wheelConstraint->SetOtherBody(GetComponent<RigidBody>()); // Connect to the hull body
+    wheelConstraint->SetWorldPosition(wheelNode->GetWorldPosition()); // Set constraint's both ends at wheel's location
+    wheelConstraint->SetAxis(Vector3::UP); // Wheel rotates around its local Y-axis
+    wheelConstraint->SetOtherAxis(offset.x_ >= 0.0 ? Vector3::RIGHT : Vector3::LEFT); // Wheel's hull axis points either left or right
+    wheelConstraint->SetLowLimit(Vector2(-180.0f, 0.0f)); // Let the wheel rotate freely around the axis
+    wheelConstraint->SetHighLimit(Vector2(180.0f, 0.0f));
+    wheelConstraint->SetDisableCollision(true); // Let the wheel intersect the vehicle hull
+    
+    return wheelNode;
+}
+
+void Vehicle::HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
+{
+    float newSteering = 0.0f;
+    float accelerator = 0.0f;
+
+    // Read controls
+    if (controls_.buttons_ & CTRL_LEFT)
+        newSteering = -1.0f;
+    if (controls_.buttons_ & CTRL_RIGHT)
+        newSteering = 1.0f;
+    if (controls_.buttons_ & CTRL_FORWARD)
+        accelerator = 1.0f;
+    if (controls_.buttons_ & CTRL_BACK)
+        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(0, steering_ * MAX_WHEEL_ANGLE, 0);
+    frontLeftAxis_->SetOtherAxis(steeringRot * Vector3::LEFT);
+    frontRightAxis_->SetOtherAxis(steeringRot * Vector3::RIGHT);
+
+    Quaternion hullRot = hullBody_->GetRotation();
+    if (accelerator != 0.0f)
+    {
+        // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
+        Vector3 torqueVec = Vector3(ENGINE_POWER * 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 = hullRot.Inverse() * hullBody_->GetLinearVelocity();
+    hullBody_->ApplyForce(hullRot * Vector3::DOWN * Abs(localVelocity.z_) * DOWN_FORCE);
+}

+ 90 - 0
Source/Samples/19_VehicleDemo/Vehicle.h

@@ -0,0 +1,90 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "Component.h"
+#include "Controls.h"
+
+namespace Urho3D
+{
+    class Constraint;
+    class Node;
+    class RigidBody;
+}
+
+using namespace Urho3D;
+
+const int CTRL_FORWARD = 1;
+const int CTRL_BACK = 2;
+const int CTRL_LEFT = 4;
+const int CTRL_RIGHT = 8;
+
+const float YAW_SENSITIVITY = 0.1f;
+const float ENGINE_POWER = 10.0f;
+const float DOWN_FORCE = 10.0f;
+const float MAX_WHEEL_ANGLE = 22.5f;
+
+/// Vehicle component, responsible for physical movement according to controls.
+class Vehicle : public Component
+{
+    OBJECT(Vehicle)
+
+public:
+    /// Construct.
+    Vehicle(Context* context);
+    
+    /// Handle node being assigned.
+    virtual void OnNodeSet(Node* node);
+    
+    /// Initialize the vehicle. Create rendering and physics components.
+    void Init();
+    
+    /// Movement controls.
+    Controls controls_;
+    
+private:
+    /// Initialize a wheel and return its scene node.
+    Node* InitWheel(const String& name, const Vector3& offset);
+    /// Handle physics world update event.
+    void HandleFixedUpdate(StringHash eventType, VariantMap& eventData);
+    
+    // Wheel scene nodes.
+    WeakPtr<Node> frontLeft_;
+    WeakPtr<Node> frontRight_;
+    WeakPtr<Node> rearLeft_;
+    WeakPtr<Node> rearRight_;
+    
+    // Steering axle constraints.
+    WeakPtr<Constraint> frontLeftAxis_;
+    WeakPtr<Constraint> frontRightAxis_;
+    
+    // Hull and wheel rigid bodies.
+    WeakPtr<RigidBody> hullBody_;
+    WeakPtr<RigidBody> frontLeftBody_;
+    WeakPtr<RigidBody> frontRightBody_;
+    WeakPtr<RigidBody> rearLeftBody_;
+    WeakPtr<RigidBody> rearRightBody_;
+    
+    /// Current left/right steering amount (-1 to 1.)
+    float steering_;
+};

+ 251 - 0
Source/Samples/19_VehicleDemo/VehicleDemo.cpp

@@ -0,0 +1,251 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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.
+//
+
+#include "Camera.h"
+#include "CollisionShape.h"
+#include "Constraint.h"
+#include "CoreEvents.h"
+#include "Engine.h"
+#include "Font.h"
+#include "Input.h"
+#include "Light.h"
+#include "Material.h"
+#include "Model.h"
+#include "Octree.h"
+#include "PhysicsWorld.h"
+#include "ProcessUtils.h"
+#include "Renderer.h"
+#include "RigidBody.h"
+#include "ResourceCache.h"
+#include "Scene.h"
+#include "StaticModel.h"
+#include "Terrain.h"
+#include "Text.h"
+#include "UI.h"
+#include "Vehicle.h"
+#include "Zone.h"
+
+#include "VehicleDemo.h"
+
+#include "DebugNew.h"
+
+const float CAMERA_MIN_DIST = 3.0f;
+const float CAMERA_MAX_DIST = 8.0f;
+
+DEFINE_APPLICATION_MAIN(VehicleDemo)
+
+VehicleDemo::VehicleDemo(Context* context) :
+    Sample(context)
+{
+    // Register factory for the Vehicle component so it can be created via CreateComponent
+    context_->RegisterFactory<Vehicle>();
+}
+
+void VehicleDemo::Start()
+{
+    // Execute base class startup
+    Sample::Start();
+    
+    // Create static scene content
+    CreateScene();
+    
+    // Create the controllable vehicle
+    CreateVehicle();
+    
+    // Create the UI content
+    CreateInstructions();
+    
+    // Subscribe to necessary events
+    SubscribeToEvents();
+}
+
+void VehicleDemo::CreateScene()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    
+    scene_ = new Scene(context_);
+    
+    // Create scene subsystem components
+    scene_->CreateComponent<Octree>();
+    scene_->CreateComponent<PhysicsWorld>();
+    
+    // Create camera and define viewport. Camera does not necessarily have to belong to the scene
+    cameraNode_ = new Node(context_);
+    Camera* camera = cameraNode_->CreateComponent<Camera>();
+    camera->SetFarClip(500.0f);
+    GetSubsystem<Renderer>()->SetViewport(0, new Viewport(context_, scene_, camera));
+    
+    // Create static scene content. First create a zone for ambient lighting and fog control
+    Node* zoneNode = scene_->CreateChild("Zone");
+    Zone* zone = zoneNode->CreateComponent<Zone>();
+    zone->SetAmbientColor(Color(0.15f, 0.15f, 0.15f));
+    zone->SetFogColor(Color(0.5f, 0.5f, 0.7f));
+    zone->SetFogStart(300.0f);
+    zone->SetFogEnd(500.0f);
+    zone->SetBoundingBox(BoundingBox(-2000.0f, 2000.0f));
+    
+    // Create a directional light with cascaded shadow mapping
+    Node* lightNode = scene_->CreateChild("DirectionalLight");
+    lightNode->SetDirection(Vector3(0.3f, -0.5f, 0.425f));
+    Light* light = lightNode->CreateComponent<Light>();
+    light->SetLightType(LIGHT_DIRECTIONAL);
+    light->SetCastShadows(true);
+    light->SetShadowBias(BiasParameters(0.0001f, 0.5f));
+    light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
+    light->SetSpecularIntensity(0.5f);
+    
+    // Create heightmap terrain with collision
+    Node* terrainNode = scene_->CreateChild("Terrain");
+    terrainNode->SetPosition(Vector3::ZERO);
+    Terrain* terrain = terrainNode->CreateComponent<Terrain>();
+    terrain->SetPatchSize(64);
+    terrain->SetSpacing(Vector3(2.0f, 0.1f, 2.0f)); // Spacing between vertices and vertical resolution of the height map
+    terrain->SetSmoothing(true);
+    terrain->SetHeightMap(cache->GetResource<Image>("Textures/HeightMap.png"));
+    terrain->SetMaterial(cache->GetResource<Material>("Materials/Terrain.xml"));
+    // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
+    // terrain patches and other objects behind it
+    terrain->SetOccluder(true);
+    
+    RigidBody* body = terrainNode->CreateComponent<RigidBody>();
+    body->SetCollisionLayer(2); // Use layer bitmask 2 for static geometry
+    CollisionShape* shape = terrainNode->CreateComponent<CollisionShape>();
+    shape->SetTerrain();
+    
+    // Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
+    const unsigned NUM_MUSHROOMS = 1000;
+    for (unsigned i = 0; i < NUM_MUSHROOMS; ++i)
+    {
+        Node* objectNode = scene_->CreateChild("Mushroom");
+        Vector3 position(Random(2000.0f) - 1000.0f, 0.0f, Random(2000.0f) - 1000.0f);
+        position.y_ = terrain->GetHeight(position) - 0.1f;
+        objectNode->SetPosition(position);
+        // Create a rotation quaternion from up vector to terrain normal
+        objectNode->SetRotation(Quaternion(Vector3::UP, terrain->GetNormal(position)));
+        objectNode->SetScale(3.0f);
+        StaticModel* object = objectNode->CreateComponent<StaticModel>();
+        object->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
+        object->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));
+        object->SetCastShadows(true);
+        
+        RigidBody* body = objectNode->CreateComponent<RigidBody>();
+        body->SetCollisionLayer(2);
+        CollisionShape* shape = objectNode->CreateComponent<CollisionShape>();
+        shape->SetTriangleMesh(object->GetModel(), 0);
+    }
+}
+
+void VehicleDemo::CreateVehicle()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    Node* vehicleNode = scene_->CreateChild("Vehicle");
+    vehicleNode->SetPosition(Vector3(0.0f, 5.0f, 0.0f));
+    
+    // Create the vehicle logic component
+    vehicle_ = vehicleNode->CreateComponent<Vehicle>();
+    // Create the rendering and physics components
+    vehicle_->Init();
+}
+
+void VehicleDemo::CreateInstructions()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    UI* ui = GetSubsystem<UI>();
+    
+    // Construct new Text object, set string to display and font to use
+    Text* instructionText = ui->GetRoot()->CreateChild<Text>();
+    instructionText->SetText(
+        "Use WASD keys to drive, mouse to rotate camera"
+    );
+    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
+    
+    // Position the text relative to the screen center
+    instructionText->SetHorizontalAlignment(HA_CENTER);
+    instructionText->SetVerticalAlignment(VA_CENTER);
+    instructionText->SetPosition(0, ui->GetRoot()->GetHeight() / 4);
+}
+
+void VehicleDemo::SubscribeToEvents()
+{
+    // Subscribe to Update event for setting the character controls before physics simulation
+    SubscribeToEvent(E_UPDATE, HANDLER(VehicleDemo, HandleUpdate));
+    
+    // Subscribe to PostUpdate event for updating the camera position after physics simulation
+    SubscribeToEvent(E_POSTUPDATE, HANDLER(VehicleDemo, HandlePostUpdate));
+}
+
+void VehicleDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace Update;
+    
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    Input* input = GetSubsystem<Input>();
+
+    if (vehicle_)
+    {
+        UI* ui = GetSubsystem<UI>();
+        
+        // Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
+        if (!ui->GetFocusElement())
+        {
+            vehicle_->controls_.Set(CTRL_FORWARD, input->GetKeyDown('W'));
+            vehicle_->controls_.Set(CTRL_BACK, input->GetKeyDown('S'));
+            vehicle_->controls_.Set(CTRL_LEFT, input->GetKeyDown('A'));
+            vehicle_->controls_.Set(CTRL_RIGHT, input->GetKeyDown('D'));
+        
+            // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
+            vehicle_->controls_.yaw_ += (float)input->GetMouseMoveX() * YAW_SENSITIVITY;
+            vehicle_->controls_.pitch_ += (float)input->GetMouseMoveY() * YAW_SENSITIVITY;
+            // Limit pitch
+            vehicle_->controls_.pitch_ = Clamp(vehicle_->controls_.pitch_, 1.0f, 80.0f);
+        }
+        else
+            vehicle_->controls_.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT, false);
+    }
+}
+
+void VehicleDemo::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    if (!vehicle_)
+        return;
+    
+    Node* vehicleNode = vehicle_->GetNode();
+    
+    // Get camera lookat dir from vehicle yaw + camera yaw and camera pitch. Do not apply vehicle's pitch
+    // angle, because it changes abruptly when going over bumps in the terrain
+    Quaternion rot = Quaternion(0.0f, vehicleNode->GetRotation().YawAngle() + vehicle_->controls_.yaw_, 0.0f);
+    Quaternion dir = rot * Quaternion(vehicle_->controls_.pitch_, Vector3::RIGHT);
+    Vector3 aimPoint = vehicleNode->GetPosition() + rot * Vector3(0.0f, 1.7f, 0.0f);
+    
+    // Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the vehicle properly
+    Vector3 rayDir = dir * Vector3::BACK;
+    float rayDistance = CAMERA_MAX_DIST;
+    PhysicsRaycastResult result;
+    scene_->GetComponent<PhysicsWorld>()->RaycastSingle(result, Ray(aimPoint, rayDir), rayDistance, 2);
+    if (result.body_)
+        rayDistance = Min(rayDistance, result.distance_);
+    rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
+    
+    cameraNode_->SetPosition(aimPoint + rayDir * rayDistance);
+    cameraNode_->SetRotation(dir);
+}

+ 72 - 0
Source/Samples/19_VehicleDemo/VehicleDemo.h

@@ -0,0 +1,72 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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.
+//
+
+#pragma once
+
+#include "Sample.h"
+
+namespace Urho3D
+{
+    class Node;
+    class Scene;
+}
+
+using namespace Urho3D;
+
+class Vehicle;
+
+/// Vehicle example.
+/// This sample demonstrates:
+///     - Creating a heightmap terrain with collision;
+///     - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints;
+class VehicleDemo : public Sample
+{
+    OBJECT(VehicleDemo);
+
+public:
+    /// Construct.
+    VehicleDemo(Context* context);
+    
+    /// Setup after engine initialization and before running the main loop.
+    virtual void Start();
+    
+private:
+    /// Create static scene content.
+    void CreateScene();
+    /// Create the vehicle.
+    void CreateVehicle();
+    /// Construct an instruction text to the UI.
+    void CreateInstructions();
+    /// Subscribe to necessary events.
+    void SubscribeToEvents();
+    /// Handle application update. Set controls to vehicle.
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle application post-update. Update camera position after vehicle has moved.
+    void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
+    
+    /// Scene.
+    SharedPtr<Scene> scene_;
+    /// Camera scene node.
+    SharedPtr<Node> cameraNode_;
+    /// The controllable vehicle component.
+    WeakPtr<Vehicle> vehicle_;
+};

+ 2 - 1
Source/Samples/CMakeLists.txt

@@ -59,4 +59,5 @@ add_subdirectory (14_SoundEffects)
 add_subdirectory (15_Navigation)
 add_subdirectory (15_Navigation)
 add_subdirectory (16_Chat)
 add_subdirectory (16_Chat)
 add_subdirectory (17_SceneReplication)
 add_subdirectory (17_SceneReplication)
-add_subdirectory (18_CharacterDemo)
+add_subdirectory (18_CharacterDemo)
+add_subdirectory (19_VehicleDemo)