Skip to content

Commit

Permalink
Add miter join (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 75e83ff commit d9e7927
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 31 deletions.
83 changes: 61 additions & 22 deletions impeller/entity/contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "impeller/entity/content_context.h"
#include "impeller/entity/entity.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/vector.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/sampler_library.h"
Expand Down Expand Up @@ -284,8 +285,7 @@ const IRect& TextureContents::GetSourceRect() const {

SolidStrokeContents::SolidStrokeContents() {
SetStrokeCap(Cap::kButt);
// TODO(99089): Change this to kMiter once implemented.
SetStrokeJoin(Join::kBevel);
SetStrokeJoin(Join::kMiter);
}

SolidStrokeContents::~SolidStrokeContents() = default;
Expand All @@ -302,7 +302,8 @@ static VertexBuffer CreateSolidStrokeVertices(
const Path& path,
HostBuffer& buffer,
const SolidStrokeContents::CapProc& cap_proc,
const SolidStrokeContents::JoinProc& join_proc) {
const SolidStrokeContents::JoinProc& join_proc,
Scalar miter_limit) {
using VS = SolidStrokeVertexShader;

VertexBufferBuilder<VS::PerVertexData> vtx_builder;
Expand Down Expand Up @@ -380,7 +381,7 @@ static VertexBuffer CreateSolidStrokeVertices(

// Generate join from the current line to the next line.
join_proc(vtx_builder, polyline.points[point_i], previous_normal,
normal);
normal, miter_limit);
}
}
}
Expand All @@ -390,7 +391,7 @@ static VertexBuffer CreateSolidStrokeVertices(
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], normal);
} else {
join_proc(vtx_builder, polyline.points[contour_start_point_i], normal,
contour_first_normal);
contour_first_normal, miter_limit);
}
}

Expand Down Expand Up @@ -419,8 +420,9 @@ bool SolidStrokeContents::Render(const ContentContext& renderer,
cmd.label = "SolidStroke";
cmd.pipeline = renderer.GetSolidStrokePipeline(OptionsFromPass(pass));
cmd.stencil_reference = entity.GetStencilDepth();
cmd.BindVertices(CreateSolidStrokeVertices(
entity.GetPath(), pass.GetTransientsBuffer(), cap_proc_, join_proc_));
cmd.BindVertices(
CreateSolidStrokeVertices(entity.GetPath(), pass.GetTransientsBuffer(),
cap_proc_, join_proc_, miter_limit_));
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
VS::BindStrokeInfo(cmd,
pass.GetTransientsBuffer().EmplaceUniform(stroke_info));
Expand All @@ -438,12 +440,15 @@ Scalar SolidStrokeContents::GetStrokeSize() const {
return stroke_size_;
}

void SolidStrokeContents::SetStrokeMiter(Scalar miter) {
miter_ = miter;
void SolidStrokeContents::SetStrokeMiter(Scalar miter_limit) {
if (miter_limit < 0) {
return; // Skia behaves like this.
}
miter_limit_ = miter_limit;
}

Scalar SolidStrokeContents::GetStrokeMiter(Scalar miter) {
return miter_;
Scalar SolidStrokeContents::GetStrokeMiter() {
return miter_limit_;
}

void SolidStrokeContents::SetStrokeCap(Cap cap) {
Expand Down Expand Up @@ -484,6 +489,26 @@ SolidStrokeContents::Cap SolidStrokeContents::GetStrokeCap() {
return cap_;
}

static Scalar CreateBevelAndGetDirection(
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);

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);

return dir;
}

void SolidStrokeContents::SetStrokeJoin(Join join) {
join_ = join;

Expand All @@ -492,23 +517,37 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
case Join::kBevel:
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_normal,
const Point& end_normal) {
const Point& end_normal, Scalar miter_limit) {
CreateBevelAndGetDirection(vtx_builder, position, start_normal, end_normal);
};
break;
case Join::kMiter:
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
const Point& position, const Point& start_normal,
const Point& end_normal, Scalar miter_limit) {
// 1 for no joint (straight line), 0 for max joint (180 degrees).
Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
if (ScalarNearlyEqual(alignment, 1)) {
return;
}

Scalar dir =
CreateBevelAndGetDirection(vtx_builder, position, start_normal, end_normal);

Point miter_point = (start_normal + end_normal) / 2 / alignment;
if (miter_point.GetDistanceSquared({0, 0}) >
miter_limit * miter_limit) {
return; // Convert to bevel when we exceed the miter limit.
}

// Outer miter point.
SolidStrokeVertexShader::PerVertexData vtx;
vtx.vertex_position = position;
vtx.pen_down = 1.0;
vtx.vertex_normal = {};
vtx_builder.AppendVertex(vtx);

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.vertex_normal = miter_point * dir;
vtx_builder.AppendVertex(vtx);
};
break;
case Join::kMiter:
FML_DLOG(ERROR) << "Unimplemented.";
break;
case Join::kRound:
FML_DLOG(ERROR) << "Unimplemented.";
break;
Expand Down
9 changes: 5 additions & 4 deletions impeller/entity/contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ class SolidStrokeContents final : public Contents {
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
const Point& position,
const Point& start_normal,
const Point& end_normal)>;
const Point& end_normal,
Scalar miter_limit)>;

SolidStrokeContents();

Expand All @@ -151,9 +152,9 @@ class SolidStrokeContents final : public Contents {

Scalar GetStrokeSize() const;

void SetStrokeMiter(Scalar miter);
void SetStrokeMiter(Scalar miter_limit);

Scalar GetStrokeMiter(Scalar miter);
Scalar GetStrokeMiter();

void SetStrokeCap(Cap cap);

Expand All @@ -171,7 +172,7 @@ class SolidStrokeContents final : public Contents {
private:
Color color_;
Scalar stroke_size_ = 0.0;
Scalar miter_ = 0.0;
Scalar miter_limit_ = 4.0;

Cap cap_;
CapProc cap_proc_;
Expand Down
65 changes: 60 additions & 5 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "entity/contents.h"
#include "flutter/testing/testing.h"
#include "imgui.h"
#include "impeller/entity/entity.h"
#include "impeller/entity/entity_playground.h"
#include "impeller/geometry/path_builder.h"
Expand Down Expand Up @@ -83,18 +84,32 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
auto callback = [&](ContentContext& context, RenderPass& pass) {
Entity entity;

auto create_contents = [](SolidStrokeContents::Cap cap) {
ImGui::SetNextWindowSize({300, 60});
ImGui::SetNextWindowPos({100, 300});
ImGui::Begin("Controls");
// Slightly above sqrt(2) by default, so that right angles are just below
// the limit and acute angles are over the limit (causing them to get
// beveled).
static Scalar miter_limit = 1.41421357;
ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30);
ImGui::End();

auto create_contents = [](SolidStrokeContents::Cap cap,
SolidStrokeContents::Join join) {
auto contents = std::make_unique<SolidStrokeContents>();
contents->SetColor(Color::Red());
contents->SetStrokeSize(20.0);
contents->SetStrokeCap(cap);
contents->SetStrokeJoin(join);
contents->SetStrokeMiter(miter_limit);
return contents;
};

const Point a_def(100, 100), b_def(100, 150), c_def(200, 100),
d_def(200, 50);
d_def(200, 50), e_def(150, 150);
const Scalar r = 10;

// Cap::kButt demo.
{
Point off(0, 0);
Point a, b, c, d;
Expand All @@ -103,10 +118,12 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
Color::Black(), Color::White());
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt));
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
SolidStrokeContents::Join::kBevel));
entity.Render(context, pass);
}

// Cap::kSquare demo.
{
Point off(0, 100);
Point a, b, c, d;
Expand All @@ -115,7 +132,34 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
Color::Black(), Color::White());
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
entity.SetContents(create_contents(SolidStrokeContents::Cap::kSquare));
entity.SetContents(create_contents(SolidStrokeContents::Cap::kSquare,
SolidStrokeContents::Join::kBevel));
entity.Render(context, pass);
}

// Join::kBevel demo.
{
Point off(200, 0);
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
entity.SetPath(
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
SolidStrokeContents::Join::kBevel));
entity.Render(context, pass);
}

// Join::kMiter demo.
{
Point off(200, 100);
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
entity.SetPath(
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
SolidStrokeContents::Join::kMiter));
entity.Render(context, pass);
}

Expand Down Expand Up @@ -379,7 +423,7 @@ TEST_F(EntityTest, SolidStrokeContentsSetStrokeCapsAndJoins) {
SolidStrokeContents stroke;
// Defaults.
ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kButt);
ASSERT_EQ(stroke.GetStrokeJoin(), SolidStrokeContents::Join::kBevel);
ASSERT_EQ(stroke.GetStrokeJoin(), SolidStrokeContents::Join::kMiter);
}

{
Expand All @@ -395,5 +439,16 @@ TEST_F(EntityTest, SolidStrokeContentsSetStrokeCapsAndJoins) {
}
}

TEST_F(EntityTest, SolidStrokeContentsSetMiter) {
SolidStrokeContents contents;
ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 4);

contents.SetStrokeMiter(8);
ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8);

contents.SetStrokeMiter(-1);
ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8);
}

} // namespace testing
} // namespace impeller
10 changes: 10 additions & 0 deletions impeller/geometry/geometry_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <limits>
#include "impeller/geometry/geometry_unittests.h"
#include <limits>
#include "flutter/testing/testing.h"
Expand All @@ -10,11 +11,20 @@
#include "impeller/geometry/path_component.h"
#include "impeller/geometry/point.h"
#include "impeller/geometry/rect.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/size.h"

namespace impeller {
namespace testing {

TEST(GeometryTest, ScalarNearlyEqual) {
ASSERT_FALSE(ScalarNearlyEqual(0.002f, 0.001f));
ASSERT_TRUE(ScalarNearlyEqual(0.002f, 0.001f, 0.0011f));
ASSERT_FALSE(ScalarNearlyEqual(0.002f, 0.001f, 0.0009f));
ASSERT_TRUE(
ScalarNearlyEqual(1.0f, 1.0f + std::numeric_limits<float>::epsilon()*4));
}

TEST(GeometryTest, RotationMatrix) {
auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4});
auto expect = Matrix{0.707, 0.707, 0, 0, //
Expand Down
9 changes: 9 additions & 0 deletions impeller/geometry/scalar.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
#pragma once

#include <cfloat>
#include <valarray>

#include "flutter/fml/logging.h"
#include "impeller/geometry/constants.h"

namespace impeller {

using Scalar = float;

constexpr inline bool ScalarNearlyEqual(Scalar x,
Scalar y,
Scalar tolerance = 1e-3) {
FML_DCHECK(tolerance >= 0);
return std::abs(x - y) <= tolerance;
}

struct Degrees;

struct Radians {
Expand Down

0 comments on commit d9e7927

Please sign in to comment.