Browse Source

Added node IDs attribute to StaticModelGroup.
Added raycast & occlusion rendering to StaticModelGroup.

Lasse Öörni 12 years ago
parent
commit
247158b7d8

+ 1 - 0
Bin/Data/Scripts/19_VehicleDemo.as

@@ -2,6 +2,7 @@
 // This sample demonstrates:
 //     - Creating a heightmap terrain with collision;
 //     - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints;
+//     - Saving and loading the variables of a script object, including node & component references;
 
 #include "Scripts/Utilities/Sample.as"
 

+ 6 - 0
Bin/Data/Scripts/Editor/AttributeEditor.as

@@ -1197,6 +1197,12 @@ void InitVectorStructs()
         "   Time"
     };
     vectorStructs.Push(VectorStruct("ParticleEmitter", "UV Animation", particleUVAnimVariables, 1));
+
+    Array<String> staticModelGroupInstanceVariables = {
+        "Instance Count",
+        "   NodeID"
+    };
+    vectorStructs.Push(VectorStruct("StaticModelGroup", "Instance Nodes", staticModelGroupInstanceVariables, 1));
 }
 
 VectorStruct@ GetVectorStruct(Array<Serializable@>@ serializables, uint index)

+ 4 - 0
Bin/Data/UI/EditorIcons.xml

@@ -95,6 +95,10 @@
         <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
         <attribute name="Image Rect" value="32 0 46 14" />
     </element>
+    <element type="StaticModelGroup">
+        <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
+        <attribute name="Image Rect" value="32 0 46 14" />
+    </element>
     <element type="Terrain">
         <attribute name="Texture" value="Texture2D;Textures/EditorIcons.png" />
         <attribute name="Image Rect" value="224 0 238 14" />

+ 2 - 0
Source/Engine/Core/Attribute.h

@@ -44,6 +44,8 @@ static const unsigned AM_NOEDIT = 0x8;
 static const unsigned AM_NODEID = 0x10;
 /// Attribute is a component ID and may need rewriting.
 static const unsigned AM_COMPONENTID = 0x20;
+/// Attribute is a node ID vector where first element is the amount of nodes.
+static const unsigned AM_NODEIDVECTOR = 0x40;
 
 class Serializable;
 

+ 1 - 0
Source/Engine/Graphics/DecalSet.cpp

