Skip to content

Commit 9058956

Browse files
bderodnfield
authored andcommitted
Add round caps and joins (#52)
1 parent d9e7927 commit 9058956

File tree

5 files changed

+158
-36
lines changed

5 files changed

+158
-36
lines changed

impeller/entity/contents.cc

+87-17
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "impeller/entity/content_context.h"
1212
#include "impeller/entity/entity.h"
1313
#include "impeller/geometry/path_builder.h"
14+
#include "impeller/geometry/path_component.h"
1415
#include "impeller/geometry/scalar.h"
1516
#include "impeller/geometry/vector.h"
1617
#include "impeller/renderer/render_pass.h"
@@ -303,7 +304,8 @@ static VertexBuffer CreateSolidStrokeVertices(
303304
HostBuffer& buffer,
304305
const SolidStrokeContents::CapProc& cap_proc,
305306
const SolidStrokeContents::JoinProc& join_proc,
306-
Scalar miter_limit) {
307+
Scalar miter_limit,
308+
const SmoothingApproximation& smoothing) {
307309
using VS = SolidStrokeVertexShader;
308310

309311
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
@@ -356,7 +358,8 @@ static VertexBuffer CreateSolidStrokeVertices(
356358

357359
// Generate start cap.
358360
if (!polyline.contours[contour_i].is_closed) {
359-
cap_proc(vtx_builder, polyline.points[contour_start_point_i], -normal);
361+
cap_proc(vtx_builder, polyline.points[contour_start_point_i], -normal,
362+
smoothing);
360363
}
361364

362365
// Generate contour geometry.
@@ -381,17 +384,18 @@ static VertexBuffer CreateSolidStrokeVertices(
381384

382385
// Generate join from the current line to the next line.
383386
join_proc(vtx_builder, polyline.points[point_i], previous_normal,
384-
normal, miter_limit);
387+
normal, miter_limit, smoothing);
385388
}
386389
}
387390
}
388391

389392
// Generate end cap or join.
390393
if (!polyline.contours[contour_i].is_closed) {
391-
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], normal);
394+
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], normal,
395+
smoothing);
392396
} else {
393397
join_proc(vtx_builder, polyline.points[contour_start_point_i], normal,
394-
contour_first_normal, miter_limit);
398+
contour_first_normal, miter_limit, smoothing);
395399
}
396400
}
397401

