Browse Source

Added Decals example.
Refactored SkeletalAnimation example to use a custom component as well.

Lasse Öörni 12 years ago
parent
commit
0f4fbda5fb

+ 3 - 0
Source/Engine/Graphics/DecalSet.h

@@ -30,6 +30,9 @@
 namespace Urho3D
 namespace Urho3D
 {
 {
 
 
+class IndexBuffer;
+class VertexBuffer;
+
 /// %Decal vertex.
 /// %Decal vertex.
 struct DecalVertex
 struct DecalVertex
 {
 {

+ 3 - 3
Source/Samples/04_StaticScene/StaticScene.cpp

@@ -86,13 +86,13 @@ void StaticScene::CreateScene()
     planeNode->SetScale(Vector3(100.0f, 1.0f, 100.0f));
     planeNode->SetScale(Vector3(100.0f, 1.0f, 100.0f));
     StaticModel* planeObject = planeNode->CreateComponent<StaticModel>();
     StaticModel* planeObject = planeNode->CreateComponent<StaticModel>();
     planeObject->SetModel(cache->GetResource<Model>("Models/Plane.mdl"));
     planeObject->SetModel(cache->GetResource<Model>("Models/Plane.mdl"));
-    planeObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
+    planeObject->SetMaterial(cache->GetResource<Material>("Materials/StoneTiled.xml"));
     
     
     // Create a directional light to the world so that we can see something. The light scene node's orientation controls the
     // Create a directional light to the world so that we can see something. The light scene node's orientation controls the
     // light direction; we will use the SetDirection() function which calculates the orientation from a forward direction vector.
     // light direction; we will use the SetDirection() function which calculates the orientation from a forward direction vector.
     // The light will use default settings (white light, no shadows)
     // The light will use default settings (white light, no shadows)
     Node* lightNode = scene_->CreateChild("Directional light");
     Node* lightNode = scene_->CreateChild("Directional light");
-    lightNode->SetDirection(Vector3(-1.0f, -1.0f, -1.0f)); // The direction vector does not need to be normalized
+    lightNode->SetDirection(Vector3(0.6f, -1.0f, 0.8f)); // The direction vector does not need to be normalized
     Light* light = lightNode->CreateComponent<Light>();
     Light* light = lightNode->CreateComponent<Light>();
     light->SetLightType(LIGHT_DIRECTIONAL);
     light->SetLightType(LIGHT_DIRECTIONAL);
     
     
@@ -108,7 +108,7 @@ void StaticScene::CreateScene()
         Node* mushroomNode = scene_->CreateChild("Mushroom");
         Node* mushroomNode = scene_->CreateChild("Mushroom");
         mushroomNode->SetPosition(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
         mushroomNode->SetPosition(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
         mushroomNode->SetRotation(Quaternion(0.0f, Random(360.0f), 0.0f));
         mushroomNode->SetRotation(Quaternion(0.0f, Random(360.0f), 0.0f));
-        mushroomNode->SetScale(1.0f + Random(1.0f));
+        mushroomNode->SetScale(0.5f + Random(2.0f));
         StaticModel* mushroomObject = mushroomNode->CreateComponent<StaticModel>();
         StaticModel* mushroomObject = mushroomNode->CreateComponent<StaticModel>();
         mushroomObject->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
         mushroomObject->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
         mushroomObject->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));
         mushroomObject->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));

+ 38 - 38
Source/Samples/05_AnimatingScene/Rotator.h

@@ -1,36 +1,36 @@
-//
-// 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"
-
-// All Urho3D classes reside in namespace Urho3D
-using namespace Urho3D;
-
-/// Custom component for rotating a scene node.
-class Rotator : public Component
-{
-public:
+//
+// 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"
+
+// All Urho3D classes reside in namespace Urho3D
+using namespace Urho3D;
+
+/// Custom component for rotating a scene node.
+class Rotator : public Component
+{
+public:
     /// Construct.
     /// Construct.
     Rotator(Context* context);
     Rotator(Context* context);
     
     
@@ -46,8 +46,8 @@ protected:
     
     
 private:
 private:
     /// Handle scene update event.
     /// Handle scene update event.
-    void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
-    
-    /// Rotation speed.
-    Vector3 rotationSpeed_;
-};
+    void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
+    
+    /// Rotation speed.
+    Vector3 rotationSpeed_;
+};

+ 75 - 0
Source/Samples/06_SkeletalAnimation/Mover.cpp

@@ -0,0 +1,75 @@
+//
+// 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 "AnimationState.h"
+#include "Mover.h"
+#include "Scene.h"
+#include "SceneEvents.h"
+
+Mover::Mover(Context* context) :
+    Component(context),
+    moveSpeed_(0.0f),
+    rotationSpeed_(0.0f)
+{
+}
+
+void Mover::SetParameters(float moveSpeed, float rotationSpeed, const BoundingBox& bounds)
+{
+    moveSpeed_ = moveSpeed;
+    rotationSpeed_ = rotationSpeed;
+    bounds_ = bounds;
+}
+
+void Mover::OnNodeSet(Node* node)
+{
+    // If the node pointer is nonzero, this component has been created into a scene node. Subscribe to the variable timestep
+    // scene update event now. If the node pointer is zero, the component is being removed from a scene node at destruction
+    // time. In that case we do nothing
+    if (node)
+    {
+        Scene* scene = node->GetScene();
+        // The scene pointer will be nonzero if the scene node belongs to a scene (it is also legal to create free-standing
+        // scene nodes)
+        if (scene)
+            SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(Mover, HandleSceneUpdate));
+    }
+}
+
+void Mover::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // Get the timestep from the update event
+    using namespace SceneUpdate;
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    
+    node_->TranslateRelative(Vector3::FORWARD * moveSpeed_ * timeStep);
+    
+    // If in risk of going outside the plane, rotate the model right
+    Vector3 pos = node_->GetPosition();
+    if (pos.x_ < bounds_.min_.x_ || pos.x_ > bounds_.max_.x_ || pos.z_ < bounds_.min_.z_ || pos.z_ > bounds_.max_.z_)
+        node_->Yaw(rotationSpeed_ * timeStep);
+    
+    // Get the model's first (only) animation state and advance its time. Note the convenience accessor to other components
+    // in the same scene node
+    AnimationState* state = GetComponent<AnimatedModel>()->GetAnimationStates()[0];
+    state->AddTime(timeStep);
+}

