소스 검색

Started work on decals.
Geometry can now define a non-indexed draw range.
Do not queue empty batches (with empty draw range) for rendering.

Lasse Öörni 13 년 전
부모
커밋
91c7374e28

+ 11 - 0
Bin/Data/Scripts/TestScene.as

@@ -464,6 +464,17 @@ 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");
+                if (decal is null)
+                {
+                    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);
+            }
         }
     }
 }

+ 59 - 0
Docs/ScriptAPI.dox

@@ -2251,6 +2251,20 @@ Properties:<br>
 - AttributeInfo[] attributeInfos (readonly)
 - uint id (readonly)
 - Node@ node (readonly)
+- bool inView (readonly)
+- bool visible
+- bool castShadows
+- bool occluder
+- bool occludee
+- float drawDistance
+- float shadowDistance
+- float lodBias
+- uint viewMask
+- uint lightMask
+- uint shadowMask
+- uint zoneMask
+- uint maxLights
+- BoundingBox worldBoundingBox (readonly)
 - Material@ material
 - bool relative
 - bool sorted
@@ -2262,6 +2276,51 @@ Properties:<br>
 - Zone@ zone (readonly)
 
 
+DecalSet
+
+Methods:<br>
+- bool Load(File@)
+- bool Save(File@)
+- bool LoadXML(const XMLElement&)
+- bool SaveXML(XMLElement&)
+- void ApplyAttributes()
+- bool SetAttribute(const String&, const Variant&)
+- Variant GetAttribute(const String&)
+- void Remove()
+- void MarkNetworkUpdate() const
+- void DrawDebugGeometry(DebugRenderer@, bool)
+- bool AddDecal(Drawable@, const Vector3&, const Quaternion&, const BoundingBox&, float, const Vector2&, const Vector2&, float)
+- void RemoveDecals(uint)
+- void RemoveAllDecals()
+
+Properties:<br>
+- ShortStringHash type (readonly)
+- String typeName (readonly)
+- uint numAttributes (readonly)
+- Variant[] attributes
+- AttributeInfo[] attributeInfos (readonly)
+- uint id (readonly)
+- Node@ node (readonly)
+- bool inView (readonly)
+- bool visible
+- bool castShadows
+- bool occluder
+- bool occludee
+- float drawDistance
+- float shadowDistance
+- float lodBias
+- uint viewMask
+- uint lightMask
+- uint shadowMask
+- uint zoneMask
+- uint maxLights
+- BoundingBox worldBoundingBox (readonly)
+- Material@ material
+- uint numDecals (readonly)
+- uint numVertices (readonly)
+- uint maxVertices
+
+
 RayQueryResult
 
 Properties:<br>

+ 17 - 1
Engine/Engine/GraphicsAPI.cpp

@@ -29,6 +29,7 @@
 #include "APITemplates.h"
 #include "Camera.h"
 #include "DebugRenderer.h"
+#include "DecalSet.h"
 #include "Graphics.h"
 #include "Light.h"
 #include "Material.h"