@@ -420,9 +424,9 @@ bool SolidStrokeContents::Render(const ContentContext& renderer,
420424
cmd.label = "SolidStroke";
421425
cmd.pipeline = renderer.GetSolidStrokePipeline(OptionsFromPass(pass));
422426
cmd.stencil_reference = entity.GetStencilDepth();
423-
cmd.BindVertices(
424-
CreateSolidStrokeVertices(entity.GetPath(), pass.GetTransientsBuffer(),
425-
cap_proc_, join_proc_, miter_limit_));
427+
cmd.BindVertices(CreateSolidStrokeVertices(
428+
entity.GetPath(), pass.GetTransientsBuffer(), cap_proc_, join_proc_,
429+
miter_limit_, arc_smoothing_approximation_));
426430
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
427431
VS::BindStrokeInfo(cmd,
428432
pass.GetTransientsBuffer().EmplaceUniform(stroke_info));
@@ -434,6 +438,7 @@ bool SolidStrokeContents::Render(const ContentContext& renderer,
434438

435439
void SolidStrokeContents::SetStrokeSize(Scalar size) {
436440
stroke_size_ = size;
441+
arc_smoothing_approximation_ = SmoothingApproximation(5.0 / size, 0.0, 0.0);
437442
}
438443

439444
Scalar SolidStrokeContents::GetStrokeSize() const {
@@ -458,14 +463,41 @@ void SolidStrokeContents::SetStrokeCap(Cap cap) {
458463
switch (cap) {
459464
case Cap::kButt:
460465
cap_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
461-
const Point& position, const Point& normal) {};
466+
const Point& position, const Point& normal,
467+
const SmoothingApproximation& smoothing) {};
462468
break;
463469
case Cap::kRound:
464-
FML_DLOG(ERROR) << "Unimplemented.";
470+
cap_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
471+
const Point& position, const Point& normal,
472+
const SmoothingApproximation& smoothing) {
473+
SolidStrokeVertexShader::PerVertexData vtx;
474+
vtx.vertex_position = position;
475+
vtx.pen_down = 1.0;
476+
477+
Point forward(normal.y, -normal.x);
478+
479+
auto arc_points =
480+
CubicPathComponent(
481+
normal, normal + forward * PathBuilder::kArcApproximationMagic,
482+
forward + normal * PathBuilder::kArcApproximationMagic, forward)
483+
.CreatePolyline(smoothing);
484+
485+
vtx.vertex_normal = normal;
486+
vtx_builder.AppendVertex(vtx);
487+
vtx.vertex_normal = -normal;
488+
vtx_builder.AppendVertex(vtx);
489+
for (const auto& point : arc_points) {
490+
vtx.vertex_normal = point;
491+
vtx_builder.AppendVertex(vtx);
492+
vtx.vertex_normal = (-point).Reflect(forward);
493+
vtx_builder.AppendVertex(vtx);
494+
}
495+
};
465496
break;
466497
case Cap::kSquare:
467498
cap_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
468-
const Point& position, const Point& normal) {
499+
const Point& position, const Point& normal,
500+
const SmoothingApproximation& smoothing) {
469501
SolidStrokeVertexShader::PerVertexData vtx;
470502
vtx.vertex_position = position;
471503
vtx.pen_down = 1.0;
@@ -517,22 +549,25 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
517549
case Join::kBevel:
518550
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
519551
const Point& position, const Point& start_normal,
520-
const Point& end_normal, Scalar miter_limit) {
521-
CreateBevelAndGetDirection(vtx_builder, position, start_normal, end_normal);
552+
const Point& end_normal, Scalar miter_limit,
553+
const SmoothingApproximation& smoothing) {
554+
CreateBevelAndGetDirection(vtx_builder, position, start_normal,
555+
end_normal);
522556
};
523557
break;
524558
case Join::kMiter:
525559
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
526560
const Point& position, const Point& start_normal,
527-
const Point& end_normal, Scalar miter_limit) {
561+
const Point& end_normal, Scalar miter_limit,
562+
const SmoothingApproximation& smoothing) {
528563
// 1 for no joint (straight line), 0 for max joint (180 degrees).
529564
Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
530565
if (ScalarNearlyEqual(alignment, 1)) {
531566
return;
532567
}
533568

534-
Scalar dir =
535-
CreateBevelAndGetDirection(vtx_builder, position, start_normal, end_normal);
569+
Scalar dir = CreateBevelAndGetDirection(vtx_builder, position,
570+
start_normal, end_normal);
536571

537572
Point miter_point = (start_normal + end_normal) / 2 / alignment;
538573
if (miter_point.GetDistanceSquared({0, 0}) >
@@ -549,7 +584,42 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
549584
};
550585
break;
551586
case Join::kRound:
552-
FML_DLOG(ERROR) << "Unimplemented.";
587+
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
588+
const Point& position, const Point& start_normal,
589+
const Point& end_normal, Scalar miter_limit,
590+
const SmoothingApproximation& smoothing) {
591+
// 0 for no joint (straight line), 1 for max joint (180 degrees).
592+
Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2;
593+
if (ScalarNearlyEqual(alignment, 0)) {
594+
return;
595+
}
596+
597+
Scalar dir =
598+
CreateBevel(vtx_builder, position, start_normal, end_normal);
599+
600+
Point middle = (start_normal + end_normal).Normalize();
601+
Point middle_handle = middle + Point(-middle.y, middle.x) *
602+
PathBuilder::kArcApproximationMagic *
603+
alignment * dir;
604+
Point start_handle =
605+
start_normal + Point(start_normal.y, -start_normal.x) *
606+
PathBuilder::kArcApproximationMagic * alignment *
607+
dir;
608+
609+
auto arc_points = CubicPathComponent(start_normal, start_handle,
610+
middle_handle, middle)
611+
.CreatePolyline(smoothing);
612+
613+
SolidStrokeVertexShader::PerVertexData vtx;
614+
vtx.vertex_position = position;
615+
vtx.pen_down = 1.0;
616+
for (const auto& point : arc_points) {
617+
vtx.vertex_normal = point * dir;
618+
vtx_builder.AppendVertex(vtx);
619+
vtx.vertex_normal = (-point * dir).Reflect(middle);
620+
vtx_builder.AppendVertex(vtx);
621+
}
622+
};
553623
break;
554624
}
555625
}

