Browse Source

Debug draw UI-elements in Editor.

Wei Tjong Yao 12 years ago
parent
commit
712de11c99

+ 3 - 0
Bin/Data/Scripts/Editor/EditorScene.as

@@ -233,6 +233,9 @@ void CreateComponent(const String&in componentType)
         }
     }
 
+    // Although the edit nodes selection are not changed, call to ensure attribute inspector notices new components of the edit nodes   
+    HandleHierarchyListSelectionChange();
+
     SaveEditActionGroup(group);
 
     SetSceneModified();

+ 9 - 12
Bin/Data/Scripts/Editor/EditorSceneWindow.as

@@ -467,10 +467,10 @@ void SelectNode(Node@ node, bool multiselect)
             uint parentIndex = GetListIndex(node);
             if (parentIndex < numItems)
                 hierarchyList.Expand(parentIndex, true);
-            
+
             hierarchyList.Expand(index, true);
         }
-        
+
         // This causes an event to be sent, in response we set the node/component selections, and refresh editors
         if (!multiselect)
             hierarchyList.selection = index;
@@ -521,7 +521,7 @@ void SelectComponent(Component@ component, bool multiselect)
                 hierarchyList.ClearSelection();
                 return;
             }
-            
+
             hierarchyList.Expand(nodeIndex, true);
         }
         // This causes an event to be sent, in response we set the node/component selections, and refresh editors
@@ -536,12 +536,6 @@ void SelectComponent(Component@ component, bool multiselect)
 
 void SelectUIElement(UIElement@ element, bool multiselect)
 {
-    if (element is null && !multiselect)
-    {
-        hierarchyList.ClearSelection();
-        return;
-    }
-
     uint index = GetListIndex(element);
     uint numItems = hierarchyList.numItems;
 
@@ -561,10 +555,10 @@ void SelectUIElement(UIElement@ element, bool multiselect)
             uint parentIndex = GetListIndex(element);
             if (parentIndex < numItems)
                 hierarchyList.Expand(parentIndex, true);
-            
+
             hierarchyList.Expand(index, true);
         }
-        
+
         if (!multiselect)
             hierarchyList.selection = index;
         else
@@ -846,8 +840,11 @@ void HandleComponentAdded(StringHash eventType, VariantMap& eventData)
     if (suppressSceneChanges)
         return;
 
+    // Insert the newly added component at last component position but before the first child node position of the parent node
+    Node@ node = eventData["Node"].GetNode();
     Component@ component = eventData["Component"].GetComponent();
-    UpdateHierarchyItem(component);
+    uint index = node.numChildren > 0 ? GetListIndex(node.children[0]) : M_MAX_UNSIGNED;
+    UpdateHierarchyItem(index, component, hierarchyList.items[GetListIndex(node)]);
 }
 
 void HandleComponentRemoved(StringHash eventType, VariantMap& eventData)

+ 14 - 13
Bin/Data/Scripts/Editor/EditorView.as

@@ -362,6 +362,10 @@ void HandlePostRenderUpdate()
     for (uint i = 0; i < selectedComponents.length; ++i)
         selectedComponents[i].DrawDebugGeometry(debug, false);
 
+    // Visualize the currently selected UI-elements
+    for (uint i = 0; i < selectedUIElements.length; ++i)
+        ui.DebugDraw(selectedUIElements[i]);
+
     if (renderingDebug)
         renderer.DrawDebugGeometry(false);
     if (physicsDebug && editorScene.physicsWorld !is null)
@@ -387,6 +391,8 @@ void ViewRaycast(bool mouseClick)
     if (IsGizmoSelected())
         return;
 
+    DebugRenderer@ debug = editorScene.debugRenderer;
+
     IntVector2 pos = ui.cursorPosition;
     UIElement@ elementAtPos = ui.GetElementAt(pos, pickMode != PICK_UI_ELEMENTS);
     if (pickMode == PICK_UI_ELEMENTS)
@@ -394,21 +400,17 @@ void ViewRaycast(bool mouseClick)
         bool leftClick = mouseClick && input.mouseButtonPress[MOUSEB_LEFT];
         bool multiselect = input.qualifierDown[QUAL_CTRL];
 
-        if (elementAtPos !is null)
+        // Only interested in user-created UI elements
+        if (elementAtPos !is editorUIElement && elementAtPos.GetElementEventSender() is editorUIElement)
         {
-            // Only interested in user-created UI elements
-            if (elementAtPos !is editorUIElement && elementAtPos.GetElementEventSender() is editorUIElement)
-            {
-                // \todo Debug draw the UIElement
-                //DebugDrawUIElement();
+            ui.DebugDraw(elementAtPos);
 
-                if (leftClick)
-                    SelectUIElement(elementAtPos, multiselect);
-            }
+            if (leftClick)
+                SelectUIElement(elementAtPos, multiselect);
         }
-        else if (leftClick && !multiselect)
-            // If clicked on emptiness in non-multiselect mode, clear the selection
-           SelectUIElement(null, false);
+        // If clicked on emptiness in non-multiselect mode, clear the selection
+        else if (leftClick && !multiselect && ui.GetElementAt(pos) is null)
+            hierarchyList.ClearSelection();
 
         return;
     }
@@ -417,7 +419,6 @@ void ViewRaycast(bool mouseClick)
     if (elementAtPos !is null)
         return;
 
-    DebugRenderer@ debug = editorScene.debugRenderer;
     Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
     Component@ selectedComponent;
 

+ 2 - 0
Docs/ScriptAPI.dox

@@ -548,6 +548,7 @@ BoundingBox
 Methods:<br>
 - void Define(const Vector3&, const Vector3&)
 - void Define(float, float)
+- void Define(const Vector3&)
 - void Define(const BoundingBox&)
 - void Define(const Frustum&)
 - void Define(const Polyhedron&)
@@ -5019,6 +5020,7 @@ UI
 Methods:<br>
 - void SendEvent(const String&, VariantMap& arg1 = VariantMap ( ))
 - void Clear()
+- void DebugDraw(UIElement@)
 - UIElement@ LoadLayout(File@)
 - UIElement@ LoadLayout(File@, XMLFile@)
 - UIElement@ LoadLayout(XMLFile@)

+ 7 - 0
Engine/Engine/MathAPI.cpp

@@ -801,6 +801,11 @@ static void ConstructBoundingBoxCopy(const BoundingBox& box, BoundingBox* ptr)
     new(ptr) BoundingBox(box);
 }
 