@@ -311,6 +311,7 @@ bool DecalSet::AddDecal(Drawable* target, const Vector3& worldPosition, const Qu
     
     // Center the decal frustum on the world position
     Vector3 adjustedWorldPosition = worldPosition - 0.5f * depth * (worldRotation * Vector3::FORWARD);
+    /// \todo target transform is not right if adding a decal to StaticModelGroup
     Matrix3x4 targetTransform = target->GetNode()->GetWorldTransform().Inverse();
     
     // For an animated model, adjust the decal position back to the bind pose

+ 241 - 2
Source/Engine/Graphics/StaticModelGroup.cpp

@@ -24,6 +24,10 @@
 #include "Batch.h"
 #include "Camera.h"
 #include "Context.h"
+#include "Geometry.h"
+#include "Material.h"
+#include "OcclusionBuffer.h"
+#include "OctreeQuery.h"
 #include "Scene.h"
 #include "StaticModelGroup.h"
 
@@ -35,8 +39,11 @@ namespace Urho3D
 extern const char* GEOMETRY_CATEGORY;
 
 StaticModelGroup::StaticModelGroup(Context* context) :
-    StaticModel(context)
+    StaticModel(context),
+    nodeIDsDirty_(false)
 {
+    // Initialize the default node IDs attribute
+    nodeIDsAttr_.Push(0);
 }
 
 StaticModelGroup::~StaticModelGroup()
@@ -48,7 +55,101 @@ void StaticModelGroup::RegisterObject(Context* context)
     context->RegisterFactory<StaticModelGroup>(GEOMETRY_CATEGORY);
 
     COPY_BASE_ATTRIBUTES(StaticModelGroup, StaticModel);
-    /// \todo Define an attribute for instance node ID's
+    REF_ACCESSOR_ATTRIBUTE(StaticModelGroup, VAR_VARIANTVECTOR, "Instance Nodes", GetNodeIDsAttr, SetNodeIDsAttr, VariantVector, Variant::emptyVariantVector, AM_DEFAULT | AM_NODEIDVECTOR);
+}
+
+void StaticModelGroup::ApplyAttributes()
+{
+    if (!nodeIDsDirty_)
+        return;
+    
+    // Remove all old instance nodes before searching for new. Can not call RemoveAllInstances() as that would modify
+    // the ID list on its own
+    for (unsigned i = 0; i < instanceNodes_.Size(); ++i)
+    {
+        Node* node = instanceNodes_[i];
+        if (node)
+            node->RemoveListener(this);
+    }
+    instanceNodes_.Clear();
+    
+    Scene* scene = GetScene();
+    
+    if (scene)
+    {
+        // The first index stores the number of IDs redundantly. This is for editing
+        for (unsigned i = 1; i < nodeIDsAttr_.Size(); ++i)
+        {
+            Node* node = scene->GetNode(nodeIDsAttr_[i].GetUInt());
+            if (node)
+            {
+                WeakPtr<Node> instanceWeak(node);
+                node->AddListener(this);
+                instanceNodes_.Push(instanceWeak);
+            }
+        }
+    }
+    
+    OnMarkedDirty(GetNode());
+    nodeIDsDirty_ = false;
+}
+
+void StaticModelGroup::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results)
+{
+    // If no bones or no bone-level testing, use the Drawable test
+    RayQueryLevel level = query.level_;
+    if (level < RAY_AABB)
+    {
+        Drawable::ProcessRayQuery(query, results);
+        return;
+    }
+
+    // Check ray hit distance to AABB before proceeding with more accurate tests
+    // GetWorldBoundingBox() updates the world transforms
+    if (query.ray_.HitDistance(GetWorldBoundingBox()) >= query.maxDistance_)
+        return;
+    
+    for (unsigned i = 0; i < worldTransforms_.Size(); ++i)
+    {
+        // Initial test using AABB
+        float distance = query.ray_.HitDistance(boundingBox_.Transformed(worldTransforms_[i]));
+        if (distance >= query.maxDistance_)
+            continue;
+        // Then proceed to OBB and triangle-level tests if necessary
+        if (level >= RAY_OBB)
+        {
+            Matrix3x4 inverse = worldTransforms_[i].Inverse();
+            Ray localRay = query.ray_.Transformed(inverse);
+            distance = localRay.HitDistance(boundingBox_);
+            if (distance >= query.maxDistance_)
+                continue;
+            if (level == RAY_TRIANGLE)
+            {
+                distance = M_INFINITY;
+                
+                for (unsigned i = 0; i < batches_.Size(); ++i)
+                {
+                    Geometry* geometry = batches_[i].geometry_;
+                    if (geometry)
+                    {
+                        distance = geometry->GetHitDistance(localRay);
+                        if (distance < query.maxDistance_)
+                            break;
+                    }
+                }
+            }
+        }
+        
+        if (distance < query.maxDistance_)
+        {
+            RayQueryResult result;
+            result.drawable_ = this;
+            result.node_ = node_;
+            result.distance_ = distance;
+            result.subObject_ = i;
+            results.Push(result);
+        }
+    }
 }
 
 void StaticModelGroup::UpdateBatches(const FrameInfo& frame)
@@ -84,6 +185,77 @@ void StaticModelGroup::UpdateBatches(const FrameInfo& frame)
     }
 }
 
