ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.14k stars 10.3k forks source link

Can ImDrawList support matrix transformation #8077

Closed sdragonx closed 3 weeks ago

sdragonx commented 3 weeks ago

Version/Branch of Dear ImGui:

-

Back-ends:

-

Compiler, OS:

-

Full config/build information:

-

Details:

request a feature

I hope to enhance the functionality of ImDrawList and add the function of matrix transformation.

exsample:

ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushClipRect(minPos, maxPos, true);
draw_list->AddRectFilled(minPos, maxPos, IM_COL32(0, 127, 255, 255));
draw_list->Translate(10.0f, 10.0f);
draw_list->AddRectFilled(minPos, maxPos, IM_COL32(0, 127, 255, 255));
draw_list->Scale(2.0f, 1.0f);
// ...
ImGui::PopClipRect();

Screenshots/Video:

-

Minimal, Complete and Verifiable Example code:

-

GamingMinds-DanielC commented 3 weeks ago

That might look like a simple extension on the surface, but only on the surface. How would simple rotations affect rectangles? You probably would want rotated rectangles, not just screen-aligned ones with transformed corner positions. That would imply transformations applied late in the process to the individual vertices added by a draw command. But then those draw commands can't set up vertices with anti-aliasing based on the resulting pixel positions. Basically most if not all of the drawing functions would have to take transformations into account for them to work properly.

Next there would be the issue of when and how to clean up your transformations. You can't just fire and forget, you need to be able to return to the previous state once you are done. Also, different users may want to use different kinds of transformations, you can't just assume that your use case is the norm. When drawing on top of a 3D scene (f.e. interactive widgets, debug lines and labels) you would have to set up 3D transformations, but then you would need 3D inputs for the drawing functions as well.

And after that there is still the performance impact. Even if no transformations are used, every single vertex of anything drawn on screen (all the standard widgets are using draw lists to draw themselves) would still be transformed by the identity matrix. That is a neutral operation, but it still takes processing time.

After these arguments against a direct integration, here are the good news: Nothing is stopping you from using transformations. You could implement a transformation stack, use it to transform vertices and feed them into the draw commands. You could integrate it into a custom wrapper and wrap the entire draw list. You can target it specifically to your needs and you don't even have to modify ImGui sources to do so.

ocornut commented 3 weeks ago

Hello,

This branch from thedmd https://github.com/thedmd/imgui/tree/feature/draw-list-transformation was an attempt as supporting this, but it raises several questions, some pointed by @GamingMinds-DanielC .

If you are trying to transform your own custom ImDrawList rendering, you can pre-transform it, or you can use drawList->AddCallback() to change the transformation matrix in your backend renderer. You would need to transform the ClipRect accordingly so that items are not misclipped during rendering.

I'm not expecting to add more support for this in the ImDrawList api now. Depending on what you are trying to achieve maybe https://github.com/john-chapman/im3d can be useful to you.

sdragonx commented 3 weeks ago

@ocornut

thank you. I plan to implement a canvas, and my current approach is to implement vertex transformations myself. I hope ImDrawList can achieve matrix transformation functionality like APIs like Gdiplus, many 2D drawing APIs have matrix transformation functionality.

Gdiplus::Graphics g(hdc);
Gdiplus::Matrix m;
m.Translate(x, y);
m.Rotate(-rotation);
m.Scale(flipX, flipY);
g.SetTransform(&m);
g.Draw(...);

my method:

void ImCanvas::drawLine(float x1, float y1, float x2, float y2)
{
    x1 = toScreenX(x1);
    y1 = toScreenY(y1);
    x2 = toScreenX(x2);
    y2 = toScreenY(y2);

    m_drawList->AddLine(m_location + ImVec2(x1, y1), m_location + ImVec2(x2, y2), m_penColor, m_penWidth);
}