+ 61 - 0
Source/Samples/06_SkeletalAnimation/Mover.h

@@ -0,0 +1,61 @@
+//
+// 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"
+
+// All Urho3D classes reside in namespace Urho3D
+using namespace Urho3D;
+
+/// Custom component for moving the animated model and rotating at area edges.
+class Mover : public Component
+{
+public:
+    /// Construct.
+    Mover(Context* context);
+    
+    /// Set motion parameters: forward movement speed, rotation speed, and movement boundaries.
+    void SetParameters(float moveSpeed, float rotateSpeed, const BoundingBox& bounds);
+    
+    /// Return forward movement speed.
+    float GetMoveSpeed() const { return moveSpeed_; }
+    /// Return rotation speed.
+    float GetRotationSpeed() const { return rotationSpeed_; }
+    /// Return movement boundaries.
+    const BoundingBox& GetBounds() const { return bounds_; }
+    
+protected:
+    /// Handle node being assigned.
+    virtual void OnNodeSet(Node* node);
+    
+private:
+    /// Handle scene update event.
+    void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
+    
+    /// Forward movement speed.
+    float moveSpeed_;
+    /// Rotation speed.
+    float rotationSpeed_;
+    /// Movement boundaries.
+    BoundingBox bounds_;
+};

+ 18 - 32
Source/Samples/06_SkeletalAnimation/SkeletalAnimation.cpp

@@ -33,6 +33,7 @@
 #include "Light.h"
 #include "Light.h"
 #include "Material.h"
 #include "Material.h"
 #include "Model.h"
 #include "Model.h"
+#include "Mover.h"
 #include "Octree.h"
 #include "Octree.h"
 #include "Renderer.h"
 #include "Renderer.h"
 #include "ResourceCache.h"
 #include "ResourceCache.h"
@@ -53,6 +54,8 @@ SkeletalAnimation::SkeletalAnimation(Context* context) :
     pitch_(0.0f),
     pitch_(0.0f),
     drawDebug_(false)
     drawDebug_(false)
 {
 {
+    // Register an object factory for our custom Mover component so that we can create them to scene nodes
+    context->RegisterFactory<Mover>();
 }
 }
 
 
 void SkeletalAnimation::Start()
 void SkeletalAnimation::Start()
