diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index da402f0d30cce..b212eef9ab38d 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -2060,7 +2060,7 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, FillCirclesRenderCorrectly) { +TEST_P(AiksTest, FilledCirclesRenderCorrectly) { Canvas canvas; canvas.Scale(GetContentScale()); Paint paint; @@ -2071,6 +2071,9 @@ TEST_P(AiksTest, FillCirclesRenderCorrectly) { Color::Crimson(), }; + paint.color = Color::White(); + canvas.DrawPaint(paint); + int c_index = 0; int radius = 600; while (radius > 0) { @@ -2083,6 +2086,100 @@ TEST_P(AiksTest, FillCirclesRenderCorrectly) { } } + std::vector gradient_colors = { + Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, + Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, + Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, + Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, + Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, + Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, + Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; + std::vector stops = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + paint.color_source = ColorSource::MakeRadialGradient( + {500, 600}, 75, std::move(gradient_colors), std::move(stops), + Entity::TileMode::kMirror, {}); + canvas.DrawCircle({500, 600}, 100, paint); + + paint.color_source = ColorSource::MakeImage( + texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, + Matrix::MakeTranslation({700, 200})); + canvas.DrawCircle({800, 300}, 100, paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, StrokedCirclesRenderCorrectly) { + Canvas canvas; + canvas.Scale(GetContentScale()); + Paint paint; + const int color_count = 3; + Color colors[color_count] = { + Color::Blue(), + Color::Green(), + Color::Crimson(), + }; + + paint.color = Color::White(); + canvas.DrawPaint(paint); + + int c_index = 0; + + auto draw = [&paint, &colors, &c_index](Canvas& canvas, Point center, + Scalar r, Scalar dr, int n) { + for (int i = 0; i < n; i++) { + paint.color = colors[(c_index++) % color_count]; + canvas.DrawCircle(center, r, paint); + r += dr; + } + }; + + paint.style = Paint::Style::kStroke; + paint.stroke_width = 1; + draw(canvas, {10, 10}, 2, 2, 14); // r = [2, 28], covers [1,29] + paint.stroke_width = 5; + draw(canvas, {10, 10}, 35, 10, 56); // r = [35, 585], covers [30,590] + + std::vector gradient_colors = { + Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, + Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, + Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, + Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, + Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, + Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, + Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; + std::vector stops = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + paint.color_source = ColorSource::MakeRadialGradient( + {500, 600}, 75, std::move(gradient_colors), std::move(stops), + Entity::TileMode::kMirror, {}); + draw(canvas, {500, 600}, 5, 10, 10); + + paint.color_source = ColorSource::MakeImage( + texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, + Matrix::MakeTranslation({700, 200})); + draw(canvas, {800, 300}, 5, 10, 10); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 02b215e41c274..da885cc6e2546 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -287,18 +287,6 @@ void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) { } void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) { - if (paint.style == Paint::Style::kStroke) { - auto circle_path = - PathBuilder{} - .AddCircle(center, radius) - .SetConvexity(Convexity::kConvex) - .SetBounds(Rect::MakeLTRB(center.x - radius, center.y - radius, - center.x + radius, center.y + radius)) - .TakePath(); - DrawPath(circle_path, paint); - return; - } - Size half_size(radius, radius); if (AttemptDrawBlurredRRect( Rect::MakeOriginSize(center - half_size, half_size * 2), radius, @@ -310,8 +298,12 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) { entity.SetTransform(GetCurrentTransform()); entity.SetClipDepth(GetClipDepth()); entity.SetBlendMode(paint.blend_mode); - entity.SetContents(paint.WithFilters( - paint.CreateContentsForGeometry(Geometry::MakeCircle(center, radius)))); + auto geometry = + paint.style == Paint::Style::kStroke + ? Geometry::MakeStrokedCircle(center, radius, paint.stroke_width) + : Geometry::MakeCircle(center, radius); + entity.SetContents( + paint.WithFilters(paint.CreateContentsForGeometry(geometry))); GetCurrentPass().AddEntity(entity); } diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index c0c42f1e8fdc4..3d9668ce42d06 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -2,15 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "impeller/entity/geometry/ellipse_geometry.h" +#include +#include "flutter/impeller/entity/geometry/ellipse_geometry.h" + +#include "flutter/impeller/entity/geometry/line_geometry.h" #include "flutter/impeller/tessellator/circle_tessellator.h" namespace impeller { EllipseGeometry::EllipseGeometry(Point center, Scalar radius) - : center_(center), radius_(radius) { + : center_(center), radius_(radius), stroke_width_(-1.0) { + FML_DCHECK(radius >= 0); +} + +EllipseGeometry::EllipseGeometry(Point center, + Scalar radius, + Scalar stroke_width) + : center_(center), + radius_(radius), + stroke_width_(std::max(stroke_width, 0.0f)) { FML_DCHECK(radius >= 0); + FML_DCHECK(stroke_width >= 0); } GeometryResult EllipseGeometry::GetPositionBuffer( @@ -20,24 +33,52 @@ GeometryResult EllipseGeometry::GetPositionBuffer( auto& host_buffer = pass.GetTransientsBuffer(); using VT = SolidFillVertexShader::PerVertexData; - Scalar radius = radius_; + Scalar half_width = stroke_width_ < 0 + ? 0.0 + : LineGeometry::ComputePixelHalfWidth( + entity.GetTransform(), stroke_width_); + Scalar outer_radius = radius_ + half_width; + Scalar inner_radius = half_width <= 0 ? 0.0 : radius_ - half_width; + const Point& center = center_; std::shared_ptr tessellator = renderer.GetTessellator(); CircleTessellator circle_tessellator(tessellator, entity.GetTransform(), - radius_); - size_t count = circle_tessellator.GetCircleVertexCount(); - auto vertex_buffer = host_buffer.Emplace( - count * sizeof(VT), alignof(VT), - [&circle_tessellator, ¢er, radius](uint8_t* buffer) { - auto vertices = reinterpret_cast(buffer); - circle_tessellator.GenerateCircleTriangleStrip( - [&vertices](const Point& p) { // - *vertices++ = { - .position = p, - }; - }, - center, radius); - }); + outer_radius); + + BufferView vertex_buffer; + size_t count; + if (inner_radius > 0) { + count = circle_tessellator.GetStrokedCircleVertexCount(); + vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&circle_tessellator, &count, ¢er, outer_radius, + inner_radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + circle_tessellator.GenerateStrokedCircleTriangleStrip( + [&vertices](const Point& p) { // + *vertices++ = { + .position = p, + }; + }, + center, outer_radius, inner_radius); + FML_DCHECK(vertices == reinterpret_cast(buffer) + count); + }); + } else { + count = circle_tessellator.GetCircleVertexCount(); + vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&circle_tessellator, &count, ¢er, outer_radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + circle_tessellator.GenerateCircleTriangleStrip( + [&vertices](const Point& p) { // + *vertices++ = { + .position = p, + }; + }, + center, outer_radius); + FML_DCHECK(vertices == reinterpret_cast(buffer) + count); + }); + } return GeometryResult{ .type = PrimitiveType::kTriangleStrip, @@ -65,25 +106,55 @@ GeometryResult EllipseGeometry::GetPositionUVBuffer( auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; - Scalar radius = radius_; + Scalar half_width = stroke_width_ < 0 + ? 0.0 + : LineGeometry::ComputePixelHalfWidth( + entity.GetTransform(), stroke_width_); + Scalar outer_radius = radius_ + half_width; + Scalar inner_radius = half_width <= 0 ? 0.0 : radius_ - half_width; + const Point& center = center_; std::shared_ptr tessellator = renderer.GetTessellator(); CircleTessellator circle_tessellator(tessellator, entity.GetTransform(), - radius_); - size_t count = circle_tessellator.GetCircleVertexCount(); - auto vertex_buffer = host_buffer.Emplace( - count * sizeof(VT), alignof(VT), - [&circle_tessellator, &uv_transform, ¢er, radius](uint8_t* buffer) { - auto vertices = reinterpret_cast(buffer); - circle_tessellator.GenerateCircleTriangleStrip( - [&vertices, &uv_transform](const Point& p) { // - *vertices++ = { - .position = p, - .texture_coords = uv_transform * p, - }; - }, - center, radius); - }); + outer_radius); + + BufferView vertex_buffer; + size_t count; + if (inner_radius > 0) { + count = circle_tessellator.GetStrokedCircleVertexCount(); + vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&circle_tessellator, &uv_transform, &count, ¢er, outer_radius, + inner_radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + circle_tessellator.GenerateStrokedCircleTriangleStrip( + [&vertices, &uv_transform](const Point& p) { // + *vertices++ = { + .position = p, + .texture_coords = uv_transform * p, + }; + }, + center, outer_radius, inner_radius); + FML_DCHECK(vertices == reinterpret_cast(buffer) + count); + }); + } else { + count = circle_tessellator.GetCircleVertexCount(); + vertex_buffer = host_buffer.Emplace( + count * sizeof(VT), alignof(VT), + [&circle_tessellator, &uv_transform, &count, ¢er, + outer_radius](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + circle_tessellator.GenerateCircleTriangleStrip( + [&vertices, &uv_transform](const Point& p) { // + *vertices++ = { + .position = p, + .texture_coords = uv_transform * p, + }; + }, + center, outer_radius); + FML_DCHECK(vertices == reinterpret_cast(buffer) + count); + }); + } return GeometryResult{ .type = PrimitiveType::kTriangleStrip, diff --git a/impeller/entity/geometry/ellipse_geometry.h b/impeller/entity/geometry/ellipse_geometry.h index ecc56331b70ae..58b3e1401d1a7 100644 --- a/impeller/entity/geometry/ellipse_geometry.h +++ b/impeller/entity/geometry/ellipse_geometry.h @@ -11,6 +11,7 @@ namespace impeller { class EllipseGeometry final : public Geometry { public: explicit EllipseGeometry(Point center, Scalar radius); + explicit EllipseGeometry(Point center, Scalar radius, Scalar stroke_width); ~EllipseGeometry() = default; @@ -59,6 +60,7 @@ class EllipseGeometry final : public Geometry { Point center_; Scalar radius_; + Scalar stroke_width_; EllipseGeometry(const EllipseGeometry&) = delete; diff --git a/impeller/entity/geometry/geometry.cc b/impeller/entity/geometry/geometry.cc index 4b1a32a6e3fe1..ac30e7baa0c70 100644 --- a/impeller/entity/geometry/geometry.cc +++ b/impeller/entity/geometry/geometry.cc @@ -123,6 +123,12 @@ std::shared_ptr Geometry::MakeCircle(Point center, Scalar radius) { return std::make_shared(center, radius); } +std::shared_ptr Geometry::MakeStrokedCircle(Point center, + Scalar radius, + Scalar stroke_width) { + return std::make_shared(center, radius, stroke_width); +} + bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const { return false; } diff --git a/impeller/entity/geometry/geometry.h b/impeller/entity/geometry/geometry.h index 2d1dac945e28e..beb43a2d5d1b5 100644 --- a/impeller/entity/geometry/geometry.h +++ b/impeller/entity/geometry/geometry.h @@ -70,6 +70,10 @@ class Geometry { static std::shared_ptr MakeCircle(Point center, Scalar radius); + static std::shared_ptr MakeStrokedCircle(Point center, + Scalar radius, + Scalar stroke_width); + static std::shared_ptr MakePointField(std::vector points, Scalar radius, bool round); diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index 2d2dd8317d783..83c48af4eec0f 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -13,19 +13,20 @@ LineGeometry::LineGeometry(Point p0, Point p1, Scalar width, Cap cap) FML_DCHECK(width >= 0); } -Scalar LineGeometry::ComputeHalfWidth(const Matrix& transform) const { +Scalar LineGeometry::ComputePixelHalfWidth(const Matrix& transform, + Scalar width) { auto determinant = transform.GetDeterminant(); if (determinant == 0) { return 0.0f; } Scalar min_size = 1.0f / sqrt(std::abs(determinant)); - return std::max(width_, min_size) * 0.5f; + return std::max(width, min_size) * 0.5f; } Vector2 LineGeometry::ComputeAlongVector(const Matrix& transform, bool allow_zero_length) const { - Scalar stroke_half_width = ComputeHalfWidth(transform); + Scalar stroke_half_width = ComputePixelHalfWidth(transform, width_); if (stroke_half_width < kEhCloseEnough) { return {}; } @@ -72,7 +73,7 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, using VT = SolidFillVertexShader::PerVertexData; auto& transform = entity.GetTransform(); - auto radius = ComputeHalfWidth(transform); + auto radius = ComputePixelHalfWidth(transform, width_); size_t count; BufferView vertex_buffer; @@ -138,7 +139,7 @@ GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, using VT = TextureFillVertexShader::PerVertexData; auto& transform = entity.GetTransform(); - auto radius = ComputeHalfWidth(transform); + auto radius = ComputePixelHalfWidth(transform, width_); auto uv_transform = texture_coverage.GetNormalizingTransform() * effect_transform; diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h index 272702d91a42a..19bf5665aed21 100644 --- a/impeller/entity/geometry/line_geometry.h +++ b/impeller/entity/geometry/line_geometry.h @@ -15,6 +15,8 @@ class LineGeometry final : public Geometry { ~LineGeometry() = default; + static Scalar ComputePixelHalfWidth(const Matrix& transform, Scalar width); + // |Geometry| bool CoversArea(const Matrix& transform, const Rect& rect) const override; @@ -43,8 +45,6 @@ class LineGeometry final : public Geometry { Vector2 ComputeAlongVector(const Matrix& transform, bool allow_zero_length) const; - Scalar ComputeHalfWidth(const Matrix& transform) const; - // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, const Entity& entity, diff --git a/impeller/tessellator/circle_tessellator.cc b/impeller/tessellator/circle_tessellator.cc index 35f0f924db38c..35932fc1df5ca 100644 --- a/impeller/tessellator/circle_tessellator.cc +++ b/impeller/tessellator/circle_tessellator.cc @@ -162,15 +162,18 @@ void CircleTessellator::GenerateCircleTriangleStrip( const TessellatedPointProc& proc, const Point& center, Scalar radius) const { + // Quadrant 1 connecting with Quadrant 4: for (auto& trig : trigs_) { auto offset = trig * radius; proc({center.x - offset.x, center.y + offset.y}); proc({center.x - offset.x, center.y - offset.y}); } + // The second half of the circle should be iterated in reverse, but // we can instead iterate forward and swap the x/y values of the // offset as the angles should be symmetric and thus should generate - // symmetrically reversed offset vectors. + // symmetrically reversed trig vectors. + // Quadrant 2 connecting with Quadrant 2: for (auto& trig : trigs_) { auto offset = trig * radius; proc({center.x + offset.y, center.y + offset.x}); @@ -178,6 +181,53 @@ void CircleTessellator::GenerateCircleTriangleStrip( } } +void CircleTessellator::GenerateStrokedCircleTriangleStrip( + const TessellatedPointProc& proc, + const Point& center, + Scalar outer_radius, + Scalar inner_radius) const { + // Zig-zag back and forth between points on the outer circle and the + // inner circle. Both circles are evaluated at the same number of + // quadrant divisions so the points for a given division should match + // 1 for 1 other than their applied radius. + + // Quadrant 1: + for (auto& trig : trigs_) { + auto outer = trig * outer_radius; + auto inner = trig * inner_radius; + proc({center.x - outer.x, center.y - outer.y}); + proc({center.x - inner.x, center.y - inner.y}); + } + + // The even quadrants of the circle should be iterated in reverse, but + // we can instead iterate forward and swap the x/y values of the + // offset as the angles should be symmetric and thus should generate + // symmetrically reversed trig vectors. + // Quadrant 2: + for (auto& trig : trigs_) { + auto outer = trig * outer_radius; + auto inner = trig * inner_radius; + proc({center.x + outer.y, center.y - outer.x}); + proc({center.x + inner.y, center.y - inner.x}); + } + + // Quadrant 3: + for (auto& trig : trigs_) { + auto outer = trig * outer_radius; + auto inner = trig * inner_radius; + proc({center.x + outer.x, center.y + outer.y}); + proc({center.x + inner.x, center.y + inner.y}); + } + + // Quadrant 4: + for (auto& trig : trigs_) { + auto outer = trig * outer_radius; + auto inner = trig * inner_radius; + proc({center.x - outer.y, center.y + outer.x}); + proc({center.x - inner.y, center.y + inner.x}); + } +} + void CircleTessellator::GenerateRoundCapLineTriangleStrip( const TessellatedPointProc& proc, const Point& p0, diff --git a/impeller/tessellator/circle_tessellator.h b/impeller/tessellator/circle_tessellator.h index 74624eb78a8c1..d2dc15d6f9ab2 100644 --- a/impeller/tessellator/circle_tessellator.h +++ b/impeller/tessellator/circle_tessellator.h @@ -67,6 +67,15 @@ class CircleTessellator { /// |GenerateRoundCapLineTriangleStrip| methods. size_t GetCircleVertexCount() const { return trigs_.size() * 4; } + /// @brief Return the number of vertices that will be generated to + /// tessellate a full stroked circle with a triangle strip. + /// + /// This value can be used to pre-allocate space in a vector + /// to hold the vertices that will be produced by the + /// |GenerateCircleTriangleStrip| and + /// |GenerateRoundCapLineTriangleStrip| methods. + size_t GetStrokedCircleVertexCount() const { return trigs_.size() * 8; } + /// @brief Generate the vertices for a triangle strip that covers the /// circle at a given |radius| from a given |center|, delivering /// the computed coordinates to the supplied |proc|. @@ -78,6 +87,19 @@ class CircleTessellator { const Point& center, Scalar radius) const; + /// @brief Generate the vertices for a triangle strip that draws the gap + /// between 2 circles at |outer_radius| and |inner_radius| + /// from a given |center|, delivering the computed coordinates to + /// the supplied |proc|. + /// + /// This procedure will generate no more than the number of + /// vertices returned by |GetStrokedCircleVertexCount| in an order + /// appropriate for rendering as a triangle strip. + void GenerateStrokedCircleTriangleStrip(const TessellatedPointProc& proc, + const Point& center, + Scalar outer_radius, + Scalar inner_radius) const; + /// @brief Generate the vertices for a triangle strip that covers the /// line from |p0| to |p1| with round caps of the specified /// |radius|, delivering the computed coordinates to the supplied diff --git a/impeller/tessellator/circle_tessellator_unittests.cc b/impeller/tessellator/circle_tessellator_unittests.cc index a84735f35b21d..2a5d8584358ab 100644 --- a/impeller/tessellator/circle_tessellator_unittests.cc +++ b/impeller/tessellator/circle_tessellator_unittests.cc @@ -11,7 +11,7 @@ namespace impeller { namespace testing { -TEST(CircleTessellator, DivisionVertexCounts) { +TEST(CircleTessellator, DivisionAndVertexCounts) { auto tessellator = std::make_shared(); auto test = [&tessellator](const Matrix& transform, Scalar radius) { @@ -22,6 +22,10 @@ TEST(CircleTessellator, DivisionVertexCounts) { (quadrant_divisions + 1) * 4) << "transform = " << transform << ", radius = " << radius; + EXPECT_EQ(circle_tessellator.GetStrokedCircleVertexCount(), + (quadrant_divisions + 1) * 8) + << "transform = " << transform << ", radius = " << radius; + // Confirm the approximation error is within the currently accepted // |kCircleTolerance| value advertised by |CircleTessellator|. // (With an additional 1% tolerance for floating point rounding.) @@ -51,7 +55,7 @@ TEST(CircleTessellator, DivisionVertexCounts) { } } -TEST(CircleTessellator, CircleTessellationVertices) { +TEST(CircleTessellator, FilledCircleTessellationVertices) { auto tessellator = std::make_shared(); auto test = [&tessellator](Scalar pixel_radius, Point center, Scalar radius) { @@ -64,26 +68,27 @@ TEST(CircleTessellator, CircleTessellationVertices) { vertices.push_back(p); }, center, radius); - ASSERT_EQ(vertices.size(), vertex_count); + EXPECT_EQ(vertices.size(), vertex_count); ASSERT_EQ(vertex_count % 4, 0u); auto quadrant_count = vertex_count / 4; for (size_t i = 0; i < quadrant_count; i++) { double angle = kPiOver2 * i / (quadrant_count - 1); + double degrees = angle * 180.0 / kPi; double rsin = sin(angle) * radius; double rcos = cos(angle) * radius; EXPECT_POINT_NEAR(vertices[i * 2], Point(center.x - rcos, center.y + rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + << "vertex " << i << ", angle = " << degrees << std::endl; EXPECT_POINT_NEAR(vertices[i * 2 + 1], Point(center.x - rcos, center.y - rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + << "vertex " << i << ", angle = " << degrees << std::endl; EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 1], Point(center.x + rcos, center.y - rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + << "vertex " << i << ", angle = " << degrees << std::endl; EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 2], Point(center.x + rcos, center.y + rsin)) - << "vertex " << i << ", angle = " << angle * 180.0 / kPi << std::endl; + << "vertex " << i << ", angle = " << degrees << std::endl; } }; @@ -93,5 +98,73 @@ TEST(CircleTessellator, CircleTessellationVertices) { test(2.0, {}, 1000.0); } +TEST(CircleTessellator, StrokedCircleTessellationVertices) { + auto tessellator = std::make_shared(); + + auto test = [&tessellator](Scalar pixel_radius, Point center, Scalar radius, + Scalar width) { + ASSERT_GT(radius, width); + CircleTessellator circle_tessellator(tessellator, {}, pixel_radius); + + auto vertex_count = circle_tessellator.GetStrokedCircleVertexCount(); + auto vertices = std::vector(); + circle_tessellator.GenerateStrokedCircleTriangleStrip( + [&vertices](const Point& p) { // + vertices.push_back(p); + }, + center, radius + width, radius - width); + EXPECT_EQ(vertices.size(), vertex_count); + ASSERT_EQ(vertex_count % 4, 0u); + + auto quadrant_count = vertex_count / 8; + + // Test outer points first + for (size_t i = 0; i < quadrant_count; i++) { + double angle = kPiOver2 * i / (quadrant_count - 1); + double degrees = angle * 180.0 / kPi; + double rsin = sin(angle) * (radius + width); + double rcos = cos(angle) * (radius + width); + EXPECT_POINT_NEAR(vertices[i * 2], + Point(center.x - rcos, center.y - rsin)) + << "vertex " << i << ", angle = " << degrees << std::endl; + EXPECT_POINT_NEAR(vertices[quadrant_count * 2 + i * 2], + Point(center.x + rsin, center.y - rcos)) + << "vertex " << i << ", angle = " << degrees << std::endl; + EXPECT_POINT_NEAR(vertices[quadrant_count * 4 + i * 2], + Point(center.x + rcos, center.y + rsin)) + << "vertex " << i << ", angle = " << degrees << std::endl; + EXPECT_POINT_NEAR(vertices[quadrant_count * 6 + i * 2], + Point(center.x - rsin, center.y + rcos)) + << "vertex " << i << ", angle = " << degrees << std::endl; + } + + // Then test innerer points + for (size_t i = 0; i < quadrant_count; i++) { + double angle = kPiOver2 * i / (quadrant_count - 1); + double degrees = angle * 180.0 / kPi; + double rsin = sin(angle) * (radius - width); + double rcos = cos(angle) * (radius - width); + EXPECT_POINT_NEAR(vertices[i * 2 + 1], + Point(center.x - rcos, center.y - rsin)) + << "vertex " << i << ", angle = " << degrees << std::endl; + EXPECT_POINT_NEAR(vertices[quadrant_count * 2 + i * 2 + 1], + Point(center.x + rsin, center.y - rcos)) + << "vertex " << i << ", angle = " << degrees << std::endl; + EXPECT_POINT_NEAR(vertices[quadrant_count * 4 + i * 2 + 1], + Point(center.x + rcos, center.y + rsin)) + << "vertex " << i << ", angle = " << degrees << std::endl; + EXPECT_POINT_NEAR(vertices[quadrant_count * 6 + i * 2 + 1], + Point(center.x - rsin, center.y + rcos)) + << "vertex " << i << ", angle = " << degrees << std::endl; + } + }; + + test(2.0, {}, 2.0, 1.0); + test(2.0, {}, 2.0, 0.5); + test(2.0, {10, 10}, 2.0, 1.0); + test(1000.0, {}, 2.0, 1.0); + test(2.0, {}, 1000.0, 10.0); +} + } // namespace testing } // namespace impeller