| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | /* | ||
| 3 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 4 | * 2013-2025 by the Falcon Player Developers. | ||
| 5 | * | ||
| 6 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 7 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 8 | * file for descriptions of what files are covered by each license. | ||
| 9 | * | ||
| 10 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 11 | * included LICENSE.LGPL file. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <cstdint> | ||
| 15 | #include <string> | ||
| 16 | #include <string_view> | ||
| 17 | #include <vector> | ||
| 18 | |||
| 19 | /* | ||
| 20 | * A POLAR view of a pixel overlay model's buffer, so WLED effects sweep | ||
| 21 | * outward from (or around) the centre of a circular model rather than | ||
| 22 | * left-to-right across its wiring order. | ||
| 23 | * | ||
| 24 | * The existing "Buffer Mapping" values choose row-major vs column-major and | ||
| 25 | * mirror an axis (see BUFFERMAPS in WLEDEffects.cpp, consumed by Bus:: in | ||
| 26 | * wled/wled.cpp). Radius cannot join them as a fifth value, because they | ||
| 27 | * operate in BUFFER space: a circular model's buffer is wiring order, so the | ||
| 28 | * distance from buffer cell (x,y) to the buffer's centre says nothing about the | ||
| 29 | * distance from that pixel to the centre of the model. | ||
| 30 | * | ||
| 31 | * What is missing is per-pixel geometry, and FPP already has it — | ||
| 32 | * config/virtualdisplaymap carries x,y,z plus an absolute channel for every | ||
| 33 | * node. So instead of a new way to walk the buffer, this builds a LUT whose | ||
| 34 | * axes ARE polar and hands it to the same Bus indexing the other mappings use. | ||
| 35 | * | ||
| 36 | * The LUT points at the model's own buffer cells, NOT at output channels, which | ||
| 37 | * is what keeps this contained: by the time doOverlay(), flushOverlayBuffer(), | ||
| 38 | * getDataJson(), setData(), text, image export or the web pixel editor run, the | ||
| 39 | * pixels are already sitting in the model's normal buffer. None of them can | ||
| 40 | * tell a polar effect from a horizontal one. | ||
| 41 | */ | ||
| 42 | |||
| 43 | /** Which polar layout an effect asked for. None means "not a polar mapping". */ | ||
| 44 | enum class PolarMode { | ||
| 45 | None = 0, | ||
| 46 | RadialOut, // x = radius, centre first | ||
| 47 | RadialIn, // x = radius, rim first | ||
| 48 | AngularCW, // x = angle, clockwise on screen | ||
| 49 | AngularCCW, // x = angle, anticlockwise | ||
| 50 | }; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * The built layout: a dense width*height LUT from the WLED linear index to a | ||
| 54 | * buffer CELL index (y * model width + x) — or Empty where no pixel occupies | ||
| 55 | * that polar cell, which is common because polar binning is inherently sparse. | ||
| 56 | */ | ||
| 57 | struct PolarBufferMap { | ||
| 58 | static constexpr uint32_t Empty = 0xFFFFFFFFu; | ||
| 59 | |||
| 60 | int width = 0; // fastest-varying axis: radius for Radial*, angle for Angular* | ||
| 61 | int height = 0; | ||
| 62 | std::vector<uint32_t> indexToCell; | ||
| 63 | |||
| 64 | 17 | bool valid() const { | |
| 65 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
34 | return width > 0 && height > 0 && |
| 66 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
34 | indexToCell.size() == static_cast<size_t>(width) * height; |
| 67 | } | ||
| 68 | }; | ||
| 69 | |||
| 70 | /** One positioned buffer cell: where it is, and which cell it is. */ | ||
| 71 | struct PolarPixel { | ||
| 72 | float x = 0.0f; | ||
| 73 | float y = 0.0f; | ||
| 74 | uint32_t cell = 0; // y * model width + x | ||
| 75 | }; | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Map a "Buffer Mapping" argument to a polar mode. | ||
| 79 | * Returns PolarMode::None for the four original values and for anything | ||
| 80 | * unrecognised, which is what makes an unknown value degrade to Horizontal | ||
| 81 | * rather than fail. | ||
| 82 | */ | ||
| 83 | PolarMode PolarModeFromName(std::string_view name); | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Lay pixels out in polar order. | ||
| 87 | * | ||
| 88 | * Returns an INVALID map (valid() == false) whenever the geometry will not | ||
| 89 | * support one — no positioned pixels, everything at a single point, or a buffer | ||
| 90 | * that would be absurdly large. Callers must treat that as "use the ordinary | ||
| 91 | * mapping", never as an error: a player with no xLights layout has no | ||
| 92 | * virtualdisplaymap and must keep behaving exactly as it does today. | ||
| 93 | */ | ||
| 94 | PolarBufferMap BuildPolarBufferMap(const std::vector<PolarPixel>& pixels, PolarMode mode); | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Read config/virtualdisplaymap into a 0-based absolute channel -> (x, y) index. | ||
| 98 | * Returns false when the file is absent or unreadable — the no-xLights case. | ||
| 99 | */ | ||
| 100 | bool LoadVirtualDisplayPositions(std::vector<std::pair<uint32_t, std::pair<float, float>>>& out); | ||
| 101 |