Skip to content

Commit

Permalink
Handle all corner cases for stroke geometry, add bevel join & cap/joi…
Browse files Browse the repository at this point in the history
…n enums (flutter#35)
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 3cff1a9 commit 451f93e
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 49 deletions.
156 changes: 125 additions & 31 deletions impeller/entity/contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -293,50 +293,120 @@ const Color& SolidStrokeContents::GetColor() const {
return color_;
}

static void CreateCap(
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
const Point& position,
const Point& normal) {}

static void CreateJoin(
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& 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;

VertexBufferBuilder<VS::PerVertexData> 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();
}
}

Expand Down Expand Up @@ -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
******************************************************************************/
Expand Down
29 changes: 29 additions & 0 deletions impeller/entity/contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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);
};
Expand Down
48 changes: 30 additions & 18 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<SolidStrokeContents>();
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<SolidStrokeContents>();
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) {
Expand Down

0 comments on commit 451f93e

Please sign in to comment.