+unsigned StaticModelGroup::GetNumOccluderTriangles()
+{
+    // Make sure instance transforms are up-to-date
+    GetWorldBoundingBox();
+    
+    unsigned triangles = 0;
+    
+    for (unsigned i = 0; i < batches_.Size(); ++i)
+    {
+        Geometry* geometry = GetLodGeometry(i, occlusionLodLevel_);
+        if (!geometry)
+            continue;
+        
+        // Check that the material is suitable for occlusion (default material always is)
+        Material* mat = batches_[i].material_;
+        if (mat && !mat->GetOcclusion())
+            continue;
+        
+        triangles += worldTransforms_.Size() * geometry->GetIndexCount() / 3;
+    }
+    
+    return triangles;
+}
+
+bool StaticModelGroup::DrawOcclusion(OcclusionBuffer* buffer)
+{
+    // Make sure instance transforms are up-to-date
+    GetWorldBoundingBox();
+    
+    for (unsigned i = 0; i < worldTransforms_.Size(); ++i)
+    {
+        for (unsigned j = 0; j < batches_.Size(); ++j)
+        {
+            Geometry* geometry = GetLodGeometry(j, occlusionLodLevel_);
+            if (!geometry)
+                continue;
+            
+            // Check that the material is suitable for occlusion (default material always is) and set culling mode
+            Material* material = batches_[j].material_;
+            if (material)
+            {
+                if (!material->GetOcclusion())
+                    continue;
+                buffer->SetCullMode(material->GetCullMode());
+            }
+            else
+                buffer->SetCullMode(CULL_CCW);
+            
+            const unsigned char* vertexData;
+            unsigned vertexSize;
+            const unsigned char* indexData;
+            unsigned indexSize;
+            unsigned elementMask;
+            
+            geometry->GetRawData(vertexData, vertexSize, indexData, indexSize, elementMask);
+            // Check for valid geometry data
+            if (!vertexData || !indexData)
+                continue;
+            
+            unsigned indexStart = geometry->GetIndexStart();
+            unsigned indexCount = geometry->GetIndexCount();
+            
+            // Draw and check for running out of triangles
+            if (!buffer->Draw(worldTransforms_[i], vertexData, vertexSize, indexData, indexSize, indexStart, indexCount))
+                return false;
+        }
+    }
+    
+    return true;
+}
+
 void StaticModelGroup::AddInstanceNode(Node* node)
 {
     if (!node)
@@ -96,6 +268,10 @@ void StaticModelGroup::AddInstanceNode(Node* node)
     // Add as a listener for the instance node, so that we know to dirty the transforms when the node moves or is enabled/disabled
     node->AddListener(this);
     instanceNodes_.Push(instanceWeak);
+    
+    OnMarkedDirty(GetNode());
+    UpdateNodeIDs();
+    MarkNetworkUpdate();
 }
 
 void StaticModelGroup::RemoveInstanceNode(Node* node)
@@ -106,6 +282,25 @@ void StaticModelGroup::RemoveInstanceNode(Node* node)
     WeakPtr<Node> instanceWeak(node);
     node->RemoveListener(this);
     instanceNodes_.Remove(instanceWeak);
+    
+    OnMarkedDirty(GetNode());
+    UpdateNodeIDs();
+    MarkNetworkUpdate();
+}
+
+void StaticModelGroup::RemoveAllInstanceNodes()
+{
+    for (unsigned i = 0; i < instanceNodes_.Size(); ++i)
+    {
+        Node* node = instanceNodes_[i];
+        if (node)
+            node->RemoveListener(this);
+    }
+    
+    instanceNodes_.Clear();
+    OnMarkedDirty(GetNode());
+    UpdateNodeIDs();
+    MarkNetworkUpdate();
 }
 
 Node* StaticModelGroup::GetInstanceNode(unsigned index) const
@@ -113,6 +308,38 @@ Node* StaticModelGroup::GetInstanceNode(unsigned index) const
     return index < instanceNodes_.Size() ? instanceNodes_[index] : (Node*)0;
 }
 
