1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include <Urho3D/Core/CoreEvents.h>
24 #include <Urho3D/Engine/Engine.h>
25 #include <Urho3D/Graphics/AnimatedModel.h>
26 #include <Urho3D/Graphics/Camera.h>
27 #include <Urho3D/Graphics/DebugRenderer.h>
28 #include <Urho3D/Graphics/Graphics.h>
29 #include <Urho3D/Graphics/Light.h>
30 #include <Urho3D/Graphics/Material.h>
31 #include <Urho3D/Graphics/Octree.h>
32 #include <Urho3D/Graphics/Renderer.h>
33 #include <Urho3D/Graphics/Zone.h>
34 #include <Urho3D/Input/Input.h>
35 #include <Urho3D/IO/File.h>
36 #include <Urho3D/IO/FileSystem.h>
37 #include <Urho3D/Physics/CollisionShape.h>
38 #include <Urho3D/Physics/PhysicsWorld.h>
39 #include <Urho3D/Physics/RigidBody.h>
40 #include <Urho3D/Resource/ResourceCache.h>
41 #include <Urho3D/Scene/Scene.h>
42 #include <Urho3D/UI/Font.h>
43 #include <Urho3D/UI/Text.h>
44 #include <Urho3D/UI/UI.h>
45 
46 #include "CreateRagdoll.h"
47 #include "Ragdolls.h"
48 
49 #include <Urho3D/DebugNew.h>
50 
URHO3D_DEFINE_APPLICATION_MAIN(Ragdolls)51 URHO3D_DEFINE_APPLICATION_MAIN(Ragdolls)
52 
53 Ragdolls::Ragdolls(Context* context) :
54     Sample(context),
55     drawDebug_(false)
56 {
57     // Register an object factory for our custom CreateRagdoll component so that we can create them to scene nodes
58     context->RegisterFactory<CreateRagdoll>();
59 }
60 
Start()61 void Ragdolls::Start()
62 {
63     // Execute base class startup
64     Sample::Start();
65 
66     // Create the scene content
67     CreateScene();
68 
69     // Create the UI content
70     CreateInstructions();
71 
72     // Setup the viewport for displaying the scene
73     SetupViewport();
74 
75     // Hook up to the frame update and render post-update events
76     SubscribeToEvents();
77 
78     // Set the mouse mode to use in the sample
79     Sample::InitMouseMode(MM_ABSOLUTE);
80 }
81 
CreateScene()82 void Ragdolls::CreateScene()
83 {
84     ResourceCache* cache = GetSubsystem<ResourceCache>();
85 
86     scene_ = new Scene(context_);
87 
88     // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
89     // Create a physics simulation world with default parameters, which will update at 60fps. Like the Octree must
90     // exist before creating drawable components, the PhysicsWorld must exist before creating physics components.
91     // Finally, create a DebugRenderer component so that we can draw physics debug geometry
92     scene_->CreateComponent<Octree>();
93     scene_->CreateComponent<PhysicsWorld>();
94     scene_->CreateComponent<DebugRenderer>();
95 
96     // Create a Zone component for ambient lighting & fog control
97     Node* zoneNode = scene_->CreateChild("Zone");
98     Zone* zone = zoneNode->CreateComponent<Zone>();
99     zone->SetBoundingBox(BoundingBox(-1000.0f, 1000.0f));
100     zone->SetAmbientColor(Color(0.15f, 0.15f, 0.15f));
101     zone->SetFogColor(Color(0.5f, 0.5f, 0.7f));
102     zone->SetFogStart(100.0f);
103     zone->SetFogEnd(300.0f);
104 
105     // Create a directional light to the world. Enable cascaded shadows on it
106     Node* lightNode = scene_->CreateChild("DirectionalLight");
107     lightNode->SetDirection(Vector3(0.6f, -1.0f, 0.8f));
108     Light* light = lightNode->CreateComponent<Light>();
109     light->SetLightType(LIGHT_DIRECTIONAL);
110     light->SetCastShadows(true);
111     light->SetShadowBias(BiasParameters(0.00025f, 0.5f));
112     // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
113     light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
114 
115     {
116         // Create a floor object, 500 x 500 world units. Adjust position so that the ground is at zero Y
117         Node* floorNode = scene_->CreateChild("Floor");
118         floorNode->SetPosition(Vector3(0.0f, -0.5f, 0.0f));
119         floorNode->SetScale(Vector3(500.0f, 1.0f, 500.0f));
120         StaticModel* floorObject = floorNode->CreateComponent<StaticModel>();
121         floorObject->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
122         floorObject->SetMaterial(cache->GetResource<Material>("Materials/StoneTiled.xml"));
123 
124         // Make the floor physical by adding RigidBody and CollisionShape components
125         RigidBody* body = floorNode->CreateComponent<RigidBody>();
126         // We will be spawning spherical objects in this sample. The ground also needs non-zero rolling friction so that
127         // the spheres will eventually come to rest
128         body->SetRollingFriction(0.15f);
129         CollisionShape* shape = floorNode->CreateComponent<CollisionShape>();
130         // Set a box shape of size 1 x 1 x 1 for collision. The shape will be scaled with the scene node scale, so the
131         // rendering and physics representation sizes should match (the box model is also 1 x 1 x 1.)
132         shape->SetBox(Vector3::ONE);
133     }
134 
135     // Create animated models
136     for (int z = -1; z <= 1; ++z)
137     {
138         for (int x = -4; x <= 4; ++x)
139         {
140             Node* modelNode = scene_->CreateChild("Jack");
141             modelNode->SetPosition(Vector3(x * 5.0f, 0.0f, z * 5.0f));
142             modelNode->SetRotation(Quaternion(0.0f, 180.0f, 0.0f));
143             AnimatedModel* modelObject = modelNode->CreateComponent<AnimatedModel>();
144             modelObject->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
145             modelObject->SetMaterial(cache->GetResource<Material>("Materials/Jack.xml"));
146             modelObject->SetCastShadows(true);
147             // Set the model to also update when invisible to avoid staying invisible when the model should come into
148             // view, but does not as the bounding box is not updated
149             modelObject->SetUpdateInvisible(true);
150 
151             // Create a rigid body and a collision shape. These will act as a trigger for transforming the
152             // model into a ragdoll when hit by a moving object
153             RigidBody* body = modelNode->CreateComponent<RigidBody>();
154             // The Trigger mode makes the rigid body only detect collisions, but impart no forces on the
155             // colliding objects
156             body->SetTrigger(true);
157             CollisionShape* shape = modelNode->CreateComponent<CollisionShape>();
158             // Create the capsule shape with an offset so that it is correctly aligned with the model, which
159             // has its origin at the feet
160             shape->SetCapsule(0.7f, 2.0f, Vector3(0.0f, 1.0f, 0.0f));
161 
162             // Create a custom component that reacts to collisions and creates the ragdoll
163             modelNode->CreateComponent<CreateRagdoll>();
164         }
165     }
166 
167     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
168     // the scene, because we want it to be unaffected by scene load / save
169     cameraNode_ = new Node(context_);
170     Camera* camera = cameraNode_->CreateComponent<Camera>();
171     camera->SetFarClip(300.0f);
172 
173     // Set an initial position for the camera scene node above the floor
174     cameraNode_->SetPosition(Vector3(0.0f, 3.0f, -20.0f));
175 }
176 
CreateInstructions()177 void Ragdolls::CreateInstructions()
178 {
179     ResourceCache* cache = GetSubsystem<ResourceCache>();
180     UI* ui = GetSubsystem<UI>();
181 
182     // Construct new Text object, set string to display and font to use
183     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
184     instructionText->SetText(
185         "Use WASD keys and mouse/touch to move\n"
186         "LMB to spawn physics objects\n"
187         "F5 to save scene, F7 to load\n"
188         "Space to toggle physics debug geometry"
189     );
190     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
191     // The text has multiple rows. Center them in relation to each other
192     instructionText->SetTextAlignment(HA_CENTER);
193 
194     // Position the text relative to the screen center
195     instructionText->SetHorizontalAlignment(HA_CENTER);
196     instructionText->SetVerticalAlignment(VA_CENTER);
197     instructionText->SetPosition(0, ui->GetRoot()->GetHeight() / 4);
198 }
199 
SetupViewport()200 void Ragdolls::SetupViewport()
201 {
202     Renderer* renderer = GetSubsystem<Renderer>();
203 
204     // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
205     SharedPtr<Viewport> viewport(new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>()));
206     renderer->SetViewport(0, viewport);
207 }
208 
MoveCamera(float timeStep)209 void Ragdolls::MoveCamera(float timeStep)
210 {
211     // Do not move if the UI has a focused element (the console)
212     if (GetSubsystem<UI>()->GetFocusElement())
213         return;
214 
215     Input* input = GetSubsystem<Input>();
216 
217     // Movement speed as world units per second
218     const float MOVE_SPEED = 20.0f;
219     // Mouse sensitivity as degrees per pixel
220     const float MOUSE_SENSITIVITY = 0.1f;
221 
222     // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
223     IntVector2 mouseMove = input->GetMouseMove();
224     yaw_ += MOUSE_SENSITIVITY * mouseMove.x_;
225     pitch_ += MOUSE_SENSITIVITY * mouseMove.y_;
226     pitch_ = Clamp(pitch_, -90.0f, 90.0f);
227 
228     // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
229     cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
230 
231     // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
232     if (input->GetKeyDown(KEY_W))
233         cameraNode_->Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
234     if (input->GetKeyDown(KEY_S))
235         cameraNode_->Translate(Vector3::BACK * MOVE_SPEED * timeStep);
236     if (input->GetKeyDown(KEY_A))
237         cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
238     if (input->GetKeyDown(KEY_D))
239         cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
240 
241     // "Shoot" a physics object with left mousebutton
242     if (input->GetMouseButtonPress(MOUSEB_LEFT))
243         SpawnObject();
244 
245     // Check for loading / saving the scene
246     if (input->GetKeyPress(KEY_F5))
247     {
248         File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/Ragdolls.xml", FILE_WRITE);
249         scene_->SaveXML(saveFile);
250     }
251     if (input->GetKeyPress(KEY_F7))
252     {
253         File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/Ragdolls.xml", FILE_READ);
254         scene_->LoadXML(loadFile);
255     }
256 
257     // Toggle physics debug geometry with space
258     if (input->GetKeyPress(KEY_SPACE))
259         drawDebug_ = !drawDebug_;
260 }
261 
SpawnObject()262 void Ragdolls::SpawnObject()
263 {
264     ResourceCache* cache = GetSubsystem<ResourceCache>();
265 
266     Node* boxNode = scene_->CreateChild("Sphere");
267     boxNode->SetPosition(cameraNode_->GetPosition());
268     boxNode->SetRotation(cameraNode_->GetRotation());
269     boxNode->SetScale(0.25f);
270     StaticModel* boxObject = boxNode->CreateComponent<StaticModel>();
271     boxObject->SetModel(cache->GetResource<Model>("Models/Sphere.mdl"));
272     boxObject->SetMaterial(cache->GetResource<Material>("Materials/StoneSmall.xml"));
273     boxObject->SetCastShadows(true);
274 
275     RigidBody* body = boxNode->CreateComponent<RigidBody>();
276     body->SetMass(1.0f);
277     body->SetRollingFriction(0.15f);
278     CollisionShape* shape = boxNode->CreateComponent<CollisionShape>();
279     shape->SetSphere(1.0f);
280 
281     const float OBJECT_VELOCITY = 10.0f;
282 
283     // Set initial velocity for the RigidBody based on camera forward vector. Add also a slight up component
284     // to overcome gravity better
285     body->SetLinearVelocity(cameraNode_->GetRotation() * Vector3(0.0f, 0.25f, 1.0f) * OBJECT_VELOCITY);
286 }
287 
SubscribeToEvents()288 void Ragdolls::SubscribeToEvents()
289 {
290     // Subscribe HandleUpdate() function for processing update events
291     SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(Ragdolls, HandleUpdate));
292 
293     // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
294     // debug geometry
295     SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(Ragdolls, HandlePostRenderUpdate));
296 }
297 
HandleUpdate(StringHash eventType,VariantMap & eventData)298 void Ragdolls::HandleUpdate(StringHash eventType, VariantMap& eventData)
299 {
300     using namespace Update;
301 
302     // Take the frame time step, which is stored as a float
303     float timeStep = eventData[P_TIMESTEP].GetFloat();
304 
305     // Move the camera, scale movement with time step
306     MoveCamera(timeStep);
307 }
308 
HandlePostRenderUpdate(StringHash eventType,VariantMap & eventData)309 void Ragdolls::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
310 {
311     // If draw debug mode is enabled, draw physics debug geometry. Use depth test to make the result easier to interpret
312     if (drawDebug_)
313         scene_->GetComponent<PhysicsWorld>()->DrawDebugGeometry(true);
314 }
315