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

252 lines
8.8 KiB
C++

#pragma once
/**
* @file corkscrew.h
* @brief Corkscrew LED strip projection and rendering
*
* The Corkscrew class provides a complete solution for drawing to densely-wrapped
* helical LED strips. It maps a cylindrical coordinate system to a linear LED
* strip, allowing you to draw on a rectangular surface and have it correctly
* projected onto the corkscrew topology.
*
* Usage:
* 1. Create a Corkscrew with the number of turns and LEDs
* 2. Draw patterns on the input surface using surface()
* 3. Call draw() to map the surface to LED pixels
* 4. Access the final LED data via rawData()
*
* The class handles:
* - Automatic cylindrical dimension calculation
* - Pixel storage (external span or internal allocation)
* - Multi-sampling for smooth projections
* - Gap compensation for non-continuous wrapping
* - Iterator interface for advanced coordinate access
*
* Parameters:
* - totalTurns: Number of helical turns around the cylinder
* - numLeds: Total number of LEDs in the strip
* - invert: Reverse the mapping direction (default: false)
* - gapParams: Optional gap compensation for solder points in a strip.
*/
#include "fl/allocator.h"
#include "fl/geometry.h"
#include "fl/math.h"
#include "fl/math_macros.h"
#include "fl/pair.h"
#include "fl/tile2x2.h"
#include "fl/vector.h"
#include "fl/shared_ptr.h"
#include "fl/variant.h"
#include "fl/span.h"
#include "crgb.h"
#include "fl/int.h"
namespace fl {
// Forward declarations
class Leds;
class ScreenMap;
template<typename T> class Grid;
// Simple constexpr functions for compile-time corkscrew dimension calculation
constexpr fl::u16 calculateCorkscrewWidth(float totalTurns, fl::u16 numLeds) {
return static_cast<fl::u16>(ceil_constexpr(static_cast<float>(numLeds) / totalTurns));
}
constexpr fl::u16 calculateCorkscrewHeight(float totalTurns, fl::u16 numLeds) {
return (calculateCorkscrewWidth(totalTurns, numLeds) * static_cast<int>(ceil_constexpr(totalTurns)) > numLeds) ?
static_cast<fl::u16>(ceil_constexpr(static_cast<float>(numLeds) / static_cast<float>(calculateCorkscrewWidth(totalTurns, numLeds)))) :
static_cast<fl::u16>(ceil_constexpr(totalTurns));
}
/**
* Struct representing gap parameters for corkscrew mapping
*/
struct Gap {
int num_leds = 0; // Number of LEDs after which gap is activated, 0 = no gap
float gap = 0.0f; // Gap value from 0 to 1, represents percentage of width unit to add
Gap() = default;
Gap(float g) : num_leds(0), gap(g) {} // Backwards compatibility constructor
Gap(int n, float g) : num_leds(n), gap(g) {} // New constructor with num_leds
// Rule of 5 for POD data
Gap(const Gap &other) = default;
Gap &operator=(const Gap &other) = default;
Gap(Gap &&other) noexcept = default;
Gap &operator=(Gap &&other) noexcept = default;
};
// Maps a Corkscrew defined by the input to a cylindrical mapping for rendering
// a densly wrapped LED corkscrew.
class Corkscrew {
public:
// Pixel storage variants - can hold either external span or owned vector
using PixelStorage = fl::Variant<fl::span<CRGB>, fl::vector<CRGB, fl::allocator_psram<CRGB>>>;
// Iterator class moved from CorkscrewState
class iterator {
public:
using value_type = vec2f;
using difference_type = fl::i32;
using pointer = vec2f *;
using reference = vec2f &;
iterator(const Corkscrew *corkscrew, fl::size position)
: corkscrew_(corkscrew), position_(position) {}
vec2f operator*() const;
iterator &operator++() {
++position_;
return *this;
}
iterator operator++(int) {
iterator temp = *this;
++position_;
return temp;
}
iterator &operator--() {
--position_;
return *this;
}
iterator operator--(int) {
iterator temp = *this;
--position_;
return temp;
}
bool operator==(const iterator &other) const {
return position_ == other.position_;
}
bool operator!=(const iterator &other) const {
return position_ != other.position_;
}
difference_type operator-(const iterator &other) const {
return static_cast<difference_type>(position_) -
static_cast<difference_type>(other.position_);
}
private:
const Corkscrew *corkscrew_;
fl::size position_;
};
// Constructors that integrate input parameters directly
// Primary constructor with default values for invert and gapParams
Corkscrew(float totalTurns, fl::u16 numLeds, bool invert = false, const Gap& gapParams = Gap());
// Constructor with external pixel buffer - these pixels will be drawn to directly
Corkscrew(float totalTurns, fl::span<CRGB> dstPixels, bool invert = false, const Gap& gapParams = Gap());
Corkscrew(const Corkscrew &) = default;
Corkscrew(Corkscrew &&) = default;
// Caching control
void setCachingEnabled(bool enabled);
// Essential API - Core functionality
fl::u16 cylinderWidth() const { return mWidth; }
fl::u16 cylinderHeight() const { return mHeight; }
// Enhanced surface handling with shared_ptr
// Note: Input surface will be created on first call
fl::shared_ptr<fl::Grid<CRGB>>& getOrCreateInputSurface();
// Draw like a regular rectangle surface - access input surface directly
fl::Grid<CRGB>& surface();
// Draw the corkscrew by reading from the internal surface and populating LED pixels
void draw(bool use_multi_sampling = true);
// Pixel storage access - works with both external and owned pixels
// This represents the pixels that will be drawn after draw() is called
CRGB* rawData();
// Returns span of pixels that will be written to when draw() is called
fl::span<CRGB> data();
fl::size pixelCount() const;
// Create and return a fully constructed ScreenMap for this corkscrew
// Each LED index will be mapped to its exact position on the cylindrical surface
fl::ScreenMap toScreenMap(float diameter = 0.5f) const;
// STL-style container interface
fl::size size() const;
iterator begin() { return iterator(this, 0); }
iterator end() { return iterator(this, size()); }
// Non-essential API - Lower level access
vec2f at_no_wrap(fl::u16 i) const;
vec2f at_exact(fl::u16 i) const;
Tile2x2_u8_wrap at_wrap(float i) const;
// Clear all buffers and free memory
void clear();
// Fill the input surface with a color
void fillInputSurface(const CRGB& color);
private:
// For internal use. Splats the pixel on the surface which
// extends past the width. This extended Tile2x2 is designed
// to be wrapped around with a Tile2x2_u8_wrap.
Tile2x2_u8 at_splat_extrapolate(float i) const;
// Read from fl::Grid<CRGB> object and populate our internal rectangular buffer
// by sampling from the XY coordinates mapped to each corkscrew LED position
// use_multi_sampling = true will use multi-sampling to sample from the source grid,
// this will give a little bit better accuracy and the screenmap will be more accurate.
void readFrom(const fl::Grid<CRGB>& source_grid, bool use_multi_sampling = true);
// Read from rectangular buffer using multi-sampling and store in target grid
// Uses Tile2x2_u8_wrap for sub-pixel accurate sampling with proper blending
void readFromMulti(const fl::Grid<CRGB>& target_grid) const;
// Initialize the rectangular buffer if not already done
void initializeBuffer() const;
// Initialize the cache if not already done and caching is enabled
void initializeCache() const;
// Calculate the tile at position i without using cache
Tile2x2_u8_wrap calculateTileAtWrap(float i) const;
// Core corkscrew parameters (moved from CorkscrewInput)
float mTotalTurns = 19.0f; // Total turns of the corkscrew
fl::u16 mNumLeds = 144; // Number of LEDs
Gap mGapParams; // Gap parameters for gap accounting
bool mInvert = false; // If true, reverse the mapping order
// Cylindrical mapping dimensions (moved from CorkscrewState)
fl::u16 mWidth = 0; // Width of cylindrical map (circumference of one turn)
fl::u16 mHeight = 0; // Height of cylindrical map (total vertical segments)
// Enhanced pixel storage - variant supports both external and owned pixels
PixelStorage mPixelStorage;
bool mOwnsPixels = false; // Track whether we own the pixel data
// Input surface for drawing operations
fl::shared_ptr<fl::Grid<CRGB>> mInputSurface;
// Caching for Tile2x2_u8_wrap objects
mutable fl::vector<Tile2x2_u8_wrap> mTileCache;
mutable bool mCacheInitialized = false;
bool mCachingEnabled = true; // Default to enabled
};
} // namespace fl