@@ -90,7 +93,7 @@ void SkeletalAnimation::CreateScene()
     planeNode->SetScale(Vector3(100.0f, 1.0f, 100.0f));
     planeNode->SetScale(Vector3(100.0f, 1.0f, 100.0f));
     StaticModel* planeObject = planeNode->CreateComponent<StaticModel>();
     StaticModel* planeObject = planeNode->CreateComponent<StaticModel>();
     planeObject->SetModel(cache->GetResource<Model>("Models/Plane.mdl"));
     planeObject->SetModel(cache->GetResource<Model>("Models/Plane.mdl"));
-    planeObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
+    planeObject->SetMaterial(cache->GetResource<Material>("Materials/StoneTiled.xml"));
     
     
     // Create a Zone component for ambient lighting & fog color control
     // Create a Zone component for ambient lighting & fog color control
     Node* zoneNode = scene_->CreateChild("Zone");
     Node* zoneNode = scene_->CreateChild("Zone");
@@ -103,7 +106,7 @@ void SkeletalAnimation::CreateScene()
     
     
     // Create a directional light to the world. Enable cascaded shadows on it
     // Create a directional light to the world. Enable cascaded shadows on it
     Node* lightNode = scene_->CreateChild("Directional light");
     Node* lightNode = scene_->CreateChild("Directional light");
-    lightNode->SetDirection(Vector3(-1.0f, -1.0f, -1.0f)); // The direction vector does not need to be normalized
+    lightNode->SetDirection(Vector3(0.6f, -1.0f, 0.8f));
     Light* light = lightNode->CreateComponent<Light>();
     Light* light = lightNode->CreateComponent<Light>();
     light->SetLightType(LIGHT_DIRECTIONAL);
     light->SetLightType(LIGHT_DIRECTIONAL);
     light->SetCastShadows(true);
     light->SetCastShadows(true);
@@ -113,6 +116,10 @@ void SkeletalAnimation::CreateScene()
     
     
     // Create animated models
     // Create animated models
     const unsigned NUM_OBJECTS = 100;
     const unsigned NUM_OBJECTS = 100;
+    const float MODEL_MOVE_SPEED = 2.0f;
+    const float MODEL_ROTATE_SPEED = 100.0f;
+    const BoundingBox bounds(Vector3(-47.0f, 0.0f, -47.0f), Vector3(47.0f, 0.0f, 47.0f));
+    
     for (unsigned i = 0; i < NUM_OBJECTS; ++i)
     for (unsigned i = 0; i < NUM_OBJECTS; ++i)
     {
     {
         Node* modelNode = scene_->CreateChild("Jack");
         Node* modelNode = scene_->CreateChild("Jack");
@@ -131,6 +138,10 @@ void SkeletalAnimation::CreateScene()
         // Enable full blending weight and looping
         // Enable full blending weight and looping
         state->SetWeight(1.0f);
         state->SetWeight(1.0f);
         state->SetLooped(true);
         state->SetLooped(true);
+        
+        // Create our custom Mover component that will move & animate the model during each frame's update
+        Mover* mover = modelNode->CreateComponent<Mover>();
+        mover->SetParameters(MODEL_MOVE_SPEED, MODEL_ROTATE_SPEED, bounds);
     }
     }
     
     
     // Create the camera. Limit far clip distance to match the fog
     // Create the camera. Limit far clip distance to match the fog
@@ -149,7 +160,10 @@ void SkeletalAnimation::CreateInstructions()
     
     
     // Construct new Text object, set string to display and font to use
     // Construct new Text object, set string to display and font to use
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
-    instructionText->SetText("Use WASD keys and mouse to move\nSpace to toggle debug geometry");
+    instructionText->SetText(
+        "Use WASD keys and mouse to move\n"
+        "Space to toggle debug geometry"
+    );
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     
     
     // Position the text relative to the screen center
     // Position the text relative to the screen center
@@ -201,33 +215,6 @@ void SkeletalAnimation::MoveCamera(float timeStep)
         cameraNode_->TranslateRelative(Vector3::RIGHT * MOVE_SPEED * timeStep);
         cameraNode_->TranslateRelative(Vector3::RIGHT * MOVE_SPEED * timeStep);
 }
 }
 
 
