Kaleidoscope
Source: src/kaleidoscope.c · 282 lines · 11 functions.
Screen captures
Views of this part running on the Mini Arcade.

Screen / module overview
- Start the effect
Randomise 18 particles. Select the first pattern and theme, set initial speed, and request a clear frame.
- Handle controls
A changes pattern, D resets particles, C changes colour theme and E changes speed.
- Update when 30 ms has elapsed
Cap elapsed motion at 50 ms and apply the speed multiplier. Advance the pulse and particle angles; reflect angles at wedge edges and reset particles beyond the radius.
- Clear or fade
Clear black after a reset; otherwise fade the previous image to retain trails.
- Render and repeat
Draw ribbons, stars or squares in reflected sectors and three scaled layers. Draw the centre and temporary control status.
Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
particleColour() · source line 78
static uint16_t particleColour(const kaleidoscope_particle_t *particle)
- Exit this function
return theme_colours[theme_index][particle->colour];
Read original function source
static uint16_t particleColour(const kaleidoscope_particle_t *particle)
{
return theme_colours[theme_index][particle->colour];
}fadeFrame() · source line 83
static void fadeFrame(uint16_t *frame)
- FOR loop
size_t index = 0U; index < (size_t)LCD_WIDTH * LCD_HEIGHT; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const uint16_t pixel = frame[index]; - Execute
const uint16_t red = (uint16_t)(((pixel >> 11U) & 0x1FU) * 15U / 16U); - Execute
const uint16_t green = (uint16_t)(((pixel >> 5U) & 0x3FU) * 15U / 16U); - Execute
const uint16_t blue = (uint16_t)((pixel & 0x1FU) * 15U / 16U); - Execute
frame[index] = (uint16_t)((red << 11U) | (green << 5U) | blue);
Read original function source
static void fadeFrame(uint16_t *frame)
{
for (size_t index = 0U; index < (size_t)LCD_WIDTH * LCD_HEIGHT; ++index) {
const uint16_t pixel = frame[index];
const uint16_t red = (uint16_t)(((pixel >> 11U) & 0x1FU) * 15U / 16U);
const uint16_t green = (uint16_t)(((pixel >> 5U) & 0x3FU) * 15U / 16U);
const uint16_t blue = (uint16_t)((pixel & 0x1FU) * 15U / 16U);
frame[index] = (uint16_t)((red << 11U) | (green << 5U) | blue);
}
}drawRibbon() · source line 94
static void drawRibbon(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)Calls into this documentation:
- Execute
const float radius = particle->radius * pulse_scale; - Execute
const float ribbon_end = (particle->radius - particle->length) * pulse_scale; - Execute
const float side_angle = angle + KALEIDOSCOPE_PI / 2.0f; - Execute
const float side_x = cosf(side_angle) * particle->width * pulse_scale; - Execute
const float side_y = sinf(side_angle) * particle->width * pulse_scale; - Execute
const int x0 = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * radius); - Execute
const int y0 = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * radius); - Execute
const int x1 = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * ribbon_end); - Execute
const int y1 = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * ribbon_end); - FOR loop
int edge = -1; edge <= 1; ++edgeRepeat body while the loop condition permits; then continue below.
- Execute
draw_line(frame, (int)lroundf((float)x0 + side_x * edge), (int)lroundf((float)y0 + side_y * edge), (int)lroundf((float)x1 + side_x * edge), (int)lroundf((float)y1 + side_y * edge), particleColour(particle));
Read original function source
static void drawRibbon(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)
{
const float radius = particle->radius * pulse_scale;
const float ribbon_end = (particle->radius - particle->length) * pulse_scale;
const float side_angle = angle + KALEIDOSCOPE_PI / 2.0f;
const float side_x = cosf(side_angle) * particle->width * pulse_scale;
const float side_y = sinf(side_angle) * particle->width * pulse_scale;
const int x0 = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * radius);
const int y0 = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * radius);
const int x1 = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * ribbon_end);
const int y1 = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * ribbon_end);
for (int edge = -1; edge <= 1; ++edge) {
draw_line(frame, (int)lroundf((float)x0 + side_x * edge),
(int)lroundf((float)y0 + side_y * edge),
(int)lroundf((float)x1 + side_x * edge),
(int)lroundf((float)y1 + side_y * edge),
particleColour(particle));
}
}drawStar() · source line 119
static void drawStar(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)Calls into this documentation:
- Execute
const int centre_x = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * particle->radius * pulse_scale); - Execute
const int centre_y = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * particle->radius * pulse_scale); - Execute
const float arm = (5.0f + particle->width * 2.0f) * pulse_scale; - FOR loop
unsigned arm_index = 0U; arm_index < 4U; ++arm_indexRepeat body while the loop condition permits; then continue below.
- Execute
const float arm_angle = angle + (float)arm_index * KALEIDOSCOPE_PI / 2.0f; - Execute
const int end_x = centre_x + (int)lroundf(cosf(arm_angle) * arm); - Execute
const int end_y = centre_y + (int)lroundf(sinf(arm_angle) * arm); - Execute
draw_line(frame, centre_x, centre_y, end_x, end_y, particleColour(particle));
- Execute
fill_circle(frame, centre_x, centre_y, 2, particleColour(particle));
Read original function source
static void drawStar(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)
{
const int centre_x = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * particle->radius * pulse_scale);
const int centre_y = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * particle->radius * pulse_scale);
const float arm = (5.0f + particle->width * 2.0f) * pulse_scale;
for (unsigned arm_index = 0U; arm_index < 4U; ++arm_index) {
const float arm_angle = angle + (float)arm_index * KALEIDOSCOPE_PI / 2.0f;
const int end_x = centre_x + (int)lroundf(cosf(arm_angle) * arm);
const int end_y = centre_y + (int)lroundf(sinf(arm_angle) * arm);
draw_line(frame, centre_x, centre_y, end_x, end_y, particleColour(particle));
}
fill_circle(frame, centre_x, centre_y, 2, particleColour(particle));
}drawSquare() · source line 136
static void drawSquare(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)Calls into this documentation:
- Execute
const int centre_x = KALEIDOSCOPE_CENTRE_X + (int)lroundf(cosf(angle) * particle->radius * pulse_scale); - Execute
const int centre_y = KALEIDOSCOPE_CENTRE_Y + (int)lroundf(sinf(angle) * particle->radius * pulse_scale); - Execute
const float half_side = (5.0f + particle->width * 2.0f) * pulse_scale; - Execute
int previous_x = 0; - Execute
int previous_y = 0; - FOR loop
unsigned corner = 0U; corner <= 4U; ++cornerRepeat body while the loop condition permits; then continue below.
- Execute
const float corner_angle = angle + KALEIDOSCOPE_PI / 4.0f + (float)(corner % 4U) * KALEIDOSCOPE_PI / 2.0f; - Execute
const int x = centre_x + (int)lroundf(cosf(corner_angle) * half_side); - Execute
const int y = centre_y + (int)lroundf(sinf(corner_angle) * half_side); - Decision
corner > 0UTrue- Execute
draw_line(frame, previous_x, previous_y, x, y, particleColour(particle));
FalseContinue after this decision.
- Execute
previous_x = x; - Execute
previous_y = y;
Read original function source
static void drawSquare(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)
{
const int centre_x = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * particle->radius * pulse_scale);
const int centre_y = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * particle->radius * pulse_scale);
const float half_side = (5.0f + particle->width * 2.0f) * pulse_scale;
int previous_x = 0;
int previous_y = 0;
for (unsigned corner = 0U; corner <= 4U; ++corner) {
const float corner_angle = angle + KALEIDOSCOPE_PI / 4.0f +
(float)(corner % 4U) * KALEIDOSCOPE_PI / 2.0f;
const int x = centre_x + (int)lroundf(cosf(corner_angle) * half_side);
const int y = centre_y + (int)lroundf(sinf(corner_angle) * half_side);
if (corner > 0U) draw_line(frame, previous_x, previous_y, x, y,
particleColour(particle));
previous_x = x;
previous_y = y;
}
}resetParticle() · source line 158
static void resetParticle(kaleidoscope_particle_t *particle)
- Execute
particle->angle = (float)(esp_random() % 1000U) * KALEIDOSCOPE_WEDGE_ANGLE / 1000.0f; - Execute
particle->radius = 6.0f + (float)(esp_random() % 6800U) / 100.0f; - Execute
particle->angle_speed = 0.10f + (float)(esp_random() % 120U) / 500.0f; - Execute
particle->radius_speed = 8.0f + (float)(esp_random() % 220U) / 10.0f; - Execute
particle->length = 18.0f + (float)(esp_random() % 4400U) / 100.0f; - Execute
particle->width = 2.0f + (float)(esp_random() % 3U); - Execute
particle->colour = (uint8_t)(esp_random() % KALEIDOSCOPE_THEME_COLOURS);
Read original function source
static void resetParticle(kaleidoscope_particle_t *particle)
{
particle->angle = (float)(esp_random() % 1000U) * KALEIDOSCOPE_WEDGE_ANGLE / 1000.0f;
particle->radius = 6.0f + (float)(esp_random() % 6800U) / 100.0f;
particle->angle_speed = 0.10f + (float)(esp_random() % 120U) / 500.0f;
particle->radius_speed = 8.0f + (float)(esp_random() % 220U) / 10.0f;
particle->length = 18.0f + (float)(esp_random() % 4400U) / 100.0f;
particle->width = 2.0f + (float)(esp_random() % 3U);
particle->colour = (uint8_t)(esp_random() % KALEIDOSCOPE_THEME_COLOURS);
}startKaleidoscope() · source line 169
void startKaleidoscope(uint32_t now_ms)
Calls into this documentation:
- FOR loop
unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
resetParticle(&particles[index]);
- Execute
last_update_ms = now_ms; - Execute
controls_changed_at = now_ms; - Execute
speed_index = 1U; - Execute
pattern_index = 0U; - Execute
theme_index = 0U; - Execute
pulse_phase = 0.0f; - Execute
pattern_changed = false; - Execute
reset_requested = false; - Execute
theme_changed = false; - Execute
clear_requested = true; - Execute
running = true;
Read original function source
void startKaleidoscope(uint32_t now_ms)
{
for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
resetParticle(&particles[index]);
}
last_update_ms = now_ms;
controls_changed_at = now_ms;
speed_index = 1U;
pattern_index = 0U;
theme_index = 0U;
pulse_phase = 0.0f;
pattern_changed = false;
reset_requested = false;
theme_changed = false;
clear_requested = true;
running = true;
}stopKaleidoscope() · source line 187
void stopKaleidoscope(void)
- Execute
running = false;
Read original function source
void stopKaleidoscope(void)
{
running = false;
}kaleidoscopeAction() · source line 192
void kaleidoscopeAction(bool new_pattern, bool reset, bool theme, bool speed, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
new_patternTrue- Execute
pattern_index = (uint8_t)((pattern_index + 1U) % KALEIDOSCOPE_PATTERN_COUNT);
FalseContinue after this decision.
- Decision
new_pattern || resetTrue- FOR loop
unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
resetParticle(&particles[index]);
- Execute
clear_requested = true;
FalseContinue after this decision.
- Decision
themeTrue- Execute
theme_index = (uint8_t)((theme_index + 1U) % KALEIDOSCOPE_THEME_COUNT);
FalseContinue after this decision.
- Decision
speedTrue- Execute
speed_index = (uint8_t)((speed_index + 1U) % KALEIDOSCOPE_SPEED_COUNT);
FalseContinue after this decision.
- Decision
!new_pattern && !reset && !theme && !speedTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
pattern_changed = new_pattern; - Execute
reset_requested = reset && !new_pattern; - Execute
theme_changed = theme && !new_pattern && !reset; - Execute
controls_changed_at = now_ms;
Read original function source
void kaleidoscopeAction(bool new_pattern, bool reset, bool theme, bool speed, uint32_t now_ms)
{
if (!running) return;
if (new_pattern) {
pattern_index = (uint8_t)((pattern_index + 1U) % KALEIDOSCOPE_PATTERN_COUNT);
}
if (new_pattern || reset) {
for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
resetParticle(&particles[index]);
}
clear_requested = true;
}
if (theme) theme_index = (uint8_t)((theme_index + 1U) % KALEIDOSCOPE_THEME_COUNT);
if (speed) speed_index = (uint8_t)((speed_index + 1U) % KALEIDOSCOPE_SPEED_COUNT);
if (!new_pattern && !reset && !theme && !speed) return;
pattern_changed = new_pattern;
reset_requested = reset && !new_pattern;
theme_changed = theme && !new_pattern && !reset;
controls_changed_at = now_ms;
}updateKaleidoscope() · source line 213
void updateKaleidoscope(uint32_t now_ms)
Calls into this documentation:
- Decision
!running || now_ms - last_update_ms < KALEIDOSCOPE_FRAME_MSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed_ms = now_ms - last_update_ms; - Execute
last_update_ms = now_ms; - Execute
const float elapsed = (float)(elapsed_ms > 50U ? 50U : elapsed_ms) / 1000.0f * speed_multipliers[speed_index]; - Execute
pulse_phase += elapsed * KALEIDOSCOPE_PULSE_RATE; - Decision
pulse_phase >= 2.0f * KALEIDOSCOPE_PITrue- Execute
pulse_phase -= 2.0f * KALEIDOSCOPE_PI;
FalseContinue after this decision.
- FOR loop
unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
kaleidoscope_particle_t *particle = &particles[index]; - Execute
particle->angle += particle->angle_speed * elapsed; - Decision
particle->angle > KALEIDOSCOPE_WEDGE_ANGLETrue- Execute
particle->angle = KALEIDOSCOPE_WEDGE_ANGLE - (particle->angle - KALEIDOSCOPE_WEDGE_ANGLE); - Execute
particle->angle_speed = -particle->angle_speed;
False- Decision
particle->angle < 0.0fTrue- Execute
particle->angle = -particle->angle; - Execute
particle->angle_speed = -particle->angle_speed;
FalseContinue after this decision.
- Execute
particle->radius += particle->radius_speed * elapsed; - Decision
particle->radius > KALEIDOSCOPE_RADIUSTrue- Execute
resetParticle(particle);
FalseContinue after this decision.
Read original function source
void updateKaleidoscope(uint32_t now_ms)
{
if (!running || now_ms - last_update_ms < KALEIDOSCOPE_FRAME_MS) return;
const uint32_t elapsed_ms = now_ms - last_update_ms;
last_update_ms = now_ms;
const float elapsed = (float)(elapsed_ms > 50U ? 50U : elapsed_ms) / 1000.0f *
speed_multipliers[speed_index];
pulse_phase += elapsed * KALEIDOSCOPE_PULSE_RATE;
if (pulse_phase >= 2.0f * KALEIDOSCOPE_PI) pulse_phase -= 2.0f * KALEIDOSCOPE_PI;
for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
kaleidoscope_particle_t *particle = &particles[index];
particle->angle += particle->angle_speed * elapsed;
if (particle->angle > KALEIDOSCOPE_WEDGE_ANGLE) {
particle->angle = KALEIDOSCOPE_WEDGE_ANGLE -
(particle->angle - KALEIDOSCOPE_WEDGE_ANGLE);
particle->angle_speed = -particle->angle_speed;
} else if (particle->angle < 0.0f) {
particle->angle = -particle->angle;
particle->angle_speed = -particle->angle_speed;
}
particle->radius += particle->radius_speed * elapsed;
if (particle->radius > KALEIDOSCOPE_RADIUS) resetParticle(particle);
}
}drawKaleidoscope() · source line 238
void drawKaleidoscope(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
clear_requestedTrue- Execute
clear(frame, COLOR_BLACK); - Execute
clear_requested = false;
False- Execute
fadeFrame(frame);
- Execute
const unsigned particle_count = pattern_index == 0U ? KALEIDOSCOPE_PARTICLES : 9U; - Execute
const float pulse_scale = 1.0f + sinf(pulse_phase) * KALEIDOSCOPE_PULSE_AMOUNT; - FOR loop
unsigned index = 0U; index < particle_count; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const kaleidoscope_particle_t *particle = &particles[index]; - FOR loop
unsigned sector = 0U; sector < KALEIDOSCOPE_SECTORS; ++sectorRepeat body while the loop condition permits; then continue below.
- Execute
const float base_angle = (float)sector * 2.0f * KALEIDOSCOPE_WEDGE_ANGLE; - FOR loop
unsigned reflection = 0U; reflection < 2U; ++reflectionRepeat body while the loop condition permits; then continue below.
- Execute
const float angle = base_angle + (reflection == 0U ? particle->angle : -particle->angle); - FOR loop
unsigned layer = 0U; layer < KALEIDOSCOPE_TUNNEL_LAYERS; ++layerRepeat body while the loop condition permits; then continue below.
- Execute
const float layer_scale = pulse_scale * tunnel_scales[layer]; - Decision
pattern_index == 0UTrue- Execute
drawRibbon(frame, particle, angle, layer_scale);
False- Decision
pattern_index == 1UTrue- Execute
drawStar(frame, particle, angle, layer_scale);
False- Execute
drawSquare(frame, particle, angle, layer_scale);
- Execute
fill_circle(frame, KALEIDOSCOPE_CENTRE_X, KALEIDOSCOPE_CENTRE_Y, (int)lroundf(4.0f * pulse_scale), COLOR_WHITE); - Decision
last_update_ms - controls_changed_at < KALEIDOSCOPE_STATUS_MSTrue- Execute
char text[18]; - Decision
pattern_changedTrue- Execute
snprintf(text, sizeof(text), "A %s", pattern_names[pattern_index]);
False- Decision
reset_requestedTrue- Execute
snprintf(text, sizeof(text), "D RESET");
False- Decision
theme_changedTrue- Execute
snprintf(text, sizeof(text), "C %s", theme_names[theme_index]);
False- Execute
snprintf(text, sizeof(text), "E SPEED %u", (unsigned)(speed_index + 1U));
- Execute
draw_text_centred(frame, KALEIDOSCOPE_CENTRE_X, 155, text, 1, COLOR_LIGHTGREY);
FalseContinue after this decision.
Read original function source
void drawKaleidoscope(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (clear_requested) {
clear(frame, COLOR_BLACK);
clear_requested = false;
} else {
fadeFrame(frame);
}
const unsigned particle_count = pattern_index == 0U ? KALEIDOSCOPE_PARTICLES : 9U;
const float pulse_scale = 1.0f + sinf(pulse_phase) * KALEIDOSCOPE_PULSE_AMOUNT;
for (unsigned index = 0U; index < particle_count; ++index) {
const kaleidoscope_particle_t *particle = &particles[index];
for (unsigned sector = 0U; sector < KALEIDOSCOPE_SECTORS; ++sector) {
const float base_angle = (float)sector * 2.0f * KALEIDOSCOPE_WEDGE_ANGLE;
for (unsigned reflection = 0U; reflection < 2U; ++reflection) {
const float angle = base_angle + (reflection == 0U ? particle->angle :
-particle->angle);
for (unsigned layer = 0U; layer < KALEIDOSCOPE_TUNNEL_LAYERS; ++layer) {
const float layer_scale = pulse_scale * tunnel_scales[layer];
if (pattern_index == 0U) drawRibbon(frame, particle, angle, layer_scale);
else if (pattern_index == 1U) drawStar(frame, particle, angle, layer_scale);
else drawSquare(frame, particle, angle, layer_scale);
}
}
}
}
fill_circle(frame, KALEIDOSCOPE_CENTRE_X, KALEIDOSCOPE_CENTRE_Y,
(int)lroundf(4.0f * pulse_scale), COLOR_WHITE);
if (last_update_ms - controls_changed_at < KALEIDOSCOPE_STATUS_MS) {
char text[18];
if (pattern_changed) {
snprintf(text, sizeof(text), "A %s", pattern_names[pattern_index]);
} else if (reset_requested) {
snprintf(text, sizeof(text), "D RESET");
} else if (theme_changed) {
snprintf(text, sizeof(text), "C %s", theme_names[theme_index]);
} else {
snprintf(text, sizeof(text), "E SPEED %u", (unsigned)(speed_index + 1U));
}
draw_text_centred(frame, KALEIDOSCOPE_CENTRE_X, 155, text, 1, COLOR_LIGHTGREY);
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "kaleidoscope.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "esp_random.h"
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#define KALEIDOSCOPE_PARTICLES 18U
#define KALEIDOSCOPE_SECTORS 8U
#define KALEIDOSCOPE_FRAME_MS 30U
#define KALEIDOSCOPE_SPEED_COUNT 4U
#define KALEIDOSCOPE_PATTERN_COUNT 3U
#define KALEIDOSCOPE_THEME_COUNT 6U
#define KALEIDOSCOPE_THEME_COLOURS 8U
#define KALEIDOSCOPE_STATUS_MS 1500U
#define KALEIDOSCOPE_PI 3.14159265f
#define KALEIDOSCOPE_PULSE_AMOUNT 0.12f
#define KALEIDOSCOPE_PULSE_RATE 0.80f
#define KALEIDOSCOPE_TUNNEL_LAYERS 3U
#define KALEIDOSCOPE_WEDGE_ANGLE (KALEIDOSCOPE_PI / (float)KALEIDOSCOPE_SECTORS)
#define KALEIDOSCOPE_CENTRE_X (LCD_WIDTH / 2)
#define KALEIDOSCOPE_CENTRE_Y (LCD_HEIGHT / 2)
#define KALEIDOSCOPE_RADIUS ((float)LCD_WIDTH / 2.0f)
typedef struct {
float angle;
float radius;
float angle_speed;
float radius_speed;
float length;
float width;
uint8_t colour;
} kaleidoscope_particle_t;
static const uint16_t theme_colours[KALEIDOSCOPE_THEME_COUNT][KALEIDOSCOPE_THEME_COLOURS] = {
{COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_LIME,
COLOR_CYAN, COLOR_MEDIUMPURPLE, COLOR_MAGENTA, COLOR_WHITE},
{COLOR_DARKRED, COLOR_RED, COLOR_ORANGERED, COLOR_ORANGE,
COLOR_GOLD, COLOR_YELLOW, COLOR_LIGHTYELLOW, COLOR_WHITE},
{COLOR_DARKBLUE, COLOR_BLUE, COLOR_DODGERBLUE, COLOR_DEEPSKYBLUE,
COLOR_CYAN, COLOR_TURQUOISE, COLOR_LIGHTBLUE, COLOR_WHITE},
{COLOR_DARKMAGENTA, COLOR_PURPLE, COLOR_MEDIUMPURPLE, COLOR_MAGENTA,
COLOR_DEEPPINK, COLOR_CYAN, COLOR_LIME, COLOR_WHITE},
{COLOR_DARKGREEN, COLOR_FORESTGREEN, COLOR_GREEN, COLOR_LIMEGREEN,
COLOR_LIME, COLOR_GREENYELLOW, COLOR_LIGHTGREEN, COLOR_WHITE},
{COLOR_DARKRED, COLOR_FIREBRICK, COLOR_CRIMSON, COLOR_RED,
COLOR_ORANGERED, COLOR_ORANGE, COLOR_LIGHTSALMON, COLOR_WHITE},
};
static const float speed_multipliers[KALEIDOSCOPE_SPEED_COUNT] = {
0.50f, 0.80f, 1.20f, 1.70f,
};
static const float tunnel_scales[KALEIDOSCOPE_TUNNEL_LAYERS] = {
0.38f, 0.66f, 1.00f,
};
static const char *const pattern_names[KALEIDOSCOPE_PATTERN_COUNT] = {
"RIBBONS", "STARS", "SQUARES",
};
static const char *const theme_names[KALEIDOSCOPE_THEME_COUNT] = {
"RAINBOW", "FIRE", "OCEAN", "NEON", "MONO", "RED",
};
static kaleidoscope_particle_t particles[KALEIDOSCOPE_PARTICLES];
static uint32_t last_update_ms;
static uint32_t controls_changed_at;
static uint8_t speed_index;
static uint8_t pattern_index;
static uint8_t theme_index;
static float pulse_phase;
static bool pattern_changed;
static bool reset_requested;
static bool theme_changed;
static bool clear_requested;
static bool running;
static uint16_t particleColour(const kaleidoscope_particle_t *particle)
{
return theme_colours[theme_index][particle->colour];
}
static void fadeFrame(uint16_t *frame)
{
for (size_t index = 0U; index < (size_t)LCD_WIDTH * LCD_HEIGHT; ++index) {
const uint16_t pixel = frame[index];
const uint16_t red = (uint16_t)(((pixel >> 11U) & 0x1FU) * 15U / 16U);
const uint16_t green = (uint16_t)(((pixel >> 5U) & 0x3FU) * 15U / 16U);
const uint16_t blue = (uint16_t)((pixel & 0x1FU) * 15U / 16U);
frame[index] = (uint16_t)((red << 11U) | (green << 5U) | blue);
}
}
static void drawRibbon(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)
{
const float radius = particle->radius * pulse_scale;
const float ribbon_end = (particle->radius - particle->length) * pulse_scale;
const float side_angle = angle + KALEIDOSCOPE_PI / 2.0f;
const float side_x = cosf(side_angle) * particle->width * pulse_scale;
const float side_y = sinf(side_angle) * particle->width * pulse_scale;
const int x0 = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * radius);
const int y0 = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * radius);
const int x1 = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * ribbon_end);
const int y1 = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * ribbon_end);
for (int edge = -1; edge <= 1; ++edge) {
draw_line(frame, (int)lroundf((float)x0 + side_x * edge),
(int)lroundf((float)y0 + side_y * edge),
(int)lroundf((float)x1 + side_x * edge),
(int)lroundf((float)y1 + side_y * edge),
particleColour(particle));
}
}
static void drawStar(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)
{
const int centre_x = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * particle->radius * pulse_scale);
const int centre_y = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * particle->radius * pulse_scale);
const float arm = (5.0f + particle->width * 2.0f) * pulse_scale;
for (unsigned arm_index = 0U; arm_index < 4U; ++arm_index) {
const float arm_angle = angle + (float)arm_index * KALEIDOSCOPE_PI / 2.0f;
const int end_x = centre_x + (int)lroundf(cosf(arm_angle) * arm);
const int end_y = centre_y + (int)lroundf(sinf(arm_angle) * arm);
draw_line(frame, centre_x, centre_y, end_x, end_y, particleColour(particle));
}
fill_circle(frame, centre_x, centre_y, 2, particleColour(particle));
}
static void drawSquare(uint16_t *frame, const kaleidoscope_particle_t *particle, float angle,
float pulse_scale)
{
const int centre_x = KALEIDOSCOPE_CENTRE_X +
(int)lroundf(cosf(angle) * particle->radius * pulse_scale);
const int centre_y = KALEIDOSCOPE_CENTRE_Y +
(int)lroundf(sinf(angle) * particle->radius * pulse_scale);
const float half_side = (5.0f + particle->width * 2.0f) * pulse_scale;
int previous_x = 0;
int previous_y = 0;
for (unsigned corner = 0U; corner <= 4U; ++corner) {
const float corner_angle = angle + KALEIDOSCOPE_PI / 4.0f +
(float)(corner % 4U) * KALEIDOSCOPE_PI / 2.0f;
const int x = centre_x + (int)lroundf(cosf(corner_angle) * half_side);
const int y = centre_y + (int)lroundf(sinf(corner_angle) * half_side);
if (corner > 0U) draw_line(frame, previous_x, previous_y, x, y,
particleColour(particle));
previous_x = x;
previous_y = y;
}
}
static void resetParticle(kaleidoscope_particle_t *particle)
{
particle->angle = (float)(esp_random() % 1000U) * KALEIDOSCOPE_WEDGE_ANGLE / 1000.0f;
particle->radius = 6.0f + (float)(esp_random() % 6800U) / 100.0f;
particle->angle_speed = 0.10f + (float)(esp_random() % 120U) / 500.0f;
particle->radius_speed = 8.0f + (float)(esp_random() % 220U) / 10.0f;
particle->length = 18.0f + (float)(esp_random() % 4400U) / 100.0f;
particle->width = 2.0f + (float)(esp_random() % 3U);
particle->colour = (uint8_t)(esp_random() % KALEIDOSCOPE_THEME_COLOURS);
}
void startKaleidoscope(uint32_t now_ms)
{
for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
resetParticle(&particles[index]);
}
last_update_ms = now_ms;
controls_changed_at = now_ms;
speed_index = 1U;
pattern_index = 0U;
theme_index = 0U;
pulse_phase = 0.0f;
pattern_changed = false;
reset_requested = false;
theme_changed = false;
clear_requested = true;
running = true;
}
void stopKaleidoscope(void)
{
running = false;
}
void kaleidoscopeAction(bool new_pattern, bool reset, bool theme, bool speed, uint32_t now_ms)
{
if (!running) return;
if (new_pattern) {
pattern_index = (uint8_t)((pattern_index + 1U) % KALEIDOSCOPE_PATTERN_COUNT);
}
if (new_pattern || reset) {
for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
resetParticle(&particles[index]);
}
clear_requested = true;
}
if (theme) theme_index = (uint8_t)((theme_index + 1U) % KALEIDOSCOPE_THEME_COUNT);
if (speed) speed_index = (uint8_t)((speed_index + 1U) % KALEIDOSCOPE_SPEED_COUNT);
if (!new_pattern && !reset && !theme && !speed) return;
pattern_changed = new_pattern;
reset_requested = reset && !new_pattern;
theme_changed = theme && !new_pattern && !reset;
controls_changed_at = now_ms;
}
void updateKaleidoscope(uint32_t now_ms)
{
if (!running || now_ms - last_update_ms < KALEIDOSCOPE_FRAME_MS) return;
const uint32_t elapsed_ms = now_ms - last_update_ms;
last_update_ms = now_ms;
const float elapsed = (float)(elapsed_ms > 50U ? 50U : elapsed_ms) / 1000.0f *
speed_multipliers[speed_index];
pulse_phase += elapsed * KALEIDOSCOPE_PULSE_RATE;
if (pulse_phase >= 2.0f * KALEIDOSCOPE_PI) pulse_phase -= 2.0f * KALEIDOSCOPE_PI;
for (unsigned index = 0U; index < KALEIDOSCOPE_PARTICLES; ++index) {
kaleidoscope_particle_t *particle = &particles[index];
particle->angle += particle->angle_speed * elapsed;
if (particle->angle > KALEIDOSCOPE_WEDGE_ANGLE) {
particle->angle = KALEIDOSCOPE_WEDGE_ANGLE -
(particle->angle - KALEIDOSCOPE_WEDGE_ANGLE);
particle->angle_speed = -particle->angle_speed;
} else if (particle->angle < 0.0f) {
particle->angle = -particle->angle;
particle->angle_speed = -particle->angle_speed;
}
particle->radius += particle->radius_speed * elapsed;
if (particle->radius > KALEIDOSCOPE_RADIUS) resetParticle(particle);
}
}
void drawKaleidoscope(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (clear_requested) {
clear(frame, COLOR_BLACK);
clear_requested = false;
} else {
fadeFrame(frame);
}
const unsigned particle_count = pattern_index == 0U ? KALEIDOSCOPE_PARTICLES : 9U;
const float pulse_scale = 1.0f + sinf(pulse_phase) * KALEIDOSCOPE_PULSE_AMOUNT;
for (unsigned index = 0U; index < particle_count; ++index) {
const kaleidoscope_particle_t *particle = &particles[index];
for (unsigned sector = 0U; sector < KALEIDOSCOPE_SECTORS; ++sector) {
const float base_angle = (float)sector * 2.0f * KALEIDOSCOPE_WEDGE_ANGLE;
for (unsigned reflection = 0U; reflection < 2U; ++reflection) {
const float angle = base_angle + (reflection == 0U ? particle->angle :
-particle->angle);
for (unsigned layer = 0U; layer < KALEIDOSCOPE_TUNNEL_LAYERS; ++layer) {
const float layer_scale = pulse_scale * tunnel_scales[layer];
if (pattern_index == 0U) drawRibbon(frame, particle, angle, layer_scale);
else if (pattern_index == 1U) drawStar(frame, particle, angle, layer_scale);
else drawSquare(frame, particle, angle, layer_scale);
}
}
}
}
fill_circle(frame, KALEIDOSCOPE_CENTRE_X, KALEIDOSCOPE_CENTRE_Y,
(int)lroundf(4.0f * pulse_scale), COLOR_WHITE);
if (last_update_ms - controls_changed_at < KALEIDOSCOPE_STATUS_MS) {
char text[18];
if (pattern_changed) {
snprintf(text, sizeof(text), "A %s", pattern_names[pattern_index]);
} else if (reset_requested) {
snprintf(text, sizeof(text), "D RESET");
} else if (theme_changed) {
snprintf(text, sizeof(text), "C %s", theme_names[theme_index]);
} else {
snprintf(text, sizeof(text), "E SPEED %u", (unsigned)(speed_index + 1U));
}
drawPet2001TextCentredTransparent(frame, KALEIDOSCOPE_CENTRE_X, 155, text,
COLOR_LIGHTGREY, 1);
}
}