+void StaticModelGroup::SetNodeIDsAttr(const VariantVector& value)
+{
+    // Just remember the node IDs. They need to go through the SceneResolver, and we actually find the nodes during
+    // ApplyAttributes()
+    if (value.Size())
+    {
+        nodeIDsAttr_.Clear();
+        
+        unsigned index = 0;
+        unsigned numInstances = value[index++].GetUInt();
+        // Prevent crash on entering negative value in the editor
+        if (numInstances > M_MAX_INT)
+            numInstances = 0;
+        
+        nodeIDsAttr_.Push(numInstances);
+        while (numInstances--)
+        {
+            // If vector contains less IDs than should, fill the rest with zeroes
+            if (index < value.Size())
+                nodeIDsAttr_.Push(value[index++].GetUInt());
+            else
+                nodeIDsAttr_.Push(0);
+        }
+    }
+    else
+    {
+        nodeIDsAttr_.Clear();
+        nodeIDsAttr_.Push(0);
+    }
+    nodeIDsDirty_ = true;
+}
+
 void StaticModelGroup::OnNodeSetEnabled(Node* node)
 {
     Drawable::OnMarkedDirty(node);
@@ -139,4 +366,16 @@ void StaticModelGroup::OnWorldBoundingBoxUpdate()
     worldTransforms_.Resize(index);
 }
 
+void StaticModelGroup::UpdateNodeIDs()
+{
+    nodeIDsAttr_.Clear();
+    nodeIDsAttr_.Push(instanceNodes_.Size());
+    
+    for (unsigned i = 0; i < instanceNodes_.Size(); ++i)
+    {
+        Node* node = instanceNodes_[i];
+        nodeIDsAttr_.Push(node ? node->GetID() : 0);
+    }
+}
+
 }

+ 19 - 3
Source/Engine/Graphics/StaticModelGroup.h

@@ -40,25 +40,34 @@ public:
     /// Register object factory. StaticModel must be registered first.
     static void RegisterObject(Context* context);
     
+    /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
+    void ApplyAttributes();
     /// Process octree raycast. May be called from a worker thread.
-    //virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
+    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);
     /// Return number of occlusion geometry triangles.
-    //virtual unsigned GetNumOccluderTriangles();
+    virtual unsigned GetNumOccluderTriangles();
     /// Draw to occlusion buffer. Return true if did not run out of triangles.
-    //virtual bool DrawOcclusion(OcclusionBuffer* buffer);
+    virtual bool DrawOcclusion(OcclusionBuffer* buffer);
     
     /// Add an instance scene node. It does not need any drawable components of its own.
     void AddInstanceNode(Node* node);
     /// Remove an instance scene node.
     void RemoveInstanceNode(Node* node);
+    /// Remove all instance scene nodes.
+    void RemoveAllInstanceNodes();
     
     /// Return number of instance nodes.
     unsigned GetNumInstanceNodes() const { return instanceNodes_.Size(); }
     /// Return instance node by index.
     Node* GetInstanceNode(unsigned index) const;
     
+    /// Set node IDs attribute.
+    void SetNodeIDsAttr(const VariantVector& value);
+    /// Return node IDs attribute.
+    const VariantVector& GetNodeIDsAttr() const { return nodeIDsAttr_; }
+    
 protected:
     /// Handle scene node enabled status changing.
     virtual void OnNodeSetEnabled(Node* node);
@@ -66,10 +75,17 @@ protected:
     virtual void OnWorldBoundingBoxUpdate();
     
 private:
+    /// Update node IDs attribute.
+    void UpdateNodeIDs();
+    
     /// Instance nodes.
     Vector<WeakPtr<Node> > instanceNodes_;
     /// World transforms of enabled instances.
     PODVector<Matrix3x4> worldTransforms_;
+    /// IDs of instance nodes for serialization.
+    mutable VariantVector nodeIDsAttr_;
+    /// Whether node IDs have been set and nodes should be searched for during ApplyAttributes.
+    bool nodeIDsDirty_;
 };
 
 }

