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