Browse Source

3D UI cleanup and update

Turns out 3D UI implementation was not flexible enough. This change allows ultimate freedom by splitting up responsibilities correctly.

UIComponent is no longer responsible for storing batches and vertex data. This information was moved to new `RenderToTextureData` struct inside `UI` subsystem. `UI` and `UIComponent` are no longer friends of each other.

New class `UIElement3D` was created. It is used as root UI element of UI rendered in a 3D scene. It implements `ScreenToElement()` method which is used for translating screen coordinates to element coordinates. Previously `UIComponent` did translation of these coordinates. `UIElement3D::ElementToScreen()` is not implemented at this time and returns `{-1, -1}` (invalid coordinates). It is also possible to set a custom viewport to root element of `UIComponent` and this viewport will be used for processing user input.

In order to render UIElement to a texture it is enough to call SetRenderTexture(texture), where texture is set up as `TEXTURE_RENDERTARGET`. In order to properly support input of `UIElement` rendered to a texture user should subclass `UIElement` and implement `ScreenToElement()` method which properly translates global screen coordinates to local element coordinates.

New API:
* UIElement::SetRenderTexture(Texture2D*)
* UI::SetRenderTexture(UIElement*, Texture2D*)
Rokas Kupstys 8 years ago
parent
commit
c5c560e759

+ 56 - 38
Source/Urho3D/UI/UI.cpp

@@ -441,32 +441,34 @@ void UI::RenderUpdate()
     }
     }
 
 
     // Get batches for UI elements rendered into textures. Each element rendered into texture is treated as root element.
     // Get batches for UI elements rendered into textures. Each element rendered into texture is treated as root element.
-    for (Vector<WeakPtr<UIComponent> >::Iterator it = renderToTexture_.Begin(); it != renderToTexture_.End();)
+    for (auto it = renderToTexture_.Begin(); it != renderToTexture_.End();)
     {
     {
-        WeakPtr<UIComponent> component = *it;
-        if (component.Null() || !component->IsEnabled())
+        RenderToTextureData& data = it->second_;
+        if (data.rootElement_.Expired())
             it = renderToTexture_.Erase(it);
             it = renderToTexture_.Erase(it);
-        else if (component->IsEnabled())
+        else if (data.rootElement_->IsEnabled())
         {
         {
-            component->batches_.Clear();
-            component->vertexData_.Clear();
-            UIElement* element = component->GetRoot();
+            data.batches_.Clear();
+            data.vertexData_.Clear();
+            UIElement* element = data.rootElement_;
             const IntVector2& size = element->GetSize();
             const IntVector2& size = element->GetSize();
             const IntVector2& pos = element->GetPosition();
             const IntVector2& pos = element->GetPosition();
             // Note: the scissors operate on unscaled coordinates. Scissor scaling is only performed during render
             // Note: the scissors operate on unscaled coordinates. Scissor scaling is only performed during render
             IntRect scissor = IntRect(pos.x_, pos.y_, pos.x_ + size.x_, pos.y_ + size.y_);
             IntRect scissor = IntRect(pos.x_, pos.y_, pos.x_ + size.x_, pos.y_ + size.y_);
-            GetBatches(component->batches_, component->vertexData_, element, scissor);
+            GetBatches(data.batches_, data.vertexData_, element, scissor);
 
 
             // UIElement does not have anything to show. Insert dummy batch that will clear the texture.
             // UIElement does not have anything to show. Insert dummy batch that will clear the texture.
-            if (component->batches_.Empty())
+            if (data.batches_.Empty())
             {
             {
-                UIBatch batch(element, BLEND_REPLACE, scissor, nullptr, &component->vertexData_);
+                UIBatch batch(element, BLEND_REPLACE, scissor, nullptr, &data.vertexData_);
                 batch.SetColor(Color::BLACK);
                 batch.SetColor(Color::BLACK);
                 batch.AddQuad(scissor.left_, scissor.top_, scissor.right_, scissor.bottom_, 0, 0);
                 batch.AddQuad(scissor.left_, scissor.top_, scissor.right_, scissor.bottom_, 0, 0);
-                component->batches_.Push(batch);
+                data.batches_.Push(batch);
             }
             }
             ++it;
             ++it;
         }
         }
+        else
+            ++it;
     }
     }
 }
 }
 
 
@@ -501,23 +503,23 @@ void UI::Render(bool renderUICommand)
     // Render to UIComponent textures. This is skipped when called from the RENDERUI command
     // Render to UIComponent textures. This is skipped when called from the RENDERUI command
     if (!renderUICommand)
     if (!renderUICommand)
     {
     {
-        for (Vector<WeakPtr<UIComponent> >::ConstIterator it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
+        for (auto it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
         {
         {
-            WeakPtr<UIComponent> component = *it;
-            if (component->IsEnabled())
+            RenderToTextureData& data = it->second_;
+            if (data.rootElement_->IsEnabled())
             {
             {
-                SetVertexData(component->vertexBuffer_, component->vertexData_);
-                SetVertexData(component->debugVertexBuffer_, component->debugVertexData_);
+                SetVertexData(data.vertexBuffer_, data.vertexData_);
+                SetVertexData(data.debugVertexBuffer_, data.debugVertexData_);
 
 
-                RenderSurface* surface = component->GetTexture()->GetRenderSurface();
+                RenderSurface* surface = data.texture_->GetRenderSurface();
                 graphics_->SetRenderTarget(0, surface);
                 graphics_->SetRenderTarget(0, surface);
                 graphics_->SetViewport(IntRect(0, 0, surface->GetWidth(), surface->GetHeight()));
                 graphics_->SetViewport(IntRect(0, 0, surface->GetWidth(), surface->GetHeight()));
                 graphics_->Clear(Urho3D::CLEAR_COLOR);
                 graphics_->Clear(Urho3D::CLEAR_COLOR);
 
 
-                Render(component->vertexBuffer_, component->batches_, 0, component->batches_.Size());
-                Render(component->debugVertexBuffer_, component->debugDrawBatches_, 0, component->debugDrawBatches_.Size());
-                component->debugDrawBatches_.Clear();
-                component->debugVertexData_.Clear();
+                Render(data.vertexBuffer_, data.batches_, 0, data.batches_.Size());
+                Render(data.debugVertexBuffer_, data.debugDrawBatches_, 0, data.debugDrawBatches_.Size());
+                data.debugDrawBatches_.Clear();
+                data.debugVertexData_.Clear();
             }
             }
         }
         }
 
 
@@ -546,12 +548,12 @@ void UI::DebugDraw(UIElement* element)
             element->GetDebugDrawBatches(debugDrawBatches_, debugVertexData_, scissor);
             element->GetDebugDrawBatches(debugDrawBatches_, debugVertexData_, scissor);
         else
         else
         {
         {
-            for (Vector<WeakPtr<UIComponent> >::Iterator it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
+            for (auto it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
             {
             {
-                WeakPtr<UIComponent> component = *it;
-                if (component.NotNull() && component->GetRoot() == root && component->IsEnabled())
+                RenderToTextureData& data = it->second_;
+                if (!data.rootElement_.Expired() && data.rootElement_ == root && data.rootElement_->IsEnabled())
                 {
                 {
-                    element->GetDebugDrawBatches(component->debugDrawBatches_, component->debugVertexData_, scissor);
+                    element->GetDebugDrawBatches(data.debugDrawBatches_, data.debugVertexData_, scissor);
                     break;
                     break;
                 }
                 }
             }
             }
@@ -768,16 +770,16 @@ UIElement* UI::GetElementAt(const IntVector2& position, bool enabledOnly, IntVec
     // Mouse was not hovering UI element. Check elements rendered on 3D objects.
     // Mouse was not hovering UI element. Check elements rendered on 3D objects.
     if (!result && renderToTexture_.Size())
     if (!result && renderToTexture_.Size())
     {
     {
-        for (Vector<WeakPtr<UIComponent> >::Iterator it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
+        for (auto it = renderToTexture_.Begin(); it != renderToTexture_.End(); it++)
         {
         {
-            WeakPtr<UIComponent> component = *it;
-            if (component.Null() || !component->IsEnabled())
+            RenderToTextureData& data = it->second_;
+            if (data.rootElement_.Expired() || !data.rootElement_->IsEnabled())
                 continue;
                 continue;
 
 
-            IntVector2 screenPosition;
-            if (component->ScreenToUIPosition(position, screenPosition))
+            IntVector2 screenPosition = data.rootElement_->ScreenToElement(position);
+            if (data.rootElement_->GetCombinedScreenRect().IsInside(screenPosition) == INSIDE)
             {
             {
-                result = GetElementAt(component->GetRoot(), screenPosition, enabledOnly);
+                result = GetElementAt(data.rootElement_, screenPosition, enabledOnly);
                 if (result)
                 if (result)
                 {
                 {
                     if (elementScreenPosition)
                     if (elementScreenPosition)
@@ -2076,16 +2078,31 @@ IntVector2 UI::GetEffectiveRootElementSize(bool applyScale) const
     return size;
     return size;
 }
 }
 
 
-void UI::SetRenderToTexture(UIComponent* component, bool enable)
+void UI::SetRenderTexture(UIElement* element, Texture2D* texture)
 {
 {
-    WeakPtr<UIComponent> weak(component);
-    if (enable)
+    if (element == nullptr)
     {
     {
-        if (!renderToTexture_.Contains(weak))
-            renderToTexture_.Push(weak);
+        URHO3D_LOGERROR("UI::SetRenderTexture called with null element.");
+        return;
+    }
+
+    auto it = renderToTexture_.Find(element);
+    if (texture && it == renderToTexture_.End())
+    {
+        RenderToTextureData data;
+        data.texture_ = texture;
+        data.rootElement_ = element;
+        data.vertexBuffer_ = new VertexBuffer(context_);
+        data.debugVertexBuffer_ = new VertexBuffer(context_);
+        renderToTexture_[element] = data;
+    }
+    else if (it != renderToTexture_.End())
+    {
+        if (texture == nullptr)
+            renderToTexture_.Erase(it);
+        else
+            it->second_.texture_ = texture;
     }
     }
-    else
-        renderToTexture_.Remove(weak);
 }
 }
 
 
 void RegisterUILibrary(Context* context)
 void RegisterUILibrary(Context* context)
@@ -2114,6 +2131,7 @@ void RegisterUILibrary(Context* context)
     ProgressBar::RegisterObject(context);
     ProgressBar::RegisterObject(context);
     ToolTip::RegisterObject(context);
     ToolTip::RegisterObject(context);
     UIComponent::RegisterObject(context);
     UIComponent::RegisterObject(context);
+    UIElement3D::RegisterObject(context);
 }
 }
 
 
 }
 }

+ 25 - 6
Source/Urho3D/UI/UI.h

@@ -23,6 +23,7 @@
 #pragma once
 #pragma once
 
 
 #include "../Core/Object.h"
 #include "../Core/Object.h"
+#include "../Graphics/VertexBuffer.h"
 #include "../UI/Cursor.h"
 #include "../UI/Cursor.h"
 #include "../UI/UIBatch.h"
 #include "../UI/UIBatch.h"
 
 
@@ -48,7 +49,6 @@ class ResourceCache;
 class Timer;
 class Timer;
 class UIBatch;
 class UIBatch;
 class UIElement;
 class UIElement;
-class VertexBuffer;
 class XMLElement;
 class XMLElement;
 class XMLFile;
 class XMLFile;
 class RenderSurface;
 class RenderSurface;
@@ -212,8 +212,8 @@ public:
     /// Return root element custom size. Returns 0,0 when custom size is not being used and automatic resizing according to window size is in use instead (default.)
     /// Return root element custom size. Returns 0,0 when custom size is not being used and automatic resizing according to window size is in use instead (default.)
     const IntVector2& GetCustomSize() const { return customSize_; }
     const IntVector2& GetCustomSize() const { return customSize_; }
 
 
-    /// Register UIElement for being rendered into a texture.
-    void SetRenderToTexture(UIComponent* component, bool enable);
+    /// Set texture to which element will be rendered.
+    void SetRenderTexture(UIElement* element, Texture2D* texture);
 
 
     /// Data structure used to represent the drag data associated to a UIElement.
     /// Data structure used to represent the drag data associated to a UIElement.
     struct DragData
     struct DragData
@@ -233,6 +233,27 @@ public:
     };
     };
 
 
 private:
 private:
+    /// Data structured used to hold data of UI elements that are rendered to texture.
+    struct RenderToTextureData
+    {
+        /// UIElement to be rendered into texture.
+        WeakPtr<UIElement> rootElement_;
+        /// Texture that UIElement will be rendered into.
+        SharedPtr<Texture2D> texture_;
+        /// UI rendering batches.
+        PODVector<UIBatch> batches_;
+        /// UI rendering vertex data.
+        PODVector<float> vertexData_;
+        /// UI vertex buffer.
+        SharedPtr<VertexBuffer> vertexBuffer_;
+        /// UI rendering batches for debug draw.
+        PODVector<UIBatch> debugDrawBatches_;
+        /// UI rendering vertex data for debug draw.
+        PODVector<float> debugVertexData_;
+        /// UI debug geometry vertex buffer.
+        SharedPtr<VertexBuffer> debugVertexBuffer_;
+    };
+
     /// Initialize when screen mode initially set.
     /// Initialize when screen mode initially set.
     void Initialize();
     void Initialize();
     /// Update UI element logic recursively.
     /// Update UI element logic recursively.
@@ -398,9 +419,7 @@ private:
     /// Root element custom size. 0,0 for automatic resizing (default.)
     /// Root element custom size. 0,0 for automatic resizing (default.)
     IntVector2 customSize_;
     IntVector2 customSize_;
     /// Elements that should be rendered to textures.
     /// Elements that should be rendered to textures.
-    Vector<WeakPtr<UIComponent> > renderToTexture_;
-
-    friend class UIComponent;
+    HashMap<UIElement*, RenderToTextureData> renderToTexture_;
 };
 };
 
 
 /// Register UI library objects.
 /// Register UI library objects.

+ 65 - 28
Source/Urho3D/UI/UIComponent.cpp

@@ -45,16 +45,21 @@ static int const UICOMPONENT_DEFAULT_TEXTURE_SIZE = 512;
 static int const UICOMPONENT_MIN_TEXTURE_SIZE = 64;
 static int const UICOMPONENT_MIN_TEXTURE_SIZE = 64;
 static int const UICOMPONENT_MAX_TEXTURE_SIZE = 4096;
 static int const UICOMPONENT_MAX_TEXTURE_SIZE = 4096;
 
 
+
 UIComponent::UIComponent(Context* context) : 
 UIComponent::UIComponent(Context* context) : 
     Component(context),
     Component(context),
     isStaticModelOwned_(false)
     isStaticModelOwned_(false)
 {
 {
-    vertexBuffer_ = new VertexBuffer(context_);
-    debugVertexBuffer_ = new VertexBuffer(context_);
     texture_ = context_->CreateObject<Texture2D>();
     texture_ = context_->CreateObject<Texture2D>();
+    texture_->SetFilterMode(FILTER_BILINEAR);
+    texture_->SetAddressMode(COORD_U, ADDRESS_CLAMP);
+    texture_->SetAddressMode(COORD_V, ADDRESS_CLAMP);
+    texture_->SetNumLevels(1);                                        // No mipmaps
 
 
-    rootElement_ = context_->CreateObject<UIElement>();
+    rootElement_ = context_->CreateObject<UIElement3D>();
+    rootElement_->SetUIComponent(this);
     rootElement_->SetTraversalMode(TM_BREADTH_FIRST);
     rootElement_->SetTraversalMode(TM_BREADTH_FIRST);
+    rootElement_->SetEnabled(true);
 
 
     material_ = context_->CreateObject<Material>();
     material_ = context_->CreateObject<Material>();
     material_->SetTechnique(0, GetSubsystem<ResourceCache>()->GetResource<Technique>("Techniques/Diff.xml"));
     material_->SetTechnique(0, GetSubsystem<ResourceCache>()->GetResource<Technique>("Techniques/Diff.xml"));
@@ -63,6 +68,7 @@ UIComponent::UIComponent(Context* context) :
     SubscribeToEvent(rootElement_, E_RESIZED, URHO3D_HANDLER(UIComponent, OnElementResized));
     SubscribeToEvent(rootElement_, E_RESIZED, URHO3D_HANDLER(UIComponent, OnElementResized));
 
 
     // Triggers resizing of texture.
     // Triggers resizing of texture.
+    rootElement_->SetRenderTexture(texture_);
     rootElement_->SetSize(UICOMPONENT_DEFAULT_TEXTURE_SIZE, UICOMPONENT_DEFAULT_TEXTURE_SIZE);
     rootElement_->SetSize(UICOMPONENT_DEFAULT_TEXTURE_SIZE, UICOMPONENT_DEFAULT_TEXTURE_SIZE);
 }
 }
 
 
@@ -90,6 +96,10 @@ Texture2D* UIComponent::GetTexture() const
     return texture_;
     return texture_;
 }
 }
 
 
+StaticModel* UIComponent::GetModel() const
+{
+    return model_;
+}
 
 
 void UIComponent::OnNodeSet(Node* node)
 void UIComponent::OnNodeSet(Node* node)
 {
 {
@@ -102,9 +112,11 @@ void UIComponent::OnNodeSet(Node* node)
             model_ = node->CreateComponent<StaticModel>();
             model_ = node->CreateComponent<StaticModel>();
         }
         }
         model_->SetMaterial(material_);
         model_->SetMaterial(material_);
+        rootElement_->SetRenderTexture(texture_);
     }
     }
     else
     else
     {
     {
+        rootElement_->SetRenderTexture(nullptr);
         model_->SetMaterial(nullptr);
         model_->SetMaterial(nullptr);
         if (isStaticModelOwned_)
         if (isStaticModelOwned_)
         {
         {
@@ -113,11 +125,6 @@ void UIComponent::OnNodeSet(Node* node)
         }
         }
         model_ = nullptr;
         model_ = nullptr;
     }
     }
-
-    UI* ui = GetSubsystem<UI>();
-    // May be null on shutdown
-    if (ui)
-        ui->SetRenderToTexture(this, node != nullptr);
 }
 }
 
 
 void UIComponent::OnElementResized(StringHash eventType, VariantMap& args)
 void UIComponent::OnElementResized(StringHash eventType, VariantMap& args)
@@ -134,38 +141,42 @@ void UIComponent::OnElementResized(StringHash eventType, VariantMap& args)
     }
     }
 
 
     if (texture_->SetSize(width, height, GetSubsystem<Graphics>()->GetRGBAFormat(), TEXTURE_RENDERTARGET))
     if (texture_->SetSize(width, height, GetSubsystem<Graphics>()->GetRGBAFormat(), TEXTURE_RENDERTARGET))