+ 33 - 3
Source/Engine/Scene/SceneResolver.cpp

@@ -82,7 +82,7 @@ void SceneResolver::Resolve()
             if (info.mode_ & AM_NODEID)
             {
                 hasIDAttributes = true;
-                unsigned oldNodeID = component->GetAttribute(j).GetInt();
+                unsigned oldNodeID = component->GetAttribute(j).GetUInt();
                 
                 if (oldNodeID)
                 {
@@ -97,10 +97,10 @@ void SceneResolver::Resolve()
                         LOGWARNING("Could not resolve node ID " + String(oldNodeID));
                 }
             }
-            if (info.mode_ & AM_COMPONENTID)
+            else if (info.mode_ & AM_COMPONENTID)
             {
                 hasIDAttributes = true;
-                unsigned oldComponentID = component->GetAttribute(j).GetInt();
+                unsigned oldComponentID = component->GetAttribute(j).GetUInt();
 
                 if (oldComponentID)
                 {
@@ -115,6 +115,36 @@ void SceneResolver::Resolve()
                         LOGWARNING("Could not resolve component ID " + String(oldComponentID));
                 }
             }
+            else if (info.mode_ & AM_NODEIDVECTOR)
+            {
+                hasIDAttributes = true;
+                const VariantVector& oldNodeIDs = component->GetAttribute(j).GetVariantVector();
+                
+                if (oldNodeIDs.Size())
+                {
+                    // The first index stores the number of IDs redundantly. This is for editing
+                    unsigned numIDs = oldNodeIDs[0].GetUInt();
+                    VariantVector newIDs;
+                    newIDs.Push(numIDs);
+                    
+                    for (unsigned k = 1; k < oldNodeIDs.Size(); ++k)
+                    {
+                        unsigned oldNodeID = oldNodeIDs[k].GetUInt();
+                        HashMap<unsigned, WeakPtr<Node> >::ConstIterator l = nodes_.Find(oldNodeID);
+                    
+                        if (l != nodes_.End() && l->second_)
+                            newIDs.Push(l->second_->GetID());
+                        else
+                        {
+                            // If node was not found, retain number of elements, just store ID 0
+                            newIDs.Push(0);
+                            LOGWARNING("Could not resolve node ID " + String(oldNodeID));
+                        }
+                    }
+                    
+                    component->SetAttribute(j, newIDs);
+                }
+            }
         }
         
         // If component type had no ID attributes, cache this fact for optimization

+ 1 - 1
Source/Engine/Scene/Serializable.cpp

@@ -485,7 +485,7 @@ void Serializable::ResetToDefault()
     for (unsigned i = 0; i < attributes->Size(); ++i)
     {
         const AttributeInfo& attr = attributes->At(i);
-        if (attr.mode_ & AM_NOEDIT || attr.mode_ & AM_NODEID || attr.mode_ & AM_COMPONENTID)
+        if (attr.mode_ & (AM_NOEDIT | AM_NODEID | AM_COMPONENTID | AM_NODEIDVECTOR))
             continue;
 
         Variant defaultValue = GetInstanceDefault(attr.name_);

+ 2 - 1
Source/Engine/Script/SceneAPI.cpp

@@ -39,7 +39,8 @@ static void RegisterSerializable(asIScriptEngine* engine)
     engine->RegisterGlobalProperty("const uint AM_NOEDIT", (void*)&AM_NOEDIT);
     engine->RegisterGlobalProperty("const uint AM_NODEID", (void*)&AM_NODEID);
     engine->RegisterGlobalProperty("const uint AM_COMPONENTID", (void*)&AM_COMPONENTID);
-
+    engine->RegisterGlobalProperty("const uint AM_NODEIDVECTOR", (void*)&AM_NODEIDVECTOR);
+    
     RegisterSerializable<Serializable>(engine, "Serializable");
 }