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

Quadratic bezier demo #3665

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -2357,8 +2357,9 @@ struct ImDrawList
IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL);
IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, bool closed, float thickness);
IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); // Note: Anti-aliased filling requires points to be in clockwise order.
IMGUI_API void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0);

IMGUI_API void AddQuadBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quad Bezier (3 control points)
IMGUI_API void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points)

// Image primitives
// - Read FAQ to understand what ImTextureID is.
// - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle.
Expand All @@ -2374,8 +2375,9 @@ struct ImDrawList
inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } // Note: Anti-aliased filling requires points to be in clockwise order.
inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); _Path.Size = 0; }
IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 10);
IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle
IMGUI_API void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0);
IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle
IMGUI_API void PathQuadBezierCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quad Bezier (3 control points)
IMGUI_API void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0); // Cubic Bezier (4 control points)
IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawCornerFlags rounding_corners = ImDrawCornerFlags_All);

// Advanced
Expand Down
152 changes: 150 additions & 2 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6918,11 +6918,13 @@ static void ShowExampleAppCustomRendering(bool* p_open)

// Draw a bunch of primitives
ImGui::Text("All primitives");
static float sz = 36.0f;
static float sz = 33.0f;
static float thickness = 3.0f;
static int ngon_sides = 6;
static bool circle_segments_override = false;
static int circle_segments_override_v = 12;
static bool curve_segments_override = false;
static int curve_segments_override_v = 8;
static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f);
ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f");
ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f");
Expand All @@ -6931,6 +6933,10 @@ static void ShowExampleAppCustomRendering(bool* p_open)
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
if (ImGui::SliderInt("Circle segments", &circle_segments_override_v, 3, 40))
circle_segments_override = true;
ImGui::Checkbox("##curvessegmentoverride", &curve_segments_override);
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
if (ImGui::SliderInt("Curves segments", &curve_segments_override_v, 3, 40))
curve_segments_override = true;
ImGui::ColorEdit4("Color", &colf.x);

const ImVec2 p = ImGui::GetCursorScreenPos();
Expand All @@ -6940,6 +6946,7 @@ static void ShowExampleAppCustomRendering(bool* p_open)
const ImDrawCornerFlags corners_all = ImDrawCornerFlags_All;
const ImDrawCornerFlags corners_tl_br = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotRight;
const int circle_segments = circle_segments_override ? circle_segments_override_v : 0;
const int curve_segments = curve_segments_override ? curve_segments_override_v : 0;
float x = p.x + 4.0f;
float y = p.y + 4.0f;
for (int n = 0; n < 2; n++)
Expand All @@ -6956,7 +6963,8 @@ static void ShowExampleAppCustomRendering(bool* p_open)
draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!)
draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!)
draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line
draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x + sz*1.3f, y + sz*0.3f), ImVec2(x + sz - sz*1.3f, y + sz - sz*0.3f), ImVec2(x + sz, y + sz), col, th);
draw_list->AddQuadBezierCurve(ImVec2(x, y + sz * 0.6), ImVec2(x + sz * 0.5, y - sz * 0.4), ImVec2(x + sz, y + sz), col, th, curve_segments); x += sz + spacing; // Quad Bezier (3 control points)
draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x + sz * 1.3f, y + sz * 0.3f), ImVec2(x + sz - sz * 1.3f, y + sz - sz * 0.3f), ImVec2(x + sz, y + sz), col, th, curve_segments);
x = p.x + 4;
y += sz + spacing;
}
Expand Down Expand Up @@ -7074,6 +7082,146 @@ static void ShowExampleAppCustomRendering(bool* p_open)
ImGui::EndTabItem();
}