+static void ConstructBoundingBoxRect(const Rect& rect, BoundingBox* ptr)
+{
+    new(ptr) BoundingBox(rect);
+}
+
 static void ConstructBoundingBoxInit(const Vector3& min, const Vector3& max, BoundingBox* ptr)
 {
     new(ptr) BoundingBox(min, max);
@@ -942,6 +947,7 @@ static void RegisterVolumes(asIScriptEngine* engine)
     
     engine->RegisterObjectBehaviour("BoundingBox", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructBoundingBox), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("BoundingBox", asBEHAVE_CONSTRUCT, "void f(const BoundingBox&in)", asFUNCTION(ConstructBoundingBoxCopy), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectBehaviour("BoundingBox", asBEHAVE_CONSTRUCT, "void f(const Rect&in)", asFUNCTION(ConstructBoundingBoxRect), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("BoundingBox", asBEHAVE_CONSTRUCT, "void f(const Vector3&in, const Vector3&in)", asFUNCTION(ConstructBoundingBoxInit), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("BoundingBox", asBEHAVE_CONSTRUCT, "void f(float, float)", asFUNCTION(ConstructBoundingBoxFloat), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("BoundingBox", asBEHAVE_CONSTRUCT, "void f(const Frustum&in)", asFUNCTION(ConstructBoundingBoxFrustum), asCALL_CDECL_OBJLAST);
@@ -951,6 +957,7 @@ static void RegisterVolumes(asIScriptEngine* engine)
     engine->RegisterObjectMethod("BoundingBox", "bool opEquals(const BoundingBox&in) const", asMETHOD(BoundingBox, operator ==), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "void Define(const Vector3&in, const Vector3&in)", asMETHODPR(BoundingBox, Define, (const Vector3&, const Vector3&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "void Define(float, float)", asMETHODPR(BoundingBox, Define, (float, float), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("BoundingBox", "void Define(const Vector3&in)", asMETHODPR(BoundingBox, Define, (const Vector3&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "void Define(const BoundingBox&in)", asMETHODPR(BoundingBox, Define, (const BoundingBox&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "void Define(const Frustum&in)", asMETHODPR(BoundingBox, Define, (const Frustum&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("BoundingBox", "void Define(const Polyhedron&in)", asMETHODPR(BoundingBox, Define, (const Polyhedron&), void), asCALL_THISCALL);

+ 1 - 0
Engine/Engine/UIAPI.cpp

@@ -531,6 +531,7 @@ static void RegisterUI(asIScriptEngine* engine)
 {
     RegisterObject<UI>(engine, "UI");
     engine->RegisterObjectMethod("UI", "void Clear()", asMETHOD(UI, Clear), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "void DebugDraw(UIElement@+)", asMETHOD(UI, DebugDraw), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@ LoadLayout(File@+)", asFUNCTION(UILoadLayoutFromFile), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("UI", "UIElement@ LoadLayout(File@+, XMLFile@+)", asFUNCTION(UILoadLayoutFromFileWithStyle), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("UI", "UIElement@ LoadLayout(XMLFile@+)", asFUNCTION(UILoadLayout), asCALL_CDECL_OBJLAST);

+ 95 - 95
Engine/Graphics/AnimatedModel.cpp

@@ -79,7 +79,7 @@ AnimatedModel::~AnimatedModel()
 void AnimatedModel::RegisterObject(Context* context)
 {
     context->RegisterFactory<AnimatedModel>();
-    
+
     ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_BOOL, "Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_RESOURCEREF, "Model", GetModelAttr, SetModelAttr, ResourceRef, ResourceRef(Model::GetTypeStatic()), AM_DEFAULT);
     REF_ACCESSOR_ATTRIBUTE(AnimatedModel, VAR_RESOURCEREFLIST, "Material", GetMaterialsAttr, SetMaterialsAttr, ResourceRefList, ResourceRefList(Material::GetTypeStatic()), AM_DEFAULT);
@@ -102,7 +102,7 @@ bool AnimatedModel::Load(Deserializer& source)
     loading_ = true;
     bool success = Component::Load(source);
     loading_ = false;
-    
+
     return success;
 }
 
@@ -111,7 +111,7 @@ bool AnimatedModel::LoadXML(const XMLElement& source)
     loading_ = true;
     bool success = Component::LoadXML(source);
     loading_ = false;
-    
+
     return success;
 }
 
@@ -130,20 +130,20 @@ void AnimatedModel::ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQu
         Drawable::ProcessRayQuery(query, results);
         return;
     }
-    
+
     // Check ray hit distance to AABB before proceeding with bone-level tests
     if (query.ray_.HitDistance(GetWorldBoundingBox()) > query.maxDistance_)
         return;
-    
+
     const Vector<Bone>& bones = skeleton_.GetBones();
     Sphere boneSphere;
-    
+
     for (unsigned i = 0; i < bones.Size(); ++i)
     {
         const Bone& bone = bones[i];
         if (!bone.node_)
             continue;
-        
+
         float distance;
 
         // Use hitbox if available
@@ -191,7 +191,7 @@ void AnimatedModel::Update(const FrameInfo& frame)
     // Update animation here
     if (!animationDirty_ && !animationOrderDirty_)
         return;
-    
+
     // If node was invisible last frame, need to decide animation LOD distance here
     // If headless, retain the current animation distance (should be 0)
     if (frame.camera_ && abs((int)frame.frameNumber_ - (int)viewFrameNumber_) > 1)
@@ -206,7 +206,7 @@ void AnimatedModel::Update(const FrameInfo& frame)
         float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
         animationLodDistance_ = frame.camera_->GetLodDistance(distance, scale, lodBias_) * invisibleLodFactor_;
     }
-    
+
     UpdateAnimation(frame);
 }
 
@@ -214,7 +214,7 @@ void AnimatedModel::UpdateBatches(const FrameInfo& frame)
 {
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
     distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center());
-    
+
     // Note: per-geometry distances do not take skinning into account
     if (batches_.Size() > 1)
     {
@@ -229,10 +229,10 @@ void AnimatedModel::UpdateBatches(const FrameInfo& frame)
         batches_[0].distance_ = distance_;
         batches_[0].worldTransform_ = &worldTransform;
     }
-    
+
     float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
     float newLodDistance = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
-    
+
     // If model is rendered from several views, use the minimum LOD distance for animation LOD
     if (frame.frameNumber_ != animationLodFrameNumber_)
     {
@@ -241,7 +241,7 @@ void AnimatedModel::UpdateBatches(const FrameInfo& frame)
     }
     else
         animationLodDistance_ = Min(animationLodDistance_, newLodDistance);
-    
+
     if (newLodDistance != lodDistance_)
     {
         lodDistance_ = newLodDistance;
@@ -253,7 +253,7 @@ void AnimatedModel::UpdateGeometry(const FrameInfo& frame)
 {
     if (morphsDirty_)
         UpdateMorphs();
-    
+
     if (skinningDirty_)
         UpdateSkinning();
 }
@@ -272,7 +272,7 @@ void AnimatedModel::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (debug && IsEnabledEffective())
     {
-        debug->AddBoundingBox(GetWorldBoundingBox(), Color(0.0f, 1.0f, 0.0f), depthTest);
+        debug->AddBoundingBox(GetWorldBoundingBox(), Color::GREEN, depthTest);
         debug->AddSkeleton(skeleton_, Color(0.75f, 0.75f, 0.75f), depthTest);
     }
 }
@@ -281,15 +281,15 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
 {
     if (!model || model == model_)
         return;
-    
+
     // Unsubscribe from the reload event of previous model (if any), then subscribe to the new
     if (model_)
         UnsubscribeFromEvent(model_, E_RELOADFINISHED);
     if (model)
         SubscribeToEvent(model, E_RELOADFINISHED, HANDLER(AnimatedModel, HandleModelReloadFinished));
-    
+
     model_ = model;
-    
+
     // Copy the subgeometry & LOD level structure
     SetNumGeometries(model->GetNumGeometries());
     const Vector<Vector<SharedPtr<Geometry> > >& geometries = model->GetGeometries();
@@ -299,14 +299,14 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
         geometries_[i] = geometries[i];
         geometryData_[i].center_ = geometryCenters[i];
     }
-    
+
     // Copy geometry bone mappings
     const Vector<PODVector<unsigned> >& geometryBoneMappings = model->GetGeometryBoneMappings();
     geometryBoneMappings_.Clear();
     geometryBoneMappings_.Reserve(geometryBoneMappings.Size());
     for (unsigned i = 0; i < geometryBoneMappings.Size(); ++i)
         geometryBoneMappings_.Push(geometryBoneMappings[i]);
-    
+
     // Copy morphs. Note: morph vertex buffers will be created later on-demand
     morphVertexBuffers_.Clear();
     morphs_.Clear();
@@ -324,12 +324,12 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
             morphElementMask_ |= j->second_.elementMask_;
         morphs_.Push(newMorph);
     }
-    
+
     // Copy bounding box & skeleton
     SetBoundingBox(model->GetBoundingBox());
     SetSkeleton(model->GetSkeleton(), createBones);
     ResetLodLevels();
-    
+
     // Enable skinning in batches
     for (unsigned i = 0; i < batches_.Size(); ++i)
     {
@@ -356,7 +356,7 @@ void AnimatedModel::SetModel(Model* model, bool createBones)
             batches_[i].shaderDataSize_ = 0;
         }
     }
-    
+
     MarkNetworkUpdate();
 }
 
@@ -367,15 +367,15 @@ AnimationState* AnimatedModel::AddAnimationState(Animation* animation)
         LOGERROR("Can not add animation state to non-master model");
         return 0;
     }
-    
+
     if (!animation || !skeleton_.GetNumBones())
         return 0;
-    
+
     // Check for not adding twice
     AnimationState* existing = GetAnimationState(animation);
     if (existing)
         return existing;
-    
+
     SharedPtr<AnimationState> newState(new AnimationState(this, animation));
     animationStates_.Push(newState);
     MarkAnimationOrderDirty();
@@ -465,7 +465,7 @@ void AnimatedModel::SetInvisibleLodFactor(float factor)
         factor = 0.0f;
     else if (factor != 0.0f && factor < 1.0f)
         factor = 1.0f;
-    
+
     invisibleLodFactor_ = factor;
     MarkNetworkUpdate();
 }
@@ -474,23 +474,23 @@ void AnimatedModel::SetMorphWeight(unsigned index, float weight)
 {
     if (index >= morphs_.Size())
         return;
-    
+
     // If morph vertex buffers have not been created yet, create now
     if (weight > 0.0f && morphVertexBuffers_.Empty())
         CloneGeometries();
-    
+
     weight = Clamp(weight, 0.0f, 1.0f);
-    
+
     if (weight != morphs_[index].weight_)
     {
         morphs_[index].weight_ = weight;
-        
+
         // For a master model, set the same morph weight on non-master models
         if (isMaster_)
         {
             PODVector<AnimatedModel*> models;
             GetComponents<AnimatedModel>(models);
-            
+
             // Indexing might not be the same, so use the name hash instead
             for (unsigned i = 1; i < models.Size(); ++i)
             {
@@ -498,7 +498,7 @@ void AnimatedModel::SetMorphWeight(unsigned index, float weight)
                     models[i]->SetMorphWeight(morphs_[index].nameHash_, weight);
             }
         }
-        
+
         MarkMorphsDirty();
         MarkNetworkUpdate();
     }
@@ -532,20 +532,20 @@ void AnimatedModel::ResetMorphWeights()
 {
     for (Vector<ModelMorph>::Iterator i = morphs_.Begin(); i != morphs_.End(); ++i)
         i->weight_ = 0.0f;
-    
+
     // For a master model, reset weights on non-master models
     if (isMaster_)
     {
         PODVector<AnimatedModel*> models;
         GetComponents<AnimatedModel>(models);
-        
+
         for (unsigned i = 1; i < models.Size(); ++i)
         {
             if (!models[i]->isMaster_)
                 models[i]->ResetMorphWeights();
         }
     }
-    
+
     MarkMorphsDirty();
     MarkNetworkUpdate();
 }
@@ -562,7 +562,7 @@ float AnimatedModel::GetMorphWeight(const String& name) const
         if (i->name_ == name)
             return i->weight_;
     }
-    
+
     return 0.0f;
 }
 
@@ -573,7 +573,7 @@ float AnimatedModel::GetMorphWeight(StringHash nameHash) const
         if (i->nameHash_ == nameHash)
             return i->weight_;
     }
-    
+
     return 0.0f;
 }
 
@@ -584,7 +584,7 @@ AnimationState* AnimatedModel::GetAnimationState(Animation* animation) const
         if ((*i)->GetAnimation() == animation)
             return *i;
     }
-    
+
     return 0;
 }
 
@@ -610,7 +610,7 @@ AnimationState* AnimatedModel::GetAnimationState(StringHash animationNameHash) c
                 return *i;
         }
     }
-    
+
     return 0;
 }
 
@@ -626,7 +626,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
         LOGERROR("AnimatedModel not attached to a scene node, can not create bone nodes");
         return;
     }
-    
+
     if (isMaster_)
     {
         // Check if bone structure has stayed compatible (reloading the model.) In that case retain the old bones and animations
@@ -635,7 +635,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
             Vector<Bone>& destBones = skeleton_.GetModifiableBones();
             const Vector<Bone>& srcBones = skeleton.GetBones();
             bool compatible = true;
-            
+
             for (unsigned i = 0; i < destBones.Size(); ++i)
             {
                 if (destBones[i].node_ && destBones[i].name_ == srcBones[i].name_ && destBones[i].parentIndex_ ==
@@ -657,9 +657,9 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
             if (compatible)
                 return;
         }
-        
+
         RemoveAllAnimationStates();
-        
+
         // Detach the rootbone of the previous model if any
         if (createBones)
         {
@@ -667,9 +667,9 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
             if (rootBone)
                 node_->RemoveChild(rootBone->node_);
         }
-        
+
         skeleton_.Define(skeleton);
-        
+
         // Remove collision information from dummy bones that do not affect skinning, to prevent them from being merged
         // to the bounding box
         Vector<Bone>& bones = skeleton_.GetModifiableBones();
@@ -680,7 +680,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
             if (i->collisionMask_ & BONECOLLISION_SPHERE && i->radius_ < M_EPSILON)
                 i->collisionMask_ &= ~BONECOLLISION_SPHERE;
         }
-        
+
         // Create scene nodes for the bones
         if (createBones)
         {
@@ -692,7 +692,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
                 boneNode->SetTransform(i->initialPosition_, i->initialRotation_, i->initialScale_);
                 i->node_ = boneNode;
             }
-            
+
             for (unsigned i = 0; i < bones.Size(); ++i)
             {
                 unsigned parentIndex = bones[i].parentIndex_;
@@ -700,11 +700,11 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
                     bones[parentIndex].node_->AddChild(bones[i].node_);
             }
         }
-        
+
         MarkAnimationDirty();
-        
+
         using namespace BoneHierarchyCreated;
-        
+
         VariantMap eventData;
         eventData[P_NODE] = (void*)node_;
         node_->SendEvent(E_BONEHIERARCHYCREATED, eventData);
@@ -713,7 +713,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
     {
         // For non-master models: use the bone nodes of the master model
         skeleton_.Define(skeleton);
-        
+
         if (createBones)
         {
             Vector<Bone>& bones = skeleton_.GetModifiableBones();
@@ -726,11 +726,11 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
             }
         }
     }
