diff --git a/imgui.h b/imgui.h index a1fdb8afe..56c5ce898 100644 --- a/imgui.h +++ b/imgui.h @@ -3546,6 +3546,7 @@ struct ImDrawList // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); + IMGUI_API void AddPolylineLegacy(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); @@ -3646,6 +3647,8 @@ struct ImDrawList IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddPolylineThin(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); + IMGUI_API void _AddPolylineThick(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); }; // All draw data to render a Dear ImGui frame diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 7ac6fcc00..d179be420 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10419,7 +10419,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) 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, 100.0f, "%.0f"); - ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); + ImGui::DragFloat("Thickness", &thickness, 0.05f, 0.0f, 32.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d156b2243..3d9535b47 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -844,9 +844,729 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c _IdxWritePtr += 3; \ } (void)0 -// TODO: Thickness anti-aliased lines cap are missing their AA fringe. -// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. +#define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) +#define IM_POLYLINE_MITER_LIMIT (4.0f) + +static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImVec2* normals, float* sqr_lengths, bool closed) +{ + // Calculate normals for each line segment + for (int i = 0; i < points_count - 1; i++) + { + float dx = points[i+1].x - points[i].x; + float dy = points[i+1].y - points[i].y; + const float d2 = dx*dx + dy*dy; + const float inv_len = (d2 > 0.0f) ? 1.f / sqrtf(d2) : 0.f; //ImRsqrt(d2) : 0.f; + normals[i].x = -dy * inv_len; + normals[i].y = dx * inv_len; + sqr_lengths[i] = d2; + } + if (closed) + { + const float dx = points[0].x - points[points_count - 1].x; + const float dy = points[0].y - points[points_count - 1].y; + const float d2 = dx*dx + dy*dy; + const float inv_len = (d2 > 0.0f) ? 1.f / sqrtf(d2) : 0.f; //ImRsqrt(d2) : 0.f; + normals[points_count - 1].x = -dy * inv_len; + normals[points_count - 1].y = dx * inv_len; + sqr_lengths[points_count - 1] = d2; + } + else + { + normals[points_count - 1] = normals[points_count - 2]; + sqr_lengths[points_count - 1] = 0.f; + } +} + +void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) +{ + const bool closed = (flags & ImDrawFlags_Closed) != 0; + + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* normals = _Data->TempBuffer.Data; + float* sqr_lengths = (float*)(normals + points_count); + CalcSegmentNormals(points, points_count, normals, sqr_lengths, closed); + + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + thickness += _FringeScale; + const float half_thickness = thickness * 0.5f; + const float miter_distance_limit = half_thickness * IM_POLYLINE_MITER_LIMIT; + const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; + const float half_aa = _FringeScale * 0.5f; + + // TODO: Each point will generate 2-7 vertices. The worst case happens really rarely. This can be issue with large polylines and 16it indices. + // One option could be to calculate the miter values and overl & bevel flags before, then alloc, and finally commit. + int idx_count = 0; + int vtx_count = 0; + if (closed) + { + vtx_count = /*body*/points_count * 7 + /*closing*/3; + idx_count = (/*body*/points_count * 5 + /*closing*/4) * 3; + } + else + { + // Body + caps + vtx_count = /*body*/(points_count - 2) * 7 + /*caps*/(6 * 2); + idx_count = (/*body*/(points_count - 2) * 5 + /*last seg*/4 + /*caps*/(4 * 2)) * 3; + } + + PrimReserve(idx_count, vtx_count); + + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + int base_idx = (int)_VtxCurrentIdx; + + ImVec2 p1; + ImVec2 n1; + float len_sqr1; + + const float half_texel = (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x; + const ImVec2 uv0(tex_uvs.x + half_texel, tex_uvs.y); + const ImVec2 uv1(tex_uvs.z - half_texel, tex_uvs.y); + const ImVec2 uv2((uv0.x+uv1.x)*0.5f, tex_uvs.y); + + int point_idx = 0; + int point_end = points_count; + if (!closed) + { + // Start cap + point_idx++; + point_end--; + p1 = points[0]; + n1 = normals[0]; + len_sqr1 = sqr_lengths[0]; + + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness , pa.y - n1.y * half_thickness , uv0, col_trans); + IM_APPEND_VTX(pa.x + n1.x * half_thickness , pa.y + n1.y * half_thickness , uv1, col_trans); + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb.x - n1.x * half_thickness , pb.y - n1.y * half_thickness , uv0, col); + IM_APPEND_VTX(pb.x + n1.x * half_thickness , pb.y + n1.y * half_thickness , uv1, col); + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + } + else + { + // Wrap around segment + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + len_sqr1 = sqr_lengths[points_count-1]; + + // This will be filled later, allocate space. + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(0, 0, uv0, col); + IM_APPEND_VTX(0, 0, uv1, col); + } + + while (point_idx < point_end) + { + ImVec2 n0 = n1; + float len_sqr0 = len_sqr1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + len_sqr1 = sqr_lengths[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? half_thickness / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + + const bool overlap = (len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT); + const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + + if (bevel) + { + // Clipped bevel + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + float bevel_normal_x = n0.x + n1.x; + float bevel_normal_y = n0.y + n1.y; + IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); + + const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; + const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); + const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; + const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; + const float sd_x = bevel_normal_y * side_offset; + const float sd_y = -bevel_normal_x * side_offset; + + if (overlap) + { + // Dislocated bevel. + if (sin_theta < 0.f) + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); // 4 + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); // 5 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 6 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 7 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 8 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + // Bevel tris + IM_APPEND_TRI(base_idx + 6, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 6, base_idx + 4, base_idx + 5); + IM_APPEND_TRI(base_idx + 6, base_idx + 5, base_idx + 7); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); // 4 + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); // 5 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 6 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 7 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 8 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 6, base_idx + 7, base_idx + 5); + IM_APPEND_TRI(base_idx + 6, base_idx + 5, base_idx + 4); + IM_APPEND_TRI(base_idx + 6, base_idx + 4, base_idx + 3); + base_idx = next_base_idx; + } + } + else + { + // Bevel + if (sin_theta < 0.f) + { +/* IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+2, base_idx+3, base_idx+4); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + base_idx = next_base_idx;*/ + + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); // 2 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); // 4 + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 5 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 5); + IM_APPEND_TRI(base_idx + 0, base_idx + 5, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 3, base_idx + 5, base_idx + 2); + IM_APPEND_TRI(base_idx + 3, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 5); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); // 2 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 4 + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); // 5 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 4, base_idx + 2); + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 3, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 5); + IM_APPEND_TRI(base_idx + 3, base_idx + 5, base_idx + 2); + base_idx = next_base_idx; + +/* IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); + + // Bevel tri + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 2); + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 2); + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 1); + base_idx = next_base_idx;*/ + } + } + } + else + { + if (overlap) + { + // Dislocated Miter + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + if (sin_theta < 0.f) + { + + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 4 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 5 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 6 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 7 + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + // Miter + IM_APPEND_TRI(base_idx+5, base_idx+2, base_idx+4); + IM_APPEND_TRI(base_idx+5, base_idx+4, base_idx+6); + + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 4 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 5 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 6 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 7 + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + // Miter + IM_APPEND_TRI(base_idx+5, base_idx+4, base_idx+3); + IM_APPEND_TRI(base_idx+5, base_idx+7, base_idx+4); + + base_idx = next_base_idx; + } + } + else + { + // Miter + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 2 + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 3 + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + + base_idx = next_base_idx; + } + } + + point_idx++; + } + + // End cap + if (!closed) + { + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + + // End cap + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness, pa.y - n1.y * half_thickness, uv0, col); + IM_APPEND_VTX(pa.x + n1.x * half_thickness, pa.y + n1.y * half_thickness, uv1, col); + + IM_APPEND_VTX(pb.x - n1.x * half_thickness, pb.y - n1.y * half_thickness, uv0, col_trans); + IM_APPEND_VTX(pb.x + n1.x * half_thickness, pb.y + n1.y * half_thickness, uv1, col_trans); + + // Connect + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + base_idx = next_base_idx; + + // AA cap + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + } + else + { + // Connect the path. + start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; + start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; + } + + // Restore unused memory + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + IM_ASSERT(vtx_used <= vtx_count && idx_used <= idx_count); + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); +} + +void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +{ + const bool closed = (flags & ImDrawFlags_Closed) != 0; + + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* normals = _Data->TempBuffer.Data; + float* sqr_lengths = (float*)(normals + points_count); + CalcSegmentNormals(points, points_count, normals, sqr_lengths, closed); + + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + // TODO: we can expand this by having one very long texture strip for the lines, + // or if place black 2x2 pixel next to the while pixel, and use texture clamping (that thickness would expand out of the texture). + const float max_width = (IM_DRAWLIST_TEX_LINES_WIDTH_MAX-1) * _FringeScale * 2.f; + thickness = ImMin(thickness, max_width); + + thickness += _FringeScale; // Place half of AA fringe each side of the line. + const float half_thickness = thickness * 0.5f; + const float miter_distance_limit = half_thickness * IM_POLYLINE_MITER_LIMIT; + const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; + const float half_aa = _FringeScale * 0.5f; + + int idx_count = 0; + int vtx_count = 0; + if (closed) + { + vtx_count = /*body*/points_count * 5 + /*closing*/3; + idx_count = (/*body*/points_count * 5 + /*closing*/4) * 3; + } + else + { + // Body + caps + vtx_count = /*body*/(points_count - 2) * 5 + /*caps*/(6 * 2); + idx_count = (/*body*/(points_count - 2) * 5 + /*last seg*/4 + /*caps*/(4 * 2)) * 3; + } + + PrimReserve(idx_count, vtx_count); + + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + int base_idx = (int)_VtxCurrentIdx; + + ImVec2 p1; + ImVec2 n1; + float len_sqr1; + + const ImVec4 tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX]; + const ImVec2 uv_out(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + const ImVec2 uv_in(tex_uvs.x + ((half_thickness + 0.5f) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + + int point_idx = 0; + int point_end = points_count; + if (!closed) + { + point_idx++; + point_end--; + p1 = points[0]; + n1 = normals[0]; + len_sqr1 = sqr_lengths[0]; + + // Start cap + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness , pa.y - n1.y * half_thickness , uv_out, col_trans); + IM_APPEND_VTX(pa.x, pa.y, uv_in, col_trans); + IM_APPEND_VTX(pa.x + n1.x * half_thickness , pa.y + n1.y * half_thickness , uv_out, col_trans); + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb.x - n1.x * half_thickness , pb.y - n1.y * half_thickness , uv_out, col); + IM_APPEND_VTX(pb.x, pb.y, uv_in, col); + IM_APPEND_VTX(pb.x + n1.x * half_thickness , pb.y + n1.y * half_thickness , uv_out, col); + + // AA cap + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + base_idx = next_base_idx; + } + else + { + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + len_sqr1 = sqr_lengths[points_count-1]; + + // This will be filled later, allocate space. + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(0, 0, uv_out, col); + IM_APPEND_VTX(0, 0, uv_in, col); + IM_APPEND_VTX(0, 0, uv_out, col); + } + + while (point_idx < point_end) + { + ImVec2 n0 = n1; + float len_sqr0 = len_sqr1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + len_sqr1 = sqr_lengths[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? half_thickness / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + + const bool overlap = (len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT); + const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + + if (bevel) + { + // Clipped bevel + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + float bevel_normal_x = n0.x + n1.x; + float bevel_normal_y = n0.y + n1.y; + IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); + + const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; + const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); + const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; + const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; + const float sd_x = bevel_normal_y * side_offset; + const float sd_y = -bevel_normal_x * side_offset; + + if (overlap) + { + // Dislocated bevel. + if (sin_theta < 0.f) + { + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+3, base_idx+5, base_idx+6); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+6); + IM_APPEND_TRI(base_idx+0, base_idx+6, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+4); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+2); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv_out, col); + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+6, base_idx+7, base_idx+4); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+6); + IM_APPEND_TRI(base_idx+0, base_idx+6, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+4); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+2); + base_idx = next_base_idx; + } + } + else + { + // Bevel + if (sin_theta < 0.f) + { + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+3, base_idx+4, base_idx+5); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+6); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+2); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+5, base_idx+6, base_idx+3); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+3); + IM_APPEND_TRI(base_idx+1, base_idx+3, base_idx+2); + base_idx = next_base_idx; + } + } + } + else + { + if (overlap) + { + // Dislocated miter + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + if (sin_theta < 0.f) + { + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv_out, col); + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+3); + IM_APPEND_TRI(base_idx+1, base_idx+3, base_idx+2); + + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+6); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+2); + + base_idx = next_base_idx; + } + } + else + { + // Miter + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + + base_idx = next_base_idx; + } + } + + point_idx++; + } + + // End cap + if (!closed) + { + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + + // End cap + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness, pa.y - n1.y * half_thickness, uv_out, col); + IM_APPEND_VTX(pa.x, pa.y, uv_in, col); + IM_APPEND_VTX(pa.x + n1.x * half_thickness, pa.y + n1.y * half_thickness, uv_out, col); + + IM_APPEND_VTX(pb.x - n1.x * half_thickness, pb.y - n1.y * half_thickness, uv_out, col_trans); + IM_APPEND_VTX(pb.x, pb.y, uv_in, col_trans); + IM_APPEND_VTX(pb.x + n1.x * half_thickness, pb.y + n1.y * half_thickness, uv_out, col_trans); + + // Connect + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + base_idx = next_base_idx; + + // AA cap + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + } + else + { + // Connect the path. + start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; + start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; + start_vtx_ptr[2] = VtxBuffer.Data[base_idx+2]; + } + + // Restore unused memory + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + IM_ASSERT(vtx_used <= vtx_count && idx_used <= idx_count); + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); +} + void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +{ + if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) + return; + + float screen_thickness = thickness / _FringeScale; + if (screen_thickness < 1.f/255.f) + return; + if (screen_thickness < 1.f) + { + const float alpha = thickness; + col = ImGui::GetColorU32(col, alpha); + screen_thickness = 1.f; + thickness = _FringeScale; + } + + // TODO: square vs miter cap + // TODO: support splitting very long lines to multiple draw calls. + + // We can use cheaper rendering if the thickness is integer size. + const int int_thickness = (int)screen_thickness; + const bool can_use_thin = ImAbs(screen_thickness - (float)int_thickness) < 0.01f && (int_thickness >= 1 && int_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX); + + if (can_use_thin) + { + const ImVec4 tex_uvs = _Data->TexUvLines[int_thickness]; + _AddPolylineThin(points, points_count, col, (float)int_thickness * _FringeScale, flags, tex_uvs); + } + else + { + _AddPolylineThick(points, points_count, col, thickness, flags); + } +} + +void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return;