@@ -735,7 +736,7 @@ static void RegisterBillboardSet(asIScriptEngine* engine)
 
 static void RegisterParticleEmitter(asIScriptEngine* engine)
 {
-    RegisterComponent<ParticleEmitter>(engine, "ParticleEmitter");
+    RegisterDrawable<ParticleEmitter>(engine, "ParticleEmitter");
     engine->RegisterObjectMethod("ParticleEmitter", "void SetActive(bool, bool)", asMETHOD(ParticleEmitter, SetActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEmitter", "void set_material(Material@+)", asMETHOD(ParticleEmitter, SetMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEmitter", "Material@+ get_material() const", asMETHOD(ParticleEmitter, SetMaterial), asCALL_THISCALL);
@@ -754,6 +755,20 @@ static void RegisterParticleEmitter(asIScriptEngine* engine)
     engine->RegisterObjectMethod("ParticleEmitter", "Zone@+ get_zone() const", asMETHOD(ParticleEmitter, GetZone), asCALL_THISCALL);
 }
 
+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", "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);
+    engine->RegisterObjectMethod("DecalSet", "Material@+ get_material() const", asMETHOD(DecalSet, GetMaterial), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DecalSet", "uint get_numDecals() const", asMETHOD(DecalSet, GetNumDecals), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DecalSet", "uint get_numVertices() const", asMETHOD(DecalSet, GetNumVertices), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DecalSet", "void set_maxVertices(uint)", asMETHOD(DecalSet, SetMaxVertices), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DecalSet", "uint get_maxVertices() const", asMETHOD(DecalSet, GetMaxVertices), asCALL_THISCALL);
+}
+
 static CScriptArray* GraphicsGetResolutions(Graphics* ptr)
 {
     return VectorToArray<IntVector2>(ptr->GetResolutions(), "Array<IntVector2>");
@@ -1048,6 +1063,7 @@ void RegisterGraphicsAPI(asIScriptEngine* engine)
     RegisterAnimationController(engine);
     RegisterBillboardSet(engine);
     RegisterParticleEmitter(engine);
+    RegisterDecalSet(engine);
     RegisterOctree(engine);
     RegisterGraphics(engine);
     RegisterRenderer(engine);

+ 0 - 1
Engine/Graphics/BillboardSet.cpp

@@ -28,7 +28,6 @@
 #include "Context.h"
 #include "Geometry.h"
 #include "Graphics.h"
-#include "GraphicsImpl.h"
 #include "IndexBuffer.h"
 #include "MemoryBuffer.h"
 #include "Node.h"

+ 301 - 0
Engine/Graphics/DecalSet.cpp

@@ -0,0 +1,301 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2012 Lasse Öörni
+//
+// 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 "Precompiled.h"
+#include "Batch.h"
+#include "Camera.h"
+#include "Context.h"
+#include "DecalSet.h"
+#include "Geometry.h"
+#include "Graphics.h"
+#include "Log.h"
+#include "Node.h"
+#include "Profiler.h"
+#include "Scene.h"
+#include "SceneEvents.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;
+
+void Decal::CalculateBoundingBox()
+{
+    boundingBox_.Clear();
+    for (unsigned i = 0; i < vertices_.Size(); ++i)
+        boundingBox_.Merge(vertices_[i].position_);
+}
+
+OBJECTTYPESTATIC(DecalSet);
+
+DecalSet::DecalSet(Context* context) :
+    Drawable(context),
+    geometry_(new Geometry(context)),
+    vertexBuffer_(new VertexBuffer(context_)),
+    numVertices_(0),
+    maxVertices_(DEFAULT_MAX_VERTICES),
+    bufferSizeDirty_(true),
+    bufferDirty_(true),
+    boundingBoxDirty_(true)
+{
+    drawableFlags_ = DRAWABLE_GEOMETRY;
+    
+    geometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1);
+    
+    batches_.Resize(1);
+    batches_[0].geometry_ = geometry_;
+}
+
+DecalSet::~DecalSet()
+{
+}
+
+void DecalSet::RegisterObject(Context* context)
+{
+    context->RegisterFactory<DecalSet>();
+    
+    /// \todo Register attributes
+}
+
+void DecalSet::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results)
+{
+    // Do not return raycast hits
+}
+
+void DecalSet::UpdateBatches(const FrameInfo& frame)
+{
+    const Matrix3x4& worldTransform = node_->GetWorldTransform();
+    distance_ = frame.camera_->GetDistance(worldTransform.Translation());
+    
+    float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
+    lodDistance_ = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
+    
+    batches_[0].distance_ = distance_;
+    batches_[0].worldTransform_ = &worldTransform;
+}
+
+void DecalSet::UpdateGeometry(const FrameInfo& frame)
+{
+    if (bufferSizeDirty_ || vertexBuffer_->IsDataLost())
+        UpdateBufferSize();
+    
+    if (bufferDirty_)
+        UpdateVertexBuffer();
+}
+
+UpdateGeometryType DecalSet::GetUpdateGeometryType()
+{
+    if (bufferDirty_ || bufferSizeDirty_ || vertexBuffer_->IsDataLost())
+        return UPDATE_MAIN_THREAD;
+    else
+        return UPDATE_NONE;
+}
+
+void DecalSet::SetMaterial(Material* material)
+{
+    batches_[0].material_ = material;
+    MarkNetworkUpdate();
+}
+
+void DecalSet::SetMaxVertices(unsigned num)
+{
+    if (num != maxVertices_)
+    {
+        bufferSizeDirty_ = true;
+        maxVertices_ = num;
+        
+        while (decals_.Size() && numVertices_ > maxVertices_)
+            RemoveDecals(1);
+    }
+}
+
+bool DecalSet::AddDecal(Drawable* target, const Vector3& worldPosition, const Quaternion& worldRotation, const BoundingBox& size, float depthBias, const Vector2& topLeftUV, const Vector2& bottomRightUV, float timeToLive)
+{
+    PROFILE(AddDecal);
+    
+    if (!node_)
+        return false;
+        
+    if (!target)
+    {
+        LOGERROR("Null target drawable for decal");
+        return false;
+    }
+    
+    Matrix3x4 worldToLocal = node_->GetWorldTransform().Inverse();
+    
+    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_));
+    
+    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();
+    
+    numVertices_ += newDecal.vertices_.Size();
+    // Remove oldest decals if total vertices exceeded
+    while (decals_.Size() && numVertices_ > maxVertices_)
+        RemoveDecals(1);
+    
+    MarkDecalsDirty();
+    return true;
+}
+
+void DecalSet::RemoveDecals(unsigned num)
+{
+    while (decals_.Size() && num)
+    {
+        RemoveDecal(decals_.Begin());
+        --num;
+    }
+}
+
+void DecalSet::RemoveAllDecals()
+{
+    if (!decals_.Empty())
+    {
+        decals_.Clear();
+        numVertices_ = 0;
+        MarkDecalsDirty();
+    }
+}
+
+Material* DecalSet::GetMaterial() const
+{
+    return batches_[0].material_;
+}
+
+void DecalSet::OnNodeSet(Node* node)
+{
+    Drawable::OnNodeSet(node);
+    
+    if (node)
+    {
+        Scene* scene = GetScene();
+        if (scene)
+            SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(DecalSet, HandleScenePostUpdate));
+    }
+}
+
+void DecalSet::OnWorldBoundingBoxUpdate()
+{
+    if (boundingBoxDirty_)
+        CalculateBoundingBox();
+    
+    worldBoundingBox_ = boundingBox_.Transformed(node_->GetWorldTransform());
+}
+
+List<Decal>::Iterator DecalSet::RemoveDecal(List<Decal>::Iterator i)
+{
+    numVertices_ -= i->vertices_.Size();
+    MarkDecalsDirty();
+    return decals_.Erase(i);
+}
+
+void DecalSet::MarkDecalsDirty()
+{
+    if (!boundingBoxDirty_)
+    {
+        boundingBoxDirty_ = true;
+        OnMarkedDirty(node_);
+    }
+    bufferDirty_ = true;
+}
+
+void DecalSet::CalculateBoundingBox()
+{
+    boundingBox_.Clear();
+    for (List<Decal>::ConstIterator i = decals_.Begin(); i != decals_.End(); ++i)
+        boundingBox_.Merge(i->boundingBox_);
+    
+    boundingBoxDirty_ = false;
+}
+
+void DecalSet::UpdateBufferSize()
+{
+    if (vertexBuffer_->GetVertexCount() != maxVertices_)
+    {
+        vertexBuffer_->SetSize(maxVertices_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1);
+        bufferDirty_ = true;
+    }
+    
+    bufferSizeDirty_ = false;
+}
+
+void DecalSet::UpdateVertexBuffer()
+{
+    geometry_->SetDrawRange(TRIANGLE_LIST, 0, 0, 0, numVertices_);
+    float* dest = (float*)vertexBuffer_->Lock(0, numVertices_);
+    if (dest)
+    {
+        for (List<Decal>::ConstIterator i = decals_.Begin(); i != decals_.End(); ++i)
+        {
+            for (unsigned j = 0; j < i->vertices_.Size(); ++j)
+            {
+                const DecalVertex& vertex = i->vertices_[j];
+                *dest++ = vertex.position_.x_;
+                *dest++ = vertex.position_.y_;
+                *dest++ = vertex.position_.z_;
+                *dest++ = vertex.normal_.x_;
+                *dest++ = vertex.normal_.y_;
+                *dest++ = vertex.normal_.z_;
+                *dest++ = vertex.texCoord_.x_;
+                *dest++ = vertex.texCoord_.y_;
+            }
+        }
+        
+        vertexBuffer_->Unlock();
+    }
+    
+    vertexBuffer_->ClearDataLost();
+    bufferDirty_ = false;
+}
+
+void DecalSet::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace ScenePostUpdate;
+    
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    
+    for (List<Decal>::Iterator i = decals_.Begin(); i != decals_.End();)
+    {
+        i->timer_ += timeStep;
+        
+        // Remove the decal if time to live expired
+        if (i->timeToLive_ > 0.0f && i->timer_ > i->timeToLive_)
+            i = RemoveDecal(i);
+        else
+            ++i;
+    }
+}

