blob: 475c614f778ed8ce7bce9d7158abe98c582e1649 [file]
/*
* Copyright 2021 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Initial import from skia:src/gpu/tessellate/Tessellation.h
* skia:src/core/SkGeometry.h
*
* Copyright 2022 Rive
*/
#pragma once
#include "rive/math/simd.hpp"
#include "rive/math/vec2d.hpp"
#include <math.h>
namespace rive
{
namespace math
{
// Finds the cubic bezier's power basis coefficients. These define the bezier
// curve as:
//
// |T^3|
// Cubic(T) = x,y = |A 3B 3C| * |T^2| + P0
// |. . .| |T |
//
// And the tangent:
//
// |T^2|
// Tangent(T) = dx,dy = |3A 6B 3C| * |T |
// | . . .| |1 |
//
struct CubicCoeffs
{
RIVE_ALWAYS_INLINE CubicCoeffs(const Vec2D pts[4])
: CubicCoeffs(
simd::load2f(&pts[0].x),
simd::load2f(&pts[1].x),
simd::load2f(&pts[2].x),
simd::load2f(&pts[3].x))
{}
RIVE_ALWAYS_INLINE CubicCoeffs(float2 p0, float2 p1, float2 p2, float2 p3)
{
C = p1 - p0;
float2 D = p2 - p1;
float2 E = p3 - p0;
B = D - C;
A = -3.f * D + E;
}
float2 A, B, C;
};
// Optimized SIMD helper for evaluating a single cubic at many points.
class EvalCubic
{
public:
RIVE_ALWAYS_INLINE EvalCubic(const Vec2D pts[])
: EvalCubic(CubicCoeffs(pts), pts[0])
{}
RIVE_ALWAYS_INLINE EvalCubic(const CubicCoeffs& coeffs, Vec2D p0)
: EvalCubic(coeffs, simd::load2f(&p0))
{}
RIVE_ALWAYS_INLINE EvalCubic(const CubicCoeffs& coeffs, float2 p0)
: // Duplicate coefficients across a float4 so we can evaluate two at
// once.
A(coeffs.A.xyxy)
, B((3.f * coeffs.B).xyxy)
, C((3.f * coeffs.C).xyxy)
, D(p0.xyxy)
{}
// Evaluates [x, y] at location t.
RIVE_ALWAYS_INLINE float2 operator()(float t) const
{
// At^3 + Bt^2 + Ct + P0
return ((A.xy * t + B.xy) * t + C.xy) * t + D.xy;
}
// Evaluates [Xa, Ya, Xb, Yb] at locations [Ta, Ta, Tb, Tb].
RIVE_ALWAYS_INLINE float4 operator()(float4 t) const
{
// At^3 + Bt^2 + Ct + P0
return ((A * t + B) * t + C) * t + D;
}
RIVE_ALWAYS_INLINE float4 at(float4 t) const
{
// At^3 + Bt^2 + Ct + P0
return ((A * t + B) * t + C) * t + D;
}
private:
const float4 A;
const float4 B;
const float4 C;
const float4 D;
};
// Decides the number of polar segments the tessellator adds for each curve.
// (Uniform steps in tangent angle.) The tessellator will add this number of
// polar segments for each radian of rotation in local path space.
template <int Precision>
float calcPolarSegmentsPerRadian(float approxDevStrokeRadius)
{
float cosTheta = 1.f - (1.f / Precision) / approxDevStrokeRadius;
return .5f / acosf(std::max(cosTheta, -1.f));
}
Vec2D evalCubicAt(const Vec2D p[4], float t);
// Given a src cubic bezier, chop it at the specified t value, where 0 <= t <=
// 1, and return the two new cubics in dst: dst[0..3] and dst[3..6]
void chopCubicAt(const Vec2D src[4], Vec2D dst[7], float t);
// Given a src cubic bezier, chop it at the specified t0 and t1 values, where 0
// <= t0 <= t1 <= 1, and return the three new cubics in dst: dst[0..3],
// dst[3..6], and dst[6..9]
void chopCubicAt(const Vec2D src[4], Vec2D dst[10], float t0, float t1);
// Given a src cubic bezier, chop it at the specified t values,
// where 0 <= t0 <= t1 <= ... <= 1, and return the new cubics in dst:
// dst[0..3],dst[3..6],...,dst[3*t_count..3*(t_count+1)]
void chopCubicAt(
const Vec2D src[4],
Vec2D dst[],
const float tValues[],
int tCount);
// Measures the angle between two vectors, in the range [0, pi].
float measureAngleBetweenVectors(Vec2D a, Vec2D b);
// Returns 0, 1, or 2 T values at which to chop the given curve in order to
// guarantee the resulting cubics are convex and rotate no more than 180
// degrees.
//
// - If the cubic is "serpentine", then the T values are any inflection points
// in [0 < T < 1].
// - If the cubic is linear, then the T values are any 180-degree cusp points
// in [0 < T < 1].
// - Otherwise the T value is the point at which rotation reaches 180 degrees,
// iff in [0 < T < 1].
//
// 'areCusps' is set to true if the chop point occurred at a cusp (within
// tolerance), or if the chop point(s) occurred at 180-degree turnaround points
// on a degenerate flat line.
int findCubicConvex180Chops(const Vec2D pts[4], float T[2], bool* areCusps);
// This is a cheap test to determine if the full chop-finding code needs to run.
// It runs on four cubics at once and returns a boolean mask. A scalar version
// of this also runs in findCubicConvex180Chops().
int4 cubicsNeedNoChops(
float4 p0x,
float4 p0y,
float4 p1x,
float4 p1y,
float4 p2x,
float4 p2y,
float4 p3x,
float4 p3y);
// findCubicConvex180Chops() for four cubics at once, one per lane, except
// for cubics that may have cusps.
struct Convex180Chops4
{
int4 numChops; // 0, 1 or 2.
float4 T0;
float4 T1; // Only valid when numChops == 2.
// Mask of cubics that may have cusps, which this doesn't handle (cusps are
// rare in content so they just go through the scalar version instead).
int4 possibleCusps;
};
Convex180Chops4 findCubicConvex180Chops4(
float4 p0x,
float4 p0y,
float4 p1x,
float4 p1y,
float4 p2x,
float4 p2y,
float4 p3x,
float4 p3y);
// Finds the tangents of the curve at T=0 and T=1 respectively.
RIVE_ALWAYS_INLINE Vec2D findCubicTan0(const Vec2D p[4])
{
Vec2D tan0 = (p[0] != p[1] ? p[1] : p[1] != p[2] ? p[2] : p[3]) - p[0];
return tan0;
}
RIVE_ALWAYS_INLINE Vec2D findCubicTan1(const Vec2D p[4])
{
Vec2D tan1 = p[3] - (p[3] != p[2] ? p[2] : p[2] != p[1] ? p[1] : p[0]);
return tan1;
}
RIVE_ALWAYS_INLINE void findCubicTangents(const Vec2D p[4], Vec2D tangents[2])
{
tangents[0] = findCubicTan0(p);
tangents[1] = findCubicTan1(p);
}
// findCubicTangents() for four cubics at once, one per lane. Returns
// {tan0x, tan0y, tan1x, tan1y}.
RIVE_ALWAYS_INLINE simd::gvec4x4<float> findCubicTangents4(
float4 p0x,
float4 p0y,
float4 p1x,
float4 p1y,
float4 p2x,
float4 p2y,
float4 p3x,
float4 p3y)
{
int4 p0p1Differ = (p0x != p1x) | (p0y != p1y);
int4 p1p2Differ = (p1x != p2x) | (p1y != p2y);
int4 p2p3Differ = (p2x != p3x) | (p2y != p3y);
float4 tan0x = simd::if_then_else(
p0p1Differ,
p1x,
simd::if_then_else(p1p2Differ, p2x, p3x)) -
p0x;
float4 tan0y = simd::if_then_else(
p0p1Differ,
p1y,
simd::if_then_else(p1p2Differ, p2y, p3y)) -
p0y;
float4 tan1x = p3x - simd::if_then_else(
p2p3Differ,
p2x,
simd::if_then_else(p1p2Differ, p1x, p0x));
float4 tan1y = p3y - simd::if_then_else(
p2p3Differ,
p2y,
simd::if_then_else(p1p2Differ, p1y, p0y));
return {tan0x, tan0y, tan1x, tan1y};
}
RIVE_ALWAYS_INLINE constexpr float pow2(float x) { return x * x; }
RIVE_ALWAYS_INLINE constexpr float pow3(float x) { return x * pow2(x); }
RIVE_ALWAYS_INLINE constexpr float lengthPow2(Vec2D v)
{
return pow2(v.x) + pow2(v.y);
}
} // namespace math
} // namespace rive