From c28adf76a537a27226ff456c9e830573ca7a32b1 Mon Sep 17 00:00:00 2001 From: Hendrik Lind Date: Mon, 16 Mar 2026 21:23:41 +0100 Subject: [PATCH 1/3] feat: support renaming scenes --- react-web/src/apiTypes/list_presets.ts | 5 +- .../src/components/home/AddPresetButton.tsx | 9 +- react-web/src/components/home/Preset.tsx | 74 +++++++++++-- .../src/components/home/PresetsSection.tsx | 19 ++-- react-web/src/components/home/StatusCard.tsx | 5 +- .../properties/NumberProperty.tsx | 5 +- react-web/src/pages/Home.tsx | 38 +++++-- react-web/src/pages/Schedules.tsx | 14 ++- .../include/shared/matrix/config/MainConfig.h | 1 + .../include/shared/matrix/config/data.h | 1 + .../src/shared/matrix/config/MainConfig.cpp | 103 ++++++++++++++++++ .../matrix/src/shared/matrix/config/data.cpp | 5 +- src_matrix/matrix_control/canvas.cpp | 16 ++- src_matrix/server/preset_management.cpp | 83 ++++++++++++-- 14 files changed, 329 insertions(+), 49 deletions(-) diff --git a/react-web/src/apiTypes/list_presets.ts b/react-web/src/apiTypes/list_presets.ts index 38034599..634734d4 100644 --- a/react-web/src/apiTypes/list_presets.ts +++ b/react-web/src/apiTypes/list_presets.ts @@ -1,5 +1,5 @@ export interface ListPresets { [key: string]: RawPreset; } -export interface RawPreset { scenes: Scene[]; transition_duration?: number; transition_name?: string; } +export interface RawPreset { scenes: Scene[]; transition_duration?: number; transition_name?: string; display_name?: string; } export interface ImageArguments { begin: number; end: number; } @@ -9,6 +9,7 @@ export interface SceneArguments { [key: string]: any; } export interface Preset { transition_duration: number; transition_name: string; + display_name: string; scenes: { [key: string]: Scene }; } @@ -16,6 +17,7 @@ export function arrayToObjectPresets(preset: RawPreset): Preset { return { transition_duration: preset.transition_duration ?? 750, transition_name: preset.transition_name ?? 'blend', + display_name: preset.display_name ?? '', scenes: preset.scenes.reduce((acc, scene) => { acc[scene.uuid] = scene return acc @@ -27,6 +29,7 @@ export function objectToArrayPresets(preset: Preset): RawPreset { return { transition_duration: preset.transition_duration, transition_name: preset.transition_name, + display_name: preset.display_name, scenes: Object.values(preset.scenes) } } diff --git a/react-web/src/components/home/AddPresetButton.tsx b/react-web/src/components/home/AddPresetButton.tsx index 5aba61ff..2bf3d4bd 100644 --- a/react-web/src/components/home/AddPresetButton.tsx +++ b/react-web/src/components/home/AddPresetButton.tsx @@ -21,10 +21,15 @@ export default function AddPresetButton({ onCreated }: AddPresetButtonProps) { if (!name.trim() || !apiUrl) return setLoading(true) try { - const res = await fetch(`${apiUrl}/preset?id=${encodeURIComponent(name.trim())}`, { + const res = await fetch(`${apiUrl}/add_preset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ scenes: [], transition_duration: 750, transition_name: 'blend' }), + body: JSON.stringify({ + scenes: [], + transition_duration: 750, + transition_name: 'blend', + display_name: name.trim(), + }), }) if (!res.ok) throw new Error('Failed to create preset') toast.success(`Preset "${name.trim()}" created`) diff --git a/react-web/src/components/home/Preset.tsx b/react-web/src/components/home/Preset.tsx index b78cccca..62a6a4c6 100644 --- a/react-web/src/components/home/Preset.tsx +++ b/react-web/src/components/home/Preset.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Play, Pencil, Trash2, MoreVertical, Layers } from 'lucide-react' +import { Play, Pencil, Trash2, MoreVertical, Layers, Type } from 'lucide-react' import { Card, CardContent } from '~/components/ui/card' import { Button } from '~/components/ui/button' import { Badge } from '~/components/ui/badge' @@ -12,19 +12,44 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '~/components/ui/alert-dialog' +import { + Dialog, DialogContent, DialogDescription, DialogFooter, + DialogHeader, DialogTitle, +} from '~/components/ui/dialog' +import { Input } from '~/components/ui/input' +import { Label } from '~/components/ui/label' import type { RawPreset } from '~/apiTypes/list_presets' interface PresetCardProps { - name: string + id: string + displayName: string preset: RawPreset isActive: boolean - onActivate: (name: string) => void - onDelete: (name: string) => void + onActivate: (id: string, displayName: string) => void + onDelete: (id: string, displayName: string) => void + onRename: (id: string, displayName: string) => void } -export default function PresetCard({ name, preset, isActive, onActivate, onDelete }: PresetCardProps) { +export default function PresetCard({ id, displayName, preset, isActive, onActivate, onDelete, onRename }: PresetCardProps) { const navigate = useNavigate() const [confirmDelete, setConfirmDelete] = useState(false) + const [showRename, setShowRename] = useState(false) + const [renameValue, setRenameValue] = useState(displayName) + + const handleOpenRename = () => { + setRenameValue(displayName) + setShowRename(true) + } + + const handleRename = () => { + const nextName = renameValue.trim() + if (!nextName || nextName === displayName) { + setShowRename(false) + return + } + onRename(id, nextName) + setShowRename(false) + } return ( <> @@ -33,7 +58,7 @@ export default function PresetCard({ name, preset, isActive, onActivate, onDelet
-

{name}

+

{displayName}

{isActive && ( Active )} @@ -49,7 +74,7 @@ export default function PresetCard({ name, preset, isActive, onActivate, onDelet variant="ghost" size="icon" className="h-8 w-8" - onClick={() => onActivate(name)} + onClick={() => onActivate(id, displayName)} title="Activate" > @@ -63,12 +88,16 @@ export default function PresetCard({ name, preset, isActive, onActivate, onDelet {!isActive && ( - onActivate(name)}> + onActivate(id, displayName)}> Activate )} - navigate(`/modify-preset/${encodeURIComponent(name)}`)}> + + + Rename + + navigate(`/modify-preset/${encodeURIComponent(id)}`)}> Edit @@ -92,20 +121,43 @@ export default function PresetCard({ name, preset, isActive, onActivate, onDelet Delete preset? - This will permanently delete "{name}". This action cannot be undone. + This will permanently delete "{displayName}". This action cannot be undone. Cancel { onDelete(name); setConfirmDelete(false) }} + onClick={() => { onDelete(id, displayName); setConfirmDelete(false) }} > Delete + + + + + Rename preset + Change the display name shown in the app. + +
+ + setRenameValue(e.target.value)} + onKeyDown={e => e.key === 'Enter' && handleRename()} + autoFocus + /> +
+ + + + +
+
) } diff --git a/react-web/src/components/home/PresetsSection.tsx b/react-web/src/components/home/PresetsSection.tsx index 55c58a7e..346d90d6 100644 --- a/react-web/src/components/home/PresetsSection.tsx +++ b/react-web/src/components/home/PresetsSection.tsx @@ -6,14 +6,15 @@ import type { ListPresets } from '~/apiTypes/list_presets' interface PresetsSectionProps { presets: ListPresets | null isLoading: boolean - activePreset: string | null - onActivate: (name: string) => void - onDelete: (name: string) => void + activePresetId: string | null + onActivate: (id: string, displayName: string) => void + onDelete: (id: string, displayName: string) => void + onRename: (id: string, displayName: string) => void onCreated: () => void } export default function PresetsSection({ - presets, isLoading, activePreset, onActivate, onDelete, onCreated + presets, isLoading, activePresetId, onActivate, onDelete, onRename, onCreated }: PresetsSectionProps) { return (
@@ -32,14 +33,16 @@ export default function PresetsSection({
) : presets && Object.keys(presets).length > 0 ? (
- {Object.entries(presets).map(([name, preset]) => ( + {Object.entries(presets).map(([id, preset]) => ( ))}
diff --git a/react-web/src/components/home/StatusCard.tsx b/react-web/src/components/home/StatusCard.tsx index 0b97b15b..81c75f57 100644 --- a/react-web/src/components/home/StatusCard.tsx +++ b/react-web/src/components/home/StatusCard.tsx @@ -7,11 +7,12 @@ import type { Status } from '~/apiTypes/status' interface StatusCardProps { status: Status | null + currentPresetLabel?: string | null isLoading: boolean onToggle: (enabled: boolean) => void } -export default function StatusCard({ status, isLoading, onToggle }: StatusCardProps) { +export default function StatusCard({ status, currentPresetLabel, isLoading, onToggle }: StatusCardProps) { if (isLoading) { return ( @@ -29,7 +30,7 @@ export default function StatusCard({ status, isLoading, onToggle }: StatusCardPr } const isOn = status ? !status.turned_off : false - const currentPreset = status?.current || 'None' + const currentPreset = currentPresetLabel || status?.current || 'None' return ( diff --git a/react-web/src/components/modify-preset/properties/NumberProperty.tsx b/react-web/src/components/modify-preset/properties/NumberProperty.tsx index c82c7daf..21cf206f 100644 --- a/react-web/src/components/modify-preset/properties/NumberProperty.tsx +++ b/react-web/src/components/modify-preset/properties/NumberProperty.tsx @@ -37,7 +37,10 @@ export default function NumberProperty({ property, value, onChange }: NumberProp const numericValue = Number.isFinite(value) ? value : 0 if (property.type_id === 'millis') { - const presets = [250, 500, 750, 1000, 2000, 5000] + const defaultPresetDurations = [1000, 1000 * 10, 1000 * 30, 1000 * 60]; + const transitionDurationPresets = [250, 500, 750, 1000, 2000, 5000]; + + const presets = property.name === 'transition_duration' ? transitionDurationPresets : defaultPresetDurations return (
diff --git a/react-web/src/pages/Home.tsx b/react-web/src/pages/Home.tsx index e8fec718..f616a52c 100644 --- a/react-web/src/pages/Home.tsx +++ b/react-web/src/pages/Home.tsx @@ -32,28 +32,48 @@ export default function Home() { } } - const handleActivate = async (name: string) => { + const handleActivate = async (id: string, displayName: string) => { if (!apiUrl) return try { - await fetch(`${apiUrl}/set_active?id=${encodeURIComponent(name)}`) - setStatus(prev => prev ? { ...prev, current: name } : null) - toast.success(`Activated "${name}"`) + await fetch(`${apiUrl}/set_active?id=${encodeURIComponent(id)}`) + setStatus(prev => prev ? { ...prev, current: id } : null) + toast.success(`Activated "${displayName}"`) } catch { toast.error('Failed to activate preset') } } - const handleDelete = async (name: string) => { + const handleDelete = async (id: string, displayName: string) => { if (!apiUrl) return try { - await fetch(`${apiUrl}/preset?id=${encodeURIComponent(name)}`, { method: 'DELETE' }) - toast.success(`Deleted "${name}"`) + await fetch(`${apiUrl}/preset?id=${encodeURIComponent(id)}`, { method: 'DELETE' }) + toast.success(`Deleted "${displayName}"`) retryPresets(r => r + 1) } catch { toast.error('Failed to delete preset') } } + const handleRename = async (id: string, displayName: string) => { + if (!apiUrl) return + try { + const res = await fetch(`${apiUrl}/preset_display_name?id=${encodeURIComponent(id)}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ display_name: displayName }), + }) + if (!res.ok) throw new Error('Failed to rename preset') + toast.success(`Renamed to "${displayName}"`) + retryPresets(r => r + 1) + } catch { + toast.error('Failed to rename preset') + } + } + + const activePresetLabel = status?.current && presets?.[status.current] + ? (presets[status.current].display_name ?? status.current) + : (status?.current ?? null) + return (
@@ -66,6 +86,7 @@ export default function Home() { ) : ( @@ -77,9 +98,10 @@ export default function Home() { retryPresets(r => r + 1)} /> )} diff --git a/react-web/src/pages/Schedules.tsx b/react-web/src/pages/Schedules.tsx index b3e4a5b2..95d53bb1 100644 --- a/react-web/src/pages/Schedules.tsx +++ b/react-web/src/pages/Schedules.tsx @@ -125,7 +125,13 @@ export default function Schedules() { })) } - const presetNames = presets ? Object.keys(presets) : [] + const presetOptions = presets + ? Object.entries(presets).map(([id, preset]) => ({ id, label: preset.display_name ?? id })) + : [] + + const presetDisplayById = presets + ? Object.fromEntries(Object.entries(presets).map(([id, preset]) => [id, preset.display_name ?? id])) + : {} return (
@@ -180,7 +186,7 @@ export default function Schedules() { {schedule.name || schedule.preset_id}
-

{schedule.preset_id}

+

{presetDisplayById[schedule.preset_id] ?? schedule.preset_id}

{formatTime(schedule.start_hour, schedule.start_minute)} {' – '} @@ -247,8 +253,8 @@ export default function Schedules() { - {presetNames.map(name => ( - {name} + {presetOptions.map(preset => ( + {preset.label} ))} diff --git a/shared/matrix/include/shared/matrix/config/MainConfig.h b/shared/matrix/include/shared/matrix/config/MainConfig.h index f59c4c9e..d1f27107 100644 --- a/shared/matrix/include/shared/matrix/config/MainConfig.h +++ b/shared/matrix/include/shared/matrix/config/MainConfig.h @@ -35,6 +35,7 @@ namespace Config { void set_curr(string id); bool delete_preset(const string &id); + bool set_preset_display_name(const string& id, const string& display_name); void set_presets(const string& id, std::shared_ptr preset); diff --git a/shared/matrix/include/shared/matrix/config/data.h b/shared/matrix/include/shared/matrix/config/data.h index 43f0a866..8d06987b 100644 --- a/shared/matrix/include/shared/matrix/config/data.h +++ b/shared/matrix/include/shared/matrix/config/data.h @@ -17,6 +17,7 @@ namespace ConfigData vector> scenes; tmillis_t transition_duration = 750; std::string transition_name = "blend"; ///< Global default transition effect name + std::string display_name; static std::shared_ptr create_default(); ~Preset() = default; // Add explicit destructor diff --git a/shared/matrix/src/shared/matrix/config/MainConfig.cpp b/shared/matrix/src/shared/matrix/config/MainConfig.cpp index 6f7c176b..09a6b9e9 100644 --- a/shared/matrix/src/shared/matrix/config/MainConfig.cpp +++ b/shared/matrix/src/shared/matrix/config/MainConfig.cpp @@ -3,13 +3,40 @@ #include "spdlog/spdlog.h" #include #include +#include #include #include +#include using namespace spdlog; namespace Config { + namespace { + bool is_uuid_like(const std::string &value) { + if (value.size() != 36) { + return false; + } + + for (size_t i = 0; i < value.size(); ++i) { + const bool is_hyphen = (i == 8 || i == 13 || i == 18 || i == 23); + if (is_hyphen) { + if (value[i] != '-') { + return false; + } + continue; + } + + const char c = value[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + return false; + } + } + + return true; + } + } + void MainConfig::mark_dirty() { unique_lock lock(this->update_mutex); @@ -65,6 +92,19 @@ namespace Config { return true; } + bool MainConfig::set_preset_display_name(const string &id, const string &display_name) { + unique_lock lock(this->data_mutex); + + const auto it = this->data.presets.find(id); + if (it == this->data.presets.end() || !it->second) { + return false; + } + + it->second->display_name = display_name; + this->mark_dirty(); + return true; + } + void MainConfig::set_presets(const string &id, std::shared_ptr preset) { unique_lock lock(this->data_mutex); spdlog::info("Setting preset {}", id); @@ -120,6 +160,69 @@ spdlog::info("Setting preset {}", id); f.close(); this->data = std::move(temp.get()); + bool migrated = false; + + if (this->data.presets.empty()) { + auto preset = ConfigData::Preset::create_default(); + const auto id = uuid::generate_uuid_v4(); + preset->display_name = "Default"; + this->data.presets[id] = std::move(preset); + this->data.curr = id; + migrated = true; + } + + std::map> migrated_presets; + std::unordered_map id_map; + + for (const auto &[old_id, old_preset]: this->data.presets) { + auto preset = old_preset; + if (!preset) { + preset = ConfigData::Preset::create_default(); + migrated = true; + } + + std::string new_id = old_id; + const bool keep_existing_id = is_uuid_like(old_id) && !migrated_presets.contains(old_id); + + if (!keep_existing_id) { + do { + new_id = uuid::generate_uuid_v4(); + } while (migrated_presets.contains(new_id)); + + id_map[old_id] = new_id; + migrated = true; + } + + if (preset->display_name.empty()) { + preset->display_name = old_id; + migrated = true; + } + + migrated_presets[new_id] = std::move(preset); + } + + if (migrated) { + this->data.presets = std::move(migrated_presets); + + if (id_map.contains(this->data.curr)) { + this->data.curr = id_map[this->data.curr]; + } + + if (!this->data.presets.contains(this->data.curr) && !this->data.presets.empty()) { + this->data.curr = this->data.presets.begin()->first; + } + + for (auto &[schedule_id, schedule]: this->data.schedules) { + (void) schedule_id; + if (id_map.contains(schedule.preset_id)) { + schedule.preset_id = id_map[schedule.preset_id]; + } + } + + info("Migrated preset IDs to UUID keys and persisted updated config"); + this->save(); + } + this->dirty = false; } diff --git a/shared/matrix/src/shared/matrix/config/data.cpp b/shared/matrix/src/shared/matrix/config/data.cpp index deeb4940..e83fe024 100644 --- a/shared/matrix/src/shared/matrix/config/data.cpp +++ b/shared/matrix/src/shared/matrix/config/data.cpp @@ -52,7 +52,8 @@ namespace ConfigData { j = json{ {"scenes", scenes_json}, {"transition_duration", p->transition_duration}, - {"transition_name", p->transition_name} + {"transition_name", p->transition_name}, + {"display_name", p->display_name} }; } @@ -159,6 +160,7 @@ namespace ConfigData { p->scenes = std::move(scenes); p->transition_duration = j.value("transition_duration", static_cast(750)); p->transition_name = j.value("transition_name", std::string("blend")); + p->display_name = j.value("display_name", std::string()); } void from_json(const json &j, std::unique_ptr &p) { @@ -192,6 +194,7 @@ namespace ConfigData { preset->scenes = scenes; preset->transition_duration = 750; preset->transition_name = "blend"; + preset->display_name = "Default"; return { preset, diff --git a/src_matrix/matrix_control/canvas.cpp b/src_matrix/matrix_control/canvas.cpp index 2e590ec9..991380e3 100644 --- a/src_matrix/matrix_control/canvas.cpp +++ b/src_matrix/matrix_control/canvas.cpp @@ -292,12 +292,24 @@ void update_canvas(RGBMatrixBase *matrix, FrameCanvas *&first_offscreen_canvas, scene->before_transition_stop(); tmillis_t transition_start_ms = GetTimeInMillis(); + int iteration = 0; while (true) { const auto now_ms = GetTimeInMillis(); - const auto current_continue = scene->render(first_offscreen_canvas); - const auto next_continue = next_scene->render(second_offscreen_canvas); + auto current_continue = true; + auto next_continue = true; + if (iteration == 0 || iteration % 2 == 0) + { + current_continue = scene->render(first_offscreen_canvas); + } + + if(iteration == 0 || iteration % 2 == 1) + { + next_continue = next_scene->render(second_offscreen_canvas); + } + + iteration++; if (!current_continue || !next_continue || interrupt_received || exit_canvas_update) { trace("Exiting scene early."); diff --git a/src_matrix/server/preset_management.cpp b/src_matrix/server/preset_management.cpp index 42368923..e625dcb7 100644 --- a/src_matrix/server/preset_management.cpp +++ b/src_matrix/server/preset_management.cpp @@ -1,6 +1,7 @@ #include "preset_management.h" #include "shared/matrix/utils/shared.h" #include "shared/matrix/server/server_utils.h" +#include "shared/matrix/utils/uuid.h" #include "nlohmann/json.hpp" #include @@ -52,14 +53,6 @@ std::unique_ptr Server::add_preset_routes(std::unique_ptrhttp_post("/add_preset", [](auto req, auto) { const auto qp = restinio::parse_query(req->header().query()); - if (!qp.has("id")) { - return reply_with_error(req, "Id not given"); - } - const std::string id{qp["id"]}; - if (id == "") { - return reply_with_error(req, "Id empty"); - } - spdlog::debug("Adding preset..."); string str_body = req->body(); json j; @@ -72,8 +65,31 @@ std::unique_ptr Server::add_preset_routes(std::unique_ptr>(); + + std::string id; + if (qp.has("id")) { + id = qp["id"]; + } else { + do { + id = uuid::generate_uuid_v4(); + } while (config->get_presets().contains(id)); + } + + if (id.empty()) { + return reply_with_error(req, "Id empty"); + } + + if (pr->display_name.empty()) { + pr->display_name = j.value("display_name", id); + } + config->set_presets(id, pr); - return reply_with_json(req, {{"success", "Preset has been added"}}); + config->save(); + return reply_with_json(req, { + {"success", "Preset has been added"}, + {"id", id}, + {"display_name", pr->display_name} + }); } catch (exception &ex) { spdlog::warn("Invalid preset with {}", ex.what()); return reply_with_error(req, "Could not serialize json"); @@ -99,6 +115,15 @@ std::unique_ptr Server::add_preset_routes(std::unique_ptr>(); + + if (pr->display_name.empty()) { + const auto presets = config->get_presets(); + const auto existing = presets.find(id); + if (existing != presets.end() && existing->second) { + pr->display_name = existing->second->display_name; + } + } + config->set_presets(id, pr); config->save(); return reply_with_json(req, { @@ -111,6 +136,46 @@ std::unique_ptr Server::add_preset_routes(std::unique_ptrhttp_post("/preset_display_name", [](auto req, auto) { + const auto qp = restinio::parse_query(req->header().query()); + if (!qp.has("id")) { + return reply_with_error(req, "Id not given"); + } + + const std::string id{qp["id"]}; + if (id.empty()) { + return reply_with_error(req, "Id empty"); + } + + json j; + try { + j = json::parse(req->body()); + } catch (exception &ex) { + spdlog::warn("Invalid json payload {}", ex.what()); + return reply_with_error(req, "Invalid json payload"); + } + + if (!j.contains("display_name") || !j["display_name"].is_string()) { + return reply_with_error(req, "display_name not given"); + } + + const std::string display_name = j["display_name"].get(); + if (display_name.empty()) { + return reply_with_error(req, "display_name empty"); + } + + if (!config->set_preset_display_name(id, display_name)) { + return reply_with_error(req, "Preset not found"); + } + + config->save(); + return reply_with_json(req, { + {"success", "Preset display name updated"}, + {"id", id}, + {"display_name", display_name} + }); + }); + // DELETE routes router->http_delete("/preset", [](auto req, auto) { const auto qp = restinio::parse_query(req->header().query()); From a52f0b7aed7a1f44346645d98644f2deba6f1680 Mon Sep 17 00:00:00 2001 From: Hendrik Lind Date: Mon, 16 Mar 2026 22:36:46 +0100 Subject: [PATCH 2/3] fix sigserv for particle scenes --- .../matrix/scenes/ParticleScene.cpp | 78 ++++++++++++++----- .../matrix/scenes/ParticleScene.h | 21 ++++- .../matrix/scenes/RainScene.cpp | 50 ++++++------ .../matrix/scenes/RainScene.h | 14 ++-- .../matrix/scenes/SparksScene.cpp | 58 +++++++++----- .../matrix/scenes/SparksScene.h | 6 +- react-web/src/pages/ModifyPreset.tsx | 2 +- src_matrix/matrix_control/canvas.cpp | 47 ++++++++--- src_matrix/server/other_routes.cpp | 7 ++ 9 files changed, 192 insertions(+), 91 deletions(-) diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp index d34c0e81..d4dcf6cc 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp +++ b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp @@ -9,41 +9,72 @@ ParticleScene::ParticleScene() prevTime(0), lastFpsLog(0), frameCount(0), - matrix(nullptr) { + matrix(nullptr) +{ } -void ParticleScene::initialize(int width, int height) { +void ParticleScene::initialize(int width, int height) +{ Scene::initialize(width, height); matrix = nullptr; renderer.reset(); animation.reset(); } -bool ParticleScene::render(rgb_matrix::FrameCanvas *canvas) { - if (matrix != canvas || !renderer.has_value() || !animation.has_value()) { +bool ParticleScene::render(rgb_matrix::FrameCanvas* canvas) +{ + if (matrix != canvas && renderer.has_value() && renderer.value()) + { + renderer.value()->setCanvas(canvas); matrix = canvas; - renderer = std::make_shared(matrix_width, matrix_height, matrix); - animation = std::unique_ptr( - new GravityParticles(renderer.value(), shake->get(), bounce->get()), - [](GravityParticles *a) { + } + + if (!renderer.has_value() || !animation.has_value() || !renderer.value() || !animation.value()) + { + spdlog::trace("Init particle scenes"); + matrix = canvas; + auto local_renderer = std::make_shared(matrix_width, matrix_height, matrix); + auto local_animation = std::shared_ptr( + new GravityParticles(local_renderer, shake->get(), bounce->get()), + [](GravityParticles* a) + { delete a; } ); - initializeParticles(); + + renderer = local_renderer; + animation = local_animation; + + initializeParticles(local_renderer, local_animation); + } + + auto current_renderer = renderer.value(); + auto current_animation = animation.value(); + + if (!current_renderer || !current_animation) + { + spdlog::warn("Particle scene renderer or animation was unexpectedly null, reinitializing on next frame."); + renderer.reset(); + animation.reset(); + matrix = nullptr; + return true; } - animation->get()->runCycle(); + preRender(current_renderer, current_animation); + current_animation->runCycle(); uint8_t MAX_FPS = 1000 / delay_ms->get(); uint32_t t; - while ((t = micros() - prevTime) < (100000L / MAX_FPS)) { + while ((t = micros() - prevTime) < (100000L / MAX_FPS)) + { } frameCount++; uint64_t now = micros(); - if (now - lastFpsLog >= 1000000) { - spdlog::trace("FPS: {:.2f}", (float) frameCount * 1000000.0f / (now - lastFpsLog)); + if (now - lastFpsLog >= 1000000) + { + spdlog::trace("FPS: {:.2f}", (float)frameCount * 1000000.0f / (now - lastFpsLog)); frameCount = 0; lastFpsLog = now; } @@ -52,13 +83,15 @@ bool ParticleScene::render(rgb_matrix::FrameCanvas *canvas) { return true; } -uint64_t ParticleScene::micros() { +uint64_t ParticleScene::micros() +{ uint64_t us = std::chrono::duration_cast(std::chrono::high_resolution_clock:: now().time_since_epoch()).count(); return us; } -void ParticleScene::register_properties() { +void ParticleScene::register_properties() +{ add_property(numParticles); add_property(velocity); add_property(accel); @@ -67,12 +100,15 @@ void ParticleScene::register_properties() { add_property(delay_ms); } -void ParticleScene::after_render_stop() { - if (this->animation.has_value()) { - this->animation->get()->clearParticles(); +void ParticleScene::after_render_stop() +{ + if (animation.has_value() && renderer.has_value()) + { + this->particle_on_render_stop(renderer.value(), animation.value()); } - animation.reset(); - renderer.reset(); - matrix = nullptr; + if (this->animation.has_value()) + { + this->animation->get()->clearParticles(); + } } diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h index 13644088..f153df31 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h +++ b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h @@ -13,6 +13,10 @@ namespace Scenes { : RGBMatrixRenderer(width, height), canvas_(canvas) { } + void setCanvas(rgb_matrix::Canvas *canvas) { + canvas_ = canvas; + } + void setPixel(uint16_t x, uint16_t y, RGB_color colour) override { if (canvas_) { canvas_->SetPixel(x, gridHeight - y - 1, colour.r, colour.g, colour.b); @@ -36,11 +40,13 @@ namespace Scenes { }; class ParticleScene : public Scene { - protected: + private: rgb_matrix::Canvas *matrix; std::optional > renderer; - std::optional > animation; + std::optional > animation; + void after_render_stop() override; + protected: PropertyPointer numParticles = MAKE_PROPERTY("numParticles", int, 40); PropertyPointer velocity = MAKE_PROPERTY("velocity", int16_t, 6000); PropertyPointer accel = MAKE_PROPERTY("acceleration", int, 1); @@ -59,7 +65,11 @@ namespace Scenes { return renderer ? renderer->get()->random_int16(a, b) : a + rand() % (b - a); } - virtual void initializeParticles() = 0; + virtual void initializeParticles(std::shared_ptr renderer, std::shared_ptr animation) = 0; + virtual void preRender(std::shared_ptr renderer, std::shared_ptr animation) + { + + } public: explicit ParticleScene(); @@ -72,6 +82,9 @@ namespace Scenes { void initialize(int width, int height) override; - void after_render_stop() override; + virtual void particle_on_render_stop(std::shared_ptr renderer, std::shared_ptr animation) + { + + } }; } diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.cpp b/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.cpp index a409c791..fc9a81f4 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.cpp +++ b/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.cpp @@ -30,13 +30,17 @@ void RainScene::initialize(int width, int height) { totalCols = matrix_width / 1.4; } -void RainScene::initializeParticles() { - animation->get()->setAcceleration(0, -accel->get()); +void RainScene::initializeParticles(std::shared_ptr renderer, std::shared_ptr animation) { + animation->setAcceleration(0, -accel->get()); initializeColumns(); - createColorPalette(); + createColorPalette(renderer); } void RainScene::initializeColumns() { + delete[] cols; + delete[] vels; + delete[] lengths; + cols = new uint16_t[totalCols]; vels = new uint16_t[totalCols]; lengths = new uint8_t[totalCols]; @@ -49,7 +53,7 @@ void RainScene::initializeColumns() { } } -void RainScene::createColorPalette() { +void RainScene::createColorPalette(std::shared_ptr renderer) { uint16_t brightness = 255; uint8_t red = 0, green = 255, blue = 0; uint8_t shadeSize = 8; @@ -62,7 +66,7 @@ void RainScene::createColorPalette() { brightness = random_int16(50, 255); red = uint16_t(brightness * i / 255); green = brightness; - colID = renderer->get()->getColourId(RGB_color(red, green, blue)); + colID = renderer->getColourId(RGB_color(red, green, blue)); } } for (uint16_t i = 0; i <= 255; i++) { @@ -71,7 +75,7 @@ void RainScene::createColorPalette() { brightness = random_int16(50, 255); red = brightness; green = uint16_t(brightness * (255 - i) / 255); - colID = renderer->get()->getColourId(RGB_color(red, green, blue)); + colID = renderer->getColourId(RGB_color(red, green, blue)); } } for (uint16_t i = 0; i <= 255; i++) { @@ -80,7 +84,7 @@ void RainScene::createColorPalette() { brightness = random_int16(50, 255); red = brightness; blue = uint16_t(brightness * i / 255); - colID = renderer->get()->getColourId(RGB_color(red, green, blue)); + colID = renderer->getColourId(RGB_color(red, green, blue)); } } for (uint16_t i = 0; i <= 255; i++) { @@ -89,7 +93,7 @@ void RainScene::createColorPalette() { brightness = random_int16(50, 255); red = uint16_t(brightness * (255 - i) / 255); blue = brightness; - colID = renderer->get()->getColourId(RGB_color(red, green, blue)); + colID = renderer->getColourId(RGB_color(red, green, blue)); } } for (uint16_t i = 0; i <= 255; i++) { @@ -98,7 +102,7 @@ void RainScene::createColorPalette() { brightness = random_int16(50, 255); green = uint16_t(brightness * i / 255); blue = brightness; - colID = renderer->get()->getColourId(RGB_color(red, green, blue)); + colID = renderer->getColourId(RGB_color(red, green, blue)); } } for (uint16_t i = 0; i <= 255; i++) { @@ -107,27 +111,31 @@ void RainScene::createColorPalette() { brightness = random_int16(50, 255); green = brightness; blue = uint16_t(brightness * (255 - i) / 255); - colID = renderer->get()->getColourId(RGB_color(red, green, blue)); + colID = renderer->getColourId(RGB_color(red, green, blue)); } } // ...additional color transitions... - totalColors = renderer->get()->getColourId(RGB_color(0, 255, 0)) - 1; + totalColors = renderer->getColourId(RGB_color(0, 255, 0)) - 1; } bool RainScene::render(rgb_matrix::FrameCanvas *canvas) { - addNewParticles(); - removeOldParticles(); - - // Call parent class render which handles animation and FPS return ParticleScene::render(canvas); } -void RainScene::addNewParticles() { - auto ren = renderer->get(); +void RainScene::preRender(std::shared_ptr renderer, std::shared_ptr animation) +{ + if (!renderer || !animation || !cols || !vels || !lengths) { + return; + } + + addNewParticles(renderer, animation); + removeOldParticles(animation); +} +void RainScene::addNewParticles(std::shared_ptr ren, std::shared_ptr animation) { const uint16_t stepSize = 1; - if (animation->get()->getParticleCount() >= numParticles->get()) return; + if (animation->getParticleCount() >= numParticles->get()) return; float v = velocity->get(); counter++; @@ -155,15 +163,13 @@ void RainScene::addNewParticles() { if (currentColorId >= totalColors) currentColorId = 1; } RGB_color color = ren->getColor(currentColorId); - animation->get()->addParticle(cols[i], ren->getGridHeight() - 1, color, 0, -vels[i]); + animation->addParticle(cols[i], ren->getGridHeight() - 1, color, 0, -vels[i]); lengths[i]--; } } } -void RainScene::removeOldParticles() { - auto anim = animation->get(); - +void RainScene::removeOldParticles(std::shared_ptr anim) { uint16_t removeNum = std::min((uint16_t) (numParticles->get() - 1), (uint16_t) matrix_width); if (anim->getParticleCount() > removeNum) { for (uint16_t i = 0; i < removeNum; i++) { diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.h b/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.h index e92911b8..17f378fd 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.h +++ b/plugins/RGBMatrixAnimations/matrix/scenes/RainScene.h @@ -18,15 +18,17 @@ namespace Scenes { uint32_t counter; - void initializeParticles() override; + void initializeParticles(std::shared_ptr renderer, std::shared_ptr animation) override; void initializeColumns(); - void createColorPalette(); + void createColorPalette(std::shared_ptr renderer); - void addNewParticles(); + void addNewParticles(std::shared_ptr renderer, std::shared_ptr animation); - void removeOldParticles(); + void removeOldParticles(std::shared_ptr animation); + protected: + void preRender(std::shared_ptr renderer, std::shared_ptr animation) override; public: explicit RainScene(); @@ -38,10 +40,6 @@ namespace Scenes { void initialize(int width, int height) override; // Add override here instead [[nodiscard]] string get_name() const override; - void after_render_stop() override { - } - - tmillis_t get_default_duration() override { return 20000; } diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.cpp b/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.cpp index 26555eeb..33c5bc1e 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.cpp +++ b/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.cpp @@ -4,52 +4,70 @@ using namespace Scenes; SparksScene::SparksScene() - : ParticleScene(), ax(0), ay(0) { + : ParticleScene(), ax(0), ay(0) +{ // Sparks-specific defaults numParticles = MAKE_PROPERTY("numParticles", int, 40); shake = MAKE_PROPERTY("shake", int, 5); bounce = MAKE_PROPERTY("bounce", int, 250); } -void SparksScene::initializeParticles() { +void SparksScene::initializeParticles(std::shared_ptr renderer, + std::shared_ptr animation) +{ RGB_color yellow = {255, 200, 120}; int16_t maxVel = 10000; - for (int i = 0; i < numParticles->get(); i++) { - int16_t vx = renderer->get()->random_int16(-maxVel, maxVel + 1); - int16_t vy = renderer->get()->random_int16(-maxVel, maxVel + 1); + for (int i = 0; i < numParticles->get(); i++) + { + int16_t vx = renderer->random_int16(-maxVel, maxVel + 1); + int16_t vy = renderer->random_int16(-maxVel, maxVel + 1); - if (vx > 0) { + if (vx > 0) + { vx += maxVel / 5; - } else { + } + else + { vx -= maxVel / 5; } - if (vy > 0) { + if (vy > 0) + { vy += maxVel / 5; - } else { + } + else + { vy -= maxVel / 5; } - animation->get()->addParticle(yellow, vx, vy); + animation->addParticle(yellow, vx, vy); } ax = 0; ay = -accel->get(); - animation->get()->setAcceleration(ax, ay); + animation->setAcceleration(ax, ay); } -void SparksScene::after_render_stop() { - animation->get()->clearParticles(); - initializeParticles(); + +void SparksScene::particle_on_render_stop(std::shared_ptr renderer, + std::shared_ptr animation) +{ + animation->clearParticles(); + initializeParticles(renderer, animation); } -string SparksScene::get_name() const { +string SparksScene::get_name() const +{ return "sparks"; } -std::unique_ptr SparksSceneWrapper::create() { - return {new SparksScene(), [](Scenes::Scene *scene) { - delete scene; - }}; -} \ No newline at end of file +std::unique_ptr SparksSceneWrapper::create() +{ + return { + new SparksScene(), [](Scenes::Scene* scene) + { + delete scene; + } + }; +} diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.h b/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.h index 5f2359e7..ba500277 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.h +++ b/plugins/RGBMatrixAnimations/matrix/scenes/SparksScene.h @@ -10,7 +10,7 @@ namespace Scenes { class SparksScene : public ParticleScene { int16_t ax, ay; - void initializeParticles() override; + void initializeParticles(std::shared_ptr renderer, std::shared_ptr animation) override; public: explicit SparksScene(); @@ -19,8 +19,6 @@ namespace Scenes { [[nodiscard]] string get_name() const override; - void after_render_stop() override; - tmillis_t get_default_duration() override { return 20000; @@ -29,6 +27,8 @@ namespace Scenes { int get_default_weight() override { return 2; } + + void particle_on_render_stop(std::shared_ptr renderer, std::shared_ptr animation) override; }; class SparksSceneWrapper : public Plugins::SceneWrapper { diff --git a/react-web/src/pages/ModifyPreset.tsx b/react-web/src/pages/ModifyPreset.tsx index a5afe6fd..8f5af874 100644 --- a/react-web/src/pages/ModifyPreset.tsx +++ b/react-web/src/pages/ModifyPreset.tsx @@ -70,7 +70,7 @@ function ModifyPresetInner({ presetId }: { presetId: string }) {

-

{presetId}

+

{rawPreset?.display_name ?? presetId}

Edit preset

diff --git a/src_matrix/matrix_control/canvas.cpp b/src_matrix/matrix_control/canvas.cpp index 991380e3..777de8f7 100644 --- a/src_matrix/matrix_control/canvas.cpp +++ b/src_matrix/matrix_control/canvas.cpp @@ -173,6 +173,20 @@ namespace } } } + + tmillis_t render_interval_ms_from_visibility(float visibility) + { + const auto clamped_visibility = std::clamp(visibility, 0.0f, 1.0f); + + // Keep visible scenes responsive while aggressively throttling nearly-hidden scenes. + constexpr tmillis_t min_interval_ms = 33; // ~30 FPS + constexpr tmillis_t max_interval_ms = 140; // ~7 FPS + + const auto interval_range = max_interval_ms - min_interval_ms; + const auto interval = max_interval_ms - static_cast(clamped_visibility * static_cast(interval_range)); + + return std::clamp(interval, min_interval_ms, max_interval_ms); + } } void render_fallback(RGBMatrixBase *canvas) @@ -292,35 +306,44 @@ void update_canvas(RGBMatrixBase *matrix, FrameCanvas *&first_offscreen_canvas, scene->before_transition_stop(); tmillis_t transition_start_ms = GetTimeInMillis(); - int iteration = 0; + tmillis_t last_current_render_ms = transition_start_ms; + tmillis_t last_next_render_ms = transition_start_ms; + + auto current_continue = scene->render(first_offscreen_canvas); + auto next_continue = next_scene->render(second_offscreen_canvas); + while (true) { const auto now_ms = GetTimeInMillis(); - auto current_continue = true; - auto next_continue = true; + const auto elapsed_transition = now_ms - transition_start_ms; + const auto alpha_progress = std::clamp( + static_cast(elapsed_transition) / static_cast(std::max(1, transition_duration)), + 0.0f, + 1.0f); + + const auto current_visibility = 1.0f - alpha_progress; + const auto next_visibility = alpha_progress; - if (iteration == 0 || iteration % 2 == 0) + const auto current_render_interval_ms = render_interval_ms_from_visibility(current_visibility); + const auto next_render_interval_ms = render_interval_ms_from_visibility(next_visibility); + + if ((now_ms - last_current_render_ms) >= current_render_interval_ms) { current_continue = scene->render(first_offscreen_canvas); + last_current_render_ms = now_ms; } - if(iteration == 0 || iteration % 2 == 1) + if ((now_ms - last_next_render_ms) >= next_render_interval_ms) { next_continue = next_scene->render(second_offscreen_canvas); + last_next_render_ms = now_ms; } - iteration++; if (!current_continue || !next_continue || interrupt_received || exit_canvas_update) { trace("Exiting scene early."); break; } - - const auto elapsed_transition = now_ms - transition_start_ms; - const auto alpha_progress = std::clamp( - static_cast(elapsed_transition) / static_cast(std::max(1, transition_duration)), - 0.0f, - 1.0f); apply_transition_frame(composite_offscreen_canvas, first_offscreen_canvas, second_offscreen_canvas, diff --git a/src_matrix/server/other_routes.cpp b/src_matrix/server/other_routes.cpp index ba9db186..7b4ff712 100644 --- a/src_matrix/server/other_routes.cpp +++ b/src_matrix/server/other_routes.cpp @@ -23,6 +23,13 @@ std::unique_ptr Server::add_other_routes(std::unique_ptrhttp_get("/web", [](auto req, auto) + { + auto response = req->create_response(restinio::status_see_other()) + .append_header(restinio::http_field::location, "/web/"); + Server::add_cors_headers(response); + return response.done(); }); + // Static file serving router->http_get("/web/:path(.*)", [](auto req, auto params) { From 6fc839d9c499c4187121d449b780a9e204d565a4 Mon Sep 17 00:00:00 2001 From: Hendrik Lind Date: Mon, 16 Mar 2026 22:49:06 +0100 Subject: [PATCH 3/3] fix again --- .../matrix/anim/RGBMatrixRenderer.cpp | 751 ++++++++++-------- .../matrix/scenes/ParticleScene.cpp | 30 +- .../matrix/scenes/ParticleScene.h | 6 +- 3 files changed, 446 insertions(+), 341 deletions(-) diff --git a/plugins/RGBMatrixAnimations/matrix/anim/RGBMatrixRenderer.cpp b/plugins/RGBMatrixAnimations/matrix/anim/RGBMatrixRenderer.cpp index f41cd419..ab9308f7 100644 --- a/plugins/RGBMatrixAnimations/matrix/anim/RGBMatrixRenderer.cpp +++ b/plugins/RGBMatrixAnimations/matrix/anim/RGBMatrixRenderer.cpp @@ -1,6 +1,6 @@ /************************************************************************************************** - * RGB Matrix Renderer abstract class - * + * RGB Matrix Renderer abstract class + * * This abstract class provides a template for building renderer classes to control LED Matrix * displays. It provides methods to set the colour of any LED/pixel in a grid, and to keep the * position of points within the bounds of the grid (with or without wrapping over the edges). @@ -8,24 +8,24 @@ * By writing a renderer class for any LED matrix hardware (or on screen display) this allows the * animations classes to be used with many different hardware display devices without requiring * code changes in the animation class. - * + * * This class also implements a wrapping mode for panels arranged as faces of a cube, including * tracking the x and y components of acceleration in the plane of each face of the cube for a - * 3D acceleration vector. The enables animations to update the motion of pixels on any panel + * 3D acceleration vector. The enables animations to update the motion of pixels on any panel * based on the cube face they appear on, and wrapping of pixels across panel edges in cube mode. * * Copyright (C) 2022 Paul Fretwell - aka 'Footleg' - * + * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this software. If not, see . */ @@ -38,35 +38,38 @@ RGBMatrixRenderer::RGBMatrixRenderer(uint16_t width, uint16_t height, uint8_t brightnessLimit, bool inCubeMode) : gridWidth(width), gridHeight(height), maxBrightness(brightnessLimit), cubeMode(inCubeMode) { - //Set panel size if in cube mode - if (inCubeMode) { - //Width has to be 3/2 of height in cube mode - if (width == height * 3 / 2) { + // Set panel size if in cube mode + if (inCubeMode) + { + // Width has to be 3/2 of height in cube mode + if (width == height * 3 / 2) + { panelSize = height / 2; } - else { - //Unsupported panel arrangement for cube mode - throw std::invalid_argument( "Cube mode only supports arrangements of 3 x 2 panels." ); + else + { + // Unsupported panel arrangement for cube mode + throw std::invalid_argument("Cube mode only supports arrangements of 3 x 2 panels."); } } - + // Allocate memory for colour palette array palette = new RGB_color[maxColours]; palette[0] = RGB_color{0, 0, 0}; coloursDefined = 0; - + // Allocate memory for pixels array img = new uint16_t[width * height]; clearImage(); -} //RGBMatrixRenderer +} // RGBMatrixRenderer // default destructor RGBMatrixRenderer::~RGBMatrixRenderer() { - delete [] palette; - delete [] img; + delete[] palette; + delete[] img; } //~RGBMatrixRenderer uint16_t RGBMatrixRenderer::getGridWidth() @@ -86,29 +89,32 @@ uint8_t RGBMatrixRenderer::getMaxBrightness() RGB_color RGBMatrixRenderer::getRandomColour() { - //Fetches a random colour from the palette if palette is full, otherwise returns a new one - if (coloursDefined >= maxColours) { + // Fetches a random colour from the palette if palette is full, otherwise returns a new one + if (coloursDefined >= maxColours) + { return getColor(random_int16(0, maxColours)); } - else { + else + { return newRandomColour(); } - } RGB_color RGBMatrixRenderer::newRandomColour() { // Init colour randomly RGB_color colour; - colour.r = random_int16(0,maxBrightness); - colour.g = random_int16(0,maxBrightness); - colour.b = random_int16(0,maxBrightness); + colour.r = random_int16(0, maxBrightness); + colour.g = random_int16(0, maxBrightness); + colour.b = random_int16(0, maxBrightness); uint8_t minBrightness = maxBrightness * 3 / 4; - - //Prevent colours being too dim - if (colour.r= dimension) newPos = dimension - 1; } - + return newPos; } @@ -166,129 +172,142 @@ uint8_t RGBMatrixRenderer::getPanel(MovingPixel pixel) uint8_t col = pixel.x / panelSize; panel = (row * gridWidth / panelSize) + col; - + return panel; }; -//Method for a cube, updates a grid coordinates while keeping on the matrix, with optional wrapping -MovingPixel RGBMatrixRenderer::updatePosition(MovingPixel pixel, bool wrap) +// Method for a cube, updates a grid coordinates while keeping on the matrix, with optional wrapping +MovingPixel RGBMatrixRenderer::updatePosition(MovingPixel pixel, bool wrap) { - MovingPixel newPixel(0,0,0,0); + MovingPixel newPixel(0, 0, 0, 0); - //Update the pixel fine position (fraction of a pixel position) + // Update the pixel fine position (fraction of a pixel position) uint16_t fineInc = abs(pixel.fineX + pixel.vx); -// char msg1[20]; -// sprintf(msg1, "FineIncX= %d ", fineInc); -// outputMessage(msg1); - if (fineInc >= SUBPIXEL_RES) { - //Moved at least one pixel position, so update + // char msg1[20]; + // sprintf(msg1, "FineIncX= %d ", fineInc); + // outputMessage(msg1); + if (fineInc >= SUBPIXEL_RES) + { + // Moved at least one pixel position, so update int16_t wholePix = fineInc / SUBPIXEL_RES; - int16_t partPix = fineInc - (wholePix*SUBPIXEL_RES); - if (pixel.vx < 0) { + int16_t partPix = fineInc - (wholePix * SUBPIXEL_RES); + if (pixel.vx < 0) + { wholePix = -wholePix; partPix = -partPix; } - newPixel.x = newPositionX(pixel.x,wholePix,wrap); + newPixel.x = newPositionX(pixel.x, wholePix, wrap); newPixel.fineX = partPix; -// char msg1[96]; -// sprintf(msg1, "WholePix=%d PartPix=%d OldFine: %d,%d NewFine: %d,%d\n", wholePix, partPix, pixel.fineX, pixel.fineY, newPixel.fineX, newPixel.fineY); -// outputMessage(msg1); + // char msg1[96]; + // sprintf(msg1, "WholePix=%d PartPix=%d OldFine: %d,%d NewFine: %d,%d\n", wholePix, partPix, pixel.fineX, pixel.fineY, newPixel.fineX, newPixel.fineY); + // outputMessage(msg1); } - else if (pixel.vx < 0) { + else if (pixel.vx < 0) + { newPixel.x = pixel.x; newPixel.fineX = -fineInc; } - else { + else + { newPixel.x = pixel.x; newPixel.fineX = fineInc; } fineInc = abs(pixel.fineY + pixel.vy); -// char msg2[20]; -// sprintf(msg2, "FineIncY= %d ", fineInc); -// outputMessage(msg2); - if (fineInc >= SUBPIXEL_RES) { - //Moved at least one pixel position, so update + // char msg2[20]; + // sprintf(msg2, "FineIncY= %d ", fineInc); + // outputMessage(msg2); + if (fineInc >= SUBPIXEL_RES) + { + // Moved at least one pixel position, so update int16_t wholePix = fineInc / SUBPIXEL_RES; - int16_t partPix = fineInc - (wholePix*SUBPIXEL_RES); - if (pixel.vy < 0) { + int16_t partPix = fineInc - (wholePix * SUBPIXEL_RES); + if (pixel.vy < 0) + { wholePix = -wholePix; partPix = -partPix; } - newPixel.y = newPositionY(pixel.y,wholePix,wrap); + newPixel.y = newPositionY(pixel.y, wholePix, wrap); newPixel.fineY = partPix; -// char msg1[96]; -// sprintf(msg1, "WholePix=%d PartPix=%d OldFine: %d,%d NewFine: %d,%d\n", wholePix, partPix, pixel.fineX, pixel.fineY, newPixel.fineX, newPixel.fineY); -// outputMessage(msg1); + // char msg1[96]; + // sprintf(msg1, "WholePix=%d PartPix=%d OldFine: %d,%d NewFine: %d,%d\n", wholePix, partPix, pixel.fineX, pixel.fineY, newPixel.fineX, newPixel.fineY); + // outputMessage(msg1); } - else if (pixel.vy < 0) { + else if (pixel.vy < 0) + { newPixel.y = pixel.y; newPixel.fineY = -fineInc; } - else { + else + { newPixel.y = pixel.y; newPixel.fineY = fineInc; } - + newPixel.vx = pixel.vx; newPixel.vy = pixel.vy; if (cubeMode) { - //For cube, the panels are arranged in 2 rows of 3. The bottom row of 3 panels represent - //3 sides with x being across, and y being up. The top row represents the top, back and bottom - //sides of the cube. x and y directions vary across these 3 panels wrt to the underlying 3 x 2 - //matrix. + // For cube, the panels are arranged in 2 rows of 3. The bottom row of 3 panels represent + // 3 sides with x being across, and y being up. The top row represents the top, back and bottom + // sides of the cube. x and y directions vary across these 3 panels wrt to the underlying 3 x 2 + // matrix. - //Determine which panel the pixel is on now + // Determine which panel the pixel is on now uint8_t panelRow = pixel.y / panelSize; uint8_t panelCol = pixel.x / panelSize; uint16_t newX; uint16_t newY; int16_t newFineX; - //int16_t newFineY; + // int16_t newFineY; - //Determine which panel the new pixel is on (before cube wrapping translation is applied) + // Determine which panel the new pixel is on (before cube wrapping translation is applied) uint8_t panelRowNew = newPixel.y / panelSize; uint8_t panelColNew = newPixel.x / panelSize; -// char msg1[96]; -// sprintf(msg1, "Cube Mode. Wrap=%d On Panel: %d,%d Moving to panel: %d,%d pos: %d,%d ", wrap, panelCol, panelRow, panelColNew, panelRowNew, pixel.x, pixel.y); -// outputMessage(msg1); + // char msg1[96]; + // sprintf(msg1, "Cube Mode. Wrap=%d On Panel: %d,%d Moving to panel: %d,%d pos: %d,%d ", wrap, panelCol, panelRow, panelColNew, panelRowNew, pixel.x, pixel.y); + // outputMessage(msg1); - //Update wrapping if changed panel - if (wrap == false) { - //If not wrapping, then don't allow move off panel + // Update wrapping if changed panel + if (wrap == false) + { + // If not wrapping, then don't allow move off panel newPixel = pixel; - } - else if (panelRow == panelRowNew) { - if (panelRow == 0) { + } + else if (panelRow == panelRowNew) + { + if (panelRow == 0) + { // Bottom Row - if ( panelCol == 2 && panelColNew == 0) { - //Wrapped off RH edge of panel3, so transpose onto panel 5 + if (panelCol == 2 && panelColNew == 0) + { + // Wrapped off RH edge of panel3, so transpose onto panel 5 newY = gridHeight - 1 - newPixel.x; newX = panelSize + newPixel.y; newPixel.x = newX; newPixel.y = newY; - //Transpose part pixel positions + // Transpose part pixel positions newFineX = newPixel.fineY; newPixel.fineY = -newPixel.fineX; newPixel.fineX = newFineX; - //Transpose X and Y velocities + // Transpose X and Y velocities newPixel.vx = pixel.vy; newPixel.vy = -pixel.vx; } - else if ( panelCol == 0 && panelColNew == 2) { - //Wrapped off LH edge of panel1, so transpose onto panel 5 + else if (panelCol == 0 && panelColNew == 2) + { + // Wrapped off LH edge of panel1, so transpose onto panel 5 newY = gridWidth - 1 - newPixel.x + panelSize; newX = panelSize + newPixel.y; newPixel.x = newX; newPixel.y = newY; - //Transpose part pixel positions + // Transpose part pixel positions newFineX = newPixel.fineY; newPixel.fineY = -newPixel.fineX; newPixel.fineX = newFineX; - //Transpose X and Y velocities + // Transpose X and Y velocities newPixel.vx = pixel.vy; newPixel.vy = -pixel.vx; } @@ -298,33 +317,36 @@ MovingPixel RGBMatrixRenderer::updatePosition(MovingPixel pixel, bool wrap) // newPixel.vy = pixel.vy; // } } - else { - //Upper Row - if ( panelCol == 2 && panelColNew == 0) { - //Wrapped off RH edge of panel6, so transpose onto panel 2 + else + { + // Upper Row + if (panelCol == 2 && panelColNew == 0) + { + // Wrapped off RH edge of panel6, so transpose onto panel 2 newY = panelSize - 1 - newPixel.x; newX = newPixel.y; newPixel.x = newX; newPixel.y = newY; - //Transpose part pixel positions + // Transpose part pixel positions newFineX = newPixel.fineY; newPixel.fineY = -newPixel.fineX; newPixel.fineX = newFineX; - //Transpose X and Y velocities + // Transpose X and Y velocities newPixel.vx = pixel.vy; newPixel.vy = -pixel.vx; } - else if ( panelCol == 0 && panelColNew == 2) { - //Wrapped off LH edge of panel4, so transpose onto panel 5 + else if (panelCol == 0 && panelColNew == 2) + { + // Wrapped off LH edge of panel4, so transpose onto panel 5 newY = gridWidth - 1 - newPixel.x; newX = newPixel.y; newPixel.x = newX; newPixel.y = newY; - //Transpose part pixel positions + // Transpose part pixel positions newFineX = newPixel.fineY; newPixel.fineY = -newPixel.fineX; newPixel.fineX = newFineX; - //Transpose X and Y velocities + // Transpose X and Y velocities newPixel.vx = pixel.vy; newPixel.vy = -pixel.vx; } @@ -335,203 +357,249 @@ MovingPixel RGBMatrixRenderer::updatePosition(MovingPixel pixel, bool wrap) // } } } - else { - //Change of panel row - if (panelCol == panelColNew) { - //Moving onto panel above or below (not diagonally) + else + { + // Change of panel row + if (panelCol == panelColNew) + { + // Moving onto panel above or below (not diagonally) uint8_t panelColNewCube = 0; uint8_t panelRowNewCube = 0; int16_t translate = 0; - if ( panelCol == 0 ) { - if ( panelRow == 0 ) { - //On panel 1 - if ( pixel.vy > 0 ) { - //Moving up from panel 1 to panel 6 - //Shift 2 panels to the right. Y wraps as normal so no change + if (panelCol == 0) + { + if (panelRow == 0) + { + // On panel 1 + if (pixel.vy > 0) + { + // Moving up from panel 1 to panel 6 + // Shift 2 panels to the right. Y wraps as normal so no change translate = 2 * panelSize; - } - else { - //Moving down from panel 1 to 4 (flip X and Y) + } + else + { + // Moving down from panel 1 to 4 (flip X and Y) panelColNewCube = 0; panelRowNewCube = 1; } - } - else { - //On panel 4 - if ( pixel.vy > 0 ) { - //Moving up from panel 4 to panel 3 - //Shift 2 panels to the right. Y wraps as normal so no change + } + else + { + // On panel 4 + if (pixel.vy > 0) + { + // Moving up from panel 4 to panel 3 + // Shift 2 panels to the right. Y wraps as normal so no change translate = 2 * panelSize; - } - else { - //Moving down from panel 4 to 1 (flip X and Y) + } + else + { + // Moving down from panel 4 to 1 (flip X and Y) panelColNewCube = 0; panelRowNewCube = 0; } } } - else if ( panelCol == 1 ) { - if ( panelRow == 0 ) { - //On panel 2 - if ( pixel.vy > 0 ) { - //Moving up from panel 2 to panel 6 (swap X and Y) + else if (panelCol == 1) + { + if (panelRow == 0) + { + // On panel 2 + if (pixel.vy > 0) + { + // Moving up from panel 2 to panel 6 (swap X and Y) panelColNewCube = 2; panelRowNewCube = 1; translate = 1; - } - else { - //Moving down from panel 2 to panel 4 (swap X and Y) + } + else + { + // Moving down from panel 2 to panel 4 (swap X and Y) panelColNewCube = 0; panelRowNewCube = 1; translate = 1; } - } - else { - //On panel 5 - if ( pixel.vy > 0 ) { - //Moving up from panel 5 to panel 3 (swap X and Y) + } + else + { + // On panel 5 + if (pixel.vy > 0) + { + // Moving up from panel 5 to panel 3 (swap X and Y) panelColNewCube = 2; panelRowNewCube = 0; translate = 1; - } - else { - //Moving down from panel 5 to panel 1 (swap X and Y) + } + else + { + // Moving down from panel 5 to panel 1 (swap X and Y) panelColNewCube = 0; panelRowNewCube = 0; translate = 1; } } } - else { - if ( panelRow == 0 ) { - //On panel 3 - if ( pixel.vy > 0 ) { - //Moving up from panel 3 to 6 (flip X and Y) + else + { + if (panelRow == 0) + { + // On panel 3 + if (pixel.vy > 0) + { + // Moving up from panel 3 to 6 (flip X and Y) panelColNewCube = 2; panelRowNewCube = 1; - } - else { - //Moving down from panel 3 to panel 4 - //Shift 2 panels to the left. - translate = - 2 * panelSize; } - } - else { - //On panel 6 - if ( pixel.vy > 0 ) { - //Moving up from panel 6 to panel 3 (flip X and Y) + else + { + // Moving down from panel 3 to panel 4 + // Shift 2 panels to the left. + translate = -2 * panelSize; + } + } + else + { + // On panel 6 + if (pixel.vy > 0) + { + // Moving up from panel 6 to panel 3 (flip X and Y) panelColNewCube = 2; panelRowNewCube = 0; - } - else { - //Moving down from panel 6 to panel 1 - //Shift 2 panels to the left. - translate = - 2 * panelSize; + } + else + { + // Moving down from panel 6 to panel 1 + // Shift 2 panels to the left. + translate = -2 * panelSize; } } } - //Process operation - if (translate == 0) { - //Flip X and Y within panel, then translate to position of final panel + // Process operation + if (translate == 0) + { + // Flip X and Y within panel, then translate to position of final panel newPixel.x = panelSize - 1 - (newPixel.x - panelColNew * panelSize) + panelColNewCube * panelSize; newPixel.y = panelSize - 1 - (newPixel.y - panelRowNew * panelSize) + panelRowNewCube * panelSize; - //Reverse part pixel positions + // Reverse part pixel positions newPixel.fineX = -newPixel.fineX; newPixel.fineY = -newPixel.fineY; - //Reverse X and Y velocities + // Reverse X and Y velocities newPixel.vx = -pixel.vx; newPixel.vy = -pixel.vy; } - else if (translate == 1) { - //Swap X and Y within panel, flip Y, then translate to position of final panel + else if (translate == 1) + { + // Swap X and Y within panel, flip Y, then translate to position of final panel newX = panelSize - 1 - (newPixel.y - panelRowNew * panelSize) + panelColNewCube * panelSize; newPixel.y = (newPixel.x - panelColNew * panelSize) + panelRowNewCube * panelSize; newPixel.x = newX; - //Transpose part pixel positions + // Transpose part pixel positions newFineX = -newPixel.fineY; newPixel.fineY = newPixel.fineX; newPixel.fineX = newFineX; - //Transpose X and Y velocities + // Transpose X and Y velocities newPixel.vx = -pixel.vy; newPixel.vy = pixel.vx; } - else { - //Translate x to new panel, Y wraps as for non-cube arrangement of flat panels, so no change in Y - newPixel.x = newPixel.x + translate; - //Retain same X and Y velocities + else + { + // Translate x to new panel, Y wraps as for non-cube arrangement of flat panels, so no change in Y + newPixel.x = newPixel.x + translate; + // Retain same X and Y velocities newPixel.vx = pixel.vx; newPixel.vy = pixel.vy; - } } - else { - //Diagonal cases - if ( panelCol == 0 ) { - if ( panelRow == 0 ) { - //On panel 1 - if ( pixel.vy > 0 ) { - //Moving up - } - else { - //Moving down + else + { + // Diagonal cases + if (panelCol == 0) + { + if (panelRow == 0) + { + // On panel 1 + if (pixel.vy > 0) + { + // Moving up } - } - else { - //On panel 4 - if ( pixel.vy > 0 ) { - //Moving up - } - else { - //Moving down + else + { + // Moving down + } + } + else + { + // On panel 4 + if (pixel.vy > 0) + { + // Moving up + } + else + { + // Moving down } } } - else if ( panelCol == 1 ) { - if ( panelRow == 0 ) { - //On panel 2 - if ( pixel.vy > 0 ) { - //Moving up - } - else { - //Moving down + else if (panelCol == 1) + { + if (panelRow == 0) + { + // On panel 2 + if (pixel.vy > 0) + { + // Moving up } - } - else { - //On panel 5 - if ( pixel.vy > 0 ) { - //Moving up - } - else { - //Moving down + else + { + // Moving down + } + } + else + { + // On panel 5 + if (pixel.vy > 0) + { + // Moving up + } + else + { + // Moving down } } } - else { - if ( panelRow == 0 ) { - //On panel 3 - if ( pixel.vy > 0 ) { - //Moving up - } - else { - //Moving down + else + { + if (panelRow == 0) + { + // On panel 3 + if (pixel.vy > 0) + { + // Moving up + } + else + { + // Moving down + } + } + else + { + // On panel 6 + if (pixel.vy > 0) + { + // Moving up } - } - else { - //On panel 6 - if ( pixel.vy > 0 ) { - //Moving up - } - else { - //Moving down + else + { + // Moving down } } } } } - } - + return newPixel; } @@ -547,20 +615,20 @@ uint16_t RGBMatrixRenderer::newPositionY(uint16_t y, uint16_t increment, bool wr RGB_color RGBMatrixRenderer::blendColour(RGB_color start, RGB_color end, uint8_t step, uint8_t steps) { - - uint8_t r = start.r + (end.r - start.r) * step / steps; - uint8_t g = start.g + (end.g - start.g) * step / steps; - uint8_t b = start.b + (end.b - start.b) * step / steps; - return RGB_color{r, g, b}; + uint8_t r = start.r + (end.r - start.r) * step / steps; + uint8_t g = start.g + (end.g - start.g) * step / steps; + uint8_t b = start.b + (end.b - start.b) * step / steps; + return RGB_color{r, g, b}; } RGB_color RGBMatrixRenderer::getColor(uint16_t id) { RGB_color colour = {0, 0, 0}; - if (id <= coloursDefined) { + if (id <= coloursDefined) + { colour = palette[id]; } @@ -573,94 +641,104 @@ uint16_t RGBMatrixRenderer::getColourId(RGB_color colour) uint16_t lowestScore = 100; uint16_t closestMatch = 0; - //Search palette for matching colour (black is always zero) - if ( (colour.r !=0) || (colour.g !=0) || (colour.b !=0) ) { - for (uint16_t i=1; i<=coloursDefined; i++) { + // Search palette for matching colour (black is always zero) + if ((colour.r != 0) || (colour.g != 0) || (colour.b != 0)) + { + for (uint16_t i = 1; i <= coloursDefined; i++) + { -// char msg1[64]; -// sprintf(msg1, "Searching palette %d (size %d))\n", i, coloursDefined); -// outputMessage(msg1); + // char msg1[64]; + // sprintf(msg1, "Searching palette %d (size %d))\n", i, coloursDefined); + // outputMessage(msg1); - if ( (palette[i].r == colour.r) - && (palette[i].g == colour.g) - && (palette[i].b == colour.b) ) { + if ((palette[i].r == colour.r) && (palette[i].g == colour.g) && (palette[i].b == colour.b)) + { id = i; lowestScore = 0; closestMatch = id; break; } - else if (coloursDefined < maxColours-1) { - //Determine if closest match so far (only needed if palette is already full) + else if (coloursDefined < maxColours - 1) + { + // Determine if closest match so far (only needed if palette is already full) uint16_t score = abs(palette[i].r - colour.r) + abs(palette[i].g - colour.g) + abs(palette[i].b - colour.b); - if (score < lowestScore) { + if (score < lowestScore) + { lowestScore = score; closestMatch = i; } } } - - //If match not found, add to palette if room - if (id == 0) { - if (coloursDefined < maxColours-1) { + + // If match not found, add to palette if room + if (id == 0) + { + if (coloursDefined < maxColours - 1) + { coloursDefined++; - -// char msg2[64]; -// sprintf(msg2, "Adding colour: %d, %d, %d (Total: %d)\n", colour.r, colour.g, colour.b, coloursDefined); -// outputMessage(msg2); + + // char msg2[64]; + // sprintf(msg2, "Adding colour: %d, %d, %d (Total: %d)\n", colour.r, colour.g, colour.b, coloursDefined); + // outputMessage(msg2); palette[coloursDefined] = colour; id = coloursDefined; } - else { - //Set to closest matching colour + else + { + // Set to closest matching colour id = closestMatch; - spdlog::warn("Asked for ({},{},{}) but got ({},{},{})", colour.r, colour.g, colour.b, palette[id].r, palette[id].g, palette[id].b); + spdlog::warn("Asked for ({},{},{}) but got ({},{},{})", colour.r, colour.g, colour.b, palette[id].r, palette[id].g, palette[id].b); } } } -// if (id > 0) { -// char msg[64]; -// sprintf(msg, "Returned colour at index: %d\n", id); -// outputMessage(msg); -// } + // if (id > 0) { + // char msg[64]; + // sprintf(msg, "Returned colour at index: %d\n", id); + // outputMessage(msg); + // } return id; } - -//Update Whole Matrix Display +// Update Whole Matrix Display void RGBMatrixRenderer::updateDisplay() { // Update pixel data on display -// uint16_t pixels = 0; - for(int y=0; y 0) { - if (y == yPrev || solid == false) { - setPixelColour(xc+x, yc+y, colour, persistent); - setPixelColour(xc-x, yc+y, colour, persistent); - setPixelColour(xc+x, yc-y, colour, persistent); - setPixelColour(xc-x, yc-y, colour, persistent); - if (solid) { - for (int lineX = xc-y; lineX <= xc+y; lineX++){ - setPixelColour(lineX, yc+x, colour, persistent); - setPixelColour(lineX, yc-x, colour, persistent); + if (x > 0) + { + if (y == yPrev || solid == false) + { + setPixelColour(xc + x, yc + y, colour, persistent); + setPixelColour(xc - x, yc + y, colour, persistent); + setPixelColour(xc + x, yc - y, colour, persistent); + setPixelColour(xc - x, yc - y, colour, persistent); + if (solid) + { + for (int lineX = xc - y; lineX <= xc + y; lineX++) + { + setPixelColour(lineX, yc + x, colour, persistent); + setPixelColour(lineX, yc - x, colour, persistent); } } - else { - setPixelColour(xc+y, yc+x, colour, persistent); - setPixelColour(xc-y, yc+x, colour, persistent); - setPixelColour(xc+y, yc-x, colour, persistent); - setPixelColour(xc-y, yc-x, colour, persistent); + else + { + setPixelColour(xc + y, yc + x, colour, persistent); + setPixelColour(xc - y, yc + x, colour, persistent); + setPixelColour(xc + y, yc - x, colour, persistent); + setPixelColour(xc - y, yc - x, colour, persistent); } } - else { - //Inner most pixels of horizontal section, so fill all postions in x range - for (int lineX = xc-x; lineX <= xc+x; lineX++){ - setPixelColour(lineX, yc+y, colour, persistent); - setPixelColour(lineX, yc-y, colour, persistent); + else + { + // Inner most pixels of horizontal section, so fill all postions in x range + for (int lineX = xc - x; lineX <= xc + x; lineX++) + { + setPixelColour(lineX, yc + y, colour, persistent); + setPixelColour(lineX, yc - y, colour, persistent); } - if (solid) { - //Middle of circle horizontal lines for every line - for (int lineX = xc-y; lineX <= xc+y; lineX++){ - setPixelColour(lineX, yc+x, colour, persistent); - setPixelColour(lineX, yc-x, colour, persistent); + if (solid) + { + // Middle of circle horizontal lines for every line + for (int lineX = xc - y; lineX <= xc + y; lineX++) + { + setPixelColour(lineX, yc + x, colour, persistent); + setPixelColour(lineX, yc - x, colour, persistent); } } } } - else { - setPixelColour(xc, yc+y, colour, persistent); - setPixelColour(xc, yc-y, colour, persistent); - if (solid) { - for (int lineX = xc-y; lineX <= xc+y; lineX++){ + else + { + setPixelColour(xc, yc + y, colour, persistent); + setPixelColour(xc, yc - y, colour, persistent); + if (solid) + { + for (int lineX = xc - y; lineX <= xc + y; lineX++) + { setPixelColour(lineX, yc, colour, persistent); } } - else { - setPixelColour(xc-y, yc, colour, persistent); - setPixelColour(xc+y, yc, colour, persistent); + else + { + setPixelColour(xc - y, yc, colour, persistent); + setPixelColour(xc + y, yc, colour, persistent); } } } - + // Function for circle-generation // using Bresenham's algorithm void RGBMatrixRenderer::drawCircle(int xc, int yc, int radius, RGB_color colour, bool solid, bool persistent) @@ -755,16 +848,18 @@ void RGBMatrixRenderer::drawCircle(int xc, int yc, int radius, RGB_color colour, int r = radius; int x = 0, y = r, yPrev = y; int d = 3 - 2 * r; - //Draw the 4 pixels marking the top, bottom, left and right most points in the circle + // Draw the 4 pixels marking the top, bottom, left and right most points in the circle drawOctants(xc, yc, x, y, yPrev, colour, solid, persistent); - //Now draw the positions either side of the last points drawn and continue moving - //around the circumference drawing 8 pixels each step - while (y >= x) { + // Now draw the positions either side of the last points drawn and continue moving + // around the circumference drawing 8 pixels each step + while (y >= x) + { x++; // check for decision parameter // and correspondingly // update d, x, y - if (d > 0) { + if (d > 0) + { y--; d = d + 4 * (x - y) + 10; } diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp index d4dcf6cc..2b8d6374 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp +++ b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.cpp @@ -8,32 +8,28 @@ ParticleScene::ParticleScene() : Scene(), prevTime(0), lastFpsLog(0), - frameCount(0), - matrix(nullptr) + frameCount(0) { } void ParticleScene::initialize(int width, int height) { Scene::initialize(width, height); - matrix = nullptr; renderer.reset(); animation.reset(); } bool ParticleScene::render(rgb_matrix::FrameCanvas* canvas) { - if (matrix != canvas && renderer.has_value() && renderer.value()) + if (renderer.has_value()) { renderer.value()->setCanvas(canvas); - matrix = canvas; } - if (!renderer.has_value() || !animation.has_value() || !renderer.value() || !animation.value()) + if (!renderer.has_value() || !animation.has_value()) { spdlog::trace("Init particle scenes"); - matrix = canvas; - auto local_renderer = std::make_shared(matrix_width, matrix_height, matrix); + auto local_renderer = std::make_shared(matrix_width, matrix_height, canvas); auto local_animation = std::shared_ptr( new GravityParticles(local_renderer, shake->get(), bounce->get()), [](GravityParticles* a) @@ -56,7 +52,6 @@ bool ParticleScene::render(rgb_matrix::FrameCanvas* canvas) spdlog::warn("Particle scene renderer or animation was unexpectedly null, reinitializing on next frame."); renderer.reset(); animation.reset(); - matrix = nullptr; return true; } @@ -102,13 +97,24 @@ void ParticleScene::register_properties() void ParticleScene::after_render_stop() { - if (animation.has_value() && renderer.has_value()) + if (animation.has_value() && renderer.value()) { this->particle_on_render_stop(renderer.value(), animation.value()); } - if (this->animation.has_value()) + if (animation.has_value()) { - this->animation->get()->clearParticles(); + animation.value()->clearParticles(); } + + if (renderer.has_value()) + { + renderer.value()->clearImage(); + } + + animation.reset(); + renderer.reset(); + prevTime = 0; + lastFpsLog = 0; + frameCount = 0; } diff --git a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h index f153df31..e87cdcc5 100644 --- a/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h +++ b/plugins/RGBMatrixAnimations/matrix/scenes/ParticleScene.h @@ -14,7 +14,12 @@ namespace Scenes { } void setCanvas(rgb_matrix::Canvas *canvas) { + if (canvas_ == canvas) { + return; + } + canvas_ = canvas; + updateDisplay(); } void setPixel(uint16_t x, uint16_t y, RGB_color colour) override { @@ -41,7 +46,6 @@ namespace Scenes { class ParticleScene : public Scene { private: - rgb_matrix::Canvas *matrix; std::optional > renderer; std::optional > animation;