if (ImGui::BeginTabItem("Curves"))
{
static ImVec2 bezier_points[4] = { ImVec2(0.12, 0.82), ImVec2(0.34, 0.21), ImVec2(0.90, 0.41), ImVec2(0.56, 0.82) }; // ratio of canvas size
static float control_point_radius = 0.02f; // ratio of canvas size
static ImVec2 scrolling(0.0f, 0.0f);
static bool opt_enable_grid = true;
static int curve_type = 0; // 0 quad, 1 cubic
static bool curve_segments_override = false;
static int curve_segments_override_v = 8;
static int selected_control_point = -1;
static bool hovered_control_point[4] = { false, false, false, false };

ImGui::Checkbox("Enable grid", &opt_enable_grid);

ImGui::RadioButton("Quad Bezier", &curve_type, 0);
ImGui::SameLine(); HelpMarker("a Quad bezier have 3 control points;");
ImGui::SameLine(); ImGui::RadioButton("Cubic Bezier", &curve_type, 1);
ImGui::SameLine(); HelpMarker("a Cubic bezier have 4 control points;");
ImGui::Checkbox("##curvessegmentoverride", &curve_segments_override);
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
if (ImGui::SliderInt("Curves segments", &curve_segments_override_v, 3, 40))
curve_segments_override = true;

ImGui::Text("Mouse Left : move control points,\nMouse Right: drag to scroll");

// Using InvisibleButton() as a convenience 1) it will advance the layout cursor and 2) allows us to use IsItemHovered()/IsItemActive()
ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates!
ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available
if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f;
if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f;
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y);

// Draw border and background color
ImGuiIO& io = ImGui::GetIO();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255));
draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255));

// This will catch our interactions
ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
const bool is_active = ImGui::IsItemActive(); // Held
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); // Lock scrolled origin
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x, io.MousePos.y - origin.y);

// Pan (we use a zero mouse threshold when there's no context menu)
// You may decide to make that threshold dynamic based on whether the mouse is hovering something etc.
if (is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, 0.0f))
{
scrolling.x += io.MouseDelta.x;
scrolling.y += io.MouseDelta.y;
}

// Draw grid + all lines in the canvas
draw_list->PushClipRect(canvas_p0, canvas_p1, true);
if (opt_enable_grid)
{
const float GRID_STEP = 64.0f;
for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP)
draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40));
for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP)
draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40));
}

const ImU32 curve_color = IM_COL32(255, 255, 0, 255);
const ImU32 point_color = IM_COL32(255, 0, 0, 255);
const ImU32 hovered_point_color = IM_COL32(0, 127, 127, 255);
const ImU32 selected_point_color = IM_COL32(0, 0, 255, 255);
const ImU32 edge_color = IM_COL32(255, 255, 255, 255);
const int curve_segments = curve_segments_override ? curve_segments_override_v : 0;
ImVec2 canvas_size = ImVec2(canvas_p1.x - canvas_p0.x, canvas_p1.y - canvas_p0.y);
const float cp_radius = control_point_radius * canvas_size.x;

ImVec2 cp[4];

for (int i = 0; i < 3 + curve_type; i++)
{
cp[i] = ImVec2(origin.x + bezier_points[i].x * canvas_size.x, origin.y + bezier_points[i].y * canvas_size.y);
hovered_control_point[i] = false;

if (ImGui::IsMouseHoveringRect(
ImVec2(cp[i].x - cp_radius * 0.5f, cp[i].y - cp_radius * 0.5f),
ImVec2(cp[i].x + cp_radius * 0.5f, cp[i].y + cp_radius * 0.5f)))
{
hovered_control_point[i] = true;

if (selected_control_point < 0 && ImGui::IsMouseDown(ImGuiMouseButton_Left))
{
selected_control_point = i;
}
}

if (selected_control_point == i && ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f))
{
bezier_points[i].x += io.MouseDelta.x / canvas_size.x;
bezier_points[i].y += io.MouseDelta.y / canvas_size.y;
}
}

if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))
{
selected_control_point = -1;
}

if (curve_type) // >1 : cubic bezier
{
// curve
draw_list->AddBezierCurve(cp[0], cp[1], cp[2], cp[3], curve_color, 2.0f, curve_segments);

// control lines
draw_list->AddLine(cp[0], cp[1], edge_color, 1.5f);
draw_list->AddLine(cp[2], cp[3], edge_color, 1.5f);
}
else // ==0 : quad bezier
{
// curve
draw_list->AddQuadBezierCurve(cp[0], cp[1], cp[2], curve_color, 2.0f, curve_segments);

// polyline of 3 points
draw_list->AddLine(cp[0], cp[1], edge_color, 1.5f);
draw_list->AddLine(cp[1], cp[2], edge_color, 1.5f);
}