-    
+
     // Reserve space for skinning matrices
     skinMatrices_.Resize(skeleton_.GetNumBones());
     SetGeometryBoneMappings();
-    
+
     assignBonesPending_ = !createBones;
 }
 
@@ -763,7 +763,7 @@ void AnimatedModel::SetAnimationStatesAttr(VariantVector value)
             const ResourceRef& animRef = value[index++].GetResourceRef();
             SharedPtr<AnimationState> newState(new AnimationState(this, cache->GetResource<Animation>(animRef.id_)));
             animationStates_.Push(newState);
-            
+
             newState->SetStartBone(skeleton_.GetBone(value[index++].GetString()));
             newState->SetLooped(value[index++].GetBool());
             newState->SetWeight(value[index++].GetFloat());
@@ -777,7 +777,7 @@ void AnimatedModel::SetAnimationStatesAttr(VariantVector value)
             animationStates_.Push(newState);
         }
     }
-    
+
     MarkAnimationOrderDirty();
 }
 
@@ -827,14 +827,14 @@ const PODVector<unsigned char>& AnimatedModel::GetMorphsAttr() const
     attrBuffer_.Clear();
     for (Vector<ModelMorph>::ConstIterator i = morphs_.Begin(); i != morphs_.End(); ++i)
         attrBuffer_.WriteUByte((unsigned char)(i->weight_ * 255.0f));
