From 451f93e83fdcdadfef36af916333515300469495 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 25 Feb 2022 13:42:50 -0800 Subject: [PATCH] Handle all corner cases for stroke geometry, add bevel join & cap/join enums (#35) --- impeller/entity/contents.cc | 156 ++++++++++++++++++++++------ impeller/entity/contents.h | 29 ++++++ impeller/entity/entity_unittests.cc | 48 +++++---- 3 files changed, 184 insertions(+), 49 deletions(-) diff --git a/impeller/entity/contents.cc b/impeller/entity/contents.cc index 9567d1c11f515..8f2ad028785a2 100644 --- a/impeller/entity/contents.cc +++ b/impeller/entity/contents.cc @@ -293,6 +293,30 @@ const Color& SolidStrokeContents::GetColor() const { return color_; } +static void CreateCap( + VertexBufferBuilder& vtx_builder, + const Point& position, + const Point& normal) {} + +static void CreateJoin( + VertexBufferBuilder& vtx_builder, + const Point& position, + const Point& start_normal, + const Point& end_normal) { + SolidStrokeVertexShader::PerVertexData vtx; + vtx.vertex_position = position; + vtx.pen_down = 1.0; + vtx.vertex_normal = {}; + vtx_builder.AppendVertex(vtx); + + // A simple bevel join to start with. + Scalar dir = start_normal.Cross(end_normal) > 0 ? -1 : 1; + vtx.vertex_normal = start_normal * dir; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = end_normal * dir; + vtx_builder.AppendVertex(vtx); +} + static VertexBuffer CreateSolidStrokeVertices(const Path& path, HostBuffer& buffer) { using VS = SolidStrokeVertexShader; @@ -300,43 +324,89 @@ static VertexBuffer CreateSolidStrokeVertices(const Path& path, VertexBufferBuilder vtx_builder; auto polyline = path.CreatePolyline(); - for (size_t i = 0, polyline_size = polyline.points.size(); i < polyline_size; - i++) { - const auto is_last_point = i == polyline_size - 1; - - const auto& p1 = polyline.points[i]; - const auto& p2 = - is_last_point ? polyline.points[i - 1] : polyline.points[i + 1]; - - const auto diff = p2 - p1; - - const Scalar direction = is_last_point ? -1.0 : 1.0; + size_t point_i = 0; + if (polyline.points.size() < 2) { + return {}; // Nothing to render. + } - const auto normal = - Point{-diff.y * direction, diff.x * direction}.Normalize(); + VS::PerVertexData vtx; + + // Cursor state. + Point direction; + Point normal; + Point previous_normal; // Used for computing joins. + auto compute_normals = [&](size_t point_i) { + previous_normal = normal; + direction = + (polyline.points[point_i] - polyline.points[point_i - 1]).Normalize(); + normal = {-direction.y, direction.x}; + }; + compute_normals(1); + + // Break state. + auto breaks_it = polyline.breaks.begin(); + size_t break_end = + breaks_it != polyline.breaks.end() ? *breaks_it : polyline.points.size(); + + while (point_i < polyline.points.size()) { + if (point_i > 0) { + compute_normals(point_i); + + // This branch only executes when we've just finished drawing a contour + // and are switching to a new one. + // We're drawing a triangle strip, so we need to "pick up the pen" by + // appending transparent vertices between the end of the previous contour + // and the beginning of the new contour. + vtx.vertex_position = polyline.points[point_i - 1]; + vtx.vertex_normal = {}; + vtx.pen_down = 0.0; + vtx_builder.AppendVertex(vtx); + vtx.vertex_position = polyline.points[point_i]; + vtx_builder.AppendVertex(vtx); + } - VS::PerVertexData vtx; - vtx.vertex_position = p1; - auto pen_down = - polyline.breaks.find(i) == polyline.breaks.end() ? 1.0 : 0.0; + // Generate start cap. + CreateCap(vtx_builder, polyline.points[point_i], -direction); + + // Generate contour geometry. + size_t contour_point_i = 0; + while (point_i < break_end) { + if (contour_point_i > 0) { + if (contour_point_i > 1) { + // Generate join from the previous line to the current line. + CreateJoin(vtx_builder, polyline.points[point_i - 1], previous_normal, + normal); + } else { + compute_normals(point_i); + } + + // Generate line rect. + vtx.vertex_position = polyline.points[point_i - 1]; + vtx.pen_down = 1.0; + vtx.vertex_normal = normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = -normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_position = polyline.points[point_i]; + vtx.vertex_normal = normal; + vtx_builder.AppendVertex(vtx); + vtx.vertex_normal = -normal; + vtx_builder.AppendVertex(vtx); - vtx.vertex_normal = normal; - vtx.pen_down = pen_down; - vtx_builder.AppendVertex(vtx); + compute_normals(point_i + 1); + } - vtx.vertex_normal = -normal; - vtx.pen_down = pen_down; - vtx_builder.AppendVertex(vtx); + ++contour_point_i; + ++point_i; + } - // Put the pen down again for the next contour. - if (!pen_down) { - vtx.vertex_normal = normal; - vtx.pen_down = 1.0; - vtx_builder.AppendVertex(vtx); + // Generate end cap. + CreateCap(vtx_builder, polyline.points[point_i - 1], -direction); - vtx.vertex_normal = -normal; - vtx.pen_down = 1.0; - vtx_builder.AppendVertex(vtx); + if (break_end < polyline.points.size()) { + ++breaks_it; + break_end = breaks_it != polyline.breaks.end() ? *breaks_it + : polyline.points.size(); } } @@ -384,6 +454,30 @@ Scalar SolidStrokeContents::GetStrokeSize() const { return stroke_size_; } +void SolidStrokeContents::SetStrokeMiter(Scalar miter) { + miter_ = miter; +} + +Scalar SolidStrokeContents::GetStrokeMiter(Scalar miter) { + return miter_; +} + +void SolidStrokeContents::SetStrokeCap(Cap cap) { + cap_ = cap; +} + +SolidStrokeContents::Cap SolidStrokeContents::GetStrokeCap() { + return cap_; +} + +void SolidStrokeContents::SetStrokeJoin(Join join) { + join_ = join; +} + +SolidStrokeContents::Join SolidStrokeContents::GetStrokeJoin() { + return join_; +} + /******************************************************************************* ******* ClipContents ******************************************************************************/ diff --git a/impeller/entity/contents.h b/impeller/entity/contents.h index f80b2375bf952..dbdffb231d08b 100644 --- a/impeller/entity/contents.h +++ b/impeller/entity/contents.h @@ -113,6 +113,20 @@ class TextureContents final : public Contents { class SolidStrokeContents final : public Contents { public: + enum class Cap { + kButt, + kRound, + kSquare, + kLast, + }; + + enum class Join { + kMiter, + kRound, + kBevel, + kLast, + }; + SolidStrokeContents(); ~SolidStrokeContents() override; @@ -125,6 +139,18 @@ class SolidStrokeContents final : public Contents { Scalar GetStrokeSize() const; + void SetStrokeMiter(Scalar miter); + + Scalar GetStrokeMiter(Scalar miter); + + void SetStrokeCap(Cap cap); + + Cap GetStrokeCap(); + + void SetStrokeJoin(Join join); + + Join GetStrokeJoin(); + // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, @@ -133,6 +159,9 @@ class SolidStrokeContents final : public Contents { private: Color color_; Scalar stroke_size_ = 0.0; + Scalar miter_ = 0.0; + Cap cap_ = Cap::kButt; + Join join_ = Join::kMiter; FML_DISALLOW_COPY_AND_ASSIGN(SolidStrokeContents); }; diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index a69a5cab934fe..29b784e1c4f5e 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -7,6 +7,7 @@ #include "impeller/entity/entity_playground.h" #include "impeller/geometry/path_builder.h" #include "impeller/playground/playground.h" +#include "impeller/playground/widgets.h" namespace impeller { namespace testing { @@ -45,25 +46,36 @@ TEST_F(EntityTest, ThreeStrokesInOnePath) { } TEST_F(EntityTest, TriangleInsideASquare) { - Path path = PathBuilder{} - .MoveTo({10, 10}) - .LineTo({210, 10}) - .LineTo({210, 210}) - .LineTo({10, 210}) - .Close() - .MoveTo({50, 50}) - .LineTo({100, 50}) - .LineTo({50, 150}) - .Close() - .TakePath(); + auto callback = [&](ContentContext& context, RenderPass& pass) { + Point a = IMPELLER_PLAYGROUND_POINT(Point(10, 10), 20, Color::White()); + Point b = IMPELLER_PLAYGROUND_POINT(Point(210, 10), 20, Color::White()); + Point c = IMPELLER_PLAYGROUND_POINT(Point(210, 210), 20, Color::White()); + Point d = IMPELLER_PLAYGROUND_POINT(Point(10, 210), 20, Color::White()); + Point e = IMPELLER_PLAYGROUND_POINT(Point(50, 50), 20, Color::White()); + Point f = IMPELLER_PLAYGROUND_POINT(Point(100, 50), 20, Color::White()); + Point g = IMPELLER_PLAYGROUND_POINT(Point(50, 150), 20, Color::White()); + Path path = PathBuilder{} + .MoveTo(a) + .LineTo(b) + .LineTo(c) + .LineTo(d) + .Close() + .MoveTo(e) + .LineTo(f) + .LineTo(g) + .Close() + .TakePath(); - Entity entity; - entity.SetPath(path); - auto contents = std::make_unique(); - contents->SetColor(Color::Red()); - contents->SetStrokeSize(5.0); - entity.SetContents(std::move(contents)); - ASSERT_TRUE(OpenPlaygroundHere(entity)); + Entity entity; + entity.SetPath(path); + auto contents = std::make_unique(); + contents->SetColor(Color::Red()); + contents->SetStrokeSize(20.0); + entity.SetContents(std::move(contents)); + + return entity.Render(context, pass); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); } TEST_F(EntityTest, CubicCurveTest) {