+ 155 - 0
Engine/Graphics/DecalSet.h

@@ -0,0 +1,155 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2012 Lasse Öörni
+//
+// 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 "Drawable.h"
+#include "List.h"
+
+/// Decal vertex.
+struct DecalVertex
+{
+    /// Construct with defaults.
+    DecalVertex()
+    {
+    }
+    
+    /// Construct with parameters.
+    DecalVertex(const Vector3& position, const Vector3& normal, const Vector2& texCoord) :
+        position_(position),
+        normal_(normal),
+        texCoord_(texCoord)
+    {
+    }
+    
+    /// Position.
+    Vector3 position_;
+    /// Normal.
+    Vector3 normal_;
+    /// Texture coordinates coordinates.
+    Vector2 texCoord_;
+};
+
+/// One decal in a decal set.
+struct Decal
+{
+    /// Construct with defaults.
+    Decal() :
+        timer_(0.0f),
+        timeToLive_(0.0f)
+    {
+    }
+    
+    /// Calculate local-space bounding box.
+    void CalculateBoundingBox();
+    
+    /// Decal age timer.
+    float timer_;
+    /// Maximum time to live in seconds (0 = infinite)
+    float timeToLive_;
+    /// Local-space bounding box.
+    BoundingBox boundingBox_;
+    /// Decal vertices.
+    PODVector<DecalVertex> vertices_;
+};
+
+/// Decal component.
+class DecalSet : public Drawable
+{
+    OBJECT(DecalSet);
+    
+    /// Construct.
+    DecalSet(Context* context);
+    /// Destruct.
+    virtual ~DecalSet();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+    
+    /// Process octree raycast. May be called from a worker thread.
+    virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
+    /// Calculate distance and prepare batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
+    /// Prepare geometry for rendering. Called from a worker thread if possible (no GPU update.)
+    virtual void UpdateGeometry(const FrameInfo& frame);
+    /// Return whether a geometry update is necessary, and if it can happen in a worker thread.
+    virtual UpdateGeometryType GetUpdateGeometryType();
+    
+    /// %Set material.
+    void SetMaterial(Material* material);
+    /// %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);
+    /// Remove n oldest decals.
+    void RemoveDecals(unsigned num);
+    /// Remove all decals.
+    void RemoveAllDecals();
+    
+    /// Return material.
+    Material* GetMaterial() const;
+    /// Return number of decals.
+    unsigned GetNumDecals() const { return decals_.Size(); }
+    /// Retur number of vertices in the decals.
+    unsigned GetNumVertices() const { return numVertices_; }
+    /// Return maximum number of decal vertices.
+    unsigned GetMaxVertices() const { return maxVertices_; }
+    
+protected:
+    /// Handle node being assigned.
+    virtual void OnNodeSet(Node* node);
+    /// Recalculate the world-space bounding box.
+    virtual void OnWorldBoundingBoxUpdate();
+    
+private:
+    /// 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.
+    void MarkDecalsDirty();
+    /// Recalculate the local-space bounding box.
+    void CalculateBoundingBox();
+    /// Resize decal vertex buffer.
+    void UpdateBufferSize();
+    /// Rewrite decal vertex buffer.
+    void UpdateVertexBuffer();
+    /// Handle scene post-update event.
+    void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);
+    
+    /// Geometry.
+    SharedPtr<Geometry> geometry_;
+    /// Vertex buffer.
+    SharedPtr<VertexBuffer> vertexBuffer_;
+    /// Decals.
+    List<Decal> decals_;
+    /// Local-space bounding box.
+    BoundingBox boundingBox_;
+    /// Vertices in the current decals.
+    unsigned numVertices_;
+    /// Maximum vertices.
+    unsigned maxVertices_;
+    /// Buffer needs resize flag.
+    bool bufferSizeDirty_;
+    /// Vertex buffer needs rewrite flag.
+    bool bufferDirty_;
+    /// Bounding box needs update flag.
+    bool boundingBoxDirty_;
+};

