Skip to content

Commit 451f93e

Browse files
bderodnfield
authored andcommitted
Handle all corner cases for stroke geometry, add bevel join & cap/join enums (#35)
1 parent 3cff1a9 commit 451f93e

File tree

3 files changed

+184
-49
lines changed

3 files changed

+184
-49
lines changed

impeller/entity/contents.cc

+125-31
Original file line numberDiff line numberDiff line change
@@ -293,50 +293,120 @@ const Color& SolidStrokeContents::GetColor() const {
293293
return color_;
294294
}
295295

296+
static void CreateCap(
297+
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
298+
const Point& position,
299+
const Point& normal) {}
300+
301+
static void CreateJoin(
302+
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
303+
const Point& position,
304+
const Point& start_normal,
305+
const Point& end_normal) {
306+
SolidStrokeVertexShader::PerVertexData vtx;
307+
vtx.vertex_position = position;
308+
vtx.pen_down = 1.0;
309+
vtx.vertex_normal = {};
310+
vtx_builder.AppendVertex(vtx);
311+
312+
// A simple bevel join to start with.
313+
Scalar dir = start_normal.Cross(end_normal) > 0 ? -1 : 1;
314+
vtx.vertex_normal = start_normal * dir;
315+
vtx_builder.AppendVertex(vtx);
316+
vtx.vertex_normal = end_normal * dir;
317+
vtx_builder.AppendVertex(vtx);
318+
}
319+
296320
static VertexBuffer CreateSolidStrokeVertices(const Path& path,
297321
HostBuffer& buffer) {
298322
using VS = SolidStrokeVertexShader;
299323

300324
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
301325
auto polyline = path.CreatePolyline();
302326

303-
for (size_t i = 0, polyline_size = polyline.points.size(); i < polyline_size;
304-
i++) {
305-
const auto is_last_point = i == polyline_size - 1;
306-
307-
const auto& p1 = polyline.points[i];
308-
const auto& p2 =
309-
is_last_point ? polyline.points[i - 1] : polyline.points[i + 1];
310-
311-
const auto diff = p2 - p1;
312-
313-
const Scalar direction = is_last_point ? -1.0 : 1.0;
327+
size_t point_i = 0;
328+
if (polyline.points.size() < 2) {
329+
return {}; // Nothing to render.
330+
}
314331

315-
const auto normal =
316-
Point{-diff.y * direction, diff.x * direction}.Normalize();
332+
VS::PerVertexData vtx;
333+
334+
// Cursor state.
335+
Point direction;
336+
Point normal;
337+
Point previous_normal; // Used for computing joins.
338+
auto compute_normals = [&](size_t point_i) {
339+
previous_normal = normal;
340+
direction =
341+
(polyline.points[point_i] - polyline.points[point_i - 1]).Normalize();
342+
normal = {-direction.y, direction.x};
343+
};
344+
compute_normals(1);
345+
346+
// Break state.
347+
auto breaks_it = polyline.breaks.begin();
348+
size_t break_end =
349+
breaks_it != polyline.breaks.end() ? *breaks_it : polyline.points.size();
350+
351+
while (point_i < polyline.points.size()) {
352+
if (point_i > 0) {
353+
compute_normals(point_i);
354+
355+
// This branch only executes when we've just finished drawing a contour
356+
// and are switching to a new one.
357+
// We're drawing a triangle strip, so we need to "pick up the pen" by
358+
// appending transparent vertices between the end of the previous contour
359+
// and the beginning of the new contour.
360+
vtx.vertex_position = polyline.points[point_i - 1];
361+
vtx.vertex_normal = {};
362+
vtx.pen_down = 0.0;
363+
vtx_builder.AppendVertex(vtx);
364+
vtx.vertex_position = polyline.points[point_i];
365+
vtx_builder.AppendVertex(vtx);
366+
}
317367

318-
VS::PerVertexData vtx;
319-
vtx.vertex_position = p1;
320-
auto pen_down =
321-
polyline.breaks.find(i) == polyline.breaks.end() ? 1.0 : 0.0;
368+
// Generate start cap.
369+
CreateCap(vtx_builder, polyline.points[point_i], -direction);
370+
371+
// Generate contour geometry.
372+
size_t contour_point_i = 0;
373+
while (point_i < break_end) {
374+
if (contour_point_i > 0) {
375+
if (contour_point_i > 1) {
376+
// Generate join from the previous line to the current line.
377+
CreateJoin(vtx_builder, polyline.points[point_i - 1], previous_normal,
378+
normal);
379+
} else {
380+
compute_normals(point_i);
381+
}
382+
383+
// Generate line rect.
384+
vtx.vertex_position = polyline.points[point_i - 1];
385+
vtx.pen_down = 1.0;
386+
vtx.vertex_normal = normal;
387+
vtx_builder.AppendVertex(vtx);
388+
vtx.vertex_normal = -normal;
389+
vtx_builder.AppendVertex(vtx);
390+
vtx.vertex_position = polyline.points[point_i];
391+
vtx.vertex_normal = normal;
392+
vtx_builder.AppendVertex(vtx);
393+
vtx.vertex_normal = -normal;
394+
vtx_builder.AppendVertex(vtx);
322395

323-
vtx.vertex_normal = normal;
324-
vtx.pen_down = pen_down;
325-
vtx_builder.AppendVertex(vtx);
396+
compute_normals(point_i + 1);
397+
}
326398

327-
vtx.vertex_normal = -normal;
328-
vtx.pen_down = pen_down;
329-
vtx_builder.AppendVertex(vtx);
399+
++contour_point_i;
400+
++point_i;
401+
}
330402

331-
// Put the pen down again for the next contour.
332-
if (!pen_down) {
333-
vtx.vertex_normal = normal;
334-
vtx.pen_down = 1.0;
335-
vtx_builder.AppendVertex(vtx);
403+
// Generate end cap.
404+
CreateCap(vtx_builder, polyline.points[point_i - 1], -direction);
336405

337-
vtx.vertex_normal = -normal;
338-
vtx.pen_down = 1.0;
339-
vtx_builder.AppendVertex(vtx);
406+
if (break_end < polyline.points.size()) {
407+
++breaks_it;
408+
break_end = breaks_it != polyline.breaks.end() ? *breaks_it
409+
: polyline.points.size();
340410
}
341411
}
342412

@@ -384,6 +454,30 @@ Scalar SolidStrokeContents::GetStrokeSize() const {
384454
return stroke_size_;
385455
}
386456

457+
void SolidStrokeContents::SetStrokeMiter(Scalar miter) {
458+
miter_ = miter;
459+
}
460+
461+
Scalar SolidStrokeContents::GetStrokeMiter(Scalar miter) {
462+
return miter_;
463+
}
464+
465+
void SolidStrokeContents::SetStrokeCap(Cap cap) {
466+
cap_ = cap;
467+
}
468+
469+
SolidStrokeContents::Cap SolidStrokeContents::GetStrokeCap() {
470+
return cap_;
471+
}
472+
473+
void SolidStrokeContents::SetStrokeJoin(Join join) {
474+
join_ = join;
475+
}
476+
477+
SolidStrokeContents::Join SolidStrokeContents::GetStrokeJoin() {
478+
return join_;
479+
}
480+
387481
/*******************************************************************************
388482
******* ClipContents
389483
******************************************************************************/

impeller/entity/contents.h

+29
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ class TextureContents final : public Contents {
113113

114114
class SolidStrokeContents final : public Contents {
115115
public:
116+
enum class Cap {
117+
kButt,
118+
kRound,
119+
kSquare,
120+
kLast,
121+
};
122+
123+
enum class Join {
124+
kMiter,
125+
kRound,
126+
kBevel,
127+
kLast,
128+
};
129+
116130
SolidStrokeContents();
117131

118132
~SolidStrokeContents() override;
@@ -125,6 +139,18 @@ class SolidStrokeContents final : public Contents {
125139

126140
Scalar GetStrokeSize() const;
127141

142+
void SetStrokeMiter(Scalar miter);
143+
144+
Scalar GetStrokeMiter(Scalar miter);
145+
146+
void SetStrokeCap(Cap cap);
147+
148+
Cap GetStrokeCap();
149+
150+
void SetStrokeJoin(Join join);
151+
152+
Join GetStrokeJoin();
153+
128154
// |Contents|
129155
bool Render(const ContentContext& renderer,
130156
const Entity& entity,
@@ -133,6 +159,9 @@ class SolidStrokeContents final : public Contents {
133159
private:
134160
Color color_;
135161
Scalar stroke_size_ = 0.0;
162+
Scalar miter_ = 0.0;
163+
Cap cap_ = Cap::kButt;
164+
Join join_ = Join::kMiter;
136165

137166
FML_DISALLOW_COPY_AND_ASSIGN(SolidStrokeContents);
138167
};

impeller/entity/entity_unittests.cc

+30-18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "impeller/entity/entity_playground.h"
88
#include "impeller/geometry/path_builder.h"
99
#include "impeller/playground/playground.h"
10+
#include "impeller/playground/widgets.h"
1011

1112
namespace impeller {
1213
namespace testing {
@@ -45,25 +46,36 @@ TEST_F(EntityTest, ThreeStrokesInOnePath) {
4546
}
4647

4748
TEST_F(EntityTest, TriangleInsideASquare) {
48-
Path path = PathBuilder{}
49-
.MoveTo({10, 10})
50-
.LineTo({210, 10})
51-
.LineTo({210, 210})
52-
.LineTo({10, 210})
53-
.Close()
54-
.MoveTo({50, 50})
55-
.LineTo({100, 50})
56-
.LineTo({50, 150})
57-
.Close()
58-
.TakePath();
49+
auto callback = [&](ContentContext& context, RenderPass& pass) {
50+
Point a = IMPELLER_PLAYGROUND_POINT(Point(10, 10), 20, Color::White());
51+
Point b = IMPELLER_PLAYGROUND_POINT(Point(210, 10), 20, Color::White());
52+
Point c = IMPELLER_PLAYGROUND_POINT(Point(210, 210), 20, Color::White());
53+
Point d = IMPELLER_PLAYGROUND_POINT(Point(10, 210), 20, Color::White());
54+
Point e = IMPELLER_PLAYGROUND_POINT(Point(50, 50), 20, Color::White());
55+
Point f = IMPELLER_PLAYGROUND_POINT(Point(100, 50), 20, Color::White());
56+
Point g = IMPELLER_PLAYGROUND_POINT(Point(50, 150), 20, Color::White());
57+
Path path = PathBuilder{}
58+
.MoveTo(a)
59+
.LineTo(b)
60+
.LineTo(c)
61+
.LineTo(d)
62+
.Close()
63+
.MoveTo(e)
64+
.LineTo(f)
65+
.LineTo(g)
66+
.Close()
67+
.TakePath();
5968

60-
Entity entity;
61-
entity.SetPath(path);
62-
auto contents = std::make_unique<SolidStrokeContents>();
63-
contents->SetColor(Color::Red());
64-
contents->SetStrokeSize(5.0);
65-
entity.SetContents(std::move(contents));
66-
ASSERT_TRUE(OpenPlaygroundHere(entity));
69+
Entity entity;
70+
entity.SetPath(path);
71+
auto contents = std::make_unique<SolidStrokeContents>();
72+
contents->SetColor(Color::Red());
73+
contents->SetStrokeSize(20.0);
74+
entity.SetContents(std::move(contents));
75+
76+
return entity.Render(context, pass);
77+
};
78+
ASSERT_TRUE(OpenPlaygroundHere(callback));
6779
}
6880

6981
TEST_F(EntityTest, CubicCurveTest) {

0 commit comments

Comments
 (0)