-    {
-        texture_->SetFilterMode(FILTER_BILINEAR);
-        texture_->SetAddressMode(COORD_U, ADDRESS_CLAMP);
-        texture_->SetAddressMode(COORD_V, ADDRESS_CLAMP);
-        texture_->SetNumLevels(1);                                                // No mipmaps
         texture_->GetRenderSurface()->SetUpdateMode(SURFACE_MANUALUPDATE);
         texture_->GetRenderSurface()->SetUpdateMode(SURFACE_MANUALUPDATE);
-    }
     else
     else
         URHO3D_LOGERROR("UIComponent: resizing texture failed.");
         URHO3D_LOGERROR("UIComponent: resizing texture failed.");
 }
 }
 
 
-bool UIComponent::ScreenToUIPosition(IntVector2 screenPos, IntVector2& result)
+IntVector2 UIElement3D::ScreenToElement(const IntVector2& screenPos)
 {
 {
-    Scene* scene = GetScene();
+    IntVector2 result(-1, -1);
+
+    Scene* scene = component_->GetScene();
     if (!scene)
     if (!scene)
-        return false;
+        return result;
 
 
     Renderer* renderer = GetSubsystem<Renderer>();
     Renderer* renderer = GetSubsystem<Renderer>();
     if (!renderer)
     if (!renderer)
-        return false;
+        return result;
 
 
     // \todo Always uses the first viewport, in case there are multiple
     // \todo Always uses the first viewport, in case there are multiple
-    Viewport* viewport = renderer->GetViewportForScene(scene, 0);
     Octree* octree = scene->GetComponent<Octree>();
     Octree* octree = scene->GetComponent<Octree>();
+    Viewport* viewport = viewport_;
+    if (viewport == nullptr)
+        viewport = renderer->GetViewportForScene(scene, 0);
 
 
     if (!viewport || !octree)
     if (!viewport || !octree)
-        return false;
+        return result;
+
+    if (viewport->GetScene() != scene)
+    {
+        URHO3D_LOGERROR("UIComponent and Viewport set to component's root element belong to different scenes.");
+        return result;
+    }
 
 
     Camera* camera = viewport->GetCamera();
     Camera* camera = viewport->GetCamera();
 
 
     if (!camera)
     if (!camera)
-        return false;
+        return result;
 
 
     IntRect rect = viewport->GetRect();
     IntRect rect = viewport->GetRect();
     if (rect == IntRect::ZERO)
     if (rect == IntRect::ZERO)
@@ -182,25 +193,51 @@ bool UIComponent::ScreenToUIPosition(IntVector2 screenPos, IntVector2& result)
     octree->Raycast(query);
     octree->Raycast(query);
 
 
     if (queryResultVector.Empty())
     if (queryResultVector.Empty())
-        return false;
+        return result;
 
 
     for (unsigned i = 0; i < queryResultVector.Size(); i++)
     for (unsigned i = 0; i < queryResultVector.Size(); i++)
     {
     {
         RayQueryResult& queryResult = queryResultVector[i];
         RayQueryResult& queryResult = queryResultVector[i];
-        if (queryResult.drawable_ != model_)
+        if (queryResult.drawable_ != component_->GetModel())
         {
         {
             // ignore billboard sets by default
             // ignore billboard sets by default
             if (queryResult.drawable_->GetTypeInfo()->IsTypeOf(BillboardSet::GetTypeStatic()))
             if (queryResult.drawable_->GetTypeInfo()->IsTypeOf(BillboardSet::GetTypeStatic()))
                 continue;
                 continue;
-            return false;
+            return result;
         }
         }
 
 
         Vector2& uv = queryResult.textureUV_;
         Vector2& uv = queryResult.textureUV_;
-        result = IntVector2(static_cast<int>(uv.x_ * rootElement_->GetWidth()),
-                            static_cast<int>(uv.y_ * rootElement_->GetHeight()));
-        return true;
+        result = IntVector2(static_cast<int>(uv.x_ * component_->GetRoot()->GetWidth()),
+            static_cast<int>(uv.y_ * component_->GetRoot()->GetHeight()));
+        return result;
     }
     }
-    return false;
+    return result;
+}
+
+IntVector2 UIElement3D::ElementToScreen(const IntVector2& position)
+{
+    URHO3D_LOGERROR("UIElement3D::ElementToScreen is not implemented.");
+    return {-1, -1};
+}
+
+void UIElement3D::RegisterObject(Context* context)
+{
+    context->RegisterFactory<UIElement3D>();
+}
+
+UIElement3D::UIElement3D(Context* context)
+    : UIElement(context)
+{
+}
+
+void UIElement3D::SetUIComponent(UIComponent* component)
+{
+    component_ = component;
+}
+
+void UIElement3D::SetViewport(Viewport* viewport)
+{
+    viewport_ = viewport;
 }
 }
 
 
 }
 }

