Skip to content

Commit

Permalink
switch to triangle strips for circles and simplify tessellation API
Browse files Browse the repository at this point in the history
  • Loading branch information
flar committed Nov 17, 2023
1 parent fbe91b4 commit 4dbaae2
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 289 deletions.
110 changes: 44 additions & 66 deletions impeller/entity/geometry/circle_tessellator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,79 +52,57 @@ const std::vector<Trig>& CircleTessellator::GetTrigForDivisions(
return trigs;
}

void CircleTessellator::ExtendRelativeQuadrantToAbsoluteCircle(
std::vector<Point>& points,
const Point& center) {
auto quadrant_points = points.size();

// The 1st quadrant points are reversed in order, reflected around
// the Y axis, and translated to become absolute 2nd quadrant points.
for (size_t i = 1; i <= quadrant_points; i++) {
auto point = points[quadrant_points - i];
points.emplace_back(center.x + point.x, center.y - point.y);
}
void CircleTessellator::GenerateCircleTriangleStrip(
const TessellatedPointProc& proc,
const Point& center,
Scalar radius) const {
auto trigs = GetTrigForDivisions(quadrant_divisions_);

// The 1st quadrant points are reflected around the X & Y axes
// and translated to become absolute 3rd quadrant points.
for (size_t i = 0; i < quadrant_points; i++) {
auto point = points[i];
points.emplace_back(center.x - point.x, center.y - point.y);
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 1st quadrant points are reversed in order, reflected around
// the X axis and translated to become absolute 4th quadrant points.
// The 1st quadrant points are also translated to the center point as
// well since this is the last time we will use them.
for (size_t i = 1; i <= quadrant_points; i++) {
auto point = points[quadrant_points - i];
points.emplace_back(center.x - point.x, center.y + point.y);

// This is the last loop where we need the first quadrant to be
// relative so we convert them to absolute as we go.
points[quadrant_points - i] = center + point;
// 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.
for (auto& trig : trigs) {
auto offset = trig * radius;
proc({center.x + offset.y, center.y + offset.x});
proc({center.x + offset.y, center.y - offset.x});
}
}

void CircleTessellator::FillQuadrantTriangles(std::vector<Point>& points,
const Point& center,
const Point& start_vector,
const Point& end_vector) const {
// We only deal with circles for now
FML_DCHECK(start_vector.GetLength() - end_vector.GetLength() <
kEhCloseEnough);
// And only for perpendicular vectors
FML_DCHECK(start_vector.Dot(end_vector) < kEhCloseEnough);

void CircleTessellator::GenerateRoundCapLineTriangleStrip(
const TessellatedPointProc& proc,
const Point& p0,
const Point& p1,
Scalar radius) const {
auto trigs = GetTrigForDivisions(quadrant_divisions_);

auto prev = center + (trigs[0].cos * start_vector + //
trigs[0].sin * end_vector);
for (size_t i = 1; i < trigs.size(); i++) {
points.emplace_back(center);
points.emplace_back(prev);
prev = center + (trigs[i].cos * start_vector + //
trigs[i].sin * end_vector);
points.emplace_back(prev);
auto along = p1 - p0;
auto length = along.GetLength();
if (length < kEhCloseEnough) {
return GenerateCircleTriangleStrip(proc, p0, radius);
}
along *= radius / length;
auto across = Point(-along.y, along.x);

for (auto& trig : trigs) {
auto relative_across = across * trig.cos;
auto relative_along = along * trig.sin;
proc({p0 + relative_across - relative_along});
proc({p1 + relative_across + relative_along});
}
// The second half of the round caps should be iterated in reverse, but
// we can instead iterate forward and swap the sin/cos values as they
// should be symmetric.
for (auto& trig : trigs) {
auto relative_across = across * trig.sin;
auto relative_along = along * trig.cos;
proc({p0 - relative_across - relative_along});
proc({p1 - relative_across + relative_along});
}
}

std::vector<Point> CircleTessellator::GetCircleTriangles(const Point& center,
Scalar radius) const {
std::vector<Point> points = std::vector<Point>();
const size_t quadrant_points = quadrant_divisions_ * 3;
points.reserve(quadrant_points * 4);

// Start with the quadrant top-center to right-center using coordinates
// relative to the (0, 0). The coordinates will be made absolute relative
// to the center during the extend method below.
FillQuadrantTriangles(points, {}, {0, -radius}, {radius, 0});
FML_DCHECK(points.size() == quadrant_points);

ExtendRelativeQuadrantToAbsoluteCircle(points, center);

FML_DCHECK(points.size() == quadrant_points * 4);

return points;
}

} // namespace impeller
108 changes: 53 additions & 55 deletions impeller/entity/geometry/circle_tessellator.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include <functional>
#include <vector>

#include "flutter/impeller/geometry/matrix.h"
Expand All @@ -22,33 +23,37 @@ struct Trig {

double cos;
double sin;

Vector2 operator*(Scalar radius) const {
return Vector2(cos * radius, sin * radius);
}

Vector2 interpolate(Vector2 start_vector, Vector2 end_vector) {
return start_vector * cos + end_vector * sin;
}
};

using TessellatedPointProc = std::function<void(const Point& p)>;

/// @brief A utility class to compute the number of divisions for a circle
/// given a transform-adjusted pixel radius and methods for generating
/// a tessellated set of triangles for a quarter or full circle.
///
/// A helper constructor is provided which can compute the device
/// pixel radius size for a geometry-space radius when viewed under
/// a specified geometry-to-device transform.
/// The constructor will compute the device pixel radius size for
/// the specified geometry-space |radius| when viewed under
/// a specified geometry-to-device |transform|.
///
/// The object should be constructed with the expected radius of the
/// circle in pixels, but can then be used to generate a triangular
/// tessellation with the indicated number of divisions for any
/// The object should be constructed with the expected transform and
/// radius of the circle, but can then be used to generate a triangular
/// tessellation with the computed number of divisions for any
/// radius after that. Since the coordinate space in which the
/// circle being tessellated is not necessarily device pixel space,
/// the radius supplied during tessellation might not match the
/// pixel radius supplied during construction, but the two values
/// pixel radius computed during construction, but the two values
/// should be related by the transform in place when the tessellated
/// triangles are rendered for maximum tessellation fidelity.
class CircleTessellator {
public:
/// @brief Constructs a CircleDivider that produces enough segments to
/// reasonably approximate a circle with a specified radius
/// in pixels.
constexpr explicit CircleTessellator(Scalar pixel_radius)
: quadrant_divisions_(ComputeQuadrantDivisions(pixel_radius)) {}

/// @brief Constructs a CircleDivider that produces enough segments to
/// reasonably approximate a circle with a specified |radius|
/// when viewed under the specified |transform|.
Expand All @@ -62,40 +67,47 @@ class CircleTessellator {
size_t GetQuadrantDivisionCount() const { return quadrant_divisions_; }

/// @brief Return the number of vertices that will be generated to
/// tessellate a single quarter circle.
size_t GetQuadrantVertexCount() const { return quadrant_divisions_ * 3; }

/// @brief Return the number of divisions computed by the algorithm for
/// a full circle.
size_t GetCircleDivisionCount() const { return quadrant_divisions_ * 4; }

/// @brief Return the number of vertices that will be generated to
/// tessellate a full circle.
size_t GetCircleVertexCount() const { return quadrant_divisions_ * 12; }

/// @brief Compute the points of a triangular tesselation of the full
/// circle of the given radius and center.
/// tessellate a full 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 GetCircleVertexCount() const { return (quadrant_divisions_ + 1) * 4; }

/// @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|.
///
/// @return the list of points on the polygonal approximation.
std::vector<Point> GetCircleTriangles(const Point& center,
Scalar radius) const;

/// @brief Adds entries in an existing vector of |vertices| to represent
/// the triangular tessellation of the quarter circle that sweeps
/// from the |start_vector| to the |end_vector| around an origin
/// specified by |center|. The length of the start and end vectors
/// controls the size of the quarter circle.
/// This procedure will generate no more than the number of
/// vertices returned by |GetCircleVertexCount| in an order
/// appropriate for rendering as a triangle strip.
void GenerateCircleTriangleStrip(const TessellatedPointProc& proc,
const Point& center,
Scalar 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
/// |proc|.
///
/// The axes must be of the same length and perpendicular. The new
/// points will be appended to the end of the vector.
void FillQuadrantTriangles(std::vector<Point>& vertices,
const Point& center,
const Point& start_vector,
const Point& end_vector) const;
/// This procedure will generate no more than the number of
/// vertices returned by |GetCircleVertexCount| in an order
/// appropriate for rendering as a triangle strip.
void GenerateRoundCapLineTriangleStrip(const TessellatedPointProc& proc,
const Point& p0,
const Point& p1,
Scalar radius) const;

private:
const size_t quadrant_divisions_;

/// @brief Constructs a CircleDivider that produces enough segments to
/// reasonably approximate a circle with a specified radius
/// in pixels.
constexpr explicit CircleTessellator(Scalar pixel_radius)
: quadrant_divisions_(ComputeQuadrantDivisions(pixel_radius)) {}

CircleTessellator(const CircleTessellator&) = delete;

CircleTessellator& operator=(const CircleTessellator&) = delete;
Expand All @@ -116,20 +128,6 @@ class CircleTessellator {
/// @return The vector of (divisions + 1) trig values.
static const std::vector<Trig>& GetTrigForDivisions(size_t divisions);

/// @brief Extend a list of |points| in the vector containing relative
/// coordinates for the first quadrant (top-center to right-center)
/// into the remaining 3 quadrants and adjust them to be relative
/// to the supplied |center|.
///
/// The incoming coordinates are assumed to be relative to a
/// center point of (0, 0) and the method will duplicate and
/// reflect them around that origin to fill in the remaining
/// 3 quadrants. As the method works, it will also adjust every
/// point (including the pre-existing 1st quadrant points) to
/// be relative to the new center.
static void ExtendRelativeQuadrantToAbsoluteCircle(std::vector<Point>& points,
const Point& center = {});

static constexpr int MAX_DIVISIONS_ = 35;

static std::vector<Trig> trigs_[MAX_DIVISIONS_ + 1];
Expand Down
Loading

0 comments on commit 4dbaae2

Please sign in to comment.