+ 3 - 1
Engine/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -28,6 +28,7 @@
 #include "Camera.h"
 #include "Context.h"
 #include "DebugRenderer.h"
+#include "DecalSet.h"
 #include "Graphics.h"
 #include "GraphicsEvents.h"
 #include "GraphicsImpl.h"
@@ -897,7 +898,7 @@ void Graphics::SetShaders(ShaderVariation* vs, ShaderVariation* ps)
             if (!vs->IsFailed())
             {
                 PROFILE(CreateVertexShader);
-                
+
                 bool success = vs->Create();
                 if (success)
                     LOGDEBUG("Created vertex shader " + vs->GetName());
@@ -2252,6 +2253,7 @@ void RegisterGraphicsLibrary(Context* context)
     AnimationController::RegisterObject(context);
     BillboardSet::RegisterObject(context);
     ParticleEmitter::RegisterObject(context);
+    DecalSet::RegisterObject(context);
     DebugRenderer::RegisterObject(context);
     Octree::RegisterObject(context);
     Zone::RegisterObject(context);

+ 29 - 11
Engine/Graphics/Geometry.cpp

@@ -24,7 +24,6 @@
 #include "Precompiled.h"
 #include "Geometry.h"
 #include "Graphics.h"
-#include "GraphicsImpl.h"
 #include "IndexBuffer.h"
 #include "Log.h"
 #include "Ray.h"
@@ -101,7 +100,7 @@ bool Geometry::SetDrawRange(PrimitiveType type, unsigned indexStart, unsigned in
 {
     if (!indexBuffer_)
     {
-        LOGERROR("Index buffer not defined, can not define draw range");
+        LOGERROR("Null index buffer, can not define indexed draw range");
         return false;
     }
     if (indexStart + indexCount > indexBuffer_->GetIndexCount())
@@ -126,15 +125,18 @@ bool Geometry::SetDrawRange(PrimitiveType type, unsigned indexStart, unsigned in
 
 bool Geometry::SetDrawRange(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount)
 {
-    if (!indexBuffer_)
+    if (indexBuffer_)
     {
-        LOGERROR("Index buffer not defined, can not define draw range");
-        return false;
+        if (indexStart + indexCount > indexBuffer_->GetIndexCount())
+        {
+            LOGERROR("Illegal draw range");
+            return false;
+        }
     }
-    if (indexStart + indexCount > indexBuffer_->GetIndexCount())
+    else
     {
-        LOGERROR("Illegal draw range");
-        return false;
+        indexStart = 0;
+        indexCount = 0;
     }
     
     primitiveType_ = type;
@@ -156,9 +158,17 @@ void Geometry::SetLodDistance(float distance)
 
 void Geometry::Draw(Graphics* graphics)
 {
-    graphics->SetIndexBuffer(indexBuffer_);
-    graphics->SetVertexBuffers(vertexBuffers_, elementMasks_);
-    graphics->Draw(primitiveType_, indexStart_, indexCount_, vertexStart_, vertexCount_);
+    if (indexBuffer_)
+    {
+        graphics->SetIndexBuffer(indexBuffer_);
+        graphics->SetVertexBuffers(vertexBuffers_, elementMasks_);
+        graphics->Draw(primitiveType_, indexStart_, indexCount_, vertexStart_, vertexCount_);
+    }
+    else
+    {
+        graphics->SetVertexBuffers(vertexBuffers_, elementMasks_);
+        graphics->Draw(primitiveType_, vertexStart_, vertexCount_);
+    }
 }
 
 VertexBuffer* Geometry::GetVertexBuffer(unsigned index) const
@@ -171,6 +181,14 @@ unsigned Geometry::GetVertexElementMask(unsigned index) const
     return index < elementMasks_.Size() ? elementMasks_[index] : 0;
 }
 
+bool Geometry::IsEmpty() const
+{
+    if (indexBuffer_)
+        return indexCount_ == 0;
+    else
+        return vertexCount_ == 0;
+}
+
 unsigned short Geometry::GetBufferHash() const
 {
     unsigned short hash = 0;

+ 2 - 0
Engine/Graphics/Geometry.h

@@ -80,6 +80,8 @@ public:
     unsigned GetVertexStart() const { return vertexStart_; }
     /// Return number of used vertices.
     unsigned GetVertexCount() const { return vertexCount_; }
+    /// Return whether draw range is empty.
+    bool IsEmpty() const;
     /// Return LOD distance.
     float GetLodDistance() const { return lodDistance_; }
     /// Return buffers' combined hash value for state sorting.

+ 2 - 0
Engine/Graphics/OpenGL/OGLGraphics.cpp

@@ -45,6 +45,7 @@
 #include "ShaderProgram.h"
 #include "ShaderVariation.h"
 #include "Skybox.h"
+#include "DecalSet.h"
 #include "StringUtils.h"
 #include "Technique.h"
 #include "Texture2D.h"
@@ -2309,6 +2310,7 @@ void RegisterGraphicsLibrary(Context* context)
     AnimationController::RegisterObject(context);
     BillboardSet::RegisterObject(context);
     ParticleEmitter::RegisterObject(context);
+    DecalSet::RegisterObject(context);
     DebugRenderer::RegisterObject(context);
     Octree::RegisterObject(context);
     Zone::RegisterObject(context);

+ 1 - 1
Engine/Graphics/Skeleton.h

@@ -69,7 +69,7 @@ struct Bone
     unsigned char collisionMask_;
     /// Radius.
     float radius_;
-    /// Bounding box.
+    /// Local-space bounding box.
     BoundingBox boundingBox_;
     /// Scene node.
     WeakPtr<Node> node_;

+ 1 - 1
Engine/Graphics/StaticModel.h

@@ -99,7 +99,7 @@ protected:
     /// Choose LOD levels based on distance.
     void CalculateLodLevels();
     
-    /// Bounding box.
+    /// Local-space bounding box.
     BoundingBox boundingBox_;
     /// Extra per-geometry data.
     PODVector<StaticModelGeometryData> geometryData_;

+ 4 - 4
Engine/Graphics/View.cpp

@@ -784,7 +784,7 @@ void View::GetBatches()
                             const SourceBatch& srcBatch = batches[l];
                             
                             Technique* tech = GetTechnique(drawable, srcBatch.material_);
-                            if (!srcBatch.geometry_ || !tech)
+                            if (!srcBatch.geometry_ || srcBatch.geometry_->IsEmpty() || !tech)
                                 continue;
                             
                             Pass* pass = tech->GetPass(PASS_SHADOW);
@@ -898,7 +898,7 @@ void View::GetBatches()
                     continue;
                 
                 Technique* tech = GetTechnique(drawable, srcBatch.material_);
-                if (!srcBatch.geometry_ || !tech)
+                if (!srcBatch.geometry_ || srcBatch.geometry_->IsEmpty() || !tech)
                     continue;
                 
                 Batch destBatch(srcBatch);
@@ -1114,7 +1114,7 @@ void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue)
         const SourceBatch& srcBatch = batches[i];
         
         Technique* tech = GetTechnique(drawable, srcBatch.material_);
-        if (!srcBatch.geometry_ || !tech)
+        if (!srcBatch.geometry_ || srcBatch.geometry_->IsEmpty() || !tech)
             continue;
         
         // Do not create pixel lit forward passes for materials that render into the G-buffer
@@ -2348,7 +2348,7 @@ void View::AddBatchToQueue(BatchQueue& batchQueue, Batch& batch, Technique* tech
         batch.material_ = renderer_->GetDefaultMaterial();
     
     // Convert to instanced if possible
-    if (allowInstancing && batch.geometryType_ == GEOM_STATIC && !batch.shaderData_ && !batch.overrideView_)
+    if (allowInstancing && batch.geometryType_ == GEOM_STATIC && batch.geometry_->GetIndexBuffer() && !batch.shaderData_ && !batch.overrideView_)
         batch.geometryType_ = GEOM_INSTANCED;
     
     if (batch.geometryType_ == GEOM_INSTANCED)

+ 1 - 1
Engine/Graphics/Zone.h

@@ -103,7 +103,7 @@ protected:
     bool override_;
     /// Ambient gradient mode flag.
     bool ambientGradient_;
-    /// Bounding box.
+    /// Local-space bounding box.
     BoundingBox boundingBox_;
     /// Last world-space bounding box.
     BoundingBox lastWorldBoundingBox_;