-    
+
     return attrBuffer_.GetBuffer();
 }
 
 void AnimatedModel::OnNodeSet(Node* node)
 {
     Drawable::OnNodeSet(node);
-    
+
     // If this AnimatedModel is the first in the node, it is the master which controls animation & morphs
     isMaster_ = GetComponent<AnimatedModel>() == this;
 }
@@ -842,7 +842,7 @@ void AnimatedModel::OnNodeSet(Node* node)
 void AnimatedModel::OnMarkedDirty(Node* node)
 {
     Drawable::OnMarkedDirty(node);
-    
+
     // If the scene node or any of the bone nodes move, mark skinning dirty
     skinningDirty_ = true;
 }
@@ -855,14 +855,14 @@ void AnimatedModel::OnWorldBoundingBoxUpdate()
     {
         // If has bones, update world bounding box based on them
         worldBoundingBox_.defined_ = false;
-        
+
         const Vector<Bone>& bones = skeleton_.GetBones();
         for (Vector<Bone>::ConstIterator i = bones.Begin(); i != bones.End(); ++i)
         {
             Node* boneNode = i->node_;
             if (!boneNode)
                 continue;
-            
+
             // Use hitbox if available. If not, use only half of the sphere radius
             if (i->collisionMask_ & BONECOLLISION_BOX)
                 worldBoundingBox_.Merge(i->boundingBox_.Transformed(boneNode->GetWorldTransform()));
@@ -875,10 +875,10 @@ void AnimatedModel::OnWorldBoundingBoxUpdate()
 void AnimatedModel::AssignBoneNodes()
 {
     assignBonesPending_ = false;
-    
+
     if (!node_)
         return;
-    
+
     // Find the bone nodes from the node hierarchy and add listeners
     Vector<Bone>& bones = skeleton_.GetModifiableBones();
     bool boneFound = false;
@@ -892,19 +892,19 @@ void AnimatedModel::AssignBoneNodes()
         }
         i->node_ = boneNode;
     }
-    
+
     // If no bones found, this may be a prefab where the bone information was left out.
     // In that case reassign the skeleton now if possible
     if (!boneFound && model_)
         SetSkeleton(model_->GetSkeleton(), true);
-    
+
     // Re-assign the same start bone to animations to get the proper bone node this time
     for (Vector<SharedPtr<AnimationState> >::Iterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
     {
         AnimationState* state = *i;
         state->SetStartBone(state->GetStartBone());
     }
-    
+
     MarkAnimationDirty();
 }
 
@@ -938,7 +938,7 @@ void AnimatedModel::CloneGeometries()
     const Vector<SharedPtr<VertexBuffer> >& originalVertexBuffers = model_->GetVertexBuffers();
     HashMap<VertexBuffer*, SharedPtr<VertexBuffer> > clonedVertexBuffers;
     morphVertexBuffers_.Resize(originalVertexBuffers.Size());
-    
+
     for (unsigned i = 0; i < originalVertexBuffers.Size(); ++i)
     {
         VertexBuffer* original = originalVertexBuffers[i];
@@ -959,7 +959,7 @@ void AnimatedModel::CloneGeometries()
         else
             morphVertexBuffers_[i].Reset();
     }
-    
+
     // Geometries will always be cloned fully. They contain only references to buffer, so they are relatively light
     for (unsigned i = 0; i < geometries_.Size(); ++i)
     {
@@ -967,7 +967,7 @@ void AnimatedModel::CloneGeometries()
         {
             SharedPtr<Geometry> original = geometries_[i][j];
             SharedPtr<Geometry> clone(new Geometry(context_));
-            
+
             // Add an additional vertex stream into the clone, which supplies only the morphable vertex data, while the static
             // data comes from the original vertex buffer(s)
             const Vector<SharedPtr<VertexBuffer> >& originalBuffers = original->GetVertexBuffers();
@@ -979,13 +979,13 @@ void AnimatedModel::CloneGeometries()
                     ++totalBuf;
             }
             clone->SetNumVertexBuffers(totalBuf);
-            
+
             unsigned l = 0;
             for (unsigned k = 0; k < originalBuffers.Size(); ++k)
             {
                 VertexBuffer* originalBuffer = originalBuffers[k];
                 unsigned originalMask = original->GetVertexElementMask(k);
-                
+
                 if (clonedVertexBuffers.Contains(originalBuffer))
                 {
                     VertexBuffer* clonedBuffer = clonedVertexBuffers[originalBuffer];
@@ -995,15 +995,15 @@ void AnimatedModel::CloneGeometries()
                 else
                     clone->SetVertexBuffer(l++, originalBuffer, originalMask);
             }
-            
+
             clone->SetIndexBuffer(original->GetIndexBuffer());
             clone->SetDrawRange(original->GetPrimitiveType(), original->GetIndexStart(), original->GetIndexCount());
             clone->SetLodDistance(original->GetLodDistance());
-            
+
             geometries_[i][j] = clone;
         }
     }
-    
+
     // Make sure the rendering batches use the new cloned geometries
     ResetLodLevels();
     MarkMorphsDirty();
@@ -1017,7 +1017,7 @@ void AnimatedModel::CopyMorphVertices(void* destVertexData, void* srcVertexData,
     unsigned vertexSize = srcBuffer->GetVertexSize();
     float* dest = (float*)destVertexData;
     unsigned char* src = (unsigned char*)srcVertexData;
-    
+
     while (vertexCount--)
     {
         if (mask & MASK_POSITION)
@@ -1042,7 +1042,7 @@ void AnimatedModel::CopyMorphVertices(void* destVertexData, void* srcVertexData,
             *dest++ = tangentSrc[2];
             *dest++ = tangentSrc[3];
         }
-        
+
         src += vertexSize;
     }
 }
@@ -1051,24 +1051,24 @@ void AnimatedModel::SetGeometryBoneMappings()
 {
     geometrySkinMatrices_.Clear();
     geometrySkinMatrixPtrs_.Clear();
-    
+
     if (!geometryBoneMappings_.Size())
         return;
-    
+
     // Check if all mappings are empty, then we do not need to use mapped skinning
     bool allEmpty = true;
     for (unsigned i = 0; i < geometryBoneMappings_.Size(); ++i)
         if (geometryBoneMappings_[i].Size())
             allEmpty = false;
-    
+
     if (allEmpty)
         return;
-    
+
     // Reserve space for per-geometry skinning matrices
     geometrySkinMatrices_.Resize(geometryBoneMappings_.Size());
     for (unsigned i = 0; i < geometryBoneMappings_.Size(); ++i)
         geometrySkinMatrices_[i].Resize(geometryBoneMappings_[i].Size());
-    
+
     // Build original-to-skinindex matrix pointer mapping for fast copying
     // Note: at this point layout of geometrySkinMatrices_ cannot be modified or pointers become invalid
     geometrySkinMatrixPtrs_.Resize(skeleton_.GetNumBones());
@@ -1096,19 +1096,19 @@ void AnimatedModel::UpdateAnimation(const FrameInfo& frame)
         else
             animationLodTimer_ = 0.0f;
     }
-    
+
     // Make sure animations are in ascending priority order
     if (animationOrderDirty_)
     {
         Sort(animationStates_.Begin(), animationStates_.End(), CompareAnimationOrder);
         animationOrderDirty_ = false;
     }
-    
+
     // Reset skeleton, then apply all animations
     skeleton_.Reset();
     for (Vector<SharedPtr<AnimationState> >::Iterator i = animationStates_.Begin(); i != animationStates_.End(); ++i)
         (*i)->Apply();
-    
+
     // Animation has changed the bounding box: mark node for octree reinsertion
     Drawable::OnMarkedDirty(node_);
     // For optimization, recalculate world bounding box already here (during the threaded update)
@@ -1122,7 +1122,7 @@ void AnimatedModel::UpdateSkinning()
     const Vector<Bone>& bones = skeleton_.GetBones();
     // Use model's world transform in case a bone is missing
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
-    
+
     // Skinning with global matrices only
     if (!geometrySkinMatrices_.Size())
     {
@@ -1145,13 +1145,13 @@ void AnimatedModel::UpdateSkinning()
                 skinMatrices_[i] = bone.node_->GetWorldTransform() * bone.offsetMatrix_;
             else
                 skinMatrices_[i] = worldTransform;
-            
+
             // Copy the skin matrix to per-geometry matrices as needed
             for (unsigned j = 0; j < geometrySkinMatrixPtrs_[i].Size(); ++j)
                 *geometrySkinMatrixPtrs_[i][j] = skinMatrices_[i];
         }
     }
