Files
klubhaus-doorbell/libraries/FastLED/src/fl/xypath_impls.cpp
2026-02-12 00:45:31 -08:00

365 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "fl/xypath_impls.h"
#include <math.h>
#include "fl/assert.h"
#include "fl/function.h"
#include "fl/lut.h"
#include "fl/map_range.h"
#include "fl/math_macros.h"
#include "fl/raster.h"
#include "fl/xypath_renderer.h"
namespace fl {
LinePath::LinePath(float x0, float y0, float x1, float y1) {
mParams = fl::make_shared<LinePathParams>();
params().x0 = x0;
params().y0 = y0;
params().x1 = x1;
params().y1 = y1;
}
vec2f LinePath::compute(float alpha) {
// α in [0,1] → (x,y) on the line
float x = params().x0 + alpha * (params().x1 - params().x0);
float y = params().y0 + alpha * (params().y1 - params().y0);
return {x, y};
}
void LinePath::set(float x0, float y0, float x1, float y1) {
params().x0 = x0;
params().y0 = y0;
params().x1 = x1;
params().y1 = y1;
}
void LinePath::set(const LinePathParams &p) { params() = p; }
vec2f CirclePath::compute(float alpha) {
// α in [0,1] → (x,y) on the unit circle [-1, 1]
float t = alpha * 2.0f * PI;
float x = cosf(t);
float y = sinf(t);
return vec2f(x, y);
}
CirclePath::CirclePath() {}
HeartPath::HeartPath() {}
vec2f HeartPath::compute(float alpha) {
// Parametric equation for a heart shape
// α in [0,1] → (x,y) on the heart curve
float t = alpha * 2.0f * PI;
// Heart formula based on a modified cardioid with improved aesthetics
float x = 16.0f * powf(sinf(t), 3);
// Modified y formula for a more balanced heart shape
// This creates a fuller bottom and more defined top curve
float y = -(13.0f * cosf(t) - 5.0f * cosf(2.0f * t) -
2.0f * cosf(3.0f * t) - cosf(4.0f * t));
// Scale to fit in [-1, 1] range
// The 16.0f divisor for x ensures x is in [-1, 1]
x /= 16.0f;
// Scale y to ensure it's in [-1, 1]
// The 16.0f divisor ensures proper scaling while maintaining proportions
y /= -16.0f;
// Apply a slight vertical stretch to fill the [-1, 1] range better
y *= 1.10f;
y += 0.17f; // Adjust y to fit within the range of [-1, 1]
return vec2f(x, y);
}
ArchimedeanSpiralPath::ArchimedeanSpiralPath(u8 turns, float radius)
: mTurns(turns), mRadius(radius) {}
vec2f ArchimedeanSpiralPath::compute(float alpha) {
// Parametric equation for an Archimedean spiral
// α in [0,1] → (x,y) on the spiral curve
// Calculate the angle based on the number of turns
float theta = alpha * 2.0f * PI * mTurns;
// Calculate the radius at this angle (grows linearly with angle)
// Scale by alpha to ensure we start at center and grow outward
float r = alpha * mRadius;
// Convert polar coordinates (r, theta) to Cartesian (x, y)
float x = r * cosf(theta);
float y = r * sinf(theta);
// Ensure the spiral fits within [-1, 1] range
// No additional scaling needed as we control the radius directly
return vec2f(x, y);
}
RosePath::RosePath(u8 n, u8 d) {
mParams = fl::make_shared<RosePathParams>();
params().n = n;
params().d = d;
}
vec2f RosePath::compute(float alpha) {
// Parametric equation for a rose curve (rhodonea)
// α in [0,1] → (x,y) on the rose curve
// Map alpha to the full range needed for the rose
// For a complete rose, we need to go through k*PI radians where k is:
// - k = n if n is odd and d is 1
// - k = 2n if n is even and d is 1
// - k = n*d if n and d are coprime
// For simplicity, we'll use 2*PI*n as a good approximation
float theta = alpha * 2.0f * PI * params().n;
// Calculate the radius using the rose formula: r = cos(n*θ/d)
// We use cosine for a rose that starts with a petal at theta=0
float r = cosf(params().n * theta / params().d);
// Scale to ensure the rose fits within [-1, 1] range
// The absolute value ensures we get the proper shape
r = fabsf(r);
// Convert polar coordinates (r, theta) to Cartesian (x, y)
float x = r * cosf(theta);
float y = r * sinf(theta);
return vec2f(x, y);
}
vec2f PhyllotaxisPath::compute(float alpha) {
// total number of points you want in the pattern
const float N = static_cast<float>(params().c);
// continuous “index” from 0…N
float n = alpha * N;
// use the golden angle in radians:
// π * (3 √5) ≈ 2.399963229728653
constexpr float goldenAngle = PI * (3.0f - 1.6180339887498948f);
// normalized radius [0…1]: sqrt(n/N) gives uniform point density
float r = sqrtf(n / N);
// spiral angle
float theta = n * goldenAngle;
// polar → Cartesian
float x = r * cosf(theta);
float y = r * sinf(theta);
return vec2f{x, y};
}
vec2f GielisCurvePath::compute(float alpha) {
// 1) map alpha to angle θ ∈ [0 … 2π)
constexpr float kTwoPi = 6.283185307179586f;
float theta = alpha * kTwoPi;
// 2) superformula parameters (members of your path)
// a, b control the “shape scale” (often both = 1)
// m controls symmetry (integer number of lobes)
// n1,n2,n3 control curvature/sharpness
float a = params().a;
float b = params().b;
float m = params().m;
float n1 = params().n1;
float n2 = params().n2;
float n3 = params().n3;
// 3) compute radius from superformula
float t2 = m * theta / 4.0f;
float part1 = powf(fabsf(cosf(t2) / a), n2);
float part2 = powf(fabsf(sinf(t2) / b), n3);
float r = powf(part1 + part2, -1.0f / n1);
// 4) polar → Cartesian in unit circle
float x = r * cosf(theta);
float y = r * sinf(theta);
return vec2f{x, y};
}
const string CirclePath::name() const { return "CirclePath"; }
vec2f PointPath::compute(float alpha) {
FASTLED_UNUSED(alpha);
return mPoint;
}
const string PointPath::name() const { return "PointPath"; }
void PointPath::set(float x, float y) { set(vec2f(x, y)); }
void PointPath::set(vec2f p) { mPoint = p; }
PointPath::PointPath(float x, float y) : mPoint(x, y) {}
PointPath::PointPath(vec2f p) : mPoint(p) {}
const string LinePath::name() const { return "LinePath"; }
LinePathParams &LinePath::params() { return *mParams; }
const LinePathParams &LinePath::params() const { return *mParams; }
LinePath::LinePath(const LinePathParamsPtr &params) : mParams(params) {}
const string HeartPath::name() const { return "HeartPath"; }
const string ArchimedeanSpiralPath::name() const {
return "ArchimedeanSpiralPath";
}
void ArchimedeanSpiralPath::setTurns(u8 turns) { mTurns = turns; }
void ArchimedeanSpiralPath::setRadius(float radius) { mRadius = radius; }
const string RosePath::name() const { return "RosePath"; }
void RosePath::setN(u8 n) { params().n = n; }
void RosePath::setD(u8 d) { params().d = d; }
RosePath::RosePath(const fl::shared_ptr<RosePathParams> &p) : mParams(p) {}
RosePathParams &RosePath::params() { return *mParams; }
const RosePathParams &RosePath::params() const { return *mParams; }
const string PhyllotaxisPath::name() const { return "PhyllotaxisPath"; }
PhyllotaxisPath::PhyllotaxisPath(const fl::shared_ptr<PhyllotaxisParams> &p)
: mParams(p) {}
PhyllotaxisParams &PhyllotaxisPath::params() { return *mParams; }
const PhyllotaxisParams &PhyllotaxisPath::params() const { return *mParams; }
GielisCurvePath::GielisCurvePath(const fl::shared_ptr<GielisCurveParams> &p)
: mParams(p) {}
const string GielisCurvePath::name() const { return "GielisCurvePath"; }
void GielisCurvePath::setA(float a) { params().a = a; }
void GielisCurvePath::setB(float b) { params().b = b; }
void GielisCurvePath::setM(float m) { params().m = m; }
void GielisCurvePath::setN1(float n1) { params().n1 = n1; }
void GielisCurvePath::setN2(float n2) { params().n2 = n2; }
void GielisCurvePath::setN3(float n3) { params().n3 = n3; }
GielisCurveParams &GielisCurvePath::params() { return *mParams; }
const GielisCurveParams &GielisCurvePath::params() const { return *mParams; }
CatmullRomPath::CatmullRomPath(const fl::shared_ptr<CatmullRomParams> &p) : mParams(p) {}
void CatmullRomPath::addPoint(vec2f p) { params().addPoint(p); }
void CatmullRomPath::addPoint(float x, float y) { params().addPoint(x, y); }
void CatmullRomPath::clear() { params().clear(); }
fl::size CatmullRomPath::size() const { return params().size(); }
CatmullRomParams &CatmullRomPath::params() { return *mParams; }
const CatmullRomParams &CatmullRomPath::params() const { return *mParams; }
vec2f CatmullRomPath::compute(float alpha) {
const auto &points = params().points;
// Need at least 2 points to define a path
if (points.size() < 2) {
// Return origin if not enough points
return vec2f(0.0f, 0.0f);
}
// If only 2 points, do linear interpolation
if (points.size() == 2) {
return vec2f(points[0].x + alpha * (points[1].x - points[0].x),
points[0].y + alpha * (points[1].y - points[0].y));
}
// For Catmull-Rom, we need 4 points to interpolate between the middle two
// Scale alpha to the number of segments
float scaledAlpha = alpha * (points.size() - 1);
// Determine which segment we're in
int segment = static_cast<int>(scaledAlpha);
// Clamp to valid range
if (segment >= static_cast<int>(points.size()) - 1) {
segment = points.size() - 2;
scaledAlpha = static_cast<float>(segment) + 1.0f;
}
// Get local alpha within this segment [0,1]
float t = scaledAlpha - static_cast<float>(segment);
// Get the four points needed for interpolation
vec2f p0, p1, p2, p3;
// Handle boundary cases
if (segment == 0) {
// For the first segment, duplicate the first point
p0 = points[0];
p1 = points[0];
p2 = points[1];
p3 = (points.size() > 2) ? points[2] : points[1];
} else if (segment == static_cast<int>(points.size()) - 2) {
// For the last segment, duplicate the last point
p0 = (segment > 0) ? points[segment - 1] : points[0];
p1 = points[segment];
p2 = points[segment + 1];
p3 = points[segment + 1];
} else {
// Normal case - we have points before and after
p0 = points[segment - 1];
p1 = points[segment];
p2 = points[segment + 1];
p3 = points[segment + 2];
}
// Perform Catmull-Rom interpolation
auto out = interpolate(p0, p1, p2, p3, t);
return out;
}
vec2f CatmullRomPath::interpolate(const vec2f &p0, const vec2f &p1,
const vec2f &p2, const vec2f &p3,
float t) const {
// Catmull-Rom interpolation formula
// Using alpha=0.5 for the "tension" parameter (standard Catmull-Rom)
float t2 = t * t;
float t3 = t2 * t;
// Coefficients for x and y
float a = -0.5f * p0.x + 1.5f * p1.x - 1.5f * p2.x + 0.5f * p3.x;
float b = p0.x - 2.5f * p1.x + 2.0f * p2.x - 0.5f * p3.x;
float c = -0.5f * p0.x + 0.5f * p2.x;
float d = p1.x;
float x = a * t3 + b * t2 + c * t + d;
a = -0.5f * p0.y + 1.5f * p1.y - 1.5f * p2.y + 0.5f * p3.y;
b = p0.y - 2.5f * p1.y + 2.0f * p2.y - 0.5f * p3.y;
c = -0.5f * p0.y + 0.5f * p2.y;
d = p1.y;
float y = a * t3 + b * t2 + c * t + d;
return vec2f(x, y);
}
const string CatmullRomPath::name() const { return "CatmullRomPath"; }
} // namespace fl