impeller/entity/contents.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "flutter/fml/macros.h"
1212
#include "impeller/entity/solid_stroke.vert.h"
1313
#include "impeller/geometry/color.h"
14+
#include "impeller/geometry/path_component.h"
1415
#include "impeller/geometry/point.h"
1516
#include "impeller/geometry/rect.h"
1617
#include "impeller/renderer/texture.h"
@@ -132,13 +133,15 @@ class SolidStrokeContents final : public Contents {
132133
using CapProc = std::function<void(
133134
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
134135
const Point& position,
135-
const Point& normal)>;
136+
const Point& normal,
137+
const SmoothingApproximation& smoothing)>;
136138
using JoinProc = std::function<void(
137139
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
138140
const Point& position,
139141
const Point& start_normal,
140142
const Point& end_normal,
141-
Scalar miter_limit)>;
143+
Scalar miter_limit,
144+
const SmoothingApproximation& smoothing)>;
142145

143146
SolidStrokeContents();
144147

@@ -170,6 +173,8 @@ class SolidStrokeContents final : public Contents {
170173
RenderPass& pass) const override;
171174

172175
private:
176+
SmoothingApproximation arc_smoothing_approximation_;
177+
173178
Color color_;
174179
Scalar stroke_size_ = 0.0;
175180
Scalar miter_limit_ = 4.0;

impeller/entity/entity_unittests.cc

+55-15
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
#include "entity/contents.h"
66
#include "flutter/testing/testing.h"
7-
#include "imgui.h"
87
#include "impeller/entity/entity.h"
98
#include "impeller/entity/entity_playground.h"
109
#include "impeller/geometry/path_builder.h"
1110
#include "impeller/playground/playground.h"
1211
#include "impeller/playground/widgets.h"
12+
#include "third_party/imgui/imgui.h"
1313