-    
+
     skinningDirty_ = false;
 }
 
@@ -1160,7 +1160,7 @@ void AnimatedModel::UpdateMorphs()
     Graphics* graphics = GetSubsystem<Graphics>();
     if (!graphics)
         return;
-    
+
     if (morphs_.Size())
     {
         // Reset the morph data range from all morphable vertex buffers, then apply morphs
@@ -1172,14 +1172,14 @@ void AnimatedModel::UpdateMorphs()
                 VertexBuffer* originalBuffer = model_->GetVertexBuffers()[i];
                 unsigned morphStart = model_->GetMorphRangeStart(i);
                 unsigned morphCount = model_->GetMorphRangeCount(i);
-                
+
                 void* dest = buffer->Lock(morphStart, morphCount);
                 if (dest)
                 {
                     // Reset morph range by copying data from the original vertex buffer
                     CopyMorphVertices(dest, originalBuffer->GetShadowData() + morphStart * originalBuffer->GetVertexSize(),
                         morphCount, buffer, originalBuffer);
-                    
+
                     for (unsigned j = 0; j < morphs_.Size(); ++j)
                     {
                         if (morphs_[j].weight_ > 0.0f)
@@ -1189,13 +1189,13 @@ void AnimatedModel::UpdateMorphs()
                                 ApplyMorph(buffer, dest, morphStart, k->second_, morphs_[j].weight_);
                         }
                     }
-                    
+
                     buffer->Unlock();
                 }
             }
         }
     }
-    
+
     morphsDirty_ = false;
 }
 
