Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImDrawList curve drawing apis #311

Closed
ocornut opened this issue Aug 28, 2015 · 7 comments
Closed

ImDrawList curve drawing apis #311

ocornut opened this issue Aug 28, 2015 · 7 comments

Comments

@ocornut
Copy link
Owner

ocornut commented Aug 28, 2015

Following my example code in #306

I would like to introduce curve to the lower level ImDrawList API. I'm a bit ignorant on this matter so had to research it.

My main wonder is to find what's the best API for this sort of features. Here using (pos0,cp0,cp1,pos1) for bezier and (pos0,tan0,pos1,tan1) for Hermite makes sense according to apparently common representation but isn't it error-prone? Could Hermite be parametrized as (pos0,tan0,-tan1-pos1) ? Is that a thing that people do? When you add Catmull Rom into the mix, which as I understand are a subset of Hermite, then typically the inputs becomes (pos0,pos1,pos2,pos3), aka (pos0-tan0, pos0, pos1, pos1+tan1) expressed from the parameter of an Hermite curve. Should I skip general Hermite alltogether and just provide bezier & catmull rom?

Confusing.
Also haven't started looking at providing API taking multiple points to draw, e.g. a catmull rom from them.

Basically I want an API where different types of curves can co-exist, and user can draw piece-wise (single curves) or pass a connected list of points (in the case of, e.g. catmull rom splines).

Curiously it seemed hard to find reference curve API that were obvious, handled variants and not bloated. Partly because they tend to implement more complex features than just drawing.

Here what I got so far in ImDrawList as part of the pathing API.

Most simple/common case:

draw_list->AddCubicBezier(p0, cp0, cp1, p1, ImColor(255,0,0), thickness);

The Add* functions are generally helpers, here

inline    void  AddCubicBezier(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments = 10) 
{
    PathCubicBezierTo(pos0, cp0, cp1, pos1, num_segments);
    PathStroke(col, false, thickness); 
}
inline    void  AddCubicHermite(const ImVec2& pos0, const ImVec2& tan0, const ImVec2& pos1, const ImVec2& tan1, ImU32 col, float thickness, int num_segments = 10) 
{
    PathCubicHermiteTo(pos0, tan0, pos1, tan1, num_segments); 
    PathStroke(col, false, thickness);
}

NanoVG does:

// Adds cubic bezier segment from last point in the path via two control points to the specified point.
void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y);

Which it was I based it on except I added the first point as well (at the cost of not adding it if its a duplicate). I could also remove the first parameter from PathCubicXXTo() functions, and make AddCubicXX() functions call PathLineTo() to add a first point as well.

You can use the path functions to create path, mix curves and lines, stroke/fill the paths, etc.

void  PathCubicBezierTo(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, int num_segments = 10);
void  PathCubicHermiteTo(const ImVec2& pos0, const ImVec2& tan0, const ImVec2& pos1, const ImVec2& tan1, int num_segments = 10);

Also considering different strategy for tesselation:

  • make num_segments==0 by default, which would use an auto-tesselation strategy based on a global threshold (set in ImGuiStyle). Lead to different algorithms.
  • use specifying a number of segments will use that explicit step.

Basic implementation:

void ImDrawList::PathCubicBezierTo(const ImVec2& p0, const ImVec2& c0, const ImVec2& c1, const ImVec2& p1, int num_segments)
{
    PathLineToMergeDuplicate(p0);
    float t_step = 1.0f / (float)num_segments;
    for (int i_step = 1; i_step <= num_segments; i_step++)
    {
        float t = t_step * i_step;
        float u = 1.0f - t;
        float wp0 = u*u*u;
        float wc0 = 3*u*u*t;
        float wc1 = 3*u*t*t;
        float wp1 = t*t*t;
        _Path.push_back(ImVec2(wp0*p0.x + wc0*c0.x + wc1*c1.x + wp1*p1.x, wp0*p0.y + wc0*c0.y + wc1*c1.y + wp1*p1.y));
    }
}

void ImDrawList::PathCubicHermiteTo(const ImVec2& p0, const ImVec2& t0, const ImVec2& p1, const ImVec2& t1, int num_segments)
{
    PathLineToMergeDuplicate(p0);
    float t_step = 1.0f / (float)num_segments;
    for (int i_step = 1; i_step <= num_segments; i_step++)
    {
        float t = t_step * i_step;
        float wp0 = +2*t*t*t - 3*t*t + 1.0f;
        float wp1 = -2*t*t*t + 3*t*t;
        float wt0 =    t*t*t - 2*t*t + t;
        float wt1 =    t*t*t -   t*t;
        _Path.push_back(ImVec2(wp0*p0.x + wp1*p1.x + wt0*t0.x + wt1*t1.x, wp0*p0.y + wp1*p1.y + wt0*t0.y + wt1*t1.y));
    }
}
@ocornut
Copy link
Owner Author

ocornut commented Aug 28, 2015

NB I have edited the post above a few times.
I suppose I am over reaching here, especially if I'm adding things I don't totally grok. I could start with just adding the single bezier curve which is pretty standard. Mostly I'm wary of adding to change those API after they have been publicized.

Some links
http://pomax.github.io/bezierinfo/
http://cubic.org/docs/hermite.htm
http://cs.wellesley.edu/~cs307/readings/10-bezier.shtml#section_7

Some Code
https://code.google.com/p/fog/source/browse/trunk/Fog/Src/Fog/G2d/Geometry/

API
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D

@dumblob
Copy link

dumblob commented Aug 28, 2015

Should I skip general Hermite alltogether and just provide bezier & catmull rom?

I myself would skip everything except for Bezier. Does anyone here sees a firm reason for adding e.g. Catmull-Rom spline?

@dumblob
Copy link

dumblob commented Aug 28, 2015

Also considering different strategy for tesselation:
...

Sounds reasonable and not complicated for maintenance.

@ocornut
Copy link
Owner Author

ocornut commented Aug 28, 2015

I'll aim to just add cubic/quad bezier for now, as per your post here
#306 (comment)

Probably rename them also

AddBezier()
PathBezierTo()

AddQuadraticBezier()
PathQuadraticBezierTo()

ocornut added a commit that referenced this issue Aug 28, 2015
@ocornut
Copy link
Owner Author

ocornut commented Aug 29, 2015

I'm closing this for now. I think we're good for a while with just the cubic beziers.

@ocornut ocornut closed this as completed Aug 29, 2015
@ocornut
Copy link
Owner Author

ocornut commented Dec 17, 2019

Everyone using/writing node editors, @rokups has now added helpers functions in imgui_internal.h which may be of use to hover/interact with curves with the mouse:

ImVec2 ImBezierCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t);                                         // Cubic Bezier
ImVec2 ImBezierClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments);       // For curves with explicit number of segments
ImVec2 ImBezierClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol);// For auto-tessellated curves you can use tess_tol = style.CurveTessellationTol

@notno
Copy link

notno commented Apr 1, 2023

What is the recommended way to have control points that sit on the curve? Catmull-Rom would be useful for this, but I'm probably missing something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants