Skip to content

Commit d9e7927

Browse files
bderodnfield
authored andcommitted
Add miter join (#49)
1 parent 75e83ff commit d9e7927

File tree

5 files changed

+145
-31
lines changed

5 files changed

+145
-31
lines changed

impeller/entity/contents.cc

+61-22
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/scalar.h"
1415
#include "impeller/geometry/vector.h"
1516
#include "impeller/renderer/render_pass.h"
1617
#include "impeller/renderer/sampler_library.h"
@@ -284,8 +285,7 @@ const IRect& TextureContents::GetSourceRect() const {
284285

285286
SolidStrokeContents::SolidStrokeContents() {
286287
SetStrokeCap(Cap::kButt);
287-
// TODO(99089): Change this to kMiter once implemented.
288-
SetStrokeJoin(Join::kBevel);
288+
SetStrokeJoin(Join::kMiter);
289289
}
290290

291291
SolidStrokeContents::~SolidStrokeContents() = default;
@@ -302,7 +302,8 @@ static VertexBuffer CreateSolidStrokeVertices(
302302
const Path& path,
303303
HostBuffer& buffer,
304304
const SolidStrokeContents::CapProc& cap_proc,
305-
const SolidStrokeContents::JoinProc& join_proc) {
305+
const SolidStrokeContents::JoinProc& join_proc,
306+
Scalar miter_limit) {
306307
using VS = SolidStrokeVertexShader;
307308

308309
VertexBufferBuilder<VS::PerVertexData> vtx_builder;
@@ -380,7 +381,7 @@ static VertexBuffer CreateSolidStrokeVertices(
380381

381382
// Generate join from the current line to the next line.
382383
join_proc(vtx_builder, polyline.points[point_i], previous_normal,
383-
normal);
384+
normal, miter_limit);
384385
}
385386
}
386387
}
@@ -390,7 +391,7 @@ static VertexBuffer CreateSolidStrokeVertices(
390391
cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], normal);
391392
} else {
392393
join_proc(vtx_builder, polyline.points[contour_start_point_i], normal,
393-
contour_first_normal);
394+
contour_first_normal, miter_limit);
394395
}
395396
}
396397

@@ -419,8 +420,9 @@ bool SolidStrokeContents::Render(const ContentContext& renderer,
419420
cmd.label = "SolidStroke";
420421
cmd.pipeline = renderer.GetSolidStrokePipeline(OptionsFromPass(pass));
421422
cmd.stencil_reference = entity.GetStencilDepth();
422-
cmd.BindVertices(CreateSolidStrokeVertices(
423-
entity.GetPath(), pass.GetTransientsBuffer(), cap_proc_, join_proc_));
423+
cmd.BindVertices(
424+
CreateSolidStrokeVertices(entity.GetPath(), pass.GetTransientsBuffer(),
425+
cap_proc_, join_proc_, miter_limit_));
424426
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
425427
VS::BindStrokeInfo(cmd,
426428
pass.GetTransientsBuffer().EmplaceUniform(stroke_info));
@@ -438,12 +440,15 @@ Scalar SolidStrokeContents::GetStrokeSize() const {
438440
return stroke_size_;
439441
}
440442

441-
void SolidStrokeContents::SetStrokeMiter(Scalar miter) {
442-
miter_ = miter;
443+
void SolidStrokeContents::SetStrokeMiter(Scalar miter_limit) {
444+
if (miter_limit < 0) {
445+
return; // Skia behaves like this.
446+
}
447+
miter_limit_ = miter_limit;
443448
}
444449

445-
Scalar SolidStrokeContents::GetStrokeMiter(Scalar miter) {
446-
return miter_;
450+
Scalar SolidStrokeContents::GetStrokeMiter() {
451+
return miter_limit_;
447452
}
448453

449454
void SolidStrokeContents::SetStrokeCap(Cap cap) {
@@ -484,6 +489,26 @@ SolidStrokeContents::Cap SolidStrokeContents::GetStrokeCap() {
484489
return cap_;
485490
}
486491

492+
static Scalar CreateBevelAndGetDirection(
493+
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
494+
const Point& position,
495+
const Point& start_normal,
496+
const Point& end_normal) {
497+
SolidStrokeVertexShader::PerVertexData vtx;
498+
vtx.vertex_position = position;
499+
vtx.pen_down = 1.0;
500+
vtx.vertex_normal = {};
501+
vtx_builder.AppendVertex(vtx);
502+
503+
Scalar dir = start_normal.Cross(end_normal) > 0 ? -1 : 1;
504+
vtx.vertex_normal = start_normal * dir;
505+
vtx_builder.AppendVertex(vtx);
506+
vtx.vertex_normal = end_normal * dir;
507+
vtx_builder.AppendVertex(vtx);
508+
509+
return dir;
510+
}
511+
487512
void SolidStrokeContents::SetStrokeJoin(Join join) {
488513
join_ = join;
489514

@@ -492,23 +517,37 @@ void SolidStrokeContents::SetStrokeJoin(Join join) {
492517
case Join::kBevel:
493518
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
494519
const Point& position, const Point& start_normal,
495-
const Point& end_normal) {
520+
const Point& end_normal, Scalar miter_limit) {
521+
CreateBevelAndGetDirection(vtx_builder, position, start_normal, end_normal);
522+
};
523+
break;
524+
case Join::kMiter:
525+
join_proc_ = [](VertexBufferBuilder<VS::PerVertexData>& vtx_builder,
526+
const Point& position, const Point& start_normal,
527+
const Point& end_normal, Scalar miter_limit) {
528+
// 1 for no joint (straight line), 0 for max joint (180 degrees).
529+
Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2;
530+
if (ScalarNearlyEqual(alignment, 1)) {
531+
return;
532+
}
533+
534+
Scalar dir =
535+
CreateBevelAndGetDirection(vtx_builder, position, start_normal, end_normal);
536+
537+
Point miter_point = (start_normal + end_normal) / 2 / alignment;
538+
if (miter_point.GetDistanceSquared({0, 0}) >
539+
miter_limit * miter_limit) {
540+
return; // Convert to bevel when we exceed the miter limit.
541+
}
542+
543+
// Outer miter point.
496544
SolidStrokeVertexShader::PerVertexData vtx;
497545
vtx.vertex_position = position;
498546
vtx.pen_down = 1.0;
499-
vtx.vertex_normal = {};
500-
vtx_builder.AppendVertex(vtx);
501-
502-
Scalar dir = start_normal.Cross(end_normal) > 0 ? -1 : 1;
503-
vtx.vertex_normal = start_normal * dir;
504-
vtx_builder.AppendVertex(vtx);
505-
vtx.vertex_normal = end_normal * dir;
547+
vtx.vertex_normal = miter_point * dir;
506548
vtx_builder.AppendVertex(vtx);
507549
};
508550
break;
509-
case Join::kMiter:
510-
FML_DLOG(ERROR) << "Unimplemented.";
511-
break;
512551
case Join::kRound:
513552
FML_DLOG(ERROR) << "Unimplemented.";
514553
break;

impeller/entity/contents.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ class SolidStrokeContents final : public Contents {
137137
VertexBufferBuilder<SolidStrokeVertexShader::PerVertexData>& vtx_builder,
138138
const Point& position,
139139
const Point& start_normal,
140-
const Point& end_normal)>;
140+
const Point& end_normal,
141+
Scalar miter_limit)>;
141142

142143
SolidStrokeContents();
143144

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

152153
Scalar GetStrokeSize() const;
153154

154-
void SetStrokeMiter(Scalar miter);
155+
void SetStrokeMiter(Scalar miter_limit);
155156

156-
Scalar GetStrokeMiter(Scalar miter);
157+
Scalar GetStrokeMiter();
157158

158159
void SetStrokeCap(Cap cap);
159160

@@ -171,7 +172,7 @@ class SolidStrokeContents final : public Contents {
171172
private:
172173
Color color_;
173174
Scalar stroke_size_ = 0.0;
174-
Scalar miter_ = 0.0;
175+
Scalar miter_limit_ = 4.0;
175176

176177
Cap cap_;
177178
CapProc cap_proc_;

impeller/entity/entity_unittests.cc

+60-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "entity/contents.h"
66
#include "flutter/testing/testing.h"
7+
#include "imgui.h"
78
#include "impeller/entity/entity.h"
89
#include "impeller/entity/entity_playground.h"
910
#include "impeller/geometry/path_builder.h"
@@ -83,18 +84,32 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
8384
auto callback = [&](ContentContext& context, RenderPass& pass) {
8485
Entity entity;
8586

86-
auto create_contents = [](SolidStrokeContents::Cap cap) {
87+
ImGui::SetNextWindowSize({300, 60});
88+
ImGui::SetNextWindowPos({100, 300});
89+
ImGui::Begin("Controls");
90+
// Slightly above sqrt(2) by default, so that right angles are just below
91+
// the limit and acute angles are over the limit (causing them to get
92+
// beveled).
93+
static Scalar miter_limit = 1.41421357;
94+
ImGui::SliderFloat("Miter limit", &miter_limit, 0, 30);
95+
ImGui::End();
96+
97+
auto create_contents = [](SolidStrokeContents::Cap cap,
98+
SolidStrokeContents::Join join) {
8799
auto contents = std::make_unique<SolidStrokeContents>();
88100
contents->SetColor(Color::Red());
89101
contents->SetStrokeSize(20.0);
90102
contents->SetStrokeCap(cap);
103+
contents->SetStrokeJoin(join);
104+
contents->SetStrokeMiter(miter_limit);
91105
return contents;
92106
};
93107

94108
const Point a_def(100, 100), b_def(100, 150), c_def(200, 100),
95-
d_def(200, 50);
109+
d_def(200, 50), e_def(150, 150);
96110
const Scalar r = 10;
97111

112+
// Cap::kButt demo.
98113
{
99114
Point off(0, 0);
100115
Point a, b, c, d;
@@ -103,10 +118,12 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
103118
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
104119
Color::Black(), Color::White());
105120
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
106-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt));
121+
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
122+
SolidStrokeContents::Join::kBevel));
107123
entity.Render(context, pass);
108124
}
109125

126+
// Cap::kSquare demo.
110127
{
111128
Point off(0, 100);
112129
Point a, b, c, d;
@@ -115,7 +132,34 @@ TEST_F(EntityTest, StrokeCapAndJoinTest) {
115132
std::tie(c, d) = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r,
116133
Color::Black(), Color::White());
117134
entity.SetPath(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath());
118-
entity.SetContents(create_contents(SolidStrokeContents::Cap::kSquare));
135+
entity.SetContents(create_contents(SolidStrokeContents::Cap::kSquare,
136+
SolidStrokeContents::Join::kBevel));
137+
entity.Render(context, pass);
138+
}
139+
140+
// Join::kBevel demo.
141+
{
142+
Point off(200, 0);
143+
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
144+
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
145+
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
146+
entity.SetPath(
147+
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
148+
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
149+
SolidStrokeContents::Join::kBevel));
150+
entity.Render(context, pass);
151+
}
152+
153+
// Join::kMiter demo.
154+
{
155+
Point off(200, 100);
156+
Point a = IMPELLER_PLAYGROUND_POINT(off + a_def, r, Color::White());
157+
Point b = IMPELLER_PLAYGROUND_POINT(off + e_def, r, Color::White());
158+
Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White());
159+
entity.SetPath(
160+
PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath());
161+
entity.SetContents(create_contents(SolidStrokeContents::Cap::kButt,
162+
SolidStrokeContents::Join::kMiter));
119163
entity.Render(context, pass);
120164
}
121165

@@ -379,7 +423,7 @@ TEST_F(EntityTest, SolidStrokeContentsSetStrokeCapsAndJoins) {
379423
SolidStrokeContents stroke;
380424
// Defaults.
381425
ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kButt);
382-
ASSERT_EQ(stroke.GetStrokeJoin(), SolidStrokeContents::Join::kBevel);
426+
ASSERT_EQ(stroke.GetStrokeJoin(), SolidStrokeContents::Join::kMiter);
383427
}
384428

385429
{
@@ -395,5 +439,16 @@ TEST_F(EntityTest, SolidStrokeContentsSetStrokeCapsAndJoins) {
395439
}
396440
}
397441

442+
TEST_F(EntityTest, SolidStrokeContentsSetMiter) {
443+
SolidStrokeContents contents;
444+
ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 4);
445+
446+
contents.SetStrokeMiter(8);
447+
ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8);
448+
449+
contents.SetStrokeMiter(-1);
450+
ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8);
451+
}
452+
398453
} // namespace testing
399454
} // namespace impeller

impeller/geometry/geometry_unittests.cc

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include <limits>
56
#include "impeller/geometry/geometry_unittests.h"
67
#include <limits>
78
#include "flutter/testing/testing.h"
@@ -10,11 +11,20 @@
1011
#include "impeller/geometry/path_component.h"
1112
#include "impeller/geometry/point.h"
1213
#include "impeller/geometry/rect.h"
14+
#include "impeller/geometry/scalar.h"
1315
#include "impeller/geometry/size.h"
1416

1517
namespace impeller {
1618
namespace testing {
1719

20+
TEST(GeometryTest, ScalarNearlyEqual) {
21+
ASSERT_FALSE(ScalarNearlyEqual(0.002f, 0.001f));
22+
ASSERT_TRUE(ScalarNearlyEqual(0.002f, 0.001f, 0.0011f));
23+
ASSERT_FALSE(ScalarNearlyEqual(0.002f, 0.001f, 0.0009f));
24+
ASSERT_TRUE(
25+
ScalarNearlyEqual(1.0f, 1.0f + std::numeric_limits<float>::epsilon()*4));
26+
}
27+
1828
TEST(GeometryTest, RotationMatrix) {
1929
auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4});
2030
auto expect = Matrix{0.707, 0.707, 0, 0, //

impeller/geometry/scalar.h

+9
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,22 @@
55
#pragma once
66

77
#include <cfloat>
8+
#include <valarray>
89

10+
#include "flutter/fml/logging.h"
911
#include "impeller/geometry/constants.h"
1012

1113
namespace impeller {
1214

1315
using Scalar = float;
1416

17+
constexpr inline bool ScalarNearlyEqual(Scalar x,
18+
Scalar y,
19+
Scalar tolerance = 1e-3) {
20+
FML_DCHECK(tolerance >= 0);
21+
return std::abs(x - y) <= tolerance;
22+
}
23+
1524
struct Degrees;
1625

1726
struct Radians {

0 commit comments

Comments
 (0)