@@ -1206,15 +1206,15 @@ void AnimatedModel::ApplyMorph(VertexBuffer* buffer, void* destVertexData, unsig
     unsigned normalOffset = buffer->GetElementOffset(ELEMENT_NORMAL);
     unsigned tangentOffset = buffer->GetElementOffset(ELEMENT_TANGENT);
     unsigned vertexSize = buffer->GetVertexSize();
-    
+
     unsigned char* srcData = morph.morphData_;
     unsigned char* destData = (unsigned char*)destVertexData;
-    
+
     while (vertexCount--)
     {
         unsigned vertexIndex = *((unsigned*)srcData) - morphRangeStart;
         srcData += sizeof(unsigned);
-        
+
         if (elementMask & MASK_POSITION)
         {
             float* dest = (float*)(destData + vertexIndex * vertexSize);

+ 14 - 14
Engine/Graphics/Drawable.cpp

@@ -1,4 +1,4 @@
-//  
+//
 
 // Copyright (c) 2008-2013 the Urho3D project.
 //
@@ -102,7 +102,7 @@ void Drawable::RegisterObject(Context* context)
 void Drawable::OnSetEnabled()
 {
     bool enabled = IsEnabledEffective();
-    
+
     if (enabled && !octant_)
         AddToOctree();
     else if (!enabled && octant_)
@@ -127,16 +127,16 @@ void Drawable::UpdateBatches(const FrameInfo& frame)
 {
     const Matrix3x4& worldTransform = node_->GetWorldTransform();
     distance_ = frame.camera_->GetDistance(node_->GetWorldPosition());
-    
+
     for (unsigned i = 0; i < batches_.Size(); ++i)
     {
         batches_[i].distance_ = distance_;
         batches_[i].worldTransform_ = &worldTransform;
     }
-    
+
     float scale = GetWorldBoundingBox().Size().DotProduct(DOT_SCALE);
     float newLodDistance = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
-    
+
     if (newLodDistance != lodDistance_)
         lodDistance_ = newLodDistance;
 }
@@ -153,7 +153,7 @@ Geometry* Drawable::GetLodGeometry(unsigned batchIndex, unsigned level)
 void Drawable::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (debug && IsEnabledEffective())
-        debug->AddBoundingBox(GetWorldBoundingBox(), Color(0.0f, 1.0f, 0.0f), depthTest);
+        debug->AddBoundingBox(GetWorldBoundingBox(), Color::GREEN, depthTest);
 }
 
 void Drawable::SetDrawDistance(float distance)
@@ -243,7 +243,7 @@ const BoundingBox& Drawable::GetWorldBoundingBox()
         OnWorldBoundingBoxUpdate();
         worldBoundingBoxDirty_ = false;
     }
-    
+
     return worldBoundingBox_;
 }
 
@@ -251,7 +251,7 @@ void Drawable::SetZone(Zone* zone, bool temporary)
 {
     zone_ = zone;
     lastZone_ = zone;
-    
+
     // If the zone assignment was temporary (inconclusive) set the dirty flag so that it will be re-evaluated on the next frame
     zoneDirty_ = temporary;
 }
@@ -311,12 +311,12 @@ void Drawable::LimitLights()
     // Maximum lights value 0 means unlimited
     if (!maxLights_ || lights_.Size() <= maxLights_)
         return;
-    
+
     // If more lights than allowed, move to vertex lights and cut the list
     const BoundingBox& box = GetWorldBoundingBox();
     for (unsigned i = 0; i < lights_.Size(); ++i)
         lights_[i]->SetIntensitySortValue(box);
-    
+
     Sort(lights_.Begin(), lights_.End(), CompareDrawables);
     vertexLights_.Insert(vertexLights_.End(), lights_.Begin() + maxLights_, lights_.End());
     lights_.Resize(maxLights_);
@@ -326,11 +326,11 @@ void Drawable::LimitVertexLights()
 {
     if (vertexLights_.Size() <= MAX_VERTEX_LIGHTS)
         return;
-    
+
     const BoundingBox& box = GetWorldBoundingBox();
     for (unsigned i = vertexLights_.Size() - 1; i < vertexLights_.Size(); --i)
         vertexLights_[i]->SetIntensitySortValue(box);
-    
+
     Sort(vertexLights_.Begin(), vertexLights_.End(), CompareDrawables);
     vertexLights_.Resize(MAX_VERTEX_LIGHTS);
 }
@@ -361,7 +361,7 @@ void Drawable::OnMarkedDirty(Node* node)
     worldBoundingBoxDirty_ = true;
     if (!reinsertionQueued_ && octant_)
         octant_->GetRoot()->QueueReinsertion(this);
-    
+
     // Mark zone assignment dirty. Due to possibly being called from a worker thread, it is unsafe to manipulate the Zone weak
     // pointer here
     if (node == node_)
@@ -373,7 +373,7 @@ void Drawable::AddToOctree()
     // Do not add to octree when disabled
     if (!IsEnabledEffective())
         return;
-    
+
     Scene* scene = GetScene();
     if (scene)
     {

+ 104 - 79
Engine/UI/UI.cpp

@@ -176,15 +176,15 @@ bool UI::SetModalElement(UIElement* modalElement, bool enable)
     // Only allow one modal element at a time, only the currently active modal element can disable itself
     if (modalElement_ && modalElement != modalElement_)
         return false;
-    
+
     // The modal element must be parented to root
     if (modalElement->GetParent() != rootElement_)
         return false;
-    
+
     // Currently only allow modal window
     if (modalElement && modalElement->GetType() != Window::GetTypeStatic())
         return false;
-    
+
     modalElement_ = enable ? modalElement : 0;
     return true;
 }
@@ -288,85 +288,28 @@ void UI::RenderUpdate()
 
 void UI::Render()
 {
-    // Engine does not render when window is closed or device is lost
-    assert(graphics_ && graphics_->IsInitialized() && !graphics_->IsDeviceLost());
-
     PROFILE(RenderUI);
+    Render(batches_, vertexData_);
+    Render(debugDrawBatches_, debugDrawVertexData_);
 
-    if (vertexData_.Empty())
-        return;
-
-    // Update quad geometry into the vertex buffer
-    unsigned numVertices = vertexData_.Size() / UI_VERTEX_SIZE;
-    // Resize the vertex buffer if too small or much too large
-    if (vertexBuffer_->GetVertexCount() < numVertices || vertexBuffer_->GetVertexCount() > numVertices * 2)
-        vertexBuffer_->SetSize(numVertices, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1, true);
-
-    vertexBuffer_->SetData(&vertexData_[0]);
-
-    Vector2 invScreenSize(1.0f / (float)graphics_->GetWidth(), 1.0f / (float)graphics_->GetHeight());
-    Vector2 scale(2.0f * invScreenSize.x_, -2.0f * invScreenSize.y_);
-    Vector2 offset(-1.0f, 1.0f);
-
-    Matrix4 projection(Matrix4::IDENTITY);
-    projection.m00_ = scale.x_;
-    projection.m03_ = offset.x_;
-    projection.m11_ = scale.y_;
-    projection.m13_ = offset.y_;
-    projection.m22_ = 1.0f;
-    projection.m23_ = 0.0f;
-    projection.m33_ = 1.0f;
-
-    graphics_->ClearParameterSources();
-    graphics_->SetCullMode(CULL_CCW);
-    graphics_->SetDepthTest(CMP_ALWAYS);
-    graphics_->SetDepthWrite(false);
-    graphics_->SetStencilTest(false);
-    graphics_->ResetRenderTargets();
-
-    ShaderVariation* ps = 0;
-    ShaderVariation* vs = 0;
-
-    unsigned alphaFormat = Graphics::GetAlphaFormat();
+    // Clear the debug draw batches and data
+    debugDrawBatches_.Clear();
+    debugDrawVertexData_.Clear();
+}
 
-    for (unsigned i = 0; i < batches_.Size(); ++i)
+void UI::DebugDraw(UIElement* element)
+{
+    if (element)
     {
-        const UIBatch& batch = batches_[i];
-        if (batch.vertexStart_ == batch.vertexEnd_)
-            continue;
-
-        if (!batch.texture_)
-        {
-            ps = noTexturePS_;
-            vs = noTextureVS_;
-        }
-        else
-        {
-            // If texture contains only an alpha channel, use alpha shader (for fonts)
-            vs = diffTextureVS_;
-
-            if (batch.texture_->GetFormat() == alphaFormat)
-                ps = alphaTexturePS_;
-            else if (batch.blendMode_ != BLEND_ALPHA && batch.blendMode_ != BLEND_ADDALPHA && batch.blendMode_ != BLEND_PREMULALPHA)
-                ps = diffMaskTexturePS_;
-            else
-                ps = diffTexturePS_;
-        }
+        const IntVector2& rootSize = rootElement_->GetSize();
+        IntRect currentScissor = IntRect(0, 0, rootSize.x_, rootSize.y_);
 
-        graphics_->SetShaders(vs, ps);
-        if (graphics_->NeedParameterUpdate(SP_OBJECTTRANSFORM, this))
-            graphics_->SetShaderParameter(VSP_MODEL, Matrix3x4::IDENTITY);
-        if (graphics_->NeedParameterUpdate(SP_CAMERA, this))
-            graphics_->SetShaderParameter(VSP_VIEWPROJ, projection);
-        if (graphics_->NeedParameterUpdate(SP_MATERIAL, this))
-            graphics_->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
+        // Set clipping scissor for child elements. No need to draw if zero size
+        element->AdjustScissor(currentScissor);
+        if (currentScissor.left_ == currentScissor.right_ || currentScissor.top_ == currentScissor.bottom_)
+            return;
 
-        graphics_->SetBlendMode(batch.blendMode_);
-        graphics_->SetScissorTest(true, batch.scissor_);
-        graphics_->SetTexture(0, batch.texture_);
-        graphics_->SetVertexBuffer(vertexBuffer_);
-        graphics_->Draw(TRIANGLE_LIST, batch.vertexStart_ / UI_VERTEX_SIZE, (batch.vertexEnd_ - batch.vertexStart_) /
-            UI_VERTEX_SIZE);
+        element->GetDebugDrawBatches(debugDrawBatches_, debugDrawVertexData_, currentScissor);
     }
 }
 
@@ -529,6 +472,88 @@ void UI::Update(float timeStep, UIElement* element)
         Update(timeStep, *i);
 }
 