1414
namespace impeller {
1515
namespace testing {
@@ -81,37 +81,50 @@ TEST_F(EntityTest, TriangleInsideASquare) {
8181
}
8282

8383
TEST_F(EntityTest, StrokeCapAndJoinTest) {
84-
auto callback = [&](ContentContext& context, RenderPass& pass) {
85-
Entity entity;
84+
const Point padding(300, 250);
85+
const Point margin(140, 180);
8686

87-
ImGui::SetNextWindowSize({300, 60});
88-
ImGui::SetNextWindowPos({100, 300});
87+
bool first_frame = true;
88+
auto callback = [&](ContentContext& context, RenderPass& pass) {
89+
if (first_frame) {
90+
first_frame = false;
91+
ImGui::SetNextWindowSize({300, 100});
92+
ImGui::SetNextWindowPos(
93+
{0 * padding.x + margin.x, 1.7f * padding.y + margin.y});
94+
}
8995
ImGui::Begin("Controls");
9096
// Slightly above sqrt(2) by default, so that right angles are just below
9197
// the limit and acute angles are over the limit (causing them to get
9298
// beveled).
9399
static Scalar miter_limit = 1.41421357;
100+
static Scalar width = 30;
94101
ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30);
102+
ImGui::SliderFloat("Stroke width", &width, 0, 100);
103+
if (ImGui::Button("Reset")) {
104+
miter_limit = 1.41421357;
105+
width = 30;
106+
}
95107
ImGui::End();
96108

97-
auto create_contents = [](SolidStrokeContents::Cap cap,
98-
SolidStrokeContents::Join join) {
109+
auto create_contents = [width = width](SolidStrokeContents::Cap cap,
110+
SolidStrokeContents::Join join) {
99111
auto contents = std::make_unique<SolidStrokeContents>();
100112
contents->SetColor(Color::Red());
101-
contents->SetStrokeSize(20.0);
113+
contents->SetStrokeSize(width);
102114
contents->SetStrokeCap(cap);
103115
contents->SetStrokeJoin(join);
104116
contents->SetStrokeMiter(miter_limit);
105117
return contents;
106118
};
107119

108-
const Point a_def(100, 100), b_def(100, 150), c_def(200, 100),
109-
d_def(200, 50), e_def(150, 150);
110-
const Scalar r = 10;
120+
Entity entity;
111121

122+
const Point a_def(0, 0), b_def(0, 100), c_def(150, 0), d_def(150, -100),
123+
e_def(75, 75);
124+
const Scalar r = 30;
112125
// Cap::kButt demo.
113126
{
114-
Point off(0, 0);
127+
Point off = Point(0, 0) * padding + margin;
115128
Point a, b, c, d;
116129
std::tie(a, b) = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
117130
Color::Black(), Color::White());
@@ -125,7 +138,7 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
125138

126139
// Cap::kSquare demo.
127140
{
128-
Point off(0, 100);
141+
Point off = Point(1, 0) * padding + margin;
129142
Point a, b, c, d;
130143
std::tie(a, b) = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
131144
Color::Black(), Color::White());
@@ -137,9 +150,23 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
137150
entity.Render(context, pass);
138151
}
139152

153+
// Cap::kRound demo.
154+
{
155+
Point off = Point(2, 0) * padding + margin;
156+
Point a, b, c, d;
157+
std::tie(a, b) = IMPELLER_PLAYGROUND_LINE(off + a_def, off + b_def, r,
158+
Color::Black(), Color::White());
159+
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
160+
Color::Black(), Color::White());
161+
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
162+
entity.SetContents(create_contents(SolidStrokeContents::Cap::kRound,
163+
SolidStrokeContents::Join::kBevel));
164+
entity.Render(context, pass);
165+
}
166+
140167
// Join::kBevel demo.
141168
{
142-
Point off(200, 0);
169+
Point off = Point(0, 1) * padding + margin;
143170
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
144171
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
145172
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
@@ -152,7 +179,7 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
152179

153180
// Join::kMiter demo.
154181
{
155-
Point off(200, 100);
182+
Point off = Point(1, 1) * padding + margin;
156183
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
157184
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
158185
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
@@ -163,6 +190,19 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
163190
entity.Render(context, pass);
164191
}
165192

193+
// Join::kRound demo.
194+
{
195+
Point off = Point(2, 1) * padding + margin;
196+
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
197+
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
198+
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
199+
entity.SetPath(
200+
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
201+
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
202+
SolidStrokeContents::Join::kRound));
203+
entity.Render(context, pass);
204+
}
205+
166206
return true;
167207
};
168208
ASSERT_TRUE(OpenPlaygroundHere(callback));

impeller/geometry/path_builder.cc

-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
namespace impeller {
88

9-
static const Scalar kArcApproximationMagic = 0.551915024494;
10-
119
PathBuilder::PathBuilder() = default;
1210

1311
PathBuilder::~PathBuilder() = default;

impeller/geometry/path_builder.h

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ namespace impeller {
1313

1414
class PathBuilder {
1515
public:
16+
/// Used for approximating quarter circle arcs with cubic curves. This is the
17+
/// control point distance which results in the smallest possible unit circle
18+
/// integration for a right angle arc. It can be used to approximate arcs less
19+
/// than 90 degrees to great effect by simply reducing it proportionally to
20+
/// the angle. However, accuracy rapidly diminishes if magnified for obtuse
21+
/// angle arcs, and so multiple cubic curves should be used when approximating
22+
/// arcs greater than 90 degrees.
23+
constexpr static const Scalar kArcApproximationMagic = 0.551915024494;
24+
1625
PathBuilder();
1726

1827
~PathBuilder();

0 commit comments

Comments
 (0)