Browse Source

Wrap decal around static geometry.
Changed OcclusionBuffer clipping to produce less degenerate triangles.
Exposed more Frustum functions to script.
Moved StaticModel software LOD (raycast / occlusion) determination into a function.

Lasse Öörni 13 years ago
parent
commit
06371a62ed

+ 2 - 2
Bin/Data/Scripts/TestScene.as

@@ -464,7 +464,7 @@ void HandlePostRenderUpdate()
             Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
             testScene.debugRenderer.AddBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
                 Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
-                
+
             if (input.keyPress['H'])
             {
                 DecalSet@ decal = result.drawable.node.GetComponent("DecalSet");
@@ -473,7 +473,7 @@ void HandlePostRenderUpdate()
                     decal = result.drawable.node.CreateComponent("DecalSet");
                     decal.material = cache.GetResource("Material", "Materials/Mushroom.xml");
                 }
-                decal.AddDecal(result.drawable, rayHitPos, cameraNode.worldRotation, BoundingBox(Vector3(-0.1, -0.1, -0.1), Vector3(0.1, 0.1, 0.1)), 0.001f, Vector2(0, 0), Vector2(1, 1), 2.0);
+                decal.AddDecal(result.drawable, rayHitPos - cameraNode.worldRotation * Vector3(0, 0, 0.1), cameraNode.worldRotation, 0.1, 1.0, 0.2, Vector2(0, 0), Vector2(1, 1));
             }
         }
     }

+ 5 - 1
Docs/ScriptAPI.dox

@@ -531,6 +531,10 @@ Properties:<br>
 Frustum
 
 Methods:<br>
+- void Define(float, float, float, float, float, const Matrix3x4&)
+- void Define(const Vector3&, const Vector3&, const Matrix3x4&)
+- void Define(const BoundingBox&, const Matrix3x4&)
+- void DefineOrtho(float, float, float, float, float, const Matrix3x4&)
 - void Transform(const Matrix3&)
 - void Transform(const Matrix3x4&)
 - Intersection IsInside(const Vector3&)
@@ -2289,7 +2293,7 @@ Methods:<br>
 - void Remove()
 - void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
-- bool AddDecal(Drawable@, const Vector3&, const Quaternion&, const BoundingBox&, float, const Vector2&, const Vector2&, float)
+- bool AddDecal(Drawable@, const Vector3&, const Quaternion&, float, float, float, const Vector2&, const Vector2&, float arg8 = 0.0, float arg9 = 0.25, float arg10 = 0.0001)
 - void RemoveDecals(uint)
 - void RemoveAllDecals()
 

+ 1 - 1
Engine/Engine/GraphicsAPI.cpp