+void UI::Render(const PODVector<UIBatch>& batches, const PODVector<float>& vertexData)
+{
+    // Engine does not render when window is closed or device is lost
+    assert(graphics_ && graphics_->IsInitialized() && !graphics_->IsDeviceLost());
+
+    if (vertexData.Empty())
+        return;
+
+    // Update quad geometry into the vertex buffer
+    unsigned numVertices = vertexData.Size() / UI_VERTEX_SIZE;
+    // Resize the vertex buffer if too small or much too large
+    if (vertexBuffer_->GetVertexCount() < numVertices || vertexBuffer_->GetVertexCount() > numVertices * 2)
+        vertexBuffer_->SetSize(numVertices, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1, true);
+
+    vertexBuffer_->SetData(&vertexData[0]);
+
+    Vector2 invScreenSize(1.0f / (float)graphics_->GetWidth(), 1.0f / (float)graphics_->GetHeight());
+    Vector2 scale(2.0f * invScreenSize.x_, -2.0f * invScreenSize.y_);
+    Vector2 offset(-1.0f, 1.0f);
+
+    Matrix4 projection(Matrix4::IDENTITY);
+    projection.m00_ = scale.x_;
+    projection.m03_ = offset.x_;
+    projection.m11_ = scale.y_;
+    projection.m13_ = offset.y_;
+    projection.m22_ = 1.0f;
+    projection.m23_ = 0.0f;
+    projection.m33_ = 1.0f;
+
+    graphics_->ClearParameterSources();
+    graphics_->SetCullMode(CULL_CCW);
+    graphics_->SetDepthTest(CMP_ALWAYS);
+    graphics_->SetDepthWrite(false);
+    graphics_->SetStencilTest(false);
+    graphics_->ResetRenderTargets();
+
+    ShaderVariation* ps = 0;
+    ShaderVariation* vs = 0;
+
+    unsigned alphaFormat = Graphics::GetAlphaFormat();
+
+    for (unsigned i = 0; i < batches.Size(); ++i)
+    {
+        const UIBatch& batch = batches[i];
+        if (batch.vertexStart_ == batch.vertexEnd_)
+            continue;
+
+        if (!batch.texture_)
+        {
+            ps = noTexturePS_;
+            vs = noTextureVS_;
+        }
+        else
+        {
+            // If texture contains only an alpha channel, use alpha shader (for fonts)
+            vs = diffTextureVS_;
+
+            if (batch.texture_->GetFormat() == alphaFormat)
+                ps = alphaTexturePS_;
+            else if (batch.blendMode_ != BLEND_ALPHA && batch.blendMode_ != BLEND_ADDALPHA && batch.blendMode_ != BLEND_PREMULALPHA)
+                ps = diffMaskTexturePS_;
+            else
+                ps = diffTexturePS_;
+        }
+
+        graphics_->SetShaders(vs, ps);
+        if (graphics_->NeedParameterUpdate(SP_OBJECTTRANSFORM, this))
+            graphics_->SetShaderParameter(VSP_MODEL, Matrix3x4::IDENTITY);
+        if (graphics_->NeedParameterUpdate(SP_CAMERA, this))
+            graphics_->SetShaderParameter(VSP_VIEWPROJ, projection);
+        if (graphics_->NeedParameterUpdate(SP_MATERIAL, this))
+            graphics_->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
+
+        graphics_->SetBlendMode(batch.blendMode_);
+        graphics_->SetScissorTest(true, batch.scissor_);
+        graphics_->SetTexture(0, batch.texture_);
+        graphics_->SetVertexBuffer(vertexBuffer_);
+        graphics_->Draw(TRIANGLE_LIST, batch.vertexStart_ / UI_VERTEX_SIZE, (batch.vertexEnd_ - batch.vertexStart_) /
+            UI_VERTEX_SIZE);
+    }
+}
+
 void UI::GetBatches(UIElement* element, IntRect currentScissor)
 {
     // Set clipping scissor for child elements. No need to draw if zero size
@@ -598,7 +623,7 @@ void UI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2&
         {
             bool shouldSkip = true;
             UIElement* originElement = static_cast<UIElement*>(element->GetVar(VAR_ORIGIN).GetPtr());
-            
+
             while (originElement)
             {
                 if (originElement == modalElement_)
@@ -608,11 +633,11 @@ void UI::GetElementAt(UIElement*& result, UIElement* current, const IntVector2&
                 }
                 originElement = originElement->GetParent();
             }
-            
+
             if (shouldSkip)
                 continue;
         }
-        
+
         bool hasChildren = element->GetNumChildren() > 0;
 
         if (element != cursor_.Get() && element->IsVisible())

+ 8 - 0
Engine/UI/UI.h

@@ -62,6 +62,8 @@ public:
     void RenderUpdate();
     /// Render the UI.
     void Render();
+    /// Debug draw a UI element.
+    void DebugDraw(UIElement* element);
     /// Load a UI layout from an XML file. Optionally specify another XML file for element style. Return the root element.
     SharedPtr<UIElement> LoadLayout(Deserializer& source, XMLFile* styleFile = 0);
     /// Load a UI layout from an XML file. Optionally specify another XML file for element style. Return the root element.
@@ -99,6 +101,8 @@ private:
     void Initialize();
     /// Update UI element logic recursively.
     void Update(float timeStep, UIElement* element);
+    /// Render the batches.
+    void Render(const PODVector<UIBatch>& batches, const PODVector<float>& vertexData);
     /// Generate batches from an UI element recursively.
     void GetBatches(UIElement* element, IntRect currentScissor);
     /// Return UI element at screen position recursively.
@@ -158,6 +162,10 @@ private:
     PODVector<UIBatch> batches_;
     /// UI rendering vertex data.
     PODVector<float> vertexData_;
+    /// UI rendering batches for debug draw.
+    PODVector<UIBatch> debugDrawBatches_;
+    /// UI rendering vertex data for debug draw.
+    PODVector<float> debugDrawVertexData_;
     /// UI vertex buffer.
     SharedPtr<VertexBuffer> vertexBuffer_;
     /// UI element query vector.

+ 16 - 0
Engine/UI/UIElement.cpp

@@ -37,6 +37,8 @@
 namespace Urho3D
 {
 
+const Color DEBUG_DRAW_COLOR(Color::BLUE);
+
 const char* horizontalAlignments[] =
 {
     "Left",
@@ -311,6 +313,20 @@ void UIElement::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertex
     hovering_ = false;
 }
 
+void UIElement::GetDebugDrawBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+{
+    UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
+    // Left
+    batch.AddQuad(0, 0, 1, size_.y_, 0, 0, 0, 0, DEBUG_DRAW_COLOR);
+    // Top
+    batch.AddQuad(0, 0, size_.x_, 1, 0, 0, 0, 0, DEBUG_DRAW_COLOR);
+    // Right
+    batch.AddQuad(size_.x_ - 1, 0, 1, size_.y_, 0, 0, 0, 0, DEBUG_DRAW_COLOR);
+    // Bottom
+    batch.AddQuad(0, size_.y_ - 1, size_.x_, 1, 0, 0, 0, 0, DEBUG_DRAW_COLOR);
+    UIBatch::AddOrMerge(batch, batches);
+}
+
 bool UIElement::IsWithinScissor(const IntRect& currentScissor)
 {
     if (!visible_)

+ 2 - 0
Engine/UI/UIElement.h

@@ -138,6 +138,8 @@ public:
     virtual const IntVector2& GetScreenPosition() const;
     /// Return UI rendering batches.
     virtual void GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
+    /// Return UI rendering batches for debug draw.
+    virtual void GetDebugDrawBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor);
     /// React to mouse hover.
     virtual void OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor);
     /// React to mouse click.

+ 20 - 28
Engine/UI/Window.cpp

@@ -74,39 +74,31 @@ void Window::RegisterObject(Context* context)
 
 void Window::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
 {
-    if (modal_ && modalShadeColor_ != Color::TRANSPARENT)
+    if (modal_)
     {
         // Modal shade
-        UIElement* rootElement = GetRoot();
-        const IntVector2& rootSize = rootElement->GetSize();
-        IntRect currentScissor(0, 0, rootSize.x_, rootSize.y_);
-        UIBatch batch(rootElement, BLEND_ALPHA, IntRect(0, 0, rootSize.x_, rootSize.y_), 0, &vertexData);
-        batch.AddQuad(0, 0, rootSize.x_, rootSize.y_, 0, 0, 0, 0, modalShadeColor_);
-        UIBatch::AddOrMerge(batch, batches);
-    }
-
-    BorderImage::GetBatches(batches, vertexData, currentScissor);
+        if (modalShadeColor_ != Color::TRANSPARENT)
+        {
+            UIElement* rootElement = GetRoot();
+            const IntVector2& rootSize = rootElement->GetSize();
+            UIBatch batch(rootElement, BLEND_ALPHA, IntRect(0, 0, rootSize.x_, rootSize.y_), 0, &vertexData);
+            batch.AddQuad(0, 0, rootSize.x_, rootSize.y_, 0, 0, 0, 0, modalShadeColor_);
+            UIBatch::AddOrMerge(batch, batches);
+        }
 
-    if (modal_ && modalFrameColor_ != Color::TRANSPARENT && modalFrameSize_ != IntVector2::ZERO)
-    {
         // Modal frame
-        UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
-
-        int x = GetIndentWidth();
-        IntVector2 size = GetSize();
-        size.x_ -= x;
-
-        // Left
-        batch.AddQuad(x - modalFrameSize_.x_, 0, modalFrameSize_.x_, size.y_, 0, 0, 0, 0, modalFrameColor_);
-        // Top
-        batch.AddQuad(x - modalFrameSize_.x_, -modalFrameSize_.y_, size.x_ + 2 * modalFrameSize_.x_, modalFrameSize_.y_, 0, 0, 0, 0, modalFrameColor_);
-        // Right
-        batch.AddQuad(size.x_, 0, modalFrameSize_.x_, size.y_, 0, 0, 0, 0, modalFrameColor_);
-        // Bottom
-        batch.AddQuad(x - modalFrameSize_.x_, size.y_, size.x_ + 2 * modalFrameSize_.x_, modalFrameSize_.y_, 0, 0, 0, 0, modalFrameColor_);
-
-        UIBatch::AddOrMerge(batch, batches);
+        if (modalFrameColor_ != Color::TRANSPARENT && modalFrameSize_ != IntVector2::ZERO)
+        {
+            UIBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
+            int x = GetIndentWidth();
+            IntVector2 size = GetSize();
+            size.x_ -= x;
+            batch.AddQuad(x - modalFrameSize_.x_, -modalFrameSize_.y_, size.x_ + 2 * modalFrameSize_.x_, size.y_ + 2 * modalFrameSize_.y_, 0, 0, 0, 0, modalFrameColor_);
+            UIBatch::AddOrMerge(batch, batches);
+        }
     }
+
+    BorderImage::GetBatches(batches, vertexData, currentScissor);
 }
 
 void Window::OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)