-void SkeletalAnimation::MoveModels(float timeStep)
-{
-    // We could store all the created models into our own array, like in the Sprites example. However, we can also query the
-    // scene for all nodes with an AnimatedModel component. That is slightly slower, as each node's components have to be looped
-    // through. We know the nodes are on the root level, so we do not need to do the query as recursive
-    PODVector<Node*> modelNodes;
-    scene_->GetChildrenWithComponent<AnimatedModel>(modelNodes);
-    
-    const float MODEL_MOVE_SPEED = 2.0f;
-    const float MODEL_ROTATE_SPEED = 100.0f;
-    
-    for (unsigned i = 0; i < modelNodes.Size(); ++i)
-    {
-        Node* modelNode = modelNodes[i];
-        modelNode->TranslateRelative(Vector3::FORWARD * MODEL_MOVE_SPEED * timeStep);
-        
-        // If in risk of going outside the plane, rotate the model right
-        Vector3 pos = modelNode->GetPosition();
-        if (pos.x_ < -47.0f || pos.x_ > 47.0f || pos.z_ < -47.0f || pos.z_ > 47.0f)
-            modelNode->Yaw(timeStep * MODEL_ROTATE_SPEED);
-        
-        // Get the model's first (only) animation state and advance its time
-        AnimationState* state = modelNode->GetComponent<AnimatedModel>()->GetAnimationStates()[0];
-        state->AddTime(timeStep);
-    }
-}
-
 void SkeletalAnimation::SubscribeToEvents()
 void SkeletalAnimation::SubscribeToEvents()
 {
 {
     // Subscribes HandleUpdate() method for processing update events
     // Subscribes HandleUpdate() method for processing update events
@@ -246,9 +233,8 @@ void SkeletalAnimation::HandleUpdate(StringHash eventType, VariantMap& eventData
     // Take the frame time step, which is stored as a float
     // Take the frame time step, which is stored as a float
     float timeStep = eventData[P_TIMESTEP].GetFloat();
     float timeStep = eventData[P_TIMESTEP].GetFloat();
     
     
-    // Move the camera and the animating models, scale movement with time step
+    // Move the camera, scale movement with time step
     MoveCamera(timeStep);
     MoveCamera(timeStep);
-    MoveModels(timeStep);
     
     
     // Check for space pressed and toggle debug geometry
     // Check for space pressed and toggle debug geometry
     if (GetSubsystem<Input>()->GetKeyPress(KEY_SPACE))
     if (GetSubsystem<Input>()->GetKeyPress(KEY_SPACE))

+ 0 - 2
Source/Samples/06_SkeletalAnimation/SkeletalAnimation.h

@@ -56,8 +56,6 @@ private:
     void SetupViewport();
     void SetupViewport();
     /// Reads input and moves the camera.
     /// Reads input and moves the camera.
     void MoveCamera(float timeStep);
     void MoveCamera(float timeStep);
-    /// Moves the animated models that have been created.
-    void MoveModels(float timeStep);
     /// Subscribe to application-wide logic update events.
     /// Subscribe to application-wide logic update events.
     void SubscribeToEvents();
     void SubscribeToEvents();
     /// Callback method invoked when a logic update event is dispatched.
     /// Callback method invoked when a logic update event is dispatched.

+ 10 - 7
Source/Samples/07_Billboards/Billboards.cpp

@@ -138,8 +138,8 @@ void Billboards::CreateScene()
     }
     }
     
     
     // Create billboard sets (floating smoke)
     // Create billboard sets (floating smoke)
-    const unsigned NUM_BILLBOARDNODES = 40;
-    const unsigned NUM_BILLBOARDS = 15;
+    const unsigned NUM_BILLBOARDNODES = 25;
+    const unsigned NUM_BILLBOARDS = 10;
 
 
     for (unsigned i = 0; i < NUM_BILLBOARDNODES; ++i)
     for (unsigned i = 0; i < NUM_BILLBOARDNODES; ++i)
     {
     {
@@ -154,7 +154,7 @@ void Billboards::CreateScene()
         for (unsigned j = 0; j < NUM_BILLBOARDS; ++j)
         for (unsigned j = 0; j < NUM_BILLBOARDS; ++j)
         {
         {
             Billboard* bb = billboardObject->GetBillboard(j);
             Billboard* bb = billboardObject->GetBillboard(j);
-            bb->position_ = Vector3(Random(15.0f) - 7.5f, Random(8.0f) - 4.0f, Random(15.0f) - 7.5f);
+            bb->position_ = Vector3(Random(12.0f) - 6.0f, Random(8.0f) - 4.0f, Random(12.0f) - 6.0f);
             bb->size_ = Vector2(Random(2.0f) + 3.0f, Random(2.0f) + 3.0f);
             bb->size_ = Vector2(Random(2.0f) + 3.0f, Random(2.0f) + 3.0f);
             bb->rotation_ = Random() * 360.0f;
             bb->rotation_ = Random() * 360.0f;
             bb->enabled_ = true;
             bb->enabled_ = true;
@@ -190,9 +190,9 @@ void Billboards::CreateScene()
         light->SetShadowBias(BiasParameters(0.00002f, 0.0f));
         light->SetShadowBias(BiasParameters(0.00002f, 0.0f));
         
         
         // Configure shadow fading for the lights. When they are far away enough, the lights eventually become unshadowed for
         // Configure shadow fading for the lights. When they are far away enough, the lights eventually become unshadowed for
-        // better CPU performance
-        light->SetShadowDistance(200.0f);
-        light->SetShadowFadeDistance(150.0f);
+        // better GPU performance. Note that we could also set the maximum distance for each object to cast shadows
+        light->SetShadowFadeDistance(100.0f); // Fade start distance
+        light->SetShadowDistance(125.0f); // Fade end distance, shadows are disabled
         // Set half resolution for the shadow maps for increased performance
         // Set half resolution for the shadow maps for increased performance
         light->SetShadowResolution(0.5f);
         light->SetShadowResolution(0.5f);
         // The spot lights will not have anything near them, so move the near plane of the shadow camera farther
         // The spot lights will not have anything near them, so move the near plane of the shadow camera farther
@@ -216,7 +216,10 @@ void Billboards::CreateInstructions()
     
     
     // Construct new Text object, set string to display and font to use
     // Construct new Text object, set string to display and font to use
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
-    instructionText->SetText("Use WASD keys and mouse to move\nSpace to toggle debug geometry\n");
+    instructionText->SetText(
+        "Use WASD keys and mouse to move\n"
+        "Space to toggle debug geometry"
+    );
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     
     
     // Position the text relative to the screen center
     // Position the text relative to the screen center

+ 32 - 0
Source/Samples/08_Decals/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 08_Decals)
+
+# 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 ()

+ 327 - 0
Source/Samples/08_Decals/Decals.cpp

@@ -0,0 +1,327 @@
+//
+// 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 "CoreEvents.h"
+#include "Cursor.h"
+#include "DebugRenderer.h"
+#include "DecalSet.h"
+#include "Engine.h"
+#include "Font.h"
+#include "Graphics.h"
+#include "Input.h"
+#include "Light.h"
+#include "Material.h"
+#include "Model.h"
+#include "Octree.h"
+#include "Renderer.h"
+#include "ResourceCache.h"
+#include "StaticModel.h"
+#include "Text.h"
+#include "UI.h"
+#include "XMLFile.h"
+#include "Zone.h"
+
+#include "Decals.h"
+
+#include "DebugNew.h"
+
+// Expands to this example's entry-point
+DEFINE_APPLICATION_MAIN(Decals)
+
+Decals::Decals(Context* context) :
+    Sample(context),
+    yaw_(0.0f),
+    pitch_(0.0f),
+    drawDebug_(false)
+{
+}
+
+void Decals::Start()
+{
+    // Execute base class startup
+    Sample::Start();
+
+    // Create the scene content
+    CreateScene();
+    
+    // Create the UI content
+    CreateUI();
+    
+    // Setup the viewport for displaying the scene
+    SetupViewport();
+
+    // Hook up to the frame update and render post-update events
+    SubscribeToEvents();
+}
+
+void Decals::CreateScene()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    
+    scene_ = new Scene(context_);
+    
+    // Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
+    // (-1000, -1000, -1000) to (1000, 1000, 1000). Also create a DebugRenderer component so that we can draw
+    // debug geometry
+    scene_->CreateComponent<Octree>();
+    scene_->CreateComponent<DebugRenderer>();
+    
+    // Create scene node & StaticModel component for showing a static plane
+    Node* planeNode = scene_->CreateChild("Plane");
+    planeNode->SetScale(Vector3(100.0f, 1.0f, 100.0f));
+    StaticModel* planeObject = planeNode->CreateComponent<StaticModel>();
+    planeObject->SetModel(cache->GetResource<Model>("Models/Plane.mdl"));
+    planeObject->SetMaterial(cache->GetResource<Material>("Materials/StoneTiled.xml"));
+    
+    // Create a Zone component for ambient lighting & fog color control
+    Node* zoneNode = scene_->CreateChild("Zone");
+    Zone* zone = zoneNode->CreateComponent<Zone>();
+    zone->SetBoundingBox(BoundingBox(-1000.0f, 1000.0f));
+    zone->SetAmbientColor(Color(0.15f, 0.15f, 0.15f));
+    zone->SetFogColor(Color(0.5f, 0.5f, 0.7f));
+    zone->SetFogStart(100.0f);
+    zone->SetFogEnd(300.0f);
+    
+    // Create a directional light to the world. Enable cascaded shadows on it
+    Node* lightNode = scene_->CreateChild("Directional light");
+    lightNode->SetDirection(Vector3(0.6f, -1.0f, 0.8f));
+    Light* light = lightNode->CreateComponent<Light>();
+    light->SetLightType(LIGHT_DIRECTIONAL);
+    light->SetCastShadows(true);
+    light->SetShadowBias(BiasParameters(0.0001f, 0.5f));
+    // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+    light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
+
+    // Create some mushrooms
+    const unsigned NUM_MUSHROOMS = 240;
+    for (unsigned i = 0; i < NUM_MUSHROOMS; ++i)
+    {
+        Node* mushroomNode = scene_->CreateChild("Mushroom");
+        mushroomNode->SetPosition(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
+        mushroomNode->SetRotation(Quaternion(0.0f, Random(360.0f), 0.0f));
+        mushroomNode->SetScale(0.5f + Random(2.0f));
+        StaticModel* mushroomObject = mushroomNode->CreateComponent<StaticModel>();
+        mushroomObject->SetModel(cache->GetResource<Model>("Models/Mushroom.mdl"));
+        mushroomObject->SetMaterial(cache->GetResource<Material>("Materials/Mushroom.xml"));
+        mushroomObject->SetCastShadows(true);
+    }
+    
+    // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+    // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+    const unsigned NUM_BOXES = 20;
+    for (unsigned i = 0; i < NUM_BOXES; ++i)
+    {
+        Node* boxNode = scene_->CreateChild("Box");
+        float size = 1.0f + Random(10.0f);
+        boxNode->SetPosition(Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f));
+        boxNode->SetScale(size);
+        StaticModel* boxObject = boxNode->CreateComponent<StaticModel>();
+        boxObject->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
+        boxObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
+        boxObject->SetCastShadows(true);
+        if (size >= 5.0f)
+            boxObject->SetOccluder(true);
+    }
+    
+    // Create the camera. Limit far clip distance to match the fog
+    cameraNode_ = scene_->CreateChild("Camera");
+    Camera* camera = cameraNode_->CreateComponent<Camera>();
+    camera->SetFarClip(300.0f);
+    
+    // Set an initial position for the camera scene node above the plane
+    cameraNode_->SetPosition(Vector3(0.0f, 5.0f, 0.0f));
+}
+
+void Decals::CreateUI()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    UI* ui = GetSubsystem<UI>();
+    
+    // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+    // control the camera, and when visible, it will point the raycast target
+    XMLFile* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
+    SharedPtr<Cursor> cursor(new Cursor(context_));
+    cursor->SetStyleAuto(style);
+    ui->SetCursor(cursor);
+
+    // Set starting position of the cursor at the rendering window center
+    Graphics* graphics = GetSubsystem<Graphics>();
+    cursor->SetPosition(graphics->GetWidth() / 2, graphics->GetHeight() / 2);
+    
+    // Construct new Text object, set string to display and font to use
+    Text* instructionText = ui->GetRoot()->CreateChild<Text>();
+    instructionText->SetText(
+        "Use WASD keys to move\n"
+        "LMB = paint decal, RMB = rotate view\n"
+        "Space to toggle debug geometry\n"
+        "O to toggle occlusion culling"
+    );
+    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 Decals::SetupViewport()
+{
+    Renderer* renderer = GetSubsystem<Renderer>();
+    
+    // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+    SharedPtr<Viewport> viewport(new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>()));
+    renderer->SetViewport(0, viewport);
+}
+
+void Decals::MoveCamera(float timeStep)
+{
+    // Do not move if the UI has a focused element (the console)
+    UI* ui = GetSubsystem<UI>();
+    if (ui->GetFocusElement())
+        return;
+    
+    Input* input = GetSubsystem<Input>();
+    
+    // Movement speed as world units per second
+    const float MOVE_SPEED = 20.0f;
+    // Mouse sensitivity as degrees per pixel
+    const float MOUSE_SENSITIVITY = 0.1f;
+    
+    // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+    // Only move the camera when the cursor is hidden
+    if (!ui->GetCursor()->IsVisible())
+    {
+        IntVector2 mouseMove = input->GetMouseMove();
+        yaw_ += MOUSE_SENSITIVITY * mouseMove.x_;
+        pitch_ += MOUSE_SENSITIVITY * mouseMove.y_;
+        pitch_ = Clamp(pitch_, -90.0f, 90.0f);
+        
+        // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+        cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
+    }
+    
+    // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+    if (input->GetKeyDown('W'))
+        cameraNode_->TranslateRelative(Vector3::FORWARD * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('S'))
+        cameraNode_->TranslateRelative(Vector3::BACK * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('A'))
+        cameraNode_->TranslateRelative(Vector3::LEFT * MOVE_SPEED * timeStep);
+    if (input->GetKeyDown('D'))
+        cameraNode_->TranslateRelative(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void Decals::PaintDecal()
+{
+    UI* ui = GetSubsystem<UI>();
+    Graphics* graphics = GetSubsystem<Graphics>();
+    IntVector2 pos = ui->GetCursorPosition();
+    
+    // Make sure no UI element in front of the cursor
+    if (ui->GetElementAt(pos, true))
+        return;
+    
+    Camera* camera = cameraNode_->GetComponent<Camera>();
+    Ray cameraRay = camera->GetScreenRay((float)pos.x_ / graphics->GetWidth(), (float)pos.y_ / graphics->GetHeight());
+    // Raycast up to 250 world units distance, pick only geometry objects, not eg. zones or lights, only get the first
+    // (closest) hit
+    PODVector<RayQueryResult> results;
+    RayOctreeQuery query(results, cameraRay, RAY_TRIANGLE, 250.0f, DRAWABLE_GEOMETRY);
+    scene_->GetComponent<Octree>()->RaycastSingle(query);
+    if (results.Size())
+    {
+        RayQueryResult& result = results[0];
+        
+        // Calculate hit position in world space
+        Vector3 rayHitPos = cameraRay.origin_ + cameraRay.direction_ * result.distance_;
+        // Check if target scene node already has a DecalSet component. If not, create now
+        Node* targetNode = result.drawable_->GetNode();
+        DecalSet* decal = targetNode->GetComponent<DecalSet>();
+        if (!decal)
+        {
+            ResourceCache* cache = GetSubsystem<ResourceCache>();
+            
+            decal = targetNode->CreateComponent<DecalSet>();
+            decal->SetMaterial(cache->GetResource<Material>("Materials/UrhoDecal.xml"));
+        }
+        // Add a square decal to the decal set using the geometry of the drawable that was hit, orient it to face the camera,
+        // use full texture UV's (0,0) to (1,1). Note that if we create several decals to a large object (such as the ground
+        // plane) over a large area using just one DecalSet component, the decals will all be culled as one unit. If that is
+        // undesirable, it may be necessary to create more than one DecalSet based on the distance
+        decal->AddDecal(result.drawable_, rayHitPos, cameraNode_->GetWorldRotation(), 0.5f, 1.0f, 1.0f, Vector2::ZERO,
+            Vector2::ONE);
+    }
+}
+
+void Decals::SubscribeToEvents()
+{
+    // Subscribes HandleUpdate() method for processing update events
+    SubscribeToEvent(E_UPDATE, HANDLER(Decals, HandleUpdate));
+    
+    // Subscribes HandlePostRenderUpdate() method for processing the post-render update event, sent after Renderer subsystem is
+    // done with defining the draw calls for the viewports (but before actually executing them)
+    SubscribeToEvent(E_POSTRENDERUPDATE, HANDLER(Decals, HandlePostRenderUpdate));
+}
+
+void Decals::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // Event parameters are always defined inside a namespace corresponding to the event's name
+    using namespace Update;
+
+    // Take the frame time step, which is stored as a float
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    
+    Input* input = GetSubsystem<Input>();
+    
+    // Right mouse button controls mouse cursor visibility: hide when pressed
+    UI* ui = GetSubsystem<UI>();
+    ui->GetCursor()->SetVisible(!input->GetMouseButtonDown(MOUSEB_RIGHT));
+    
+    // Paint a decal when the left mouse button is pressed
+    if (input->GetMouseButtonPress(MOUSEB_LEFT))
+        PaintDecal();
+    
+    // Check for space pressed and toggle debug geometry
+    if (input->GetKeyPress(KEY_SPACE))
+        drawDebug_ = !drawDebug_;
+    
+    // Check for toggling of occlusion
+    if (input->GetKeyPress('O'))
+    {
+        Renderer* renderer = GetSubsystem<Renderer>();
+        // 5000 is the default amount of maximum triangles to software-rasterize for occlusion, so EORing acts as a toggle
+        renderer->SetMaxOccluderTriangles(renderer->GetMaxOccluderTriangles() ^ 5000);
+    }
+    
+    
+    // Move the camera, scale movement with time step
+    MoveCamera(timeStep);
+}
+
+void Decals::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // If draw debug mode is enabled, draw viewport debug geometry, which will show eg. drawable bounding boxes and skeleton
+    // bones. Disable depth test so that we can see the effect of occlusion
+    if (drawDebug_)
+        GetSubsystem<Renderer>()->DrawDebugGeometry(false);
+}

+ 76 - 0
Source/Samples/08_Decals/Decals.h

@@ -0,0 +1,76 @@
+//
+// 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"
+#include "Scene.h"
+
+// All Urho3D classes reside in namespace Urho3D
+using namespace Urho3D;
+
+/// Decals example.
+/// This sample demonstrates:
+///     - Performing a raycast to the octree and adding a decal to the hit location;
+///     - Marking suitable (large) objects as occluders for occlusion culling;
+///     - Displaying renderer debug geometry to see the effect of occlusion;
+class Decals : public Sample
+{
+    // Mandatory when deriving from Object, enables type information
+    OBJECT(Decals)
+
+public:
+    /// Construct.
+    Decals(Context* context);
+
+    /// Setup after engine initialization and before running the main loop.
+    virtual void Start();
+
+private:
+    /// Constructs the scene content.
+    void CreateScene();
+    /// Constructs user interface elements.
+    void CreateUI();
+    /// Sets up a viewport for displaying the scene.
+    void SetupViewport();
+    /// Reads input and moves the camera.
+    void MoveCamera(float timeStep);
+    /// Paint a decal using a ray cast from the mouse cursor.
+    void PaintDecal();
+    /// Subscribe to application-wide logic update events.
+    void SubscribeToEvents();
+    /// Callback method invoked when a logic update event is dispatched.
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+    /// Callback method invoked when the post-render update event is dispatched.
+    void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
+    
+    /// Scene.
+    SharedPtr<Scene> scene_;
+    /// Camera scene node.
+    SharedPtr<Node> cameraNode_;
+    /// Camera yaw angle.
+    float yaw_;
+    /// Camera pitch angle.
+    float pitch_;
+    /// Flag for drawing debug geometry.
+    bool drawDebug_;
+};

+ 2 - 0
Source/Samples/CMakeLists.txt

@@ -44,3 +44,5 @@ add_subdirectory (04_StaticScene)
 add_subdirectory (05_AnimatingScene)
 add_subdirectory (05_AnimatingScene)
 add_subdirectory (06_SkeletalAnimation)
 add_subdirectory (06_SkeletalAnimation)
 add_subdirectory (07_Billboards)
 add_subdirectory (07_Billboards)
+add_subdirectory (08_Decals)
+