// control points
for (int i = 0; i < 3 + curve_type; i++)
{
if (selected_control_point == i)
draw_list->AddCircleFilled(cp[i], cp_radius, selected_point_color);
else if (hovered_control_point[i])
draw_list->AddCircleFilled(cp[i], cp_radius, hovered_point_color);
else
draw_list->AddCircleFilled(cp[i], cp_radius, point_color);

draw_list->AddCircle(cp[i], cp_radius, edge_color, 0, 2.0f);
}

draw_list->PopClipRect();

ImGui::EndTabItem();
}

if (ImGui::BeginTabItem("BG/FG draw lists"))
{
static bool draw_bg = true;
Expand Down
79 changes: 69 additions & 10 deletions imgui_draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,7 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa
}
}

// Cubic bezier
ImVec2 ImBezierCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t)
{
float u = 1.0f - t;
Expand All @@ -1060,13 +1061,14 @@ ImVec2 ImBezierCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const
return ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y);
}

// Cubic bezier
// Closely mimics BezierClosestPointCasteljauStep() in imgui.cpp
static void PathBezierToCasteljau(ImVector<ImVec2>* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level)
{
float dx = x4 - x1;
float dy = y4 - y1;
float d2 = ((x2 - x4) * dy - (y2 - y4) * dx);
float d3 = ((x3 - x4) * dy - (y3 - y4) * dx);
float d2 = (x2 - x4) * dy - (y2 - y4) * dx;
float d3 = (x3 - x4) * dy - (y3 - y4) * dx;
d2 = (d2 >= 0) ? d2 : -d2;
d3 = (d3 >= 0) ? d3 : -d3;
if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy))
Expand All @@ -1075,17 +1077,18 @@ static void PathBezierToCasteljau(ImVector<ImVec2>* path, float x1, float y1, fl
}
else if (level < 10)
{
float x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f;
float x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f;
float x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f;
float x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f;
float x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f;
float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f;
PathBezierToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1);
PathBezierToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1);
float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f;
float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f;
float x34 = (x3 + x4) * 0.5f, y34 = (y3 + y4) * 0.5f;
float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f;
float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f;
float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f;
PathBezierToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1);
PathBezierToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1);
}
}

// Cubic bezier
void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments)
{
ImVec2 p1 = _Path.back();
Expand All @@ -1101,6 +1104,51 @@ void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImV
}
}

// Quadratic bezier
ImVec2 ImQuadBezierCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t)
{
float u = 1.0f - t;
float w1 = u * u;
float w2 = 2 * u * t;
float w3 = t * t;
return ImVec2(w1 * p1.x + w2 * p2.x + w3 * p3.x, w1 * p1.y + w2 * p2.y + w3 * p3.y);
}

// Quadratic bezier
static void PathQuadBezierToCasteljau(ImVector<ImVec2>* path, float x1, float y1, float x2, float y2, float x3, float y3, float tess_tol, int level)
{
float dx = x3 - x1, dy = y3 - y1;
float det = (x2 - x3) * dy - (y2 - y3) * dx;
if (det * det * 4.0f < tess_tol * (dx * dx + dy * dy))
{
path->push_back(ImVec2(x3, y3));
}
else if (level < 10)
{
float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f;
float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f;
float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f;
PathQuadBezierToCasteljau(path, x1, y1, x12, y12, x123, y123, tess_tol, level + 1);
PathQuadBezierToCasteljau(path, x123, y123, x23, y23, x3, y3, tess_tol, level + 1);
}
}

// Quadratic bezier
void ImDrawList::PathQuadBezierCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments)
{
ImVec2 p1 = _Path.back();
if (num_segments == 0)
{
PathQuadBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0); // Auto-tessellated
}
else
{
float t_step = 1.0f / (float)num_segments;
for (int i_step = 1; i_step <= num_segments; i_step++)
_Path.push_back(ImQuadBezierCalc(p1, p2, p3, t_step * i_step));
}
}

void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawCornerFlags rounding_corners)
{
rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f);
Expand Down Expand Up @@ -1310,6 +1358,17 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in
PathFillConvex(col);
}

// Quad Bezier takes 3 controls points
void ImDrawList::AddQuadBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments)
{
if ((col & IM_COL32_A_MASK) == 0)
return;

PathLineTo(p1);
PathQuadBezierCurveTo(p2, p3, num_segments);
PathStroke(col, false, thickness);
}

// Cubic Bezier takes 4 controls points
void ImDrawList::AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments)
{
Expand Down