+ 32 - 16
Source/Urho3D/UI/UIComponent.h

@@ -22,6 +22,7 @@
 #pragma once
 #pragma once
 
 
 #include "../Scene/Component.h"
 #include "../Scene/Component.h"
+#include "../UI/UIElement.h"
 
 
 namespace Urho3D
 namespace Urho3D
 {
 {
@@ -33,6 +34,7 @@ class Viewport;
 class UIElement;
 class UIElement;
 class UIBatch;
 class UIBatch;
 class VertexBuffer;
 class VertexBuffer;
+class UIElement3D;
 
 
 class URHO3D_API UIComponent : public Component
 class URHO3D_API UIComponent : public Component
 {
 {
@@ -52,6 +54,8 @@ public:
     Material* GetMaterial() const;
     Material* GetMaterial() const;
     /// Return texture which will be used for rendering UI to.
     /// Return texture which will be used for rendering UI to.
     Texture2D* GetTexture() const;
     Texture2D* GetTexture() const;
+    /// Return static model on to which UI will be rendered.
+    StaticModel* GetModel() const;
 
 
 protected:
 protected:
     /// Material that is set to the model.
     /// Material that is set to the model.
@@ -61,29 +65,41 @@ protected:
     /// Model that texture will be applied to.
     /// Model that texture will be applied to.
     SharedPtr<StaticModel> model_;
     SharedPtr<StaticModel> model_;
     /// UIElement to be rendered into texture.
     /// UIElement to be rendered into texture.
-    SharedPtr<UIElement> rootElement_;
-    /// UI rendering batches.
-    PODVector<UIBatch> batches_;
-    /// UI rendering vertex data.
-    PODVector<float> vertexData_;
-    /// UI vertex buffer.
-    SharedPtr<VertexBuffer> vertexBuffer_;
-    /// UI rendering batches for debug draw.
-    PODVector<UIBatch> debugDrawBatches_;
-    /// UI rendering vertex data for debug draw.
-    PODVector<float> debugVertexData_;
-    /// UI debug geometry vertex buffer.
-    SharedPtr<VertexBuffer> debugVertexBuffer_;
+    SharedPtr<UIElement3D> rootElement_;
     /// Is StaticModel component created by this component.
     /// Is StaticModel component created by this component.
     bool isStaticModelOwned_;
     bool isStaticModelOwned_;
 
 
+    /// Handle component being added to Node or removed from it.
     virtual void OnNodeSet(Node* node) override;
     virtual void OnNodeSet(Node* node) override;
     /// Handle resizing of element. Setting size of element will automatically resize texture. UIElement size matches size of texture.
     /// Handle resizing of element. Setting size of element will automatically resize texture. UIElement size matches size of texture.
     void OnElementResized(StringHash eventType, VariantMap& args);
     void OnElementResized(StringHash eventType, VariantMap& args);
-    /// Convert screen position to position on UIElement.
-    bool ScreenToUIPosition(IntVector2 screenPos, IntVector2& result);
+};
+
+class URHO3D_API UIElement3D : public UIElement
+{
+    URHO3D_OBJECT(UIElement3D, UIElement);
+public:
+    /// Construct.
+    UIElement3D(Context* context);
+    /// Destruct.
+    virtual ~UIElement3D() override = default;
+    /// Register object factory.
+    static void RegisterObject(Context* context);
 
 
-    friend class UI;
+    /// Set UIComponent which is using this element as root element.
+    void SetUIComponent(UIComponent* component);
+    /// Set active viewport through which this element is rendered. If viewport is not set, it defaults to first viewport.
+    void SetViewport(Viewport* viewport);
+    /// Convert screen coordinates to element coordinates.
+    IntVector2 ScreenToElement(const IntVector2& screenPos) override;
+    /// Convert element coordinates to screen coordinates.
+    IntVector2 ElementToScreen(const IntVector2& position) override;
+
+protected:
+    /// A UIComponent which owns this element.
+    WeakPtr<UIComponent> component_;
+    /// Viewport which renders this element.
+    WeakPtr<Viewport> viewport_;
 };
 };
 
 
 }
 }

+ 6 - 0
Source/Urho3D/UI/UIElement.cpp

@@ -2277,4 +2277,10 @@ void UIElement::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
     UpdateAttributeAnimations(eventData[P_TIMESTEP].GetFloat());
     UpdateAttributeAnimations(eventData[P_TIMESTEP].GetFloat());
 }
 }
 
 
+void UIElement::SetRenderTexture(Texture2D* texture)
+{
+    if (UI* ui = GetSubsystem<UI>())
+        ui->SetRenderTexture(this, texture);
+}
+
 }
 }

+ 4 - 0
Source/Urho3D/UI/UIElement.h

@@ -109,6 +109,7 @@ static const unsigned DD_SOURCE_AND_TARGET = 0x3;
 
 
 class Cursor;
 class Cursor;
 class ResourceCache;
 class ResourceCache;
+class Texture2D;
 
 
 /// Base class for %UI elements.
 /// Base class for %UI elements.
 class URHO3D_API UIElement : public Animatable
 class URHO3D_API UIElement : public Animatable
@@ -640,6 +641,9 @@ public:
     /// Return effective minimum size, also considering layout. Used internally.
     /// Return effective minimum size, also considering layout. Used internally.
     IntVector2 GetEffectiveMinSize() const;
     IntVector2 GetEffectiveMinSize() const;
 
 
+    /// Set texture to which element will be rendered.
+    void SetRenderTexture(Texture2D* texture);
+
 protected:
 protected:
     /// Handle attribute animation added.
     /// Handle attribute animation added.
     virtual void OnAttributeAnimationAdded() override;
     virtual void OnAttributeAnimationAdded() override;