@@ -758,7 +758,7 @@ static void RegisterParticleEmitter(asIScriptEngine* engine)
 static void RegisterDecalSet(asIScriptEngine* engine)
 {
     RegisterDrawable<DecalSet>(engine, "DecalSet");
-    engine->RegisterObjectMethod("DecalSet", "bool AddDecal(Drawable@+, const Vector3&in, const Quaternion&in, const BoundingBox&in, float, const Vector2&in, const Vector2&in, float)", asMETHOD(DecalSet, AddDecal), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DecalSet", "bool AddDecal(Drawable@+, const Vector3&in, const Quaternion&in, float, float, float, const Vector2&in, const Vector2&in, float timeToLive = 0.0, float normalCutoff = 0.25, float depthBias = 0.0001)", asMETHOD(DecalSet, AddDecal), asCALL_THISCALL);
     engine->RegisterObjectMethod("DecalSet", "void RemoveDecals(uint)", asMETHOD(DecalSet, RemoveDecals), asCALL_THISCALL);
     engine->RegisterObjectMethod("DecalSet", "void RemoveAllDecals()", asMETHOD(DecalSet, RemoveAllDecals), asCALL_THISCALL);
     engine->RegisterObjectMethod("DecalSet", "void set_material(Material@+)", asMETHOD(DecalSet, SetMaterial), asCALL_THISCALL);

+ 4 - 0
Engine/Engine/MathAPI.cpp

@@ -889,6 +889,10 @@ static void RegisterVolumes(asIScriptEngine* engine)
     engine->RegisterObjectBehaviour("Frustum", asBEHAVE_CONSTRUCT, "void f(const Frustum&in)", asFUNCTION(ConstructFrustumCopy), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("Frustum", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructFrustum), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Frustum", "Frustum& opAssign(const Frustum&in)", asMETHODPR(Frustum, operator =, (const Frustum&), Frustum&), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Frustum", "void Define(float, float, float, float, float, const Matrix3x4&in)", asMETHODPR(Frustum, Define, (float, float, float, float, float, const Matrix3x4&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Frustum", "void Define(const Vector3&in, const Vector3&in, const Matrix3x4&in)", asMETHODPR(Frustum, Define, (const Vector3&, const Vector3&, const Matrix3x4&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Frustum", "void Define(const BoundingBox&in, const Matrix3x4&in)", asMETHODPR(Frustum, Define, (const BoundingBox&, const Matrix3x4&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Frustum", "void DefineOrtho(float, float, float, float, float, const Matrix3x4&in)", asMETHOD(Frustum, DefineOrtho), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "void Transform(const Matrix3&in)", asMETHODPR(Frustum, Transform, (const Matrix3&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "void Transform(const Matrix3x4&in)", asMETHODPR(Frustum, Transform, (const Matrix3x4&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "Intersection IsInside(const Vector3&in)", asMETHODPR(Frustum, IsInside, (const Vector3&) const, Intersection), asCALL_THISCALL);

+ 1 - 1
Engine/Graphics/AnimatedModel.cpp

@@ -229,7 +229,7 @@ void AnimatedModel::Update(const FrameInfo& frame)
 void AnimatedModel::UpdateBatches(const FrameInfo& frame)
 {
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
-    distance_ = frame.camera_->GetDistance(worldTransform.Translation());
+    distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
     
     // Note: per-geometry distances do not take skinning into account
     if (batches_.Size() > 1)

+ 1 - 1
Engine/Graphics/BillboardSet.cpp

@@ -119,7 +119,7 @@ void BillboardSet::UpdateBatches(const FrameInfo& frame)
         }
     }
     
-    distance_ = frame.camera_->GetDistance(worldPos);
+    distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
     
     // Calculate scaled distance for animation LOD
     float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);

+ 7 - 7
Engine/Graphics/DebugRenderer.cpp

@@ -289,7 +289,7 @@ void DebugRenderer::AddTriangleMesh(const void* vertexData, unsigned vertexSize,
     if (!depthTest)
         dest = &noDepthLines_;
     
-    const unsigned char* vertexDataChar = (const unsigned char*)vertexData;
+    const unsigned char* srcData = (const unsigned char*)vertexData;
     unsigned oldSize = dest->Size();
     dest->Resize(oldSize + indexCount);
     DebugLine* destLines = &dest->At(oldSize);
@@ -302,9 +302,9 @@ void DebugRenderer::AddTriangleMesh(const void* vertexData, unsigned vertexSize,
         
         while (indices < indicesEnd)
         {
-            const Vector3& v0 = *((const Vector3*)(&vertexDataChar[indices[0] * vertexSize]));
-            const Vector3& v1 = *((const Vector3*)(&vertexDataChar[indices[1] * vertexSize]));
-            const Vector3& v2 = *((const Vector3*)(&vertexDataChar[indices[2] * vertexSize]));
+            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
             
             destLines[0].start_ = transform * v0;
             destLines[0].end_ = transform * v1;
@@ -329,9 +329,9 @@ void DebugRenderer::AddTriangleMesh(const void* vertexData, unsigned vertexSize,
         
         while (indices < indicesEnd)
         {
-            const Vector3& v0 = *((const Vector3*)(&vertexDataChar[indices[0] * vertexSize]));
-            const Vector3& v1 = *((const Vector3*)(&vertexDataChar[indices[1] * vertexSize]));
-            const Vector3& v2 = *((const Vector3*)(&vertexDataChar[indices[2] * vertexSize]));
+            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
             
             destLines[0].start_ = transform * v0;
             destLines[0].end_ = transform * v1;

+ 269 - 18
Engine/Graphics/DecalSet.cpp

@@ -33,12 +33,25 @@
 #include "Profiler.h"
 #include "Scene.h"
 #include "SceneEvents.h"
+#include "StaticModel.h"
 #include "VertexBuffer.h"
 
 #include "DebugNew.h"
 
 static const Vector3 DOT_SCALE(1 / 3.0f, 1 / 3.0f, 1 / 3.0f);
-static const unsigned DEFAULT_MAX_VERTICES = 256;
+static const unsigned DEFAULT_MAX_VERTICES = 1024;
+
+inline Vector3 ClipEdge(const Vector3& v0, const Vector3& v1, float d0, float d1)
+{
+    float t = d0 / (d0 - d1);
+    return v0 + t * (v1 - v0);
+}
+
+inline Vector2 ClipEdge(const Vector2& v0, const Vector2& v1, float d0, float d1)
+{
+    float t = d0 / (d0 - d1);
+    return v0 + t * (v1 - v0);
+}
 
 void Decal::CalculateBoundingBox()
 {
@@ -86,7 +99,7 @@ void DecalSet::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryRe
 void DecalSet::UpdateBatches(const FrameInfo& frame)
 {
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
-    distance_ = frame.camera_->GetDistance(worldTransform.Translation());
+    distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
     
     float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
     lodDistance_ = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
@@ -130,44 +143,84 @@ void DecalSet::SetMaxVertices(unsigned num)
     }
 }
 
-bool DecalSet::AddDecal(Drawable* target, const Vector3& worldPosition, const Quaternion& worldRotation, const BoundingBox& size, float depthBias, const Vector2& topLeftUV, const Vector2& bottomRightUV, float timeToLive)
+bool DecalSet::AddDecal(Drawable* target, const Vector3& worldPosition, const Quaternion& worldRotation, float size, float aspectRatio, float depth, const Vector2& topLeftUV, const Vector2& bottomRightUV, float timeToLive, float normalCutoff, float depthBias)
 {
     PROFILE(AddDecal);
     
     if (!node_)
         return false;
-        
-    if (!target)
+    
+    if (!target || !target->GetNode())
     {
         LOGERROR("Null target drawable for decal");
         return false;
     }
     
-    Matrix3x4 worldToLocal = node_->GetWorldTransform().Inverse();
+    Matrix3x4 targetTransform = target->GetNode()->GetWorldTransform().Inverse();
+    
+    // Build the decal frustum
+    Frustum decalFrustum;
+    Matrix3x4 frustumTransform = targetTransform * Matrix3x4(worldPosition, worldRotation, 1.0f);
+    decalFrustum.DefineOrtho(size, aspectRatio, 1.0, 0.0f, depth, frustumTransform);
     
-    Vector3 topLeft = worldToLocal * (worldPosition + worldRotation * (Vector3::UP * size.max_.y_ + Vector3::RIGHT * size.min_.x_));
-    Vector3 topRight = worldToLocal * (worldPosition + worldRotation * (Vector3::UP * size.max_.y_ + Vector3::RIGHT * size.max_.x_));
-    Vector3 bottomLeft = worldToLocal * (worldPosition + worldRotation * (Vector3::UP * size.min_.y_ + Vector3::RIGHT * size.min_.x_));
-    Vector3 bottomRight = worldToLocal * (worldPosition + worldRotation * (Vector3::UP * size.min_.y_ + Vector3::RIGHT * size.max_.x_));
+    Vector3 biasVector = targetTransform * Vector4(worldRotation * (Vector3::BACK * depthBias), 0.0f);
+    Vector3 decalNormal = (targetTransform * Vector4(worldRotation * Vector3::BACK, 0.0f)).Normalized();
     
     decals_.Resize(decals_.Size() + 1);
     Decal& newDecal = decals_.Back();
     newDecal.timeToLive_ = timeToLive;
     
-    /// \todo Wrap decal around target geometry, use depth bias parameter
-    newDecal.vertices_.Push(DecalVertex(topLeft, Vector3::UP, topLeftUV));
-    newDecal.vertices_.Push(DecalVertex(topRight, Vector3::UP, Vector2(bottomRightUV.x_, topLeftUV.y_)));
-    newDecal.vertices_.Push(DecalVertex(bottomLeft, Vector3::UP, Vector2(topLeftUV.x_, bottomRightUV.y_)));
-    newDecal.vertices_.Push(DecalVertex(topRight, Vector3::UP, Vector2(bottomRightUV.x_, topLeftUV.y_)));
-    newDecal.vertices_.Push(DecalVertex(bottomRight, Vector3::UP, bottomRightUV));
-    newDecal.vertices_.Push(DecalVertex(bottomLeft, Vector3::UP, Vector2(topLeftUV.x_, bottomRightUV.y_)));
-    newDecal.CalculateBoundingBox();
+    // Special handling for static models, as they may use LOD: use the fixed software LOD level for decals
+    StaticModel* staticModel = dynamic_cast<StaticModel*>(target);
+    if (staticModel)
+    {
+        for (unsigned i = 0; i < staticModel->GetNumGeometries(); ++i)
+            AddVertices(newDecal, decalFrustum.planes_[PLANE_FAR], staticModel->GetSoftwareGeometry(i), decalNormal, normalCutoff);
+    }
+    else
+    {
+        // Else use the Drawable API to find out the source geometries
+        const Vector<SourceBatch>& batches = target->GetBatches();
+        for (unsigned i = 0; i < batches.Size(); ++i)
+            AddVertices(newDecal, decalFrustum.planes_[PLANE_FAR], batches[i].geometry_, decalNormal, normalCutoff);
+    }
+    
+    // Clip the acquired vertices with rest of the decal frustum planes
+    PODVector<DecalVertex> tempVertices;
+    for (unsigned i = PLANE_NEAR; i < PLANE_FAR; ++i)
+        ClipVertices(newDecal, decalFrustum.planes_[i], tempVertices);
+    
+    // Check if resulted in no triangles
+    if (newDecal.vertices_.Empty())
+    {
+        decals_.Pop();
+        return true;
+    }
+    
+    if (newDecal.vertices_.Size() > maxVertices_)
+    {
+        LOGWARNING("Can not add decal, vertex count " + String(newDecal.vertices_.Size()) + " exceeds maximum " +
+            String(maxVertices_));
+        decals_.Pop();
+        return false;
+    }
     
+    newDecal.vertices_.Compact();
     numVertices_ += newDecal.vertices_.Size();
+    
+    // Finally transform vertices to the DecalSet's local space
+    Matrix3x4 decalTransform = node_->GetWorldTransform().Inverse() * target->GetNode()->GetWorldTransform();
+    CalculateUVs(newDecal, frustumTransform.Inverse(), size, aspectRatio, depth, topLeftUV, bottomRightUV);
+    TransformVertices(newDecal, decalTransform, biasVector);
+    
+    newDecal.CalculateBoundingBox();
+    
     // Remove oldest decals if total vertices exceeded
     while (decals_.Size() && numVertices_ > maxVertices_)
         RemoveDecals(1);
     
+    LOGINFO("Added decal with " + String(newDecal.vertices_.Size()) + " vertices");
+    
     MarkDecalsDirty();
     return true;
 }
@@ -216,6 +269,204 @@ void DecalSet::OnWorldBoundingBoxUpdate()
     worldBoundingBox_ = boundingBox_.Transformed(node_->GetWorldTransform());
 }
 
+void DecalSet::AddVertices(Decal& decal, const Plane& plane, Geometry* geometry, const Vector3& decalNormal, float normalCutoff)
+{
+    if (!geometry)
+        return;
+    
+    const unsigned char* vertexData;
+    unsigned vertexSize;
+    const unsigned char* indexData;
+    unsigned indexSize;
+    
+    geometry->GetRawData(vertexData, vertexSize, indexData, indexSize);
+    if (!vertexData || !indexData)
+    {
+        LOGWARNING("Can not add decal, object does not have CPU-side geometry data");
+        return;
+    }
+    
+    unsigned indexStart = geometry->GetIndexStart();
+    unsigned indexCount = geometry->GetIndexCount();
+    bool hasNormals = vertexSize >= 6 * sizeof(float);
+    
+    const unsigned char* srcData = (const unsigned char*)vertexData;
+    
+    // 16-bit indices
+    if (indexSize == sizeof(unsigned short))
+    {
+        const unsigned short* indices = ((const unsigned short*)indexData) + indexStart;
+        const unsigned short* indicesEnd = indices + indexCount;
+        
+        while (indices < indicesEnd)
+        {
+            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
+            const Vector3& n0 = hasNormals ? *((const Vector3*)(&srcData[indices[0] * vertexSize + 3 * sizeof(float)])) : Vector3::ZERO;
+            const Vector3& n1 = hasNormals ? *((const Vector3*)(&srcData[indices[1] * vertexSize + 3 * sizeof(float)])) : Vector3::ZERO;
+            const Vector3& n2 = hasNormals ? *((const Vector3*)(&srcData[indices[2] * vertexSize + 3 * sizeof(float)])) : Vector3::ZERO;
+            
+            if (!hasNormals || decalNormal.DotProduct((n0 + n1 + n2) / 3.0f) >= normalCutoff)
+                AddTriangle(decal.vertices_, plane, v0, v1, v2, n0, n1, n2);
+            
+            indices += 3;
+        }
+    }
+    // 32-bit indices
+    else
+    {
+        const unsigned* indices = ((const unsigned*)indexData) + indexStart;
+        const unsigned* indicesEnd = indices + indexCount;
+        
+        while (indices < indicesEnd)
+        {
+            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
+            const Vector3& n0 = hasNormals ? *((const Vector3*)(&srcData[indices[0] * vertexSize + 3 * sizeof(float)])) : Vector3::ZERO;
+            const Vector3& n1 = hasNormals ? *((const Vector3*)(&srcData[indices[1] * vertexSize + 3 * sizeof(float)])) : Vector3::ZERO;
+            const Vector3& n2 = hasNormals ? *((const Vector3*)(&srcData[indices[2] * vertexSize + 3 * sizeof(float)])) : Vector3::ZERO;
+            
+            if (!hasNormals || decalNormal.DotProduct((n0 + n1 + n2) / 3.0f) >= normalCutoff)
+                AddTriangle(decal.vertices_, plane, v0, v1, v2, n0, n1, n2);
+            
+            indices += 3;
+        }
+    }
+}
+
+void DecalSet::AddTriangle(PODVector<DecalVertex>& dest, const Plane& plane, Vector3 v0, Vector3 v1, Vector3 v2, Vector3 n0, Vector3 n1, Vector3 n2)
+{
+    /// \todo This should be optimized by turning the triangles into polyhedrons during clipping and finally triangulating them
+    float d0 = plane.Distance(v0);
+    float d1 = plane.Distance(v1);
+    float d2 = plane.Distance(v2);
+    
+    // Reject triangle if all vertices behind the plane
+    if (d0 < 0.0f && d1 < 0.0f && d2 < 0.0f)
+        return;
+    // If 2 vertices behind the plane, create a new triangle in-place
+    else if (d0 < 0.0f && d1 < 0.0f)
+    {
+        v0 = ClipEdge(v0, v2, d0, d2);
+        v1 = ClipEdge(v1, v2, d1, d2);
+        n0 = ClipEdge(n0, n2, d0, d2);
+        n1 = ClipEdge(n1, n2, d1, d2);
+    }
+    else if (d0 < 0.0f && d2 < 0.0f)
+    {
+        v0 = ClipEdge(v0, v1, d0, d1);
+        v2 = ClipEdge(v2, v1, d2, d1);
+        n0 = ClipEdge(n0, n1, d0, d1);
+        n2 = ClipEdge(n2, n1, d2, d1);
+    }
+    else if (d1 < 0.0f && d2 < 0.0f)
+    {
+        v1 = ClipEdge(v1, v0, d1, d0);
+        v2 = ClipEdge(v2, v0, d2, d0);
+        n1 = ClipEdge(n1, n0, d1, d0);
+        n2 = ClipEdge(n2, n0, d2, d0);
+    }
+    // 1 vertex behind the plane: create one new triangle, and modify one in-place
+    else if (d0 < 0.0f)
+    {
+        Vector3 v3, v4, v5, n3, n4, n5;
+        v3 = ClipEdge(v0, v2, d0, d2);
+        v4 = v0 = ClipEdge(v0, v1, d0, d1);
+        v5 = v2;
+        n3 = ClipEdge(n0, n2, d0, d2);
+        n4 = n0 = ClipEdge(n0, n1, d0, d1);
+        n5 = n2;
+        
+        dest.Push(DecalVertex(v3, n3, Vector2::ZERO));
+        dest.Push(DecalVertex(v4, n4, Vector2::ZERO));
+        dest.Push(DecalVertex(v5, n5, Vector2::ZERO));
+    }
+    else if (d1 < 0.0f)
+    {
+        Vector3 v3, v4, v5, n3, n4, n5;
+        v4 = ClipEdge(v1, v0, d1, d0);
+        v5 = v1 = ClipEdge(v1, v2, d1, d2);
+        v3 = v0;
+        n4 = ClipEdge(n1, n0, d1, d0);
+        n5 = n1 = ClipEdge(n1, n2, d1, d2);
+        n3 = n0;
+        
+        dest.Push(DecalVertex(v3, n3, Vector2::ZERO));
+        dest.Push(DecalVertex(v4, n4, Vector2::ZERO));
+        dest.Push(DecalVertex(v5, n5, Vector2::ZERO));
+    }
+    else if (d2 < 0.0f)
+    {
+        Vector3 v3, v4, v5, n3, n4, n5;
+        v5 = ClipEdge(v2, v1, d2, d1);
+        v3 = v2 = ClipEdge(v2, v0, d2, d0);
+        v4 = v1;
+        n5 = ClipEdge(n2, n1, d2, d1);
+        n3 = n2 = ClipEdge(n2, n0, d2, d0);
+        n4 = n1;
+        
+        dest.Push(DecalVertex(v3, n3, Vector2::ZERO));
+        dest.Push(DecalVertex(v4, n4, Vector2::ZERO));
+        dest.Push(DecalVertex(v5, n5, Vector2::ZERO));
+    }
+    
+    dest.Push(DecalVertex(v0, n0, Vector2::ZERO));
+    dest.Push(DecalVertex(v1, n1, Vector2::ZERO));
+    dest.Push(DecalVertex(v2, n2, Vector2::ZERO));
+}
+
+void DecalSet::ClipVertices(Decal& decal, const Plane& plane, PODVector<DecalVertex>& tempVertices)
+{
+    tempVertices.Clear();
+    
+    for (unsigned i = 0; i < decal.vertices_.Size(); i += 3)
+    {
+        AddTriangle(tempVertices, plane, decal.vertices_[i].position_, decal.vertices_[i + 1].position_,
+            decal.vertices_[i + 2].position_, decal.vertices_[i].normal_, decal.vertices_[i + 1].normal_,
+            decal.vertices_[i + 2].normal_);
+    }
+    
+    decal.vertices_ = tempVertices;
+}
+
+void DecalSet::CalculateUVs(Decal& decal, const Matrix3x4& view, float size, float aspectRatio, float depth, const Vector2& topLeftUV, const Vector2& bottomRightUV)
+{
+    Matrix4 projection(Matrix4::ZERO);
+    
+    float h = (1.0f / (size * 0.5f));
+    float w = h / aspectRatio;
+    float q = 1.0f / depth;
+    float r = 0.0f;
+    
+    projection.m00_ = w;
+    projection.m11_ = h;
+    projection.m22_ = q;
+    projection.m23_ = r;
+    projection.m33_ = 1.0f;
+    
+    Matrix4 viewProj = projection * view;
+    
+    for (PODVector<DecalVertex>::Iterator i = decal.vertices_.Begin(); i != decal.vertices_.End(); ++i)
+    {
+        Vector3 projected = viewProj * i->position_;
+        i->texCoord_ = Vector2(
+            Lerp(topLeftUV.x_, bottomRightUV.x_, projected.x_ * 0.5f + 0.5f),
+            Lerp(bottomRightUV.y_, topLeftUV.y_, projected.y_ * 0.5f + 0.5f)
+        );
+    }
+}
+
+void DecalSet::TransformVertices(Decal& decal, const Matrix3x4& transform, const Vector3& biasVector)
+{
+    for (PODVector<DecalVertex>::Iterator i = decal.vertices_.Begin(); i != decal.vertices_.End(); ++i)
+    {
+        i->position_ = transform * (i->position_ + biasVector);
+        i->normal_ = (transform * i->normal_).Normalized();
+    }
+}
+
 List<Decal>::Iterator DecalSet::RemoveDecal(List<Decal>::Iterator i)
 {
     numVertices_ -= i->vertices_.Size();

+ 11 - 1
Engine/Graphics/DecalSet.h

@@ -99,7 +99,7 @@ class DecalSet : public Drawable
     /// %Set maximum number of decal vertices.
     void SetMaxVertices(unsigned num);
     /// Add a decal at world coordinates, using an existing drawable's geometry for reference. Return true if successful.
-    bool AddDecal(Drawable* target, const Vector3& worldPosition, const Quaternion& worldRotation, const BoundingBox& size, float depthBias, const Vector2& topLeftUV, const Vector2& bottomRightUV, float timeToLive);
+    bool AddDecal(Drawable* target, const Vector3& worldPosition, const Quaternion& worldRotation, float size, float aspectRatio, float depth, const Vector2& topLeftUV, const Vector2& bottomRightUV, float timeToLive = 0.0f, float normalCutoff = 0.25f, float depthBias = 0.0001f);
     /// Remove n oldest decals.
     void RemoveDecals(unsigned num);
     /// Remove all decals.
@@ -121,6 +121,16 @@ protected:
     virtual void OnWorldBoundingBoxUpdate();
     
 private:
+    /// Add vertices to decal from a geometry and clip with the initial plane.
+    void AddVertices(Decal& decal, const Plane& plane, Geometry* geometry, const Vector3& decalNormal, float normalCutoff);
+    /// Clip and add one triangle with a plane.
+    void AddTriangle(PODVector<DecalVertex>& dest, const Plane& plane, Vector3 v0, Vector3 v1, Vector3 v2, Vector3 n0, Vector3 n1, Vector3 n2);
+    /// Clip decal's vertices with a plane.
+    void ClipVertices(Decal& decal, const Plane& plane, PODVector<DecalVertex>& tempVertices);
+    /// Calculate UV coordinates for the decal.
+    void CalculateUVs(Decal& decal, const Matrix3x4& view, float size, float aspectRatio, float depth, const Vector2& topLeftUV, const Vector2& bottomRightUV);
+    /// Transform decal's vertices from the target geometry to the decal set local space.
+    void TransformVertices(Decal& decal, const Matrix3x4& transform, const Vector3& biasVector);
     /// Remove a decal by iterator and return iterator to the next decal.
     List<Decal>::Iterator RemoveDecal(List<Decal>::Iterator i);
     /// Mark decals and the bounding box dirty.

+ 18 - 18
Engine/Graphics/OcclusionBuffer.cpp

@@ -151,7 +151,7 @@ void OcclusionBuffer::Clear()
 bool OcclusionBuffer::Draw(const Matrix3x4& model, const void* vertexData, unsigned vertexSize, const void* indexData,
     unsigned indexSize, unsigned indexStart, unsigned indexCount)
 {
-    const unsigned char* vertexDataChar = (const unsigned char*)vertexData;
+    const unsigned char* srcData = (const unsigned char*)vertexData;
     
     Matrix4 modelViewProj = viewProj_ * model;
     depthHierarchyDirty_ = true;
@@ -170,9 +170,9 @@ bool OcclusionBuffer::Draw(const Matrix3x4& model, const void* vertexData, unsig
             if (numTriangles_ >= maxTriangles_)
                 return false;
             
-            const Vector3& v0 = *((const Vector3*)(&vertexDataChar[indices[0] * vertexSize]));
-            const Vector3& v1 = *((const Vector3*)(&vertexDataChar[indices[1] * vertexSize]));
-            const Vector3& v2 = *((const Vector3*)(&vertexDataChar[indices[2] * vertexSize]));
+            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
             
             vertices[0] = ModelTransform(modelViewProj, v0);
             vertices[1] = ModelTransform(modelViewProj, v1);
@@ -192,9 +192,9 @@ bool OcclusionBuffer::Draw(const Matrix3x4& model, const void* vertexData, unsig
             if (numTriangles_ >= maxTriangles_)
                 return false;
             
-            const Vector3& v0 = *((const Vector3*)(&vertexDataChar[indices[0] * vertexSize]));
-            const Vector3& v1 = *((const Vector3*)(&vertexDataChar[indices[1] * vertexSize]));
-            const Vector3& v2 = *((const Vector3*)(&vertexDataChar[indices[2] * vertexSize]));
+            const Vector3& v0 = *((const Vector3*)(&srcData[indices[0] * vertexSize]));
+            const Vector3& v1 = *((const Vector3*)(&srcData[indices[1] * vertexSize]));
+            const Vector3& v2 = *((const Vector3*)(&srcData[indices[2] * vertexSize]));
             
             vertices[0] = ModelTransform(modelViewProj, v0);
             vertices[1] = ModelTransform(modelViewProj, v1);
@@ -617,20 +617,20 @@ void OcclusionBuffer::ClipVertices(const Vector4& plane, Vector4* vertices, bool
                 unsigned newIdx = numTriangles * 3;
                 triangles[numTriangles] = true;
                 ++numTriangles;
-                    
-                vertices[newIdx] = ClipEdge(vertices[index], vertices[index + 1], d0, d1);
-                vertices[newIdx + 1] = vertices[index + 1];
-                vertices[newIdx + 2] = vertices[index] = ClipEdge(vertices[index], vertices[index + 2], d0, d2);
+                
+                vertices[newIdx] = ClipEdge(vertices[index], vertices[index + 2], d0, d2);
+                vertices[newIdx + 1] = vertices[index] = ClipEdge(vertices[index], vertices[index + 1], d0, d1);
+                vertices[newIdx + 2] = vertices[index + 2];
             }
             else if (d1 < 0.0f)
             {
                 unsigned newIdx = numTriangles * 3;
                 triangles[numTriangles] = true;
                 ++numTriangles;
-                    
-                vertices[newIdx + 1] = ClipEdge(vertices[index + 1], vertices[index + 2], d1, d2);
-                vertices[newIdx + 2] = vertices[index + 2];
-                vertices[newIdx] = vertices[index + 1] = ClipEdge(vertices[index + 1], vertices[index], d1, d0);
+                
+                vertices[newIdx + 1] = ClipEdge(vertices[index + 1], vertices[index], d1, d0);
+                vertices[newIdx + 2] = vertices[index + 1] = ClipEdge(vertices[index + 1], vertices[index + 2], d1, d2);
+                vertices[newIdx] = vertices[index];
             }
             else if (d2 < 0.0f)
             {
@@ -638,9 +638,9 @@ void OcclusionBuffer::ClipVertices(const Vector4& plane, Vector4* vertices, bool
                 triangles[numTriangles] = true;
                 ++numTriangles;
                 
-                vertices[newIdx + 2] = ClipEdge(vertices[index + 2], vertices[index], d2, d0);
-                vertices[newIdx] = vertices[index];
-                vertices[newIdx + 1] = vertices[index + 2] = ClipEdge(vertices[index + 2], vertices[index + 1], d2, d1);
+                vertices[newIdx + 2] = ClipEdge(vertices[index + 2], vertices[index + 1], d2, d1);
+                vertices[newIdx] = vertices[index + 2] = ClipEdge(vertices[index + 2], vertices[index], d2, d0);
+                vertices[newIdx + 1] = vertices[index + 1];
             }
         }
     }

+ 19 - 25
Engine/Graphics/StaticModel.cpp

@@ -110,14 +110,7 @@ void StaticModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQuer
                 // Then the actual test using triangle geometry
                 for (unsigned i = 0; i < geometries_.Size(); ++i)
                 {
-                    unsigned lodLevel;
-                    // Check whether to use same LOD as visible, or a specific LOD
-                    if (softwareLodLevel_ == M_MAX_UNSIGNED)
-                        lodLevel = geometryData_[i].lodLevel_;
-                    else
-                        lodLevel = Clamp(softwareLodLevel_, 0, geometries_[i].Size());
-                    
-                    Geometry* geom = geometries_[i][lodLevel];
+                    Geometry* geom = GetSoftwareGeometry(i);
                     if (geom)
                     {
                         distance = geom->GetDistance(localRay);
@@ -142,7 +135,7 @@ void StaticModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQuer
 void StaticModel::UpdateBatches(const FrameInfo& frame)
 {
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
-    distance_ = frame.camera_->GetDistance(worldTransform.Translation());
+    distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
     
     if (batches_.Size() > 1)
     {
@@ -174,14 +167,7 @@ unsigned StaticModel::GetNumOccluderTriangles()
     
     for (unsigned i = 0; i < batches_.Size(); ++i)
     {
-        unsigned lodLevel;
-        // Check whether to use same LOD as visible, or a specific LOD
-        if (softwareLodLevel_ == M_MAX_UNSIGNED)
-            lodLevel = geometryData_[i].lodLevel_;
-        else
-            lodLevel = Clamp(softwareLodLevel_, 0, geometries_[i].Size());
-        
-        Geometry* geom = geometries_[i][lodLevel];
+        Geometry* geom = GetSoftwareGeometry(i);
         if (!geom)
             continue;
         
@@ -202,14 +188,7 @@ bool StaticModel::DrawOcclusion(OcclusionBuffer* buffer)
     
     for (unsigned i = 0; i < batches_.Size(); ++i)
     {
-        unsigned lodLevel;
-        // Check whether to use same LOD as visible, or a specific LOD
-        if (softwareLodLevel_ == M_MAX_UNSIGNED)
-            lodLevel = geometryData_[i].lodLevel_;
-        else
-            lodLevel = Clamp(softwareLodLevel_, 0, geometries_[i].Size());
-        
-        Geometry* geom = geometries_[i][lodLevel];
+        Geometry* geom = GetSoftwareGeometry(i);
         if (!geom)
             continue;
         
@@ -308,6 +287,21 @@ Material* StaticModel::GetMaterial(unsigned index) const
     return index < batches_.Size() ? batches_[index].material_ : (Material*)0;
 }
 
+Geometry* StaticModel::GetSoftwareGeometry(unsigned index) const
+{
+    if (index >= geometries_.Size())
+        return 0;
+    
+    unsigned lodLevel;
+    // Check whether to use same LOD as visible, or a specific LOD
+    if (softwareLodLevel_ == M_MAX_UNSIGNED)
+        lodLevel = geometryData_[index].lodLevel_;
+    else
+        lodLevel = Clamp(softwareLodLevel_, 0, geometries_[index].Size());
+    
+    return geometries_[index][lodLevel];
+}
+
 void StaticModel::SetBoundingBox(const BoundingBox& box)
 {
     boundingBox_ = box;

+ 2 - 0
Engine/Graphics/StaticModel.h

@@ -77,6 +77,8 @@ public:
     Material* GetMaterial(unsigned index) const;
     /// Return software LOD level.
     unsigned GetSoftwareLodLevel() const { return softwareLodLevel_; }
+    /// Return geometry to use in software rendering or raycasts.
+    Geometry* GetSoftwareGeometry(unsigned index) const;
     
     /// %Set model attribute.
     void SetModelAttr(ResourceRef value);

+ 14 - 0
Engine/Math/Frustum.cpp

@@ -104,6 +104,20 @@ void Frustum::Define(const Vector3& near, const Vector3& far, const Matrix3x4& t
     UpdatePlanes();
 }
 
+void Frustum::Define(const BoundingBox& box, const Matrix3x4& transform)
+{
+    vertices_[0] = transform * Vector3(box.max_.x_, box.max_.y_, box.min_.z_);
+    vertices_[1] = transform * Vector3(box.max_.x_, box.min_.y_, box.min_.z_);
+    vertices_[2] = transform * Vector3(box.min_.x_, box.min_.y_, box.min_.z_);
+    vertices_[3] = transform * Vector3(box.min_.x_, box.max_.y_, box.min_.z_);
+    vertices_[4] = transform * Vector3(box.max_.x_, box.max_.y_, box.max_.z_);
+    vertices_[5] = transform * Vector3(box.max_.x_, box.min_.y_, box.max_.z_);
+    vertices_[6] = transform * Vector3(box.min_.x_, box.min_.y_, box.max_.z_);
+    vertices_[7] = transform * Vector3(box.min_.x_, box.max_.y_, box.max_.z_);
+    
+    UpdatePlanes();
+}
+
 void Frustum::DefineOrtho(float orthoSize, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform)
 {
     nearZ = Max(nearZ, 0.0f);

+ 2 - 0
Engine/Math/Frustum.h

@@ -59,6 +59,8 @@ public:
     void Define(float fov, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform = Matrix3x4::IDENTITY);
     /// Define with near and far dimension vectors and a transform matrix.
     void Define(const Vector3& near, const Vector3& far, const Matrix3x4& transform = Matrix3x4::IDENTITY);
+    /// Define with a bounding box and a transform matrix.
+    void Define(const BoundingBox& box, const Matrix3x4& transform = Matrix3x4::IDENTITY);
     /// Define with orthographic projection parameters and a transform matrix.
     void DefineOrtho(float orthoSize, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform = Matrix3x4::IDENTITY);
     /// Transform by a 3x3 matrix.