blob: 10f64493d1e0e04d65d0be6b17da0640723fa33a [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"
#include "impeller/geometry/geometry_asserts.h"
#include "impeller/geometry/path.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/tessellator/tessellator.h"
namespace impeller {
namespace testing {
TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) {
// Zero points.
{
Tessellator t;
auto path = PathBuilder{}.TakePath(FillType::kPositive);
Tessellator::Result result = t.Tessellate(
path, 1.0f,
[](const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) { return true; });
ASSERT_EQ(result, Tessellator::Result::kInputError);
}
// One point.
{
Tessellator t;
auto path = PathBuilder{}.LineTo({0, 0}).TakePath(FillType::kPositive);
Tessellator::Result result = t.Tessellate(
path, 1.0f,
[](const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) { return true; });
ASSERT_EQ(result, Tessellator::Result::kSuccess);
}
// Two points.
{
Tessellator t;
auto path =
PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath(FillType::kPositive);
Tessellator::Result result = t.Tessellate(
path, 1.0f,
[](const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) { return true; });
ASSERT_EQ(result, Tessellator::Result::kSuccess);
}
// Many points.
{
Tessellator t;
PathBuilder builder;
for (int i = 0; i < 1000; i++) {
auto coord = i * 1.0f;
builder.AddLine({coord, coord}, {coord + 1, coord + 1});
}
auto path = builder.TakePath(FillType::kPositive);
Tessellator::Result result = t.Tessellate(
path, 1.0f,
[](const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) { return true; });
ASSERT_EQ(result, Tessellator::Result::kSuccess);
}
// Closure fails.
{
Tessellator t;
auto path =
PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath(FillType::kPositive);
Tessellator::Result result = t.Tessellate(
path, 1.0f,
[](const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) { return false; });
ASSERT_EQ(result, Tessellator::Result::kInputError);
}
// More than uint16 points, odd fill mode.
{
Tessellator t;
PathBuilder builder = {};
for (auto i = 0; i < 1000; i++) {
builder.AddCircle(Point(i, i), 4);
}
auto path = builder.TakePath(FillType::kOdd);
bool no_indices = false;
size_t count = 0u;
Tessellator::Result result = t.Tessellate(
path, 1.0f,
[&no_indices, &count](const float* vertices, size_t vertices_count,
const uint16_t* indices, size_t indices_count) {
no_indices = indices == nullptr;
count = vertices_count;
return true;
});
ASSERT_TRUE(no_indices);
ASSERT_TRUE(count >= USHRT_MAX);
ASSERT_EQ(result, Tessellator::Result::kSuccess);
}
}
TEST(TessellatorTest, TessellateConvex) {
{
Tessellator t;
// Sanity check simple rectangle.
auto pts = t.TessellateConvex(
PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 10, 10)).TakePath(), 1.0);
std::vector<Point> expected = {
{0, 0}, {10, 0}, {0, 10}, {10, 10}, //
};
EXPECT_EQ(pts, expected);
}
{
Tessellator t;
auto pts = t.TessellateConvex(PathBuilder{}
.AddRect(Rect::MakeLTRB(0, 0, 10, 10))
.AddRect(Rect::MakeLTRB(20, 20, 30, 30))
.TakePath(),
1.0);
std::vector<Point> expected = {{0, 0}, {10, 0}, {0, 10}, {10, 10},
{10, 10}, {20, 20}, {20, 20}, {30, 20},
{20, 30}, {30, 30}};
EXPECT_EQ(pts, expected);
}
}
TEST(TessellatorTest, CircleVertexCounts) {
auto tessellator = std::make_shared<Tessellator>();
auto test = [&tessellator](const Matrix& transform, Scalar radius) {
auto generator = tessellator->FilledCircle(transform, {}, radius);
size_t quadrant_divisions = generator.GetVertexCount() / 4;
// Confirm the approximation error is within the currently accepted
// |kCircleTolerance| value advertised by |CircleTessellator|.
// (With an additional 1% tolerance for floating point rounding.)
double angle = kPiOver2 / quadrant_divisions;
Point first = {radius, 0};
Point next = {static_cast<Scalar>(cos(angle) * radius),
static_cast<Scalar>(sin(angle) * radius)};
Point midpoint = (first + next) * 0.5;
EXPECT_GE(midpoint.GetLength(),
radius - Tessellator::kCircleTolerance * 1.01)
<< ", transform = " << transform << ", radius = " << radius
<< ", divisions = " << quadrant_divisions;
};
test({}, 0.0);
test({}, 0.9);
test({}, 1.0);
test({}, 1.9);
test(Matrix::MakeScale(Vector2(2.0, 2.0)), 0.95);
test({}, 2.0);
test(Matrix::MakeScale(Vector2(2.0, 2.0)), 1.0);
test({}, 11.9);
test({}, 12.0);
test({}, 35.9);
for (int i = 36; i < 10000; i += 4) {
test({}, i);
}
}
TEST(TessellatorTest, FilledCircleTessellationVertices) {
auto tessellator = std::make_shared<Tessellator>();
auto test = [&tessellator](const Matrix& transform, const Point& center,
Scalar radius) {
auto generator = tessellator->FilledCircle(transform, center, radius);
EXPECT_EQ(generator.GetTriangleType(), PrimitiveType::kTriangleStrip);
auto vertex_count = generator.GetVertexCount();
auto vertices = std::vector<Point>();
generator.GenerateVertices([&vertices](const Point& p) { //
vertices.push_back(p);
});
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 = " << degrees << std::endl;
EXPECT_POINT_NEAR(vertices[i * 2 + 1],
Point(center.x - rcos, center.y - rsin))
<< "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 = " << degrees << std::endl;
EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 2],
Point(center.x + rcos, center.y + rsin))
<< "vertex " << i << ", angle = " << degrees << std::endl;
}
};
test({}, {}, 2.0);
test({}, {10, 10}, 2.0);
test(Matrix::MakeScale({500.0, 500.0, 0.0}), {}, 2.0);
test(Matrix::MakeScale({0.002, 0.002, 0.0}), {}, 1000.0);
}
TEST(TessellatorTest, StrokedCircleTessellationVertices) {
auto tessellator = std::make_shared<Tessellator>();
auto test = [&tessellator](const Matrix& transform, const Point& center,
Scalar radius, Scalar half_width) {
ASSERT_GT(radius, half_width);
auto generator =
tessellator->StrokedCircle(transform, center, radius, half_width);
EXPECT_EQ(generator.GetTriangleType(), PrimitiveType::kTriangleStrip);
auto vertex_count = generator.GetVertexCount();
auto vertices = std::vector<Point>();
generator.GenerateVertices([&vertices](const Point& p) { //
vertices.push_back(p);
});
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 + half_width);
double rcos = cos(angle) * (radius + half_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 - half_width);
double rcos = cos(angle) * (radius - half_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, 1.0);
test({}, {}, 2.0, 0.5);
test({}, {10, 10}, 2.0, 1.0);
test(Matrix::MakeScale({500.0, 500.0, 0.0}), {}, 2.0, 1.0);
test(Matrix::MakeScale({0.002, 0.002, 0.0}), {}, 1000.0, 10.0);
}
TEST(TessellatorTest, RoundCapLineTessellationVertices) {
auto tessellator = std::make_shared<Tessellator>();
auto test = [&tessellator](const Matrix& transform, const Point& p0,
const Point& p1, Scalar radius) {
auto generator = tessellator->RoundCapLine(transform, p0, p1, radius);
EXPECT_EQ(generator.GetTriangleType(), PrimitiveType::kTriangleStrip);
auto vertex_count = generator.GetVertexCount();
auto vertices = std::vector<Point>();
generator.GenerateVertices([&vertices](const Point& p) { //
vertices.push_back(p);
});
EXPECT_EQ(vertices.size(), vertex_count);
ASSERT_EQ(vertex_count % 4, 0u);
Point along = p1 - p0;
Scalar length = along.GetLength();
if (length > 0) {
along *= radius / length;
} else {
along = {radius, 0};
}
Point across = {-along.y, along.x};
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;
Point relative_along = along * cos(angle);
Point relative_across = across * sin(angle);
EXPECT_POINT_NEAR(vertices[i * 2], //
p0 - relative_along + relative_across)
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "line = " << p0 << " => " << p1 << ", " //
<< "radius = " << radius << std::endl;
EXPECT_POINT_NEAR(vertices[i * 2 + 1], //
p0 - relative_along - relative_across)
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "line = " << p0 << " => " << p1 << ", " //
<< "radius = " << radius << std::endl;
EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 1], //
p1 + relative_along - relative_across)
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "line = " << p0 << " => " << p1 << ", " //
<< "radius = " << radius << std::endl;
EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 2], //
p1 + relative_along + relative_across)
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "line = " << p0 << " => " << p1 << ", " //
<< "radius = " << radius << std::endl;
}
};
// Empty line should actually use the circle generator, but its
// results should match the same math as the round cap generator.
test({}, {0, 0}, {0, 0}, 10);
test({}, {0, 0}, {10, 0}, 2);
test({}, {10, 0}, {0, 0}, 2);
test({}, {0, 0}, {10, 10}, 2);
test(Matrix::MakeScale({500.0, 500.0, 0.0}), {0, 0}, {10, 0}, 2);
test(Matrix::MakeScale({500.0, 500.0, 0.0}), {10, 0}, {0, 0}, 2);
test(Matrix::MakeScale({500.0, 500.0, 0.0}), {0, 0}, {10, 10}, 2);
test(Matrix::MakeScale({0.002, 0.002, 0.0}), {0, 0}, {10, 0}, 2);
test(Matrix::MakeScale({0.002, 0.002, 0.0}), {10, 0}, {0, 0}, 2);
test(Matrix::MakeScale({0.002, 0.002, 0.0}), {0, 0}, {10, 10}, 2);
}
TEST(TessellatorTest, FilledEllipseTessellationVertices) {
auto tessellator = std::make_shared<Tessellator>();
auto test = [&tessellator](const Matrix& transform, const Rect& bounds) {
auto center = bounds.GetCenter();
auto half_size = bounds.GetSize() * 0.5f;
auto generator = tessellator->FilledEllipse(transform, bounds);
EXPECT_EQ(generator.GetTriangleType(), PrimitiveType::kTriangleStrip);
auto vertex_count = generator.GetVertexCount();
auto vertices = std::vector<Point>();
generator.GenerateVertices([&vertices](const Point& p) { //
vertices.push_back(p);
});
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 rcos = cos(angle) * half_size.width;
double rsin = sin(angle) * half_size.height;
EXPECT_POINT_NEAR(vertices[i * 2],
Point(center.x - rcos, center.y + rsin))
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "bounds = " << bounds << std::endl;
EXPECT_POINT_NEAR(vertices[i * 2 + 1],
Point(center.x - rcos, center.y - rsin))
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "bounds = " << bounds << std::endl;
EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 1],
Point(center.x + rcos, center.y - rsin))
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "bounds = " << bounds << std::endl;
EXPECT_POINT_NEAR(vertices[vertex_count - i * 2 - 2],
Point(center.x + rcos, center.y + rsin))
<< "vertex " << i << ", angle = " << degrees << ", " //
<< "bounds = " << bounds << std::endl;
}
};
// Square bounds should actually use the circle generator, but its
// results should match the same math as the ellipse generator.
test({}, Rect::MakeLTRB(0, 0, 2, 2));
test({}, Rect::MakeLTRB(0, 0, 2, 3));
test({}, Rect::MakeLTRB(0, 0, 3, 2));
test({}, Rect::MakeLTRB(5, 10, 2, 3));
test({}, Rect::MakeLTRB(16, 7, 3, 2));
test(Matrix::MakeScale({500.0, 500.0, 0.0}), Rect::MakeLTRB(5, 10, 3, 2));
test(Matrix::MakeScale({500.0, 500.0, 0.0}), Rect::MakeLTRB(5, 10, 2, 3));
test(Matrix::MakeScale({0.002, 0.002, 0.0}),
Rect::MakeLTRB(5000, 10000, 3000, 2000));
test(Matrix::MakeScale({0.002, 0.002, 0.0}),
Rect::MakeLTRB(5000, 10000, 2000, 3000));
}
} // namespace testing
} // namespace impeller