Demo
Source: src/demo.c · 4405 lines · 85 functions.
Screen captures
Views of this part running on the Mini Arcade.

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
bitmapPixelSet() · source line 2068
static bool bitmapPixelSet(const uint8_t *bitmap, unsigned x, unsigned y)
- Execute
const size_t byte_index = (size_t)y * DEMO_LOGO_ROW_BYTES + x / 8U; - Execute
const uint8_t bit = (uint8_t)(7U - x % 8U); - Exit this function
return (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
Read original function source
static bool bitmapPixelSet(const uint8_t *bitmap, unsigned x, unsigned y)
{
const size_t byte_index = (size_t)y * DEMO_LOGO_ROW_BYTES + x / 8U;
const uint8_t bit = (uint8_t)(7U - x % 8U);
return (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
}advanceIconSlot() · source line 2075
static void advanceIconSlot(void)
- Execute
active_icon_slot = (active_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT; - Execute
active_logo_bitmap = demo_icon_slots[active_icon_slot];
Read original function source
static void advanceIconSlot(void)
{
active_icon_slot = (active_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
active_logo_bitmap = demo_icon_slots[active_icon_slot];
}selectDemoIconColourScheme() · source line 2081
static void selectDemoIconColourScheme(void)
- Execute
demo_icon_colour_scheme_index = (size_t)esp_random() % DEMO_ICON_COLOUR_SCHEME_COUNT;
Read original function source
static void selectDemoIconColourScheme(void)
{
demo_icon_colour_scheme_index =
(size_t)esp_random() % DEMO_ICON_COLOUR_SCHEME_COUNT;
}demoStageInactivityTimeout() · source line 2087
static uint32_t demoStageInactivityTimeout(void)
Calls into this documentation:
- Exit this function
return demoSelectorStageInactivityTimeout(selected_stage);
Read original function source
static uint32_t demoStageInactivityTimeout(void)
{
return demoSelectorStageInactivityTimeout(selected_stage);
}buildStageThreeBallMap() · source line 2092
static void buildStageThreeBallMap(void)
- Decision
stage_three_ball_map_readyTrue- Exit this function
return;
FalseContinue after this decision.
- FOR loop
int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x; - Execute
const float normal_x = (float)(x - DEMO_STAGE_THREE_BALL_RADIUS) / (float)DEMO_STAGE_THREE_BALL_RADIUS; - Execute
const float normal_y = (float)(y - DEMO_STAGE_THREE_BALL_RADIUS) / (float)DEMO_STAGE_THREE_BALL_RADIUS; - Execute
const float radius_squared = normal_x * normal_x + normal_y * normal_y; - Decision
radius_squared > 1.0fTrue- Execute
stage_three_ball_shade_map[index] = 0U; - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const float depth = sqrtf(1.0f - radius_squared); - Execute
const float longitude = (atan2f(normal_x, depth) + DEMO_PI) / (2.0f * DEMO_PI); - Execute
const float latitude = (asinf(normal_y) + DEMO_PI * 0.5f) / DEMO_PI; - Execute
stage_three_ball_longitude_map[index] = (uint8_t)(longitude * 255.0f); - Execute
stage_three_ball_latitude_map[index] = (uint8_t)(latitude * 255.0f); - Execute
stage_three_ball_shade_map[index] = depth > 0.78f ? 3U : depth > 0.42f ? 2U : 1U;
- Execute
stage_three_ball_map_ready = true;
Read original function source
static void buildStageThreeBallMap(void)
{
if (stage_three_ball_map_ready) return;
for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
const float normal_x = (float)(x - DEMO_STAGE_THREE_BALL_RADIUS) /
(float)DEMO_STAGE_THREE_BALL_RADIUS;
const float normal_y = (float)(y - DEMO_STAGE_THREE_BALL_RADIUS) /
(float)DEMO_STAGE_THREE_BALL_RADIUS;
const float radius_squared = normal_x * normal_x + normal_y * normal_y;
if (radius_squared > 1.0f) {
stage_three_ball_shade_map[index] = 0U;
continue;
}
const float depth = sqrtf(1.0f - radius_squared);
const float longitude = (atan2f(normal_x, depth) + DEMO_PI) / (2.0f * DEMO_PI);
const float latitude = (asinf(normal_y) + DEMO_PI * 0.5f) / DEMO_PI;
stage_three_ball_longitude_map[index] = (uint8_t)(longitude * 255.0f);
stage_three_ball_latitude_map[index] = (uint8_t)(latitude * 255.0f);
stage_three_ball_shade_map[index] = depth > 0.78f ? 3U : depth > 0.42f ? 2U : 1U;
}
}
stage_three_ball_map_ready = true;
}startStageThreeBall() · source line 2119
static void startStageThreeBall(uint32_t now_ms)
Calls into this documentation:
- Execute
buildStageThreeBallMap(); - Execute
stage_three_next_icon_change_at = now_ms + DEMO_STAGE_THREE_ICON_CHANGE_MS; - Execute
stage_three_icon_slot = 0U; - Execute
stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot]; - Execute
stage_three_ball_x = 82.0f; - Execute
stage_three_ball_y = 52.0f; - Execute
stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY; - Execute
stage_three_ball_velocity_y = 0.0f; - Execute
stage_three_ball_rotation_phase = 0.0f;
Read original function source
static void startStageThreeBall(uint32_t now_ms)
{
buildStageThreeBallMap();
stage_three_next_icon_change_at = now_ms + DEMO_STAGE_THREE_ICON_CHANGE_MS;
stage_three_icon_slot = 0U;
stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
stage_three_ball_x = 82.0f;
stage_three_ball_y = 52.0f;
stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
stage_three_ball_velocity_y = 0.0f;
stage_three_ball_rotation_phase = 0.0f;
}selectStageSixMesh() · source line 2132
static void selectStageSixMesh(void)
- Execute
const demo_mesh_t *next_mesh; - DO / WHILE loop
next_mesh == stage_six_current_meshRun body, then test condition; repeat while true.
- Execute
next_mesh = demo_stage_six_mesh_catalog[esp_random() % DEMO_STAGE_SIX_MESH_CATALOG_COUNT];
- Execute
stage_six_current_mesh = next_mesh; - Execute
size_t next_colour_scheme = (size_t)esp_random() % DEMO_SOLID_COLOUR_SCHEME_COUNT; - Decision
stage_six_colour_scheme_index < DEMO_SOLID_COLOUR_SCHEME_COUNT && DEMO_SOLID_COLOUR_SCHEME_COUNT > 1UTrue- Execute
next_colour_scheme = (size_t)esp_random() % (DEMO_SOLID_COLOUR_SCHEME_COUNT - 1U); - Decision
next_colour_scheme >= stage_six_colour_scheme_indexTrue- Execute
++next_colour_scheme;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
stage_six_colour_scheme_index = next_colour_scheme;
Read original function source
static void selectStageSixMesh(void)
{
const demo_mesh_t *next_mesh;
do {
next_mesh = demo_stage_six_mesh_catalog[esp_random() % DEMO_STAGE_SIX_MESH_CATALOG_COUNT];
} while (next_mesh == stage_six_current_mesh);
stage_six_current_mesh = next_mesh;
size_t next_colour_scheme =
(size_t)esp_random() % DEMO_SOLID_COLOUR_SCHEME_COUNT;
if (stage_six_colour_scheme_index < DEMO_SOLID_COLOUR_SCHEME_COUNT &&
DEMO_SOLID_COLOUR_SCHEME_COUNT > 1U) {
next_colour_scheme = (size_t)esp_random() % (DEMO_SOLID_COLOUR_SCHEME_COUNT - 1U);
if (next_colour_scheme >= stage_six_colour_scheme_index) ++next_colour_scheme;
}
stage_six_colour_scheme_index = next_colour_scheme;
}startStageSixSolid() · source line 2151
static void startStageSixSolid(uint32_t now_ms)
Calls into this documentation:
- Execute
stage_six_next_change_at = now_ms + DEMO_STAGE_SIX_CHANGE_MS; - Execute
stage_six_current_mesh = NULL; - Execute
selectStageSixMesh();
Read original function source
static void startStageSixSolid(uint32_t now_ms)
{
stage_six_next_change_at = now_ms + DEMO_STAGE_SIX_CHANGE_MS;
stage_six_current_mesh = NULL;
selectStageSixMesh();
}randomTextCornerIconTravelDuration() · source line 2158
static uint32_t randomTextCornerIconTravelDuration(void)
- Execute
const uint32_t range = DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS - DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + 1U; - Exit this function
return DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + esp_random() % range;
Read original function source
static uint32_t randomTextCornerIconTravelDuration(void)
{
// Keep the range inclusive so setting MIN and MAX equal later creates a
// fixed, repeatable period without changing the animation code.
const uint32_t range = DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS -
DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + 1U;
return DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + esp_random() % range;
}randomTextCornerIconRotationSpeed() · source line 2167
static int16_t randomTextCornerIconRotationSpeed(void)
- Execute
const uint32_t range = (uint32_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND - DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + 1); - Execute
const int16_t speed = (int16_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + (int)(esp_random() % range)); - Exit this function
return (esp_random() & 1U) != 0U ? speed : (int16_t)-speed;
Read original function source
static int16_t randomTextCornerIconRotationSpeed(void)
{
const uint32_t range = (uint32_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND -
DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + 1);
const int16_t speed = (int16_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND +
(int)(esp_random() % range));
// Give neighbouring icons visibly different movement by independently
// choosing their clockwise or anti-clockwise direction as well as speed.
return (esp_random() & 1U) != 0U ? speed : (int16_t)-speed;
}startTextCornerIconCycles() · source line 2178
static void startTextCornerIconCycles(uint32_t now_ms)
Calls into this documentation:
- FOR loop
size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++cornerRepeat body while the loop condition permits; then continue below.
- Execute
demo_text_corner_icon_cycles[corner].cycle_started_at = now_ms; - Execute
demo_text_corner_icon_cycles[corner].cycle_duration_ms = randomTextCornerIconTravelDuration(); - Execute
demo_text_corner_icon_cycles[corner].rotation_started_at = now_ms; - Execute
demo_text_corner_icon_cycles[corner].rotation_start_degrees = (float)(esp_random() % 360U); - Execute
demo_text_corner_icon_cycles[corner].rotation_degrees_per_second = randomTextCornerIconRotationSpeed();
Read original function source
static void startTextCornerIconCycles(uint32_t now_ms)
{
// A text segment always starts with every icon at its own outer corner.
// The first random duration then separates the icons naturally as they
// travel toward the centre X line at different rates.
for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
demo_text_corner_icon_cycles[corner].cycle_started_at = now_ms;
demo_text_corner_icon_cycles[corner].cycle_duration_ms =
randomTextCornerIconTravelDuration();
// Rotation does not restart with every travel cycle: it stays smooth
// while movement chooses a new random duration at each outer edge.
demo_text_corner_icon_cycles[corner].rotation_started_at = now_ms;
demo_text_corner_icon_cycles[corner].rotation_start_degrees =
(float)(esp_random() % 360U);
demo_text_corner_icon_cycles[corner].rotation_degrees_per_second =
randomTextCornerIconRotationSpeed();
}
}updateTextCornerIconCycles() · source line 2197
static void updateTextCornerIconCycles(uint32_t now_ms)
Calls into this documentation:
- FOR loop
size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++cornerRepeat body while the loop condition permits; then continue below.
- Execute
demo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner]; - WHILE loop
(uint32_t)(now_ms - cycle->cycle_started_at) >= cycle->cycle_duration_msRepeat body while the loop condition permits; then continue below.
- Execute
cycle->cycle_started_at += cycle->cycle_duration_ms; - Execute
cycle->cycle_duration_ms = randomTextCornerIconTravelDuration();
Read original function source
static void updateTextCornerIconCycles(uint32_t now_ms)
{
for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
demo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner];
// A late frame can cross more than one complete cycle. Preserve the
// remaining time by advancing from the old start, rather than snapping
// the icon back to its corner at the current frame time.
while ((uint32_t)(now_ms - cycle->cycle_started_at) >= cycle->cycle_duration_ms) {
cycle->cycle_started_at += cycle->cycle_duration_ms;
cycle->cycle_duration_ms = randomTextCornerIconTravelDuration();
}
}
}startStageSevenWalloons() · source line 2211
static void startStageSevenWalloons(uint32_t now_ms)
- Execute
stage_seven_phase = WALLOONS_TITLE_CARD; - Execute
stage_seven_phase_started_at = now_ms; - Execute
stage_seven_completed_bounces = 0U;
Read original function source
static void startStageSevenWalloons(uint32_t now_ms)
{
// Begin with the boxed PET title. After the short title interval, the box
// disappears and the original action begins with the left Walloon walking
// from PET cell (2, 15) to (14, 15).
stage_seven_phase = WALLOONS_TITLE_CARD;
stage_seven_phase_started_at = now_ms;
stage_seven_completed_bounces = 0U;
}releaseStageTwelveImage() · source line 2222
static void releaseStageTwelveImage(void)
- Execute
heap_caps_free(stage_twelve_image); - Execute
stage_twelve_image = NULL; - Execute
stage_twelve_image_ready = false; - Execute
stage_twelve_image_path[0] = '\0'; - Execute
stage_twelve_next_image_change_at = 0U;
Read original function source
static void releaseStageTwelveImage(void)
{
heap_caps_free(stage_twelve_image);
stage_twelve_image = NULL;
stage_twelve_image_ready = false;
stage_twelve_image_path[0] = '\0';
stage_twelve_next_image_change_at = 0U;
}releaseStageFifteenGroundSource() · source line 2232
static void releaseStageFifteenGroundSource(void)
- Execute
heap_caps_free(stage_fifteen_ground_source); - Execute
stage_fifteen_ground_source = NULL;
Read original function source
static void releaseStageFifteenGroundSource(void)
{
heap_caps_free(stage_fifteen_ground_source);
stage_fifteen_ground_source = NULL;
}startStageFifteenBattleZoneRaster() · source line 2238
static void startStageFifteenBattleZoneRaster(void)
Calls into this documentation:
- Execute
releaseStageFifteenGroundSource(); - Execute
const size_t ground_pixels = (size_t)LCD_WIDTH * (LCD_HEIGHT - BATTLE_ZONE_HORIZON_Y); - Execute
stage_fifteen_ground_source = heap_caps_malloc(ground_pixels * sizeof(stage_fifteen_ground_source[0]), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
Read original function source
static void startStageFifteenBattleZoneRaster(void)
{
releaseStageFifteenGroundSource();
const size_t ground_pixels = (size_t)LCD_WIDTH *
(LCD_HEIGHT - BATTLE_ZONE_HORIZON_Y);
stage_fifteen_ground_source = heap_caps_malloc(ground_pixels *
sizeof(stage_fifteen_ground_source[0]),
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
}chooseStageTwelveLensEdgePoint() · source line 2249
static void chooseStageTwelveLensEdgePoint(stage_twelve_lens_edge_t edge,
float *x, float *y)- Select a case
edgeEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case STAGE_TWELVE_LENS_EDGE_TOP: - Execute
*x = (float)(esp_random() % LCD_WIDTH); - Execute
*y = 0.0f; - Exit enclosing loop or switch
break; - Case / label
case STAGE_TWELVE_LENS_EDGE_RIGHT: - Execute
*x = (float)(LCD_WIDTH - 1); - Execute
*y = (float)(esp_random() % LCD_HEIGHT); - Exit enclosing loop or switch
break; - Case / label
case STAGE_TWELVE_LENS_EDGE_BOTTOM: - Execute
*x = (float)(esp_random() % LCD_WIDTH); - Execute
*y = (float)(LCD_HEIGHT - 1); - Exit enclosing loop or switch
break; - Case / label
case STAGE_TWELVE_LENS_EDGE_LEFT: - Case / label
default: - Execute
*x = 0.0f; - Execute
*y = (float)(esp_random() % LCD_HEIGHT); - Exit enclosing loop or switch
break;
Read original function source
static void chooseStageTwelveLensEdgePoint(stage_twelve_lens_edge_t edge,
float *x, float *y)
{
// The centre is always on one screen edge. The lens radius can therefore
// extend outside the display, while every sampled source pixel stays valid.
switch (edge) {
case STAGE_TWELVE_LENS_EDGE_TOP:
*x = (float)(esp_random() % LCD_WIDTH);
*y = 0.0f;
break;
case STAGE_TWELVE_LENS_EDGE_RIGHT:
*x = (float)(LCD_WIDTH - 1);
*y = (float)(esp_random() % LCD_HEIGHT);
break;
case STAGE_TWELVE_LENS_EDGE_BOTTOM:
*x = (float)(esp_random() % LCD_WIDTH);
*y = (float)(LCD_HEIGHT - 1);
break;
case STAGE_TWELVE_LENS_EDGE_LEFT:
default:
*x = 0.0f;
*y = (float)(esp_random() % LCD_HEIGHT);
break;
}
}chooseStageTwelveLensTarget() · source line 2275
static void chooseStageTwelveLensTarget(void)
Calls into this documentation:
- FOR loop
unsigned attempt = 0U; attempt < DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS; ++attemptRepeat body while the loop condition permits; then continue below.
- DO / WHILE loop
stage_twelve_lens_target_edge == stage_twelve_lens_current_edgeRun body, then test condition; repeat while true.
- Execute
stage_twelve_lens_target_edge = (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
- Execute
chooseStageTwelveLensEdgePoint(stage_twelve_lens_target_edge, &stage_twelve_lens_target_x, &stage_twelve_lens_target_y); - Execute
const float dx = stage_twelve_lens_target_x - stage_twelve_lens_x; - Execute
const float dy = stage_twelve_lens_target_y - stage_twelve_lens_y; - Execute
const float distance_squared = dx * dx + dy * dy; - Execute
const float minimum_squared = DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE * DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE; - Decision
distance_squared >= minimum_squaredTrue- Exit this function
return;
FalseContinue after this decision.
Read original function source
static void chooseStageTwelveLensTarget(void)
{
// Pick a different edge and a visibly long route. The bounded retry count
// guarantees progress even if future tuning makes the distance too strict.
for (unsigned attempt = 0U; attempt < DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS; ++attempt) {
do {
stage_twelve_lens_target_edge =
(stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
} while (stage_twelve_lens_target_edge == stage_twelve_lens_current_edge);
chooseStageTwelveLensEdgePoint(stage_twelve_lens_target_edge,
&stage_twelve_lens_target_x,
&stage_twelve_lens_target_y);
const float dx = stage_twelve_lens_target_x - stage_twelve_lens_x;
const float dy = stage_twelve_lens_target_y - stage_twelve_lens_y;
const float distance_squared = dx * dx + dy * dy;
const float minimum_squared = DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE *
DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE;
if (distance_squared >= minimum_squared) return;
}
}selectStageTwelveSlideshowImage() · source line 2296
static bool selectStageTwelveSlideshowImage(char *path, size_t path_size,
const char *excluded_path)Calls into this documentation:
- Execute
DIR *const directory = opendir(DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY); - Decision
directory == NULLTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
char selected_name[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX] = {0}; - Execute
size_t supported_count = 0U; - Execute
struct dirent *entry; - WHILE loop
(entry = readdir(directory)) != NULLRepeat body while the loop condition permits; then continue below.
- Decision
!imageIsSupportedName(entry->d_name)True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
char candidate_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX]; - Execute
const int candidate_written = snprintf(candidate_path, sizeof(candidate_path), "%s/%s", DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, entry->d_name); - Decision
candidate_written < 0 || (size_t)candidate_written >= sizeof(candidate_path)True- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
excluded_path != NULL && strcmp(candidate_path, excluded_path) == 0True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
++supported_count; - Decision
esp_random() % supported_count != 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int written = snprintf(selected_name, sizeof(selected_name), "%s", entry->d_name); - Decision
written < 0 || (size_t)written >= sizeof(selected_name)True- Execute
closedir(directory); - Exit this function
return false;
FalseContinue after this decision.
- Execute
closedir(directory); - Decision
supported_count == 0UTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
const int written = snprintf(path, path_size, "%s/%s", DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, selected_name); - Exit this function
return written >= 0 && (size_t)written < path_size;
Read original function source
static bool selectStageTwelveSlideshowImage(char *path, size_t path_size,
const char *excluded_path)
{
DIR *const directory = opendir(DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY);
if (directory == NULL) return false;
// Reservoir sampling gives every supported file an equal chance without
// storing the slideshow directory's full filename list in RAM.
char selected_name[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX] = {0};
size_t supported_count = 0U;
struct dirent *entry;
while ((entry = readdir(directory)) != NULL) {
if (!imageIsSupportedName(entry->d_name)) continue;
char candidate_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
const int candidate_written = snprintf(candidate_path, sizeof(candidate_path), "%s/%s",
DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, entry->d_name);
if (candidate_written < 0 || (size_t)candidate_written >= sizeof(candidate_path)) continue;
// Do not immediately repeat the current picture when the directory
// contains another supported file to choose from.
if (excluded_path != NULL && strcmp(candidate_path, excluded_path) == 0) continue;
++supported_count;
if (esp_random() % supported_count != 0U) continue;
const int written = snprintf(selected_name, sizeof(selected_name), "%s", entry->d_name);
if (written < 0 || (size_t)written >= sizeof(selected_name)) {
closedir(directory);
return false;
}
}
closedir(directory);
if (supported_count == 0U) return false;
const int written = snprintf(path, path_size, "%s/%s",
DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, selected_name);
return written >= 0 && (size_t)written < path_size;
}beginStageTwelveLensGlide() · source line 2332
static void beginStageTwelveLensGlide(uint32_t now_ms)
Calls into this documentation:
- Execute
stage_twelve_lens_start_x = stage_twelve_lens_x; - Execute
stage_twelve_lens_start_y = stage_twelve_lens_y; - Execute
chooseStageTwelveLensTarget(); - Execute
stage_twelve_lens_glide_started_at = now_ms; - Execute
stage_twelve_lens_glide_duration_ms = DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS + (esp_random() % DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS);
Read original function source
static void beginStageTwelveLensGlide(uint32_t now_ms)
{
stage_twelve_lens_start_x = stage_twelve_lens_x;
stage_twelve_lens_start_y = stage_twelve_lens_y;
chooseStageTwelveLensTarget();
stage_twelve_lens_glide_started_at = now_ms;
stage_twelve_lens_glide_duration_ms = DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS +
(esp_random() % DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS);
}startStageTwelveSlideshowLens() · source line 2342
static void startStageTwelveSlideshowLens(uint32_t now_ms)
Calls into this documentation:
- Execute
releaseStageTwelveImage(); - Execute
stage_twelve_image = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(stage_twelve_image[0]), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Decision
stage_twelve_image == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX]; - Decision
!selectStageTwelveSlideshowImage(image_path, sizeof(image_path), NULL) || !imageDecodeFile(image_path, stage_twelve_image)True- Execute
releaseStageTwelveImage(); - Exit this function
return;
FalseContinue after this decision.
- Execute
stage_twelve_image_ready = true; - Execute
snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path); - Execute
stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS; - Execute
stage_twelve_lens_current_edge = (stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT); - Execute
chooseStageTwelveLensEdgePoint(stage_twelve_lens_current_edge, &stage_twelve_lens_x, &stage_twelve_lens_y); - Execute
beginStageTwelveLensGlide(now_ms);
Read original function source
static void startStageTwelveSlideshowLens(uint32_t now_ms)
{
releaseStageTwelveImage();
stage_twelve_image = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT *
sizeof(stage_twelve_image[0]),
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (stage_twelve_image == NULL) return;
char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
if (!selectStageTwelveSlideshowImage(image_path, sizeof(image_path), NULL) ||
!imageDecodeFile(image_path, stage_twelve_image)) {
releaseStageTwelveImage();
return;
}
stage_twelve_image_ready = true;
snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
stage_twelve_lens_current_edge =
(stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
chooseStageTwelveLensEdgePoint(stage_twelve_lens_current_edge,
&stage_twelve_lens_x, &stage_twelve_lens_y);
beginStageTwelveLensGlide(now_ms);
}updateStageTwelveSlideshowLens() · source line 2367
static void updateStageTwelveSlideshowLens(uint32_t now_ms)
Calls into this documentation:
- Decision
!stage_twelve_image_readyTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
(int32_t)(now_ms - stage_twelve_next_image_change_at) >= 0True- Execute
char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX]; - Decision
selectStageTwelveSlideshowImage(image_path, sizeof(image_path), stage_twelve_image_path) && imageDecodeFile(image_path, stage_twelve_image)True- Execute
snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
FalseContinue after this decision.
- Execute
stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
FalseContinue after this decision.
- Execute
const uint32_t elapsed_ms = now_ms - stage_twelve_lens_glide_started_at; - Decision
elapsed_ms >= stage_twelve_lens_glide_duration_msTrue- Execute
stage_twelve_lens_x = stage_twelve_lens_target_x; - Execute
stage_twelve_lens_y = stage_twelve_lens_target_y; - Execute
stage_twelve_lens_current_edge = stage_twelve_lens_target_edge; - Execute
beginStageTwelveLensGlide(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Execute
const float progress = (float)elapsed_ms / (float)stage_twelve_lens_glide_duration_ms; - Execute
stage_twelve_lens_x = stage_twelve_lens_start_x + (stage_twelve_lens_target_x - stage_twelve_lens_start_x) * progress; - Execute
stage_twelve_lens_y = stage_twelve_lens_start_y + (stage_twelve_lens_target_y - stage_twelve_lens_start_y) * progress;
Read original function source
static void updateStageTwelveSlideshowLens(uint32_t now_ms)
{
if (!stage_twelve_image_ready) return;
if ((int32_t)(now_ms - stage_twelve_next_image_change_at) >= 0) {
char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
// Reuse the existing PSRAM image buffer. If a new file cannot be
// selected or decoded, leave the already visible picture in place.
if (selectStageTwelveSlideshowImage(image_path, sizeof(image_path),
stage_twelve_image_path) &&
imageDecodeFile(image_path, stage_twelve_image)) {
snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
}
stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
}
const uint32_t elapsed_ms = now_ms - stage_twelve_lens_glide_started_at;
if (elapsed_ms >= stage_twelve_lens_glide_duration_ms) {
stage_twelve_lens_x = stage_twelve_lens_target_x;
stage_twelve_lens_y = stage_twelve_lens_target_y;
stage_twelve_lens_current_edge = stage_twelve_lens_target_edge;
beginStageTwelveLensGlide(now_ms);
return;
}
const float progress = (float)elapsed_ms / (float)stage_twelve_lens_glide_duration_ms;
// Use constant-speed interpolation rather than smoothstep. Smoothstep
// reaches zero speed at every target, which made the lens visibly pause.
stage_twelve_lens_x = stage_twelve_lens_start_x +
(stage_twelve_lens_target_x - stage_twelve_lens_start_x) * progress;
stage_twelve_lens_y = stage_twelve_lens_start_y +
(stage_twelve_lens_target_y - stage_twelve_lens_start_y) * progress;
}walloonsPhaseSteps() · source line 2400
static unsigned walloonsPhaseSteps(uint32_t now_ms, unsigned maximum_steps)
- Execute
const unsigned elapsed_steps = (now_ms - stage_seven_phase_started_at) / DEMO_STAGE_SEVEN_STEP_MS; - Exit this function
return elapsed_steps < maximum_steps ? elapsed_steps : maximum_steps;
Read original function source
static unsigned walloonsPhaseSteps(uint32_t now_ms, unsigned maximum_steps)
{
const unsigned elapsed_steps = (now_ms - stage_seven_phase_started_at) /
DEMO_STAGE_SEVEN_STEP_MS;
return elapsed_steps < maximum_steps ? elapsed_steps : maximum_steps;
}setWalloonsPhase() · source line 2407
static void setWalloonsPhase(walloons_phase_t phase, uint32_t now_ms)
- Execute
stage_seven_phase = phase; - Execute
stage_seven_phase_started_at = now_ms;
Read original function source
static void setWalloonsPhase(walloons_phase_t phase, uint32_t now_ms)
{
stage_seven_phase = phase;
stage_seven_phase_started_at = now_ms;
}updateStageSevenWalloons() · source line 2413
static void updateStageSevenWalloons(uint32_t now_ms)
Calls into this documentation:
- Execute
const uint32_t elapsed = now_ms - stage_seven_phase_started_at; - Execute
const uint32_t walk_duration = 12U * DEMO_STAGE_SEVEN_STEP_MS; - Execute
const uint32_t platform_duration = 14U * DEMO_STAGE_SEVEN_STEP_MS; - Execute
const uint32_t vertical_duration = 13U * DEMO_STAGE_SEVEN_STEP_MS; - Select a case
stage_seven_phaseEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case WALLOONS_TITLE_CARD: - Decision
elapsed >= DEMO_STAGE_SEVEN_TITLE_MSTrue- Execute
setWalloonsPhase(WALLOONS_LEFT_WALK_IN, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_LEFT_WALK_IN: - Decision
elapsed >= walk_durationTrue- Execute
setWalloonsPhase(WALLOONS_PAUSE_ON_SEESAW, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_PAUSE_ON_SEESAW: - Decision
elapsed >= DEMO_STAGE_SEVEN_PAUSE_MSTrue- Execute
setWalloonsPhase(WALLOONS_RIGHT_WALK_ON_PLATFORM, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_RIGHT_WALK_ON_PLATFORM: - Decision
elapsed >= platform_durationTrue- Execute
setWalloonsPhase(WALLOONS_RIGHT_DROP, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_RIGHT_DROP: - Decision
elapsed >= vertical_durationTrue- Execute
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_BOUNCE_RIGHT_UP: - Decision
elapsed >= vertical_durationTrue- Execute
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_DOWN, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_BOUNCE_RIGHT_DOWN: - Decision
elapsed >= vertical_durationTrue- Execute
setWalloonsPhase(WALLOONS_BOUNCE_LEFT_UP, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_BOUNCE_LEFT_UP: - Decision
elapsed >= vertical_durationTrue- Execute
setWalloonsPhase(WALLOONS_BOUNCE_LEFT_DOWN, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_BOUNCE_LEFT_DOWN: - Decision
elapsed >= vertical_durationTrue- Execute
setWalloonsPhase(WALLOONS_LEFT_LANDED, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_LEFT_LANDED: - Decision
elapsed >= DEMO_STAGE_SEVEN_LANDING_MSTrue- Execute
++stage_seven_completed_bounces; - Decision
stage_seven_completed_bounces >= DEMO_STAGE_SEVEN_BOUNCE_COUNTTrue- Execute
setWalloonsPhase(WALLOONS_FINISHED, now_ms);
False- Execute
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case WALLOONS_FINISHED: - Exit enclosing loop or switch
break;
Read original function source
static void updateStageSevenWalloons(uint32_t now_ms)
{
const uint32_t elapsed = now_ms - stage_seven_phase_started_at;
const uint32_t walk_duration = 12U * DEMO_STAGE_SEVEN_STEP_MS;
const uint32_t platform_duration = 14U * DEMO_STAGE_SEVEN_STEP_MS;
// There are twelve row moves. Hold the twelfth (landing) cell for one full
// step interval instead of changing phase before drawDemo can show it.
const uint32_t vertical_duration = 13U * DEMO_STAGE_SEVEN_STEP_MS;
switch (stage_seven_phase) {
case WALLOONS_TITLE_CARD:
if (elapsed >= DEMO_STAGE_SEVEN_TITLE_MS) {
setWalloonsPhase(WALLOONS_LEFT_WALK_IN, now_ms);
}
break;
case WALLOONS_LEFT_WALK_IN:
if (elapsed >= walk_duration) {
setWalloonsPhase(WALLOONS_PAUSE_ON_SEESAW, now_ms);
}
break;
case WALLOONS_PAUSE_ON_SEESAW:
if (elapsed >= DEMO_STAGE_SEVEN_PAUSE_MS) {
setWalloonsPhase(WALLOONS_RIGHT_WALK_ON_PLATFORM, now_ms);
}
break;
case WALLOONS_RIGHT_WALK_ON_PLATFORM:
if (elapsed >= platform_duration) {
setWalloonsPhase(WALLOONS_RIGHT_DROP, now_ms);
}
break;
case WALLOONS_RIGHT_DROP:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
}
break;
case WALLOONS_BOUNCE_RIGHT_UP:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_DOWN, now_ms);
}
break;
case WALLOONS_BOUNCE_RIGHT_DOWN:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_LEFT_UP, now_ms);
}
break;
case WALLOONS_BOUNCE_LEFT_UP:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_LEFT_DOWN, now_ms);
}
break;
case WALLOONS_BOUNCE_LEFT_DOWN:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_LEFT_LANDED, now_ms);
}
break;
case WALLOONS_LEFT_LANDED:
// Keep the left Walloon visibly on the new low end before launching
// the right Walloon. This is the original stunt's missing landing beat.
if (elapsed >= DEMO_STAGE_SEVEN_LANDING_MS) {
++stage_seven_completed_bounces;
if (stage_seven_completed_bounces >= DEMO_STAGE_SEVEN_BOUNCE_COUNT) {
setWalloonsPhase(WALLOONS_FINISHED, now_ms);
} else {
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
}
}
break;
case WALLOONS_FINISHED:
// Hold the completed stunt until C or the shared inactivity timer advances Demo.
break;
}
}demoTimeNow() · source line 2486
static uint32_t demoTimeNow(uint32_t now_ms)
- Exit this function
return now_ms - demo_paused_total_ms;
Read original function source
static uint32_t demoTimeNow(uint32_t now_ms)
{
return now_ms - demo_paused_total_ms;
}enterDemoStage() · source line 2491
static void enterDemoStage(demo_stage_t stage, uint32_t now_ms)
Calls into this documentation:
- Decision
selected_stage == DEMO_STAGE_SLIDESHOW_LENSTrue- Execute
demoStageSlideshowLensStop();
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_GAME_OF_LIFETrue- Execute
demoStageGameOfLifeStop();
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTERTrue- Execute
releaseStageFifteenGroundSource();
FalseContinue after this decision.
- Execute
selected_stage = stage; - Execute
demo_last_interaction_at = now_ms; - Decision
selected_stage == DEMO_STAGE_ICONTrue- Execute
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_AMIGA_BALLTrue- Execute
startStageThreeBall(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_GEOMETRIC_SOLIDTrue- Execute
startStageSixSolid(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLERTrue- Execute
startTextCornerIconCycles(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_WALLOONSTrue- Execute
demoStageWalloonsStart(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_KALEIDOTrue- Execute
demoStageKaleidoStart(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SPARKLETrue- Execute
demoStageSparkleStart(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SQUARESTrue- Execute
demoStageSquaresStart(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTERTrue- Execute
startStageFifteenBattleZoneRaster();
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_BEVEL_GEARSTrue- Execute
stage_seventeen_rig_pitch_degrees = -12.0f; - Execute
stage_seventeen_rig_yaw_degrees = 18.0f; - Execute
stage_seventeen_rig_roll_degrees = 0.0f; - Execute
stage_seventeen_gear_degrees[0] = 0.0f; - Execute
stage_seventeen_gear_degrees[1] = 286.0f; - Execute
stage_seventeen_gear_degrees[2] = 226.0f; - Execute
stage_seventeen_style = STAGE_SEVENTEEN_STYLE_FLAT;
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_MOUSE_MAZETrue- Execute
demoStageMouseMazeStart(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTERTrue- Execute
raster_band_effect_elapsed_ms = 0U; - Decision
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLERTrue- Execute
raster_band_palette_index = DEMO_RASTER_FIRE_PALETTE_INDEX;
False- Decision
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTERTrue- Execute
raster_band_palette_index = DEMO_RASTER_GREEN_PALETTE_INDEX;
False- Execute
raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER && scroll_text != NULLTrue- Execute
scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE; - Execute
scroll_text_x = scroll_speed_pixels_per_second >= 0.0f ? (float)LCD_WIDTH : -(float)scroll_text_width;
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SLIDESHOW_LENSTrue- Execute
demoStageSlideshowLensStart(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_PLASMATrue- Execute
demoStagePlasmaStart();
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_GAME_OF_LIFETrue- Execute
demoStageGameOfLifeStart(now_ms);
FalseContinue after this decision.
Read original function source
static void enterDemoStage(demo_stage_t stage, uint32_t now_ms)
{
// Stage 12 owns a full-screen PSRAM source only while it is visible.
if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStop();
if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStop();
if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) releaseStageFifteenGroundSource();
selected_stage = stage;
// Entering any segment begins a fresh shared inactivity interval.
demo_last_interaction_at = now_ms;
if (selected_stage == DEMO_STAGE_ICON) {
// A full Demo loop begins a fresh automatic Stage-1 effect interval.
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
}
if (selected_stage == DEMO_STAGE_AMIGA_BALL) startStageThreeBall(now_ms);
if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) startStageSixSolid(now_ms);
if (selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER) startTextCornerIconCycles(now_ms);
if (selected_stage == DEMO_STAGE_WALLOONS) demoStageWalloonsStart(now_ms);
if (selected_stage == DEMO_STAGE_KALEIDO) demoStageKaleidoStart(now_ms);
if (selected_stage == DEMO_STAGE_SPARKLE) demoStageSparkleStart(now_ms);
if (selected_stage == DEMO_STAGE_SQUARES) demoStageSquaresStart(now_ms);
if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) startStageFifteenBattleZoneRaster();
if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
stage_seventeen_rig_pitch_degrees = -12.0f;
stage_seventeen_rig_yaw_degrees = 18.0f;
stage_seventeen_rig_roll_degrees = 0.0f;
stage_seventeen_gear_degrees[0] = 0.0f;
// Each driven gear begins half a tooth out of phase with the lead gear
// at its fixed mesh contact. Their later angles are derived by motion.
stage_seventeen_gear_degrees[1] = 286.0f;
stage_seventeen_gear_degrees[2] = 226.0f;
// Start with the clearest filled treatment; B cycles outline, flat-retro,
// and shaded-metal versions without restarting the rig.
stage_seventeen_style = STAGE_SEVENTEEN_STYLE_FLAT;
}
if (selected_stage == DEMO_STAGE_MOUSE_MAZE) demoStageMouseMazeStart(now_ms);
if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
raster_band_effect_elapsed_ms = 0U;
// Each raster segment starts on its own fitting palette. B can then
// cycle every palette from the shared list without interrupting motion.
if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
raster_band_palette_index = DEMO_RASTER_FIRE_PALETTE_INDEX;
} else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
raster_band_palette_index = DEMO_RASTER_GREEN_PALETTE_INDEX;
} else {
raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
}
}
if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER && scroll_text != NULL) {
// This segment is wider than the shared scale-2 ticker. Restart the
// current message just outside its travel edge using its scale-3 width.
scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH *
(size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE;
scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
? (float)LCD_WIDTH : -(float)scroll_text_width;
}
if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStart(now_ms);
if (selected_stage == DEMO_STAGE_PLASMA) demoStagePlasmaStart();
if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStart(now_ms);
}advanceDemoStage() · source line 2554
static void advanceDemoStage(uint32_t now_ms)
Calls into this documentation:
- Execute
enterDemoStage(demoSelectorNextAutomaticStage(selected_stage), now_ms);
Read original function source
static void advanceDemoStage(uint32_t now_ms)
{
enterDemoStage(demoSelectorNextAutomaticStage(selected_stage), now_ms);
}updateStageThreeBall() · source line 2559
static void updateStageThreeBall(float elapsed_seconds)
- Execute
stage_three_ball_x += stage_three_ball_velocity_x * elapsed_seconds; - Execute
stage_three_ball_y += stage_three_ball_velocity_y * elapsed_seconds; - Execute
stage_three_ball_velocity_y += DEMO_STAGE_THREE_BALL_GRAVITY * elapsed_seconds; - Execute
stage_three_ball_rotation_phase += DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND * elapsed_seconds; - Decision
stage_three_ball_rotation_phase >= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDSTrue- Execute
stage_three_ball_rotation_phase -= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS;
FalseContinue after this decision.
- Decision
stage_three_ball_x < (float)DEMO_STAGE_THREE_BALL_RADIUSTrue- Execute
stage_three_ball_x = (float)DEMO_STAGE_THREE_BALL_RADIUS; - Execute
stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
False- Decision
stage_three_ball_x > (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS)True- Execute
stage_three_ball_x = (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS); - Execute
stage_three_ball_velocity_x = -DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
FalseContinue after this decision.
- Decision
stage_three_ball_y > DEMO_STAGE_THREE_BALL_FLOOR_Y - (float)DEMO_STAGE_THREE_BALL_RADIUSTrue- Execute
stage_three_ball_y = DEMO_STAGE_THREE_BALL_FLOOR_Y - (float)DEMO_STAGE_THREE_BALL_RADIUS; - Execute
stage_three_ball_velocity_y = DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY;
FalseContinue after this decision.
Read original function source
static void updateStageThreeBall(float elapsed_seconds)
{
stage_three_ball_x += stage_three_ball_velocity_x * elapsed_seconds;
stage_three_ball_y += stage_three_ball_velocity_y * elapsed_seconds;
stage_three_ball_velocity_y += DEMO_STAGE_THREE_BALL_GRAVITY * elapsed_seconds;
stage_three_ball_rotation_phase +=
DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND * elapsed_seconds;
if (stage_three_ball_rotation_phase >= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) {
stage_three_ball_rotation_phase -= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS;
}
if (stage_three_ball_x < (float)DEMO_STAGE_THREE_BALL_RADIUS) {
stage_three_ball_x = (float)DEMO_STAGE_THREE_BALL_RADIUS;
stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
} else if (stage_three_ball_x >
(float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS)) {
stage_three_ball_x = (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS);
stage_three_ball_velocity_x = -DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
}
if (stage_three_ball_y > DEMO_STAGE_THREE_BALL_FLOOR_Y -
(float)DEMO_STAGE_THREE_BALL_RADIUS) {
stage_three_ball_y = DEMO_STAGE_THREE_BALL_FLOOR_Y -
(float)DEMO_STAGE_THREE_BALL_RADIUS;
stage_three_ball_velocity_y = DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY;
}
}selectRandomStageTransition() · source line 2585
static void selectRandomStageTransition(void)
- Execute
selected_stage_transition = (demo_stage_transition_effect_t) (esp_random() % DEMO_STAGE_TRANSITION_EFFECT_COUNT); - Decision
selected_stage_transition != DEMO_STAGE_TRANSITION_SCROLLTrue- Exit this function
return;
FalseContinue after this decision.
- Select a case
esp_random() % 4UEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case 0U: - Execute
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT; - Execute
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT; - Exit enclosing loop or switch
break; - Case / label
case 1U: - Execute
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_RIGHT; - Execute
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_LEFT; - Exit enclosing loop or switch
break; - Case / label
case 2U: - Execute
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_UP; - Execute
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_DOWN; - Exit enclosing loop or switch
break; - Case / label
default: - Execute
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_DOWN; - Execute
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_UP; - Exit enclosing loop or switch
break;
Read original function source
static void selectRandomStageTransition(void)
{
selected_stage_transition = (demo_stage_transition_effect_t)
(esp_random() % DEMO_STAGE_TRANSITION_EFFECT_COUNT);
if (selected_stage_transition != DEMO_STAGE_TRANSITION_SCROLL) return;
// Each pair keeps both frames on the same axis, travelling in opposite directions.
// The index is random so each scroll transition can go left, right, up, or down.
switch (esp_random() % 4U) {
case 0U:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
break;
case 1U:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_LEFT;
break;
case 2U:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_UP;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_DOWN;
break;
default:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_DOWN;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_UP;
break;
}
}selectScrollMessage() · source line 2613
static void selectScrollMessage(bool switch_logo)
Calls into this documentation:
- Execute
size_t next = (size_t)esp_random() % DEMO_SCROLL_MESSAGE_COUNT; - Decision
scroll_message_index < DEMO_SCROLL_MESSAGE_COUNTTrue- Execute
next = (size_t)esp_random() % (DEMO_SCROLL_MESSAGE_COUNT - 1U); - Decision
next >= scroll_message_indexTrue- Execute
++next;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
scroll_message_index = next; - Execute
scroll_text = demo_scroll_messages[scroll_message_index]; - Execute
const size_t scroll_scale = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ? (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE : DEMO_SCROLL_SCALE; - Execute
scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * scroll_scale; - Execute
scroll_text_x = scroll_speed_pixels_per_second >= 0.0f ? (float)LCD_WIDTH : -(float)scroll_text_width; - Decision
switch_logo && !DEMO_LOCK_ICON_TO_PRIMARYTrue- Execute
advanceIconSlot();
FalseContinue after this decision.
- Execute
scroll_text_effect_elapsed_ms = 0U;
Read original function source
static void selectScrollMessage(bool switch_logo)
{
size_t next = (size_t)esp_random() % DEMO_SCROLL_MESSAGE_COUNT;
if (scroll_message_index < DEMO_SCROLL_MESSAGE_COUNT) {
next = (size_t)esp_random() % (DEMO_SCROLL_MESSAGE_COUNT - 1U);
if (next >= scroll_message_index) ++next;
}
scroll_message_index = next;
scroll_text = demo_scroll_messages[scroll_message_index];
const size_t scroll_scale = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER
? (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE
: DEMO_SCROLL_SCALE;
scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * scroll_scale;
scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
? (float)LCD_WIDTH : -(float)scroll_text_width;
if (switch_logo && !DEMO_LOCK_ICON_TO_PRIMARY) {
advanceIconSlot();
}
scroll_text_effect_elapsed_ms = 0U;
}dimFlamesBehindTickerGlyph() · source line 2634
static bool dimFlamesBehindTickerGlyph(uint16_t *frame, int centre_x, int centre_y,
float glyph_scale)- Decision
selected_effect != DEMO_EFFECT_RADIAL_FLAME || glyph_scale <= 0.0fTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
const int half_width = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale * 0.5f); - Execute
const int half_height = (int)ceilf((float)PET2001_FONT_GLYPH_HEIGHT * glyph_scale * 0.5f); - Execute
bool touches_flame = false; - FOR loop
int y = centre_y - half_height; y <= centre_y + half_height; ++yRepeat body while the loop condition permits; then continue below.
- Decision
y < 0 || y >= LCD_HEIGHTTrue- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
int x = centre_x - half_width; x <= centre_x + half_width; ++xRepeat body while the loop condition permits; then continue below.
- Decision
x < 0 || x >= LCD_WIDTHTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
uint16_t *pixel = &frame[(size_t)y * LCD_WIDTH + (size_t)x]; - FOR loop
size_t colour = 0U; colour < sizeof(demo_ring_colours) / sizeof(demo_ring_colours[0]); ++colourRepeat body while the loop condition permits; then continue below.
- Decision
*pixel != demo_ring_colours[colour]True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
*pixel = colour == 0U ? COLOR_BLACK : demo_ring_colours[colour - 1U]; - Execute
touches_flame = true; - Exit enclosing loop or switch
break;
- Exit this function
return touches_flame;
Read original function source
static bool dimFlamesBehindTickerGlyph(uint16_t *frame, int centre_x, int centre_y,
float glyph_scale)
{
if (selected_effect != DEMO_EFFECT_RADIAL_FLAME || glyph_scale <= 0.0f) return false;
const int half_width = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale * 0.5f);
const int half_height = (int)ceilf((float)PET2001_FONT_GLYPH_HEIGHT * glyph_scale * 0.5f);
bool touches_flame = false;
for (int y = centre_y - half_height; y <= centre_y + half_height; ++y) {
if (y < 0 || y >= LCD_HEIGHT) continue;
for (int x = centre_x - half_width; x <= centre_x + half_width; ++x) {
if (x < 0 || x >= LCD_WIDTH) continue;
uint16_t *pixel = &frame[(size_t)y * LCD_WIDTH + (size_t)x];
for (size_t colour = 0U; colour < sizeof(demo_ring_colours) /
sizeof(demo_ring_colours[0]); ++colour) {
if (*pixel != demo_ring_colours[colour]) continue;
// Retain the moving fire shape but step it down for white PET text.
*pixel = colour == 0U ? COLOR_BLACK : demo_ring_colours[colour - 1U];
touches_flame = true;
break;
}
}
}
return touches_flame;
}demoScrollRainbowColour() · source line 2659
static uint16_t demoScrollRainbowColour(unsigned glyph_index)
- Execute
const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) / sizeof(demo_scroll_rainbow_colours[0]); - Execute
const unsigned time_step = scroll_text_effect_elapsed_ms / DEMO_SCROLL_RAINBOW_MS_PER_COLOUR; - Execute
const unsigned glyph_step = glyph_index / DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR; - Exit this function
return demo_scroll_rainbow_colours[(glyph_step + time_step) % colour_count];
Read original function source
static uint16_t demoScrollRainbowColour(unsigned glyph_index)
{
const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
sizeof(demo_scroll_rainbow_colours[0]);
const unsigned time_step = scroll_text_effect_elapsed_ms /
DEMO_SCROLL_RAINBOW_MS_PER_COLOUR;
const unsigned glyph_step = glyph_index / DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR;
return demo_scroll_rainbow_colours[(glyph_step + time_step) % colour_count];
}stageFourMagnifierScale() · source line 2670
static float stageFourMagnifierScale(int glyph_centre_x)
- Execute
const float distance = fabsf((float)glyph_centre_x - (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X); - Execute
float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS; - Decision
influence < 0.0fTrue- Execute
influence = 0.0f;
FalseContinue after this decision.
- Execute
influence *= influence; - Exit this function
return (float)DEMO_SCROLL_SCALE + (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence;
Read original function source
static float stageFourMagnifierScale(int glyph_centre_x)
{
const float distance = fabsf((float)glyph_centre_x -
(float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X);
float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
if (influence < 0.0f) influence = 0.0f;
// Squaring concentrates the largest letters around the exact screen centre.
influence *= influence;
return (float)DEMO_SCROLL_SCALE +
(DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence;
}stageFourMagnifierPosition() · source line 2682
static float stageFourMagnifierPosition(int glyph_centre_x)
- Execute
const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X; - Execute
const float offset = (float)glyph_centre_x - centre; - Execute
const float distance = fabsf(offset); - Decision
distance >= DEMO_STAGE_FOUR_MAGNIFIER_RADIUSTrue- Exit this function
return (float)glyph_centre_x;
FalseContinue after this decision.
- Execute
const float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS; - Exit this function
return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH * influence * influence);
Read original function source
static float stageFourMagnifierPosition(int glyph_centre_x)
{
const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
const float offset = (float)glyph_centre_x - centre;
const float distance = fabsf(offset);
if (distance >= DEMO_STAGE_FOUR_MAGNIFIER_RADIUS) return (float)glyph_centre_x;
const float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
// Spread the glyph centres with the lens so expanded neighbouring letters do not overlap.
return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
influence * influence);
}stageFiveMagnifierScale() · source line 2696
static float stageFiveMagnifierScale(int glyph_centre_x)
- Execute
float influence = fabsf((float)glyph_centre_x - (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS; - Decision
influence > 1.0fTrue- Execute
influence = 1.0f;
FalseContinue after this decision.
- Exit this function
return (float)DEMO_SCROLL_SCALE + (DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence * influence;
Read original function source
static float stageFiveMagnifierScale(int glyph_centre_x)
{
float influence = fabsf((float)glyph_centre_x -
(float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X) /
DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
if (influence > 1.0f) influence = 1.0f;
// Inverse lens: smallest at centre, largest at the screen edges and beyond.
return (float)DEMO_SCROLL_SCALE +
(DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) *
influence * influence;
}stageFiveMagnifierPosition() · source line 2708
static float stageFiveMagnifierPosition(int glyph_centre_x)
- Execute
const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X; - Execute
const float offset = (float)glyph_centre_x - centre; - Execute
float influence = fabsf(offset) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS; - Decision
influence > 1.0fTrue- Execute
influence = 1.0f;
FalseContinue after this decision.
- Exit this function
return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH * influence * influence);
Read original function source
static float stageFiveMagnifierPosition(int glyph_centre_x)
{
const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
const float offset = (float)glyph_centre_x - centre;
float influence = fabsf(offset) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
if (influence > 1.0f) influence = 1.0f;
// Keep the maximum expansion beyond both screen edges so large letters enter
// and leave cleanly instead of snapping back to their ordinary spacing.
return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
influence * influence);
}drawScrollText() · source line 2722
static void drawScrollText(uint16_t *frame)
Calls into this documentation:
- Decision
scroll_text == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const unsigned text_length = (unsigned)strlen(scroll_text); - Execute
const int base_x = (int)lroundf(scroll_text_x); - Execute
const bool magnifier_active = selected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLER; - Execute
const bool raster_band_scroller = selected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER; - Execute
const bool lower_raster_band_scroller = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER; - Execute
const bool battle_zone_raster_scroller = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER; - Execute
const int advance = PET2001_FONT_GLYPH_WIDTH * (lower_raster_band_scroller ? (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE : DEMO_SCROLL_SCALE); - Execute
const uint32_t text_wave_elapsed_ms = raster_band_scroller ? raster_band_effect_elapsed_ms : scroll_text_effect_elapsed_ms; - Execute
const bool reverse_magnifier = selected_stage == DEMO_STAGE_REVERSE_SCROLLER; - FOR loop
unsigned index = 0U; index < text_length; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const int unscaled_centre_x = base_x + (int)index * advance + advance / 2; - Execute
const int centre_x = !magnifier_active ? unscaled_centre_x : (int)lroundf(reverse_magnifier ? stageFiveMagnifierPosition(unscaled_centre_x) : stageFourMagnifierPosition(unscaled_centre_x)); - Execute
const float glyph_scale = !magnifier_active ? (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_SCALE : (float)DEMO_SCROLL_SCALE) : (reverse_magnifier ? stageFiveMagnifierScale(unscaled_centre_x) : stageFourMagnifierScale(unscaled_centre_x)); - Execute
const int glyph_margin = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale); - Execute
const int centre_y = magnifier_active ? DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y : (lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y : (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y : (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y : DEMO_SCROLL_Y))) + graphics_text_effect_wave_offset(text_wave_elapsed_ms, index, lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE : (battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE : (raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE : DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE))) + (raster_band_scroller ? 0 : advance / 2); - Decision
centre_x + glyph_margin < 0 || centre_x - glyph_margin >= LCD_WIDTH || centre_y + glyph_margin < 0 || centre_y - glyph_margin >= LCD_HEIGHTTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const uint16_t glyph_color = dimFlamesBehindTickerGlyph(frame, centre_x, centre_y, glyph_scale) ? COLOR_WHITE : demoScrollRainbowColour(index); - Execute
drawPet2001AsciiCharTransformed(frame, centre_x, centre_y, scroll_text[index], glyph_scale, 0.0f, glyph_color);
Read original function source
static void drawScrollText(uint16_t *frame)
{
if (scroll_text == NULL) return;
const unsigned text_length = (unsigned)strlen(scroll_text);
const int base_x = (int)lroundf(scroll_text_x);
const bool magnifier_active = selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
const bool raster_band_scroller = selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
const bool lower_raster_band_scroller =
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
const bool battle_zone_raster_scroller =
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
const int advance = PET2001_FONT_GLYPH_WIDTH *
(lower_raster_band_scroller ? (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
DEMO_SCROLL_SCALE);
const uint32_t text_wave_elapsed_ms = raster_band_scroller ?
raster_band_effect_elapsed_ms : scroll_text_effect_elapsed_ms;
const bool reverse_magnifier = selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
for (unsigned index = 0U; index < text_length; ++index) {
const int unscaled_centre_x = base_x + (int)index * advance + advance / 2;
const int centre_x = !magnifier_active ? unscaled_centre_x :
(int)lroundf(reverse_magnifier ? stageFiveMagnifierPosition(unscaled_centre_x) :
stageFourMagnifierPosition(unscaled_centre_x));
const float glyph_scale = !magnifier_active ?
(lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
(float)DEMO_SCROLL_SCALE) :
(reverse_magnifier ? stageFiveMagnifierScale(unscaled_centre_x) :
stageFourMagnifierScale(unscaled_centre_x));
const int glyph_margin = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale);
const int centre_y = magnifier_active ? DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y :
(lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y :
(battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y :
(raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y : DEMO_SCROLL_Y))) +
graphics_text_effect_wave_offset(text_wave_elapsed_ms, index,
lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE :
(battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE :
(raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE :
DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE))) +
(raster_band_scroller ? 0 : advance / 2);
// Long messages contain many off-screen glyphs; skip their transform work.
if (centre_x + glyph_margin < 0 || centre_x - glyph_margin >= LCD_WIDTH ||
centre_y + glyph_margin < 0 || centre_y - glyph_margin >= LCD_HEIGHT) {
continue;
}
const uint16_t glyph_color = dimFlamesBehindTickerGlyph(frame, centre_x, centre_y,
glyph_scale)
? COLOR_WHITE : demoScrollRainbowColour(index);
drawPet2001AsciiCharTransformed(frame, centre_x, centre_y, scroll_text[index],
glyph_scale, 0.0f, glyph_color);
}
}projectPoint() · source line 2777
static graphics_point_t projectPoint(float source_x, float source_y, float depth,
const demo_rotation_t *rotation)- Execute
const float scaled_x = source_x * DEMO_FACE_SCALE; - Execute
const float scaled_y = source_y * DEMO_FACE_SCALE; - Execute
const float pitched_y = scaled_y * rotation->pitch_cosine - depth * rotation->pitch_sine; - Execute
const float pitched_z = scaled_y * rotation->pitch_sine + depth * rotation->pitch_cosine; - Execute
const float yawed_x = scaled_x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine; - Execute
const float yawed_z = -scaled_x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine; - Execute
const float rotated_x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine; - Execute
const float rotated_y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine; - Execute
const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z); - Execute
const graphics_point_t result = { .x = DEMO_CENTRE_X + (int)lroundf(rotated_x * perspective), .y = DEMO_CENTRE_Y + (int)lroundf(rotated_y * perspective), }; - Exit this function
return result;
Read original function source
static graphics_point_t projectPoint(float source_x, float source_y, float depth,
const demo_rotation_t *rotation)
{
const float scaled_x = source_x * DEMO_FACE_SCALE;
const float scaled_y = source_y * DEMO_FACE_SCALE;
const float pitched_y = scaled_y * rotation->pitch_cosine -
depth * rotation->pitch_sine;
const float pitched_z = scaled_y * rotation->pitch_sine +
depth * rotation->pitch_cosine;
const float yawed_x = scaled_x * rotation->yaw_cosine +
pitched_z * rotation->yaw_sine;
const float yawed_z = -scaled_x * rotation->yaw_sine +
pitched_z * rotation->yaw_cosine;
const float rotated_x = yawed_x * rotation->roll_cosine -
pitched_y * rotation->roll_sine;
const float rotated_y = yawed_x * rotation->roll_sine +
pitched_y * rotation->roll_cosine;
const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
const graphics_point_t result = {
.x = DEMO_CENTRE_X + (int)lroundf(rotated_x * perspective),
.y = DEMO_CENTRE_Y + (int)lroundf(rotated_y * perspective),
};
return result;
}demoRingInnerRadius() · source line 2802
static float demoRingInnerRadius(void)
Calls into this documentation:
- Execute
const float pitch = pitch_degrees * DEMO_PI / 180.0f; - Execute
const float yaw = yaw_degrees * DEMO_PI / 180.0f; - Execute
const float roll = roll_degrees * DEMO_PI / 180.0f; - Execute
const demo_rotation_t rotation = { cosf(pitch), sinf(pitch), cosf(yaw), sinf(yaw), cosf(roll), sinf(roll), }; - Execute
float outer_radius = 0.0f; - FOR loop
unsigned sample = 0U; sample < 16U; ++sampleRepeat body while the loop condition permits; then continue below.
- Execute
const float angle = (float)sample * DEMO_PI * 2.0f / 16.0f; - FOR loop
unsigned depth_index = 0U; depth_index < 2U; ++depth_indexRepeat body while the loop condition permits; then continue below.
- Execute
const graphics_point_t point = projectPoint(cosf(angle) * 30.0f, sinf(angle) * 30.0f, depth_index == 0U ? 0.0f : DEMO_EXTRUSION_DEPTH, &rotation); - Execute
const float dx = (float)point.x - DEMO_CENTRE_X; - Execute
const float dy = (float)point.y - DEMO_CENTRE_Y; - Execute
const float radius = sqrtf(dx * dx + dy * dy); - Decision
radius > outer_radiusTrue- Execute
outer_radius = radius;
FalseContinue after this decision.
- Exit this function
return outer_radius + DEMO_RING_ICON_GAP;
Read original function source
static float demoRingInnerRadius(void)
{
const float pitch = pitch_degrees * DEMO_PI / 180.0f;
const float yaw = yaw_degrees * DEMO_PI / 180.0f;
const float roll = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
cosf(pitch), sinf(pitch), cosf(yaw), sinf(yaw), cosf(roll), sinf(roll),
};
float outer_radius = 0.0f;
for (unsigned sample = 0U; sample < 16U; ++sample) {
const float angle = (float)sample * DEMO_PI * 2.0f / 16.0f;
for (unsigned depth_index = 0U; depth_index < 2U; ++depth_index) {
const graphics_point_t point = projectPoint(cosf(angle) * 30.0f,
sinf(angle) * 30.0f, depth_index == 0U ? 0.0f : DEMO_EXTRUSION_DEPTH,
&rotation);
const float dx = (float)point.x - DEMO_CENTRE_X;
const float dy = (float)point.y - DEMO_CENTRE_Y;
const float radius = sqrtf(dx * dx + dy * dy);
if (radius > outer_radius) outer_radius = radius;
}
}
return outer_radius + DEMO_RING_ICON_GAP;
}drawLogoSlice() · source line 2826
static void drawLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
const demo_rotation_t *rotation, uint16_t color)Calls into this documentation:
- FOR loop
unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- Execute
unsigned run_start = 0U; - WHILE loop
run_start < DEMO_LOGO_WIDTHRepeat body while the loop condition permits; then continue below.
- WHILE loop
run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)Repeat body while the loop condition permits; then continue below.
- Execute
++run_start;
- Decision
run_start == DEMO_LOGO_WIDTHTrue- Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Execute
unsigned run_end = run_start + 1U; - WHILE loop
run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)Repeat body while the loop condition permits; then continue below.
- Execute
++run_end;
- Execute
const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f; - Execute
const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f; - Execute
const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f; - Execute
const graphics_point_t run[4] = { projectPoint(left, top, depth, rotation), projectPoint(right, top, depth, rotation), projectPoint(right, top + 1.0f, depth, rotation), projectPoint(left, top + 1.0f, depth, rotation), }; - Execute
fill_convex_polygon(frame, run, 4U, color); - Execute
run_start = run_end;
Read original function source
static void drawLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
const demo_rotation_t *rotation, uint16_t color)
{
for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
unsigned run_start = 0U;
while (run_start < DEMO_LOGO_WIDTH) {
while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
++run_start;
}
if (run_start == DEMO_LOGO_WIDTH) break;
unsigned run_end = run_start + 1U;
while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
++run_end;
}
const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
const graphics_point_t run[4] = {
projectPoint(left, top, depth, rotation),
projectPoint(right, top, depth, rotation),
projectPoint(right, top + 1.0f, depth, rotation),
projectPoint(left, top + 1.0f, depth, rotation),
};
fill_convex_polygon(frame, run, 4U, color);
run_start = run_end;
}
}
}projectApproachPoint() · source line 2856
static graphics_point_t projectApproachPoint(float source_x, float source_y, float depth,
const demo_rotation_t *rotation, float scale,
int centre_x, int centre_y)- Execute
const float scaled_x = source_x * scale; - Execute
const float scaled_y = source_y * scale; - Execute
const float scaled_depth = depth * scale; - Execute
const float pitched_y = scaled_y * rotation->pitch_cosine - scaled_depth * rotation->pitch_sine; - Execute
const float pitched_z = scaled_y * rotation->pitch_sine + scaled_depth * rotation->pitch_cosine; - Execute
const float yawed_x = scaled_x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine; - Execute
const float yawed_z = -scaled_x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine; - Execute
const float rotated_x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine; - Execute
const float rotated_y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine; - Execute
const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z); - Execute
const graphics_point_t result = { .x = centre_x + (int)lroundf(rotated_x * perspective), .y = centre_y + (int)lroundf(rotated_y * perspective), }; - Exit this function
return result;
Read original function source
static graphics_point_t projectApproachPoint(float source_x, float source_y, float depth,
const demo_rotation_t *rotation, float scale,
int centre_x, int centre_y)
{
const float scaled_x = source_x * scale;
const float scaled_y = source_y * scale;
const float scaled_depth = depth * scale;
const float pitched_y = scaled_y * rotation->pitch_cosine -
scaled_depth * rotation->pitch_sine;
const float pitched_z = scaled_y * rotation->pitch_sine +
scaled_depth * rotation->pitch_cosine;
const float yawed_x = scaled_x * rotation->yaw_cosine +
pitched_z * rotation->yaw_sine;
const float yawed_z = -scaled_x * rotation->yaw_sine +
pitched_z * rotation->yaw_cosine;
const float rotated_x = yawed_x * rotation->roll_cosine -
pitched_y * rotation->roll_sine;
const float rotated_y = yawed_x * rotation->roll_sine +
pitched_y * rotation->roll_cosine;
const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
const graphics_point_t result = {
.x = centre_x + (int)lroundf(rotated_x * perspective),
.y = centre_y + (int)lroundf(rotated_y * perspective),
};
return result;
}drawApproachLogoSlice() · source line 2883
static void drawApproachLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
const demo_rotation_t *rotation, float scale,
int centre_x, int centre_y, uint16_t color)Calls into this documentation:
- FOR loop
unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- Execute
unsigned run_start = 0U; - WHILE loop
run_start < DEMO_LOGO_WIDTHRepeat body while the loop condition permits; then continue below.
- WHILE loop
run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)Repeat body while the loop condition permits; then continue below.
- Execute
++run_start;
- Decision
run_start == DEMO_LOGO_WIDTHTrue- Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Execute
unsigned run_end = run_start + 1U; - WHILE loop
run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)Repeat body while the loop condition permits; then continue below.
- Execute
++run_end;
- Execute
const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f; - Execute
const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f; - Execute
const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f; - Execute
const graphics_point_t run[4] = { projectApproachPoint(left, top, depth, rotation, scale, centre_x, centre_y), projectApproachPoint(right, top, depth, rotation, scale, centre_x, centre_y), projectApproachPoint(right, top + 1.0f, depth, rotation, scale, centre_x, centre_y), projectApproachPoint(left, top + 1.0f, depth, rotation, scale, centre_x, centre_y), }; - Execute
fill_convex_polygon(frame, run, 4U, color); - Execute
run_start = run_end;
Read original function source
static void drawApproachLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
const demo_rotation_t *rotation, float scale,
int centre_x, int centre_y, uint16_t color)
{
for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
unsigned run_start = 0U;
while (run_start < DEMO_LOGO_WIDTH) {
while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
++run_start;
}
if (run_start == DEMO_LOGO_WIDTH) break;
unsigned run_end = run_start + 1U;
while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
++run_end;
}
const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
const graphics_point_t run[4] = {
projectApproachPoint(left, top, depth, rotation, scale, centre_x, centre_y),
projectApproachPoint(right, top, depth, rotation, scale, centre_x, centre_y),
projectApproachPoint(right, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
projectApproachPoint(left, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
};
fill_convex_polygon(frame, run, 4U, color);
run_start = run_end;
}
}
}beginDemoSegments() · source line 2916
static void beginDemoSegments(uint32_t now_ms)
- Execute
demo_instructions_active = false; - Execute
demo_last_interaction_at = now_ms; - Execute
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
Read original function source
static void beginDemoSegments(uint32_t now_ms)
{
demo_instructions_active = false;
demo_last_interaction_at = now_ms;
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
}startDemo() · source line 2923
void startDemo(uint32_t now_ms)
Calls into this documentation:
- Execute
demoStageSlideshowLensStop(); - Execute
demoStageGameOfLifeStop(); - Execute
releaseStageFifteenGroundSource(); - Execute
demoStagePlasmaInitialize(); - Execute
pitch_degrees = 0.0f; - Execute
yaw_degrees = 0.0f; - Execute
roll_degrees = 0.0f; - Execute
active_icon_slot = 0U; - Execute
active_logo_bitmap = demo_icon_slots[active_icon_slot]; - Execute
selectDemoIconColourScheme(); - Execute
scroll_speed_pixels_per_second = DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND; - Execute
selected_stage = DEMO_STAGE_ICON; - Execute
stage_two_distance = DEMO_STAGE_TWO_START_DISTANCE; - Execute
stage_two_auto_approaching = true; - Execute
stage_three_next_icon_change_at = 0U; - Execute
stage_three_icon_slot = 0U; - Execute
stage_three_icon_bitmap = NULL; - Execute
stage_six_next_change_at = 0U; - Execute
stage_six_current_mesh = NULL; - Execute
stage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT; - Execute
raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX; - Execute
demo_paused_total_ms = 0U; - Execute
demo_selector_active = false; - Execute
demo_selector_clear_new_stage = false; - Execute
demo_selector_item = 0U; - Execute
demo_selector_joystick_direction = 0; - Execute
demo_selector_repeating = false; - Execute
demo_instructions_active = true; - Execute
demo_instructions_started_at = now_ms; - Execute
starfield_initialize(&demo_instruction_starfield, 60U, 0, 0, LCD_WIDTH, LCD_HEIGHT, STARFIELD_COLOUR); - Execute
stage_one_next_effect_change_at = 0U; - Execute
demo_last_interaction_at = now_ms; - Execute
screenTransitionReset(&stage_transition); - Execute
stage_change_requested = false; - Execute
stage_zoom_in_requested = false; - Execute
stage_zooming_in = false; - Execute
stage_scroll_in_requested = false; - Execute
stage_reveal_requested = false; - Execute
selected_stage_transition = DEMO_STAGE_TRANSITION_SHRINK_ZOOM; - Execute
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT; - Execute
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT; - Execute
selectScrollMessage(false); - Execute
last_update_ms = now_ms; - Execute
sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS; - Execute
radial_flame_initialize(&demo_ring_flame, demo_ring_heat, DEMO_RING_SEGMENTS, DEMO_RING_LAYERS, DEMO_RING_LAYER_DEPTH, esp_random()); - Execute
selected_effect = DEMO_EFFECT_RADIAL_FLAME; - Execute
running = true;
Read original function source
void startDemo(uint32_t now_ms)
{
demoStageSlideshowLensStop();
demoStageGameOfLifeStop();
releaseStageFifteenGroundSource();
demoStagePlasmaInitialize();
pitch_degrees = 0.0f;
yaw_degrees = 0.0f;
roll_degrees = 0.0f;
active_icon_slot = 0U;
active_logo_bitmap = demo_icon_slots[active_icon_slot];
selectDemoIconColourScheme();
scroll_speed_pixels_per_second = DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND;
selected_stage = DEMO_STAGE_ICON;
stage_two_distance = DEMO_STAGE_TWO_START_DISTANCE;
stage_two_auto_approaching = true;
stage_three_next_icon_change_at = 0U;
stage_three_icon_slot = 0U;
stage_three_icon_bitmap = NULL;
stage_six_next_change_at = 0U;
stage_six_current_mesh = NULL;
stage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT;
raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
demo_paused_total_ms = 0U;
demo_selector_active = false;
demo_selector_clear_new_stage = false;
demo_selector_item = 0U;
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
demo_instructions_active = true;
demo_instructions_started_at = now_ms;
starfield_initialize(&demo_instruction_starfield, 60U, 0, 0, LCD_WIDTH, LCD_HEIGHT,
STARFIELD_COLOUR);
stage_one_next_effect_change_at = 0U;
demo_last_interaction_at = now_ms;
screenTransitionReset(&stage_transition);
stage_change_requested = false;
stage_zoom_in_requested = false;
stage_zooming_in = false;
stage_scroll_in_requested = false;
stage_reveal_requested = false;
selected_stage_transition = DEMO_STAGE_TRANSITION_SHRINK_ZOOM;
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
selectScrollMessage(false);
last_update_ms = now_ms;
sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
radial_flame_initialize(&demo_ring_flame, demo_ring_heat, DEMO_RING_SEGMENTS,
DEMO_RING_LAYERS, DEMO_RING_LAYER_DEPTH, esp_random());
// Start with the outward fire ring; A/K then selects the remaining effects.
selected_effect = DEMO_EFFECT_RADIAL_FLAME;
running = true;
}stopDemo() · source line 2977
void stopDemo(void)
Calls into this documentation:
- Execute
screenTransitionDeinitialize(&stage_transition); - Execute
demoStageSlideshowLensStop(); - Execute
demoStageGameOfLifeStop(); - Execute
releaseStageFifteenGroundSource(); - Execute
running = false;
Read original function source
void stopDemo(void)
{
screenTransitionDeinitialize(&stage_transition);
demoStageSlideshowLensStop();
demoStageGameOfLifeStop();
releaseStageFifteenGroundSource();
running = false;
}moveDemoSelector() · source line 2986
static void moveDemoSelector(int direction, uint32_t now_ms)
- Execute
const size_t item_count = DEMO_STAGE_COUNT + 1U; - Decision
direction < 0True- Execute
demo_selector_item = demo_selector_item == 0U ? item_count - 1U : demo_selector_item - 1U;
False- Decision
direction > 0True- Execute
demo_selector_item = (demo_selector_item + 1U) % item_count;
FalseContinue after this decision.
- Execute
demo_selector_last_move_at = now_ms;
Read original function source
static void moveDemoSelector(int direction, uint32_t now_ms)
{
const size_t item_count = DEMO_STAGE_COUNT + 1U;
if (direction < 0) {
demo_selector_item = demo_selector_item == 0U ? item_count - 1U :
demo_selector_item - 1U;
} else if (direction > 0) {
demo_selector_item = (demo_selector_item + 1U) % item_count;
}
demo_selector_last_move_at = now_ms;
}closeDemoSelector() · source line 2998
static void closeDemoSelector(uint32_t now_ms)
Calls into this documentation:
- Execute
demo_paused_total_ms += now_ms - demo_selector_started_at; - Execute
demo_selector_active = false; - Execute
demo_selector_joystick_direction = 0; - Execute
demo_selector_repeating = false; - Execute
last_update_ms = demoTimeNow(now_ms);
Read original function source
static void closeDemoSelector(uint32_t now_ms)
{
demo_paused_total_ms += now_ms - demo_selector_started_at;
demo_selector_active = false;
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
last_update_ms = demoTimeNow(now_ms);
}openDemoSelector() · source line 3007
static void openDemoSelector(uint32_t now_ms)
- Execute
demo_selector_active = true; - Execute
demo_selector_started_at = now_ms; - Execute
demo_selector_item = 0U; - Execute
demo_selector_joystick_direction = 0; - Execute
demo_selector_repeating = false;
Read original function source
static void openDemoSelector(uint32_t now_ms)
{
demo_selector_active = true;
demo_selector_started_at = now_ms;
demo_selector_item = 0U;
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
}demoAction() · source line 3016
void demoAction(bool next_effect, bool toggle_icon, bool next_stage, bool user_long_pressed,
bool selector_previous, bool selector_next, uint32_t now_ms)Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
demo_selector_activeTrue- Decision
selector_previousTrue- Execute
moveDemoSelector(-1, now_ms);
False- Decision
selector_nextTrue- Execute
moveDemoSelector(1, now_ms);
False- Decision
next_effectTrue- Execute
const size_t selected_item = demo_selector_item; - Execute
closeDemoSelector(now_ms); - Decision
selected_item != 0UTrue- Execute
screenTransitionReset(&stage_transition); - Execute
stage_change_requested = false; - Execute
stage_zoom_in_requested = false; - Execute
stage_zooming_in = false; - Execute
stage_scroll_in_requested = false; - Execute
stage_reveal_requested = false; - Execute
demo_instructions_active = false; - Execute
enterDemoStage((demo_stage_t)(selected_item - 1U), demoTimeNow(now_ms)); - Execute
demo_selector_clear_new_stage = true;
FalseContinue after this decision.
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
user_long_pressedTrue- Execute
openDemoSelector(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Execute
now_ms = demoTimeNow(now_ms); - Decision
demo_instructions_activeTrue- Decision
next_stageTrue- Execute
beginDemoSegments(now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
toggle_iconTrue- Decision
selected_stage == DEMO_STAGE_GEOMETRIC_SOLIDTrue- Execute
selectStageSixMesh(); - Execute
demo_last_interaction_at = now_ms;
False- Decision
selected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTERTrue- Execute
raster_band_palette_index = (raster_band_palette_index + 1U) % DEMO_RASTER_BAND_PALETTE_COUNT; - Execute
demo_last_interaction_at = now_ms;
False- Decision
selected_stage == DEMO_STAGE_PLASMATrue- Execute
demoStagePlasmaNextPalette(); - Execute
demo_last_interaction_at = now_ms;
False- Decision
selected_stage == DEMO_STAGE_BEVEL_GEARSTrue- Execute
stage_seventeen_style = (stage_seventeen_style_t) (((unsigned)stage_seventeen_style + 1U) % STAGE_SEVENTEEN_STYLE_COUNT); - Execute
demo_last_interaction_at = now_ms;
False- Decision
selected_stage != DEMO_STAGE_AMIGA_BALL && selected_stage != DEMO_STAGE_SCROLLER && selected_stage != DEMO_STAGE_REVERSE_SCROLLER && selected_stage != DEMO_STAGE_WALLOONS && selected_stage != DEMO_STAGE_KALEIDO && selected_stage != DEMO_STAGE_SPARKLE && selected_stage != DEMO_STAGE_SQUARES && selected_stage != DEMO_STAGE_SLIDESHOW_LENS && !DEMO_LOCK_ICON_TO_PRIMARYTrue- Execute
advanceIconSlot(); - Execute
demo_last_interaction_at = now_ms;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
next_stage && !stage_change_requested && !stage_zoom_in_requested && !stage_scroll_in_requested && !stage_reveal_requested && !screenTransitionActive(&stage_transition) && !screenRevealActive(&stage_transition) && !screenScrollActive(&stage_transition) && !screenSliceActive(&stage_transition)True- Execute
selectRandomStageTransition(); - Execute
stage_change_requested = true;
False- Decision
next_effect && selected_stage == DEMO_STAGE_ICONTrue- Execute
selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT); - Execute
demo_last_interaction_at = now_ms;
FalseContinue after this decision.
Read original function source
void demoAction(bool next_effect, bool toggle_icon, bool next_stage, bool user_long_pressed,
bool selector_previous, bool selector_next, uint32_t now_ms)
{
if (!running) return;
if (demo_selector_active) {
if (selector_previous) {
moveDemoSelector(-1, now_ms);
} else if (selector_next) {
moveDemoSelector(1, now_ms);
} else if (next_effect) {
const size_t selected_item = demo_selector_item;
closeDemoSelector(now_ms);
if (selected_item != 0U) {
screenTransitionReset(&stage_transition);
stage_change_requested = false;
stage_zoom_in_requested = false;
stage_zooming_in = false;
stage_scroll_in_requested = false;
stage_reveal_requested = false;
demo_instructions_active = false;
enterDemoStage((demo_stage_t)(selected_item - 1U), demoTimeNow(now_ms));
// Present one clean black frame before the newly initialized segment draws.
demo_selector_clear_new_stage = true;
}
}
return;
}
if (user_long_pressed) {
openDemoSelector(now_ms);
return;
}
now_ms = demoTimeNow(now_ms);
if (demo_instructions_active) {
// C skips the control page and starts Segment 1. A, B, and the stick
// remain inactive here so the Demo always begins from its known state.
if (next_stage) beginDemoSegments(now_ms);
return;
}
if (toggle_icon) {
if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
// B previews another catalogue solid and restarts the shared inactivity timer.
// Deliberately leave the independent five-second automatic change deadline alone.
selectStageSixMesh();
demo_last_interaction_at = now_ms;
} else if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
// B cycles the shared palette list without disturbing the bar's
// travel, colour-roll phase, or the scroller's wave.
raster_band_palette_index =
(raster_band_palette_index + 1U) % DEMO_RASTER_BAND_PALETTE_COUNT;
demo_last_interaction_at = now_ms;
} else if (selected_stage == DEMO_STAGE_PLASMA) {
// B changes only the plasma ramp; wave phases and ticker movement continue.
demoStagePlasmaNextPalette();
demo_last_interaction_at = now_ms;
} else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
// B changes only the presentation treatment; gear and rig phases stay continuous.
stage_seventeen_style = (stage_seventeen_style_t)
(((unsigned)stage_seventeen_style + 1U) % STAGE_SEVENTEEN_STYLE_COUNT);
demo_last_interaction_at = now_ms;
} else if (selected_stage != DEMO_STAGE_AMIGA_BALL &&
selected_stage != DEMO_STAGE_SCROLLER &&
selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
selected_stage != DEMO_STAGE_WALLOONS &&
selected_stage != DEMO_STAGE_KALEIDO &&
selected_stage != DEMO_STAGE_SPARKLE &&
selected_stage != DEMO_STAGE_SQUARES &&
selected_stage != DEMO_STAGE_SLIDESHOW_LENS &&
!DEMO_LOCK_ICON_TO_PRIMARY) {
advanceIconSlot();
demo_last_interaction_at = now_ms;
}
}
if (next_stage && !stage_change_requested && !stage_zoom_in_requested &&
!stage_scroll_in_requested && !stage_reveal_requested &&
!screenTransitionActive(&stage_transition) && !screenRevealActive(&stage_transition) &&
!screenScrollActive(&stage_transition) && !screenSliceActive(&stage_transition)) {
// Capture happens after this update, once drawDemo has completed the outgoing frame.
selectRandomStageTransition();
stage_change_requested = true;
} else if (next_effect && selected_stage == DEMO_STAGE_ICON) {
selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) %
DEMO_EFFECT_COUNT);
demo_last_interaction_at = now_ms;
}
}updateDemo() · source line 3104
void updateDemo(int joystick_x, int joystick_y, bool joystick_valid, uint32_t now_ms)
Calls into this documentation:
- Decision
demo_selector_activeTrue- Execute
int direction = 0; - Decision
joystick_validTrue- Decision
joystick_y > DEMO_SELECTOR_JOYSTICK_ENTERTrue- Execute
direction = -1;
False- Decision
joystick_y < -DEMO_SELECTOR_JOYSTICK_ENTERTrue- Execute
direction = 1;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
direction == 0True- Decision
joystick_y >= -DEMO_SELECTOR_JOYSTICK_RELEASE && joystick_y <= DEMO_SELECTOR_JOYSTICK_RELEASETrue- Execute
demo_selector_joystick_direction = 0; - Execute
demo_selector_repeating = false;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
direction != demo_selector_joystick_directionTrue- Execute
demo_selector_joystick_direction = direction; - Execute
demo_selector_repeating = false; - Execute
moveDemoSelector(direction, now_ms);
False- Decision
now_ms - demo_selector_last_move_at >= (demo_selector_repeating ? DEMO_SELECTOR_JOYSTICK_REPEAT_MS : DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS)True- Execute
demo_selector_repeating = true; - Execute
moveDemoSelector(direction, now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
now_ms = demoTimeNow(now_ms); - Decision
!running || now_ms - last_update_ms < DEMO_FRAME_MSTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
uint32_t elapsed_ms = now_ms - last_update_ms; - Decision
elapsed_ms > 50UTrue- Execute
elapsed_ms = 50U;
FalseContinue after this decision.
- Execute
last_update_ms = now_ms; - Decision
demo_instructions_activeTrue- Execute
starfield_update(&demo_instruction_starfield, elapsed_ms); - Decision
(uint32_t)(now_ms - demo_instructions_started_at) >= DEMO_INSTRUCTIONS_DURATION_MSTrue- Execute
beginDemoSegments(now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
screenTransitionActive(&stage_transition)True- Decision
screenTransitionComplete(&stage_transition)True- Execute
screenTransitionReset(&stage_transition); - Decision
stage_zooming_inTrue- Execute
stage_zooming_in = false;
False- Execute
advanceDemoStage(now_ms); - Execute
stage_zoom_in_requested = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
screenRevealActive(&stage_transition)True- Decision
screenRevealComplete(&stage_transition)True- Execute
screenRevealReset(&stage_transition);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
screenScrollActive(&stage_transition)True- Decision
screenScrollComplete(&stage_transition)True- Execute
screenScrollReset(&stage_transition);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
screenSliceActive(&stage_transition)True- Decision
screenSliceComplete(&stage_transition)True- Execute
screenSliceReset(&stage_transition); - Execute
advanceDemoStage(now_ms); - Execute
stage_zoom_in_requested = true;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
pitch_degrees += DEMO_PITCH_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f; - Execute
yaw_degrees += DEMO_YAW_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f; - Execute
roll_degrees += DEMO_ROLL_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f; - Execute
const float elapsed_seconds = (float)elapsed_ms / 1000.0f; - Decision
selected_stage == DEMO_STAGE_AMIGA_BALLTrue- Execute
updateStageThreeBall(elapsed_seconds); - Decision
(int32_t)(now_ms - stage_three_next_icon_change_at) >= 0True- Execute
stage_three_icon_slot = (stage_three_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT; - Execute
stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot]; - Execute
stage_three_next_icon_change_at += DEMO_STAGE_THREE_ICON_CHANGE_MS;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_GEOMETRIC_SOLIDTrue- Decision
(int32_t)(now_ms - stage_six_next_change_at) >= 0True- Execute
selectStageSixMesh(); - Execute
stage_six_next_change_at += DEMO_STAGE_SIX_CHANGE_MS;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_WALLOONSTrue- Execute
demoStageWalloonsUpdate(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_KALEIDOTrue- Execute
demoStageKaleidoUpdate(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SPARKLETrue- Execute
demoStageSparkleUpdate(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SQUARESTrue- Execute
demoStageSquaresUpdate(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_RASTER_BANDS || selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER || selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTERTrue- Execute
raster_band_effect_elapsed_ms += elapsed_ms;
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SLIDESHOW_LENSTrue- Execute
demoStageSlideshowLensUpdate(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_PLASMATrue- Execute
demoStagePlasmaUpdate(elapsed_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_GAME_OF_LIFETrue- Execute
demoStageGameOfLifeUpdate(elapsed_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_BEVEL_GEARSTrue- Execute
const float angle_step = DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND * elapsed_seconds; - Execute
stage_seventeen_rig_pitch_degrees += DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND * elapsed_seconds; - Execute
stage_seventeen_rig_yaw_degrees += DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND * elapsed_seconds; - Execute
stage_seventeen_rig_roll_degrees += DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND * elapsed_seconds; - Execute
stage_seventeen_gear_degrees[0] += angle_step; - Execute
stage_seventeen_gear_degrees[1] -= angle_step; - Execute
stage_seventeen_gear_degrees[2] -= angle_step;
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_MOUSE_MAZETrue- Execute
demoStageMouseMazeUpdate(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLERTrue- Execute
updateTextCornerIconCycles(now_ms);
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_ICON && (int32_t)(now_ms - stage_one_next_effect_change_at) >= 0True- Execute
selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT); - Execute
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
FalseContinue after this decision.
- Decision
joystick_valid && (joystick_x < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE || joystick_x > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE || (selected_stage == DEMO_STAGE_APPROACH && (joystick_y < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE || joystick_y > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE)))True- Execute
demo_last_interaction_at = now_ms;
FalseContinue after this decision.
- Decision
joystick_valid && (joystick_x < -DEMO_SCROLL_JOYSTICK_DEAD_ZONE || joystick_x > DEMO_SCROLL_JOYSTICK_DEAD_ZONE)True- Execute
float deflection = (float)(joystick_x < 0 ? -joystick_x : joystick_x) - (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE; - Execute
deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL - (float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE; - Decision
deflection > 1.0fTrue- Execute
deflection = 1.0f;
FalseContinue after this decision.
- Execute
const float direction = joystick_x < 0 ? 1.0f : -1.0f; - Execute
scroll_speed_pixels_per_second += direction * DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED * deflection * elapsed_seconds; - Decision
scroll_speed_pixels_per_second > DEMO_SCROLL_MAX_PIXELS_PER_SECONDTrue- Execute
scroll_speed_pixels_per_second = DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
False- Decision
scroll_speed_pixels_per_second < -DEMO_SCROLL_MAX_PIXELS_PER_SECONDTrue- Execute
scroll_speed_pixels_per_second = -DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_APPROACH && joystick_valid && (joystick_y < -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE || joystick_y > DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE)True- Execute
float deflection = (float)(joystick_y < 0 ? -joystick_y : joystick_y) - (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE; - Execute
deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL - (float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE; - Decision
deflection > 1.0fTrue- Execute
deflection = 1.0f;
FalseContinue after this decision.
- Execute
const float direction = joystick_y > 0 ? 1.0f : -1.0f; - Execute
stage_two_distance -= direction * DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND * deflection * elapsed_seconds; - Decision
stage_two_distance < DEMO_STAGE_TWO_MIN_DISTANCETrue- Execute
stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
False- Decision
stage_two_distance > DEMO_STAGE_TWO_MAX_DISTANCETrue- Execute
stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
selected_stage == DEMO_STAGE_APPROACH && (!joystick_valid || (joystick_y >= -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE && joystick_y <= DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE))True- Execute
const float auto_distance_step = DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND * elapsed_seconds; - Decision
stage_two_auto_approachingTrue- Execute
stage_two_distance -= auto_distance_step; - Decision
stage_two_distance <= DEMO_STAGE_TWO_MIN_DISTANCETrue- Execute
stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE; - Execute
stage_two_auto_approaching = false;
FalseContinue after this decision.
False- Execute
stage_two_distance += auto_distance_step; - Decision
stage_two_distance >= DEMO_STAGE_TWO_MAX_DISTANCETrue- Execute
stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE; - Execute
stage_two_auto_approaching = true;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
now_ms - demo_last_interaction_at >= demoStageInactivityTimeout() && !stage_change_requestedTrue- Execute
selectRandomStageTransition(); - Execute
stage_change_requested = true;
FalseContinue after this decision.
- Execute
scroll_text_x -= scroll_speed_pixels_per_second * elapsed_seconds; - Execute
scroll_text_effect_elapsed_ms += elapsed_ms; - Decision
pitch_degrees >= 360.0fTrue- Execute
pitch_degrees -= 360.0f;
FalseContinue after this decision.
- Decision
yaw_degrees >= 360.0fTrue- Execute
yaw_degrees -= 360.0f;
FalseContinue after this decision.
- Decision
roll_degrees >= 360.0fTrue- Execute
roll_degrees -= 360.0f;
FalseContinue after this decision.
- Decision
stage_seventeen_rig_pitch_degrees >= 360.0fTrue- Execute
stage_seventeen_rig_pitch_degrees -= 360.0f;
FalseContinue after this decision.
- Decision
stage_seventeen_rig_yaw_degrees >= 360.0fTrue- Execute
stage_seventeen_rig_yaw_degrees -= 360.0f;
FalseContinue after this decision.
- Decision
stage_seventeen_rig_roll_degrees >= 360.0fTrue- Execute
stage_seventeen_rig_roll_degrees -= 360.0f;
FalseContinue after this decision.
- FOR loop
size_t gear = 0U; gear < 3U; ++gearRepeat body while the loop condition permits; then continue below.
- WHILE loop
stage_seventeen_gear_degrees[gear] >= 360.0fRepeat body while the loop condition permits; then continue below.
- Execute
stage_seventeen_gear_degrees[gear] -= 360.0f;
- WHILE loop
stage_seventeen_gear_degrees[gear] < 0.0fRepeat body while the loop condition permits; then continue below.
- Execute
stage_seventeen_gear_degrees[gear] += 360.0f;
- Decision
(scroll_speed_pixels_per_second > 0.0f && scroll_text_x <= -(float)scroll_text_width) || (scroll_speed_pixels_per_second < 0.0f && scroll_text_x >= (float)LCD_WIDTH)True- Execute
selectScrollMessage(selected_stage != DEMO_STAGE_SCROLLER && selected_stage != DEMO_STAGE_REVERSE_SCROLLER && selected_stage != DEMO_STAGE_RASTER_BANDS && selected_stage != DEMO_STAGE_BATTLE_ZONE_RASTER);
FalseContinue after this decision.
- Execute
sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS; - Execute
radial_flame_update_wavy(&demo_ring_flame, DEMO_RING_WAVE_AMPLITUDE, DEMO_RING_WAVE_COUNT, (float)now_ms * DEMO_RING_WAVE_DEGREES_PER_SECOND / 1000.0f, DEMO_RING_EXTRA_COOLING);
Read original function source
void updateDemo(int joystick_x, int joystick_y, bool joystick_valid, uint32_t now_ms)
{
if (demo_selector_active) {
int direction = 0;
if (joystick_valid) {
if (joystick_y > DEMO_SELECTOR_JOYSTICK_ENTER) direction = -1;
else if (joystick_y < -DEMO_SELECTOR_JOYSTICK_ENTER) direction = 1;
}
if (direction == 0) {
if (joystick_y >= -DEMO_SELECTOR_JOYSTICK_RELEASE &&
joystick_y <= DEMO_SELECTOR_JOYSTICK_RELEASE) {
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
}
return;
}
if (direction != demo_selector_joystick_direction) {
demo_selector_joystick_direction = direction;
demo_selector_repeating = false;
moveDemoSelector(direction, now_ms);
} else if (now_ms - demo_selector_last_move_at >=
(demo_selector_repeating ? DEMO_SELECTOR_JOYSTICK_REPEAT_MS :
DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS)) {
demo_selector_repeating = true;
moveDemoSelector(direction, now_ms);
}
return;
}
now_ms = demoTimeNow(now_ms);
if (!running || now_ms - last_update_ms < DEMO_FRAME_MS) return;
uint32_t elapsed_ms = now_ms - last_update_ms;
if (elapsed_ms > 50U) elapsed_ms = 50U;
last_update_ms = now_ms;
if (demo_instructions_active) {
// The control page has its own fixed duration and does not consume any
// of Segment 1's normal inactivity timeout.
starfield_update(&demo_instruction_starfield, elapsed_ms);
if ((uint32_t)(now_ms - demo_instructions_started_at) >=
DEMO_INSTRUCTIONS_DURATION_MS) {
beginDemoSegments(now_ms);
}
return;
}
if (screenTransitionActive(&stage_transition)) {
if (screenTransitionComplete(&stage_transition)) {
// The transition's finished frame was shown. Either resume the new stage or
// select it and request its off-screen capture for the incoming zoom.
screenTransitionReset(&stage_transition);
if (stage_zooming_in) {
stage_zooming_in = false;
} else {
advanceDemoStage(now_ms);
stage_zoom_in_requested = true;
}
}
return;
}
if (screenRevealActive(&stage_transition)) {
if (screenRevealComplete(&stage_transition)) {
screenRevealReset(&stage_transition);
}
return;
}
if (screenScrollActive(&stage_transition)) {
if (screenScrollComplete(&stage_transition)) {
screenScrollReset(&stage_transition);
}
return;
}
if (screenSliceActive(&stage_transition)) {
if (screenSliceComplete(&stage_transition)) {
screenSliceReset(&stage_transition);
advanceDemoStage(now_ms);
stage_zoom_in_requested = true;
}
return;
}
pitch_degrees += DEMO_PITCH_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
yaw_degrees += DEMO_YAW_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
roll_degrees += DEMO_ROLL_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
const float elapsed_seconds = (float)elapsed_ms / 1000.0f;
if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
updateStageThreeBall(elapsed_seconds);
// This signed comparison continues to work if the millisecond counter wraps.
if ((int32_t)(now_ms - stage_three_next_icon_change_at) >= 0) {
stage_three_icon_slot = (stage_three_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
stage_three_next_icon_change_at += DEMO_STAGE_THREE_ICON_CHANGE_MS;
}
}
if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
if ((int32_t)(now_ms - stage_six_next_change_at) >= 0) {
selectStageSixMesh();
stage_six_next_change_at += DEMO_STAGE_SIX_CHANGE_MS;
}
}
if (selected_stage == DEMO_STAGE_WALLOONS) {
demoStageWalloonsUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_KALEIDO) {
demoStageKaleidoUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_SPARKLE) {
demoStageSparkleUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_SQUARES) {
demoStageSquaresUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
raster_band_effect_elapsed_ms += elapsed_ms;
}
if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
demoStageSlideshowLensUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_PLASMA) {
demoStagePlasmaUpdate(elapsed_ms);
}
if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
demoStageGameOfLifeUpdate(elapsed_ms);
}
if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
const float angle_step = DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND *
elapsed_seconds;
stage_seventeen_rig_pitch_degrees +=
DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND * elapsed_seconds;
stage_seventeen_rig_yaw_degrees +=
DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND * elapsed_seconds;
stage_seventeen_rig_roll_degrees +=
DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND * elapsed_seconds;
// The two driven gears reverse from the common lead phase, preserving
// their fixed tooth-to-gap relationship at both mesh contacts.
stage_seventeen_gear_degrees[0] += angle_step;
stage_seventeen_gear_degrees[1] -= angle_step;
stage_seventeen_gear_degrees[2] -= angle_step;
}
if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
demoStageMouseMazeUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
updateTextCornerIconCycles(now_ms);
}
if (selected_stage == DEMO_STAGE_ICON &&
(int32_t)(now_ms - stage_one_next_effect_change_at) >= 0) {
// Keep the automatic sequence in the same enum order as a manual A press.
selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT);
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
}
if (joystick_valid &&
(joystick_x < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
joystick_x > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
(selected_stage == DEMO_STAGE_APPROACH &&
(joystick_y < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
joystick_y > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE)))) {
demo_last_interaction_at = now_ms;
}
if (joystick_valid && (joystick_x < -DEMO_SCROLL_JOYSTICK_DEAD_ZONE ||
joystick_x > DEMO_SCROLL_JOYSTICK_DEAD_ZONE)) {
float deflection = (float)(joystick_x < 0 ? -joystick_x : joystick_x) -
(float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
(float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
if (deflection > 1.0f) deflection = 1.0f;
const float direction = joystick_x < 0 ? 1.0f : -1.0f;
scroll_speed_pixels_per_second += direction *
DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED *
deflection * elapsed_seconds;
if (scroll_speed_pixels_per_second > DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
scroll_speed_pixels_per_second = DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
} else if (scroll_speed_pixels_per_second < -DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
scroll_speed_pixels_per_second = -DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
}
}
if (selected_stage == DEMO_STAGE_APPROACH && joystick_valid &&
(joystick_y < -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE ||
joystick_y > DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE)) {
float deflection = (float)(joystick_y < 0 ? -joystick_y : joystick_y) -
(float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
(float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
if (deflection > 1.0f) deflection = 1.0f;
const float direction = joystick_y > 0 ? 1.0f : -1.0f;
stage_two_distance -= direction * DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND *
deflection * elapsed_seconds;
if (stage_two_distance < DEMO_STAGE_TWO_MIN_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
} else if (stage_two_distance > DEMO_STAGE_TWO_MAX_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
}
}
if (selected_stage == DEMO_STAGE_APPROACH &&
(!joystick_valid ||
(joystick_y >= -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE &&
joystick_y <= DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE))) {
// With no vertical-stick input, travel toward the current end and reverse there.
const float auto_distance_step = DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND *
elapsed_seconds;
if (stage_two_auto_approaching) {
stage_two_distance -= auto_distance_step;
if (stage_two_distance <= DEMO_STAGE_TWO_MIN_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
stage_two_auto_approaching = false;
}
} else {
stage_two_distance += auto_distance_step;
if (stage_two_distance >= DEMO_STAGE_TWO_MAX_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
stage_two_auto_approaching = true;
}
}
}
if (now_ms - demo_last_interaction_at >= demoStageInactivityTimeout() &&
!stage_change_requested) {
// Match C's normal route so every automatic advance uses a regular transition.
selectRandomStageTransition();
stage_change_requested = true;
}
scroll_text_x -= scroll_speed_pixels_per_second * elapsed_seconds;
scroll_text_effect_elapsed_ms += elapsed_ms;
if (pitch_degrees >= 360.0f) pitch_degrees -= 360.0f;
if (yaw_degrees >= 360.0f) yaw_degrees -= 360.0f;
if (roll_degrees >= 360.0f) roll_degrees -= 360.0f;
if (stage_seventeen_rig_pitch_degrees >= 360.0f) stage_seventeen_rig_pitch_degrees -= 360.0f;
if (stage_seventeen_rig_yaw_degrees >= 360.0f) stage_seventeen_rig_yaw_degrees -= 360.0f;
if (stage_seventeen_rig_roll_degrees >= 360.0f) stage_seventeen_rig_roll_degrees -= 360.0f;
for (size_t gear = 0U; gear < 3U; ++gear) {
while (stage_seventeen_gear_degrees[gear] >= 360.0f) {
stage_seventeen_gear_degrees[gear] -= 360.0f;
}
while (stage_seventeen_gear_degrees[gear] < 0.0f) {
stage_seventeen_gear_degrees[gear] += 360.0f;
}
}
if ((scroll_speed_pixels_per_second > 0.0f &&
scroll_text_x <= -(float)scroll_text_width) ||
(scroll_speed_pixels_per_second < 0.0f && scroll_text_x >= (float)LCD_WIDTH)) {
selectScrollMessage(selected_stage != DEMO_STAGE_SCROLLER &&
selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
selected_stage != DEMO_STAGE_RASTER_BANDS &&
selected_stage != DEMO_STAGE_BATTLE_ZONE_RASTER);
}
sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
radial_flame_update_wavy(&demo_ring_flame, DEMO_RING_WAVE_AMPLITUDE,
DEMO_RING_WAVE_COUNT,
(float)now_ms * DEMO_RING_WAVE_DEGREES_PER_SECOND / 1000.0f,
DEMO_RING_EXTRA_COOLING);
}drawApproachStage() · source line 3355
static void drawApproachStage(uint16_t *frame)
Calls into this documentation:
- Execute
static const int terrain_bands[] = {58, 76, 101, 134}; - Execute
const int horizon = 42; - FOR loop
size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++indexRepeat body while the loop condition permits; then continue below.
- Execute
draw_line(frame, 0, terrain_bands[index], LCD_WIDTH - 1, terrain_bands[index], COLOR_DARKGREEN);
- FOR loop
int lane = -3; lane <= 3; ++laneRepeat body while the loop condition permits; then continue below.
- Execute
draw_line(frame, DEMO_CENTRE_X + lane * 10, horizon, DEMO_CENTRE_X + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
- Execute
draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, COLOR_LIME); - Execute
int icon_size = (int)lroundf(2400.0f / stage_two_distance); - Decision
icon_size < 4True- Execute
icon_size = 4;
FalseContinue after this decision.
- Decision
icon_size > DEMO_STAGE_TWO_MAX_ICON_PIXELSTrue- Execute
icon_size = DEMO_STAGE_TWO_MAX_ICON_PIXELS;
FalseContinue after this decision.
- Execute
const int icon_base_y = horizon + (int)lroundf(2000.0f / stage_two_distance); - Execute
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f; - Execute
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f; - Execute
const float roll_radians = roll_degrees * DEMO_PI / 180.0f; - Execute
const demo_rotation_t rotation = { .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians), .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians), .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians), }; - Execute
const float scale = (float)icon_size / (float)DEMO_LOGO_WIDTH; - Execute
const int centre_y = icon_base_y - icon_size / 2; - Decision
rotation.pitch_cosine * rotation.yaw_cosine >= 0.0fTrue- FOR loop
unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U; depth -= DEMO_EXTRUSION_STEPRepeat body while the loop condition permits; then continue below.
- Execute
drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
- Execute
drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
False- Execute
drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front); - FOR loop
unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH; depth += DEMO_EXTRUSION_STEPRepeat body while the loop condition permits; then continue below.
- Execute
drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale, DEMO_CENTRE_X, centre_y, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
Read original function source
static void drawApproachStage(uint16_t *frame)
{
static const int terrain_bands[] = {58, 76, 101, 134};
const int horizon = 42;
for (size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++index) {
draw_line(frame, 0, terrain_bands[index], LCD_WIDTH - 1, terrain_bands[index],
COLOR_DARKGREEN);
}
for (int lane = -3; lane <= 3; ++lane) {
draw_line(frame, DEMO_CENTRE_X + lane * 10, horizon,
DEMO_CENTRE_X + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
}
draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, COLOR_LIME);
int icon_size = (int)lroundf(2400.0f / stage_two_distance);
if (icon_size < 4) icon_size = 4;
if (icon_size > DEMO_STAGE_TWO_MAX_ICON_PIXELS) icon_size = DEMO_STAGE_TWO_MAX_ICON_PIXELS;
const int icon_base_y = horizon + (int)lroundf(2000.0f / stage_two_distance);
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = cosf(pitch_radians),
.pitch_sine = sinf(pitch_radians),
.yaw_cosine = cosf(yaw_radians),
.yaw_sine = sinf(yaw_radians),
.roll_cosine = cosf(roll_radians),
.roll_sine = sinf(roll_radians),
};
const float scale = (float)icon_size / (float)DEMO_LOGO_WIDTH;
const int centre_y = icon_base_y - icon_size / 2;
if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
depth -= DEMO_EXTRUSION_STEP) {
drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
} else {
drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
depth += DEMO_EXTRUSION_STEP) {
drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
}
}drawStageThreeBall() · source line 3409
static void drawStageThreeBall(uint16_t *frame)
Calls into this documentation:
- Execute
clear(frame, RGB565(0, 32, 175)); - Execute
const int centre_x = (int)lroundf(stage_three_ball_x); - Execute
const int centre_y = (int)lroundf(stage_three_ball_y); - Execute
const uint8_t rotation = (uint8_t)stage_three_ball_rotation_phase; - Execute
const uint16_t shadow = RGB565(0, 0, 80); - Execute
fill_rect(frame, centre_x - 13, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y, 27, 1, shadow); - Execute
fill_rect(frame, centre_x - 19, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 1, 39, 2, shadow); - Execute
fill_rect(frame, centre_x - 15, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 3, 31, 1, shadow); - Execute
const uint8_t *bitmap = stage_three_icon_bitmap != NULL ? stage_three_icon_bitmap : demo_jd_bitmap; - FOR loop
int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++yRepeat body while the loop condition permits; then continue below.
- Execute
const int screen_y = centre_y - DEMO_STAGE_THREE_BALL_RADIUS + y; - Decision
screen_y < 0 || screen_y >= LCD_HEIGHTTrue- Next loop iteration
continue;
FalseContinue after this decision.
- FOR loop
int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x; - Execute
const uint8_t shade = stage_three_ball_shade_map[index]; - Decision
shade == 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int screen_x = centre_x - DEMO_STAGE_THREE_BALL_RADIUS + x; - Decision
screen_x < 0 || screen_x >= LCD_WIDTHTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const unsigned texture_x = ((((unsigned)stage_three_ball_longitude_map[index] * DEMO_LOGO_WIDTH) >> 8U) + (unsigned)rotation * DEMO_LOGO_WIDTH / DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) % DEMO_LOGO_WIDTH; - Execute
const unsigned texture_y = ((unsigned)stage_three_ball_latitude_map[index] * DEMO_LOGO_HEIGHT) >> 8U; - Execute
const size_t byte_index = (size_t)texture_y * DEMO_LOGO_ROW_BYTES + texture_x / 8U; - Execute
const uint8_t bit = (uint8_t)(7U - texture_x % 8U); - Execute
const bool icon_pixel = (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U; - Execute
frame[(size_t)screen_y * LCD_WIDTH + (size_t)screen_x] = icon_pixel ? stage_three_red_shades[shade - 1U] : stage_three_white_shades[shade - 1U];
Read original function source
static void drawStageThreeBall(uint16_t *frame)
{
// Keep the classic blue field, while the scrolling PET text is layered later by drawDemo.
clear(frame, RGB565(0, 32, 175));
const int centre_x = (int)lroundf(stage_three_ball_x);
const int centre_y = (int)lroundf(stage_three_ball_y);
const uint8_t rotation = (uint8_t)stage_three_ball_rotation_phase;
const uint16_t shadow = RGB565(0, 0, 80);
fill_rect(frame, centre_x - 13, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y, 27, 1, shadow);
fill_rect(frame, centre_x - 19, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 1, 39, 2, shadow);
fill_rect(frame, centre_x - 15, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 3, 31, 1, shadow);
const uint8_t *bitmap = stage_three_icon_bitmap != NULL ? stage_three_icon_bitmap :
demo_jd_bitmap;
for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
const int screen_y = centre_y - DEMO_STAGE_THREE_BALL_RADIUS + y;
if (screen_y < 0 || screen_y >= LCD_HEIGHT) continue;
for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
const uint8_t shade = stage_three_ball_shade_map[index];
if (shade == 0U) continue;
const int screen_x = centre_x - DEMO_STAGE_THREE_BALL_RADIUS + x;
if (screen_x < 0 || screen_x >= LCD_WIDTH) continue;
// The packed Demo icon is a rotating spherical texture. Set pixels replace
// the Amiga ball's red checks; clear pixels retain the shaded white surface.
const unsigned texture_x = ((((unsigned)stage_three_ball_longitude_map[index] *
DEMO_LOGO_WIDTH) >> 8U) +
(unsigned)rotation * DEMO_LOGO_WIDTH /
DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) % DEMO_LOGO_WIDTH;
const unsigned texture_y = ((unsigned)stage_three_ball_latitude_map[index] *
DEMO_LOGO_HEIGHT) >> 8U;
const size_t byte_index = (size_t)texture_y * DEMO_LOGO_ROW_BYTES + texture_x / 8U;
const uint8_t bit = (uint8_t)(7U - texture_x % 8U);
const bool icon_pixel = (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
frame[(size_t)screen_y * LCD_WIDTH + (size_t)screen_x] = icon_pixel
? stage_three_red_shades[shade - 1U] : stage_three_white_shades[shade - 1U];
}
}
}rotateMeshPoint() · source line 3450
static demo_point_3d_t rotateMeshPoint(demo_point_3d_t point, const demo_rotation_t *rotation)
- Execute
const float pitched_y = point.y * rotation->pitch_cosine - point.z * rotation->pitch_sine; - Execute
const float pitched_z = point.y * rotation->pitch_sine + point.z * rotation->pitch_cosine; - Execute
const float yawed_x = point.x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine; - Execute
const float yawed_z = -point.x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine; - Execute
const demo_point_3d_t result = { .x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine, .y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine, .z = yawed_z, }; - Exit this function
return result;
Read original function source
static demo_point_3d_t rotateMeshPoint(demo_point_3d_t point, const demo_rotation_t *rotation)
{
const float pitched_y = point.y * rotation->pitch_cosine - point.z * rotation->pitch_sine;
const float pitched_z = point.y * rotation->pitch_sine + point.z * rotation->pitch_cosine;
const float yawed_x = point.x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
const float yawed_z = -point.x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
const demo_point_3d_t result = {
.x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine,
.y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine,
.z = yawed_z,
};
return result;
}projectMeshPoint() · source line 3464
static graphics_point_t projectMeshPoint(const demo_point_3d_t *point)
- Execute
const float perspective = DEMO_STAGE_SIX_CAMERA_DISTANCE / (DEMO_STAGE_SIX_CAMERA_DISTANCE + point->z); - Execute
const graphics_point_t result = { .x = DEMO_STAGE_SIX_CENTRE_X + (int)lroundf(point->x * perspective), .y = DEMO_STAGE_SIX_CENTRE_Y + (int)lroundf(point->y * perspective), }; - Exit this function
return result;
Read original function source
static graphics_point_t projectMeshPoint(const demo_point_3d_t *point)
{
const float perspective = DEMO_STAGE_SIX_CAMERA_DISTANCE /
(DEMO_STAGE_SIX_CAMERA_DISTANCE + point->z);
const graphics_point_t result = {
.x = DEMO_STAGE_SIX_CENTRE_X + (int)lroundf(point->x * perspective),
.y = DEMO_STAGE_SIX_CENTRE_Y + (int)lroundf(point->y * perspective),
};
return result;
}meshFaceColour() · source line 3475
static uint16_t meshFaceColour(float brightness)
- Execute
const demo_solid_colour_scheme_t *scheme = &demo_solid_colour_schemes[stage_six_colour_scheme_index]; - Decision
brightness > 0.76fTrue- Exit this function
return scheme->face_shades[0];
FalseContinue after this decision.
- Decision
brightness > 0.52fTrue- Exit this function
return scheme->face_shades[1];
FalseContinue after this decision.
- Decision
brightness > 0.28fTrue- Exit this function
return scheme->face_shades[2];
FalseContinue after this decision.
- Exit this function
return scheme->face_shades[3];
Read original function source
static uint16_t meshFaceColour(float brightness)
{
const demo_solid_colour_scheme_t *scheme =
&demo_solid_colour_schemes[stage_six_colour_scheme_index];
if (brightness > 0.76f) return scheme->face_shades[0];
if (brightness > 0.52f) return scheme->face_shades[1];
if (brightness > 0.28f) return scheme->face_shades[2];
return scheme->face_shades[3];
}meshFaceBrightness() · source line 3485
static float meshFaceBrightness(const demo_point_3d_t *first, const demo_point_3d_t *second,
const demo_point_3d_t *third)- Execute
const demo_point_3d_t first_edge = { .x = second->x - first->x, .y = second->y - first->y, .z = second->z - first->z, }; - Execute
const demo_point_3d_t second_edge = { .x = third->x - first->x, .y = third->y - first->y, .z = third->z - first->z, }; - Execute
const demo_point_3d_t normal = { .x = first_edge.y * second_edge.z - first_edge.z * second_edge.y, .y = first_edge.z * second_edge.x - first_edge.x * second_edge.z, .z = first_edge.x * second_edge.y - first_edge.y * second_edge.x, }; - Execute
const float length = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z); - Decision
length <= 0.0fTrue- Exit this function
return 0.0f;
FalseContinue after this decision.
- Execute
const float light = fabsf((normal.x * 0.35f - normal.y * 0.45f + normal.z * 0.82f) / length); - Exit this function
return light > 1.0f ? 1.0f : light;
Read original function source
static float meshFaceBrightness(const demo_point_3d_t *first, const demo_point_3d_t *second,
const demo_point_3d_t *third)
{
const demo_point_3d_t first_edge = {
.x = second->x - first->x, .y = second->y - first->y, .z = second->z - first->z,
};
const demo_point_3d_t second_edge = {
.x = third->x - first->x, .y = third->y - first->y, .z = third->z - first->z,
};
const demo_point_3d_t normal = {
.x = first_edge.y * second_edge.z - first_edge.z * second_edge.y,
.y = first_edge.z * second_edge.x - first_edge.x * second_edge.z,
.z = first_edge.x * second_edge.y - first_edge.y * second_edge.x,
};
const float length = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
if (length <= 0.0f) return 0.0f;
// Both sides are shaded because depth sorting, rather than culling, is what lets
// one generic face table render solids with either winding direction.
const float light = fabsf((normal.x * 0.35f - normal.y * 0.45f + normal.z * 0.82f) / length);
return light > 1.0f ? 1.0f : light;
}drawFilledMesh() · source line 3507
static void drawFilledMesh(uint16_t *frame, const demo_mesh_t *mesh,
const demo_rotation_t *rotation)Calls into this documentation:
- Decision
mesh == NULL || mesh->vertices == NULL || mesh->faces == NULL || mesh->vertex_count > DEMO_MESH_MAX_VERTICES || mesh->face_count > DEMO_MESH_MAX_FACESTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
demo_point_3d_t rotated_vertices[DEMO_MESH_MAX_VERTICES]; - Execute
demo_mesh_face_draw_t draw_order[DEMO_MESH_MAX_FACES]; - FOR loop
size_t vertex = 0U; vertex < mesh->vertex_count; ++vertexRepeat body while the loop condition permits; then continue below.
- Execute
demo_point_3d_t scaled = mesh->vertices[vertex]; - Execute
scaled.x *= DEMO_STAGE_SIX_SOLID_SCALE; - Execute
scaled.y *= DEMO_STAGE_SIX_SOLID_SCALE; - Execute
scaled.z *= DEMO_STAGE_SIX_SOLID_SCALE; - Execute
rotated_vertices[vertex] = rotateMeshPoint(scaled, rotation);
- FOR loop
size_t face = 0U; face < mesh->face_count; ++faceRepeat body while the loop condition permits; then continue below.
- Execute
const demo_mesh_face_t *source_face = &mesh->faces[face]; - Decision
source_face->vertex_indices == NULL || source_face->vertex_count < 3U || source_face->vertex_count > DEMO_MESH_MAX_FACE_VERTICESTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
float total_depth = 0.0f; - FOR loop
size_t corner = 0U; corner < source_face->vertex_count; ++cornerRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t vertex = source_face->vertex_indices[corner]; - Decision
vertex >= mesh->vertex_countTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
total_depth += rotated_vertices[vertex].z;
- Execute
draw_order[face] = (demo_mesh_face_draw_t) { .face_index = face, .depth = total_depth / (float)source_face->vertex_count, .brightness = meshFaceBrightness(&rotated_vertices[source_face->vertex_indices[0]], &rotated_vertices[source_face->vertex_indices[1]], &rotated_vertices[source_face->vertex_indices[2]]), };
- FOR loop
size_t pass = 0U; pass + 1U < mesh->face_count; ++passRepeat body while the loop condition permits; then continue below.
- FOR loop
size_t index = 0U; index + 1U < mesh->face_count - pass; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
draw_order[index].depth <= draw_order[index + 1U].depthTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const demo_mesh_face_draw_t temporary = draw_order[index]; - Execute
draw_order[index] = draw_order[index + 1U]; - Execute
draw_order[index + 1U] = temporary;
- FOR loop
size_t order = 0U; order < mesh->face_count; ++orderRepeat body while the loop condition permits; then continue below.
- Execute
const demo_mesh_face_t *source_face = &mesh->faces[draw_order[order].face_index]; - Execute
graphics_point_t polygon[DEMO_MESH_MAX_FACE_VERTICES]; - FOR loop
size_t corner = 0U; corner < source_face->vertex_count; ++cornerRepeat body while the loop condition permits; then continue below.
- Execute
polygon[corner] = projectMeshPoint(&rotated_vertices[source_face->vertex_indices[corner]]);
- Execute
fill_convex_polygon(frame, polygon, source_face->vertex_count, meshFaceColour(draw_order[order].brightness)); - FOR loop
size_t corner = 0U; corner < source_face->vertex_count; ++cornerRepeat body while the loop condition permits; then continue below.
- Execute
const size_t next_corner = (corner + 1U) % source_face->vertex_count; - Execute
draw_line(frame, polygon[corner].x, polygon[corner].y, polygon[next_corner].x, polygon[next_corner].y, demo_solid_colour_schemes[stage_six_colour_scheme_index].edge);
Read original function source
static void drawFilledMesh(uint16_t *frame, const demo_mesh_t *mesh,
const demo_rotation_t *rotation)
{
if (mesh == NULL || mesh->vertices == NULL || mesh->faces == NULL ||
mesh->vertex_count > DEMO_MESH_MAX_VERTICES || mesh->face_count > DEMO_MESH_MAX_FACES) {
return;
}
demo_point_3d_t rotated_vertices[DEMO_MESH_MAX_VERTICES];
demo_mesh_face_draw_t draw_order[DEMO_MESH_MAX_FACES];
for (size_t vertex = 0U; vertex < mesh->vertex_count; ++vertex) {
demo_point_3d_t scaled = mesh->vertices[vertex];
scaled.x *= DEMO_STAGE_SIX_SOLID_SCALE;
scaled.y *= DEMO_STAGE_SIX_SOLID_SCALE;
scaled.z *= DEMO_STAGE_SIX_SOLID_SCALE;
rotated_vertices[vertex] = rotateMeshPoint(scaled, rotation);
}
for (size_t face = 0U; face < mesh->face_count; ++face) {
const demo_mesh_face_t *source_face = &mesh->faces[face];
if (source_face->vertex_indices == NULL || source_face->vertex_count < 3U ||
source_face->vertex_count > DEMO_MESH_MAX_FACE_VERTICES) {
return;
}
float total_depth = 0.0f;
for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
const uint8_t vertex = source_face->vertex_indices[corner];
if (vertex >= mesh->vertex_count) return;
total_depth += rotated_vertices[vertex].z;
}
draw_order[face] = (demo_mesh_face_draw_t) {
.face_index = face,
.depth = total_depth / (float)source_face->vertex_count,
.brightness = meshFaceBrightness(&rotated_vertices[source_face->vertex_indices[0]],
&rotated_vertices[source_face->vertex_indices[1]],
&rotated_vertices[source_face->vertex_indices[2]]),
};
}
// Painter's order draws the farthest polygon first, allowing nearer filled faces
// to cover it without a full per-pixel depth buffer.
for (size_t pass = 0U; pass + 1U < mesh->face_count; ++pass) {
for (size_t index = 0U; index + 1U < mesh->face_count - pass; ++index) {
if (draw_order[index].depth <= draw_order[index + 1U].depth) continue;
const demo_mesh_face_draw_t temporary = draw_order[index];
draw_order[index] = draw_order[index + 1U];
draw_order[index + 1U] = temporary;
}
}
for (size_t order = 0U; order < mesh->face_count; ++order) {
const demo_mesh_face_t *source_face = &mesh->faces[draw_order[order].face_index];
graphics_point_t polygon[DEMO_MESH_MAX_FACE_VERTICES];
for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
polygon[corner] = projectMeshPoint(&rotated_vertices[source_face->vertex_indices[corner]]);
}
fill_convex_polygon(frame, polygon, source_face->vertex_count,
meshFaceColour(draw_order[order].brightness));
for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
const size_t next_corner = (corner + 1U) % source_face->vertex_count;
draw_line(frame, polygon[corner].x, polygon[corner].y,
polygon[next_corner].x, polygon[next_corner].y,
demo_solid_colour_schemes[stage_six_colour_scheme_index].edge);
}
}
}drawStageSixSolid() · source line 3574
static void drawStageSixSolid(uint16_t *frame)
Calls into this documentation:
- Execute
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f; - Execute
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f; - Execute
const float roll_radians = roll_degrees * DEMO_PI / 180.0f; - Execute
const demo_rotation_t rotation = { .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians), .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians), .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians), }; - Execute
drawFilledMesh(frame, stage_six_current_mesh, &rotation);
Read original function source
static void drawStageSixSolid(uint16_t *frame)
{
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians),
.yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians),
.roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians),
};
drawFilledMesh(frame, stage_six_current_mesh, &rotation);
}stageSeventeenRotation() · source line 3587
static demo_rotation_t stageSeventeenRotation(float pitch_degrees, float yaw_degrees,
float roll_degrees)- Execute
const float pitch = pitch_degrees * DEMO_PI / 180.0f; - Execute
const float yaw = yaw_degrees * DEMO_PI / 180.0f; - Execute
const float roll = roll_degrees * DEMO_PI / 180.0f; - Exit this function
return (demo_rotation_t) { .pitch_cosine = cosf(pitch), .pitch_sine = sinf(pitch), .yaw_cosine = cosf(yaw), .yaw_sine = sinf(yaw), .roll_cosine = cosf(roll), .roll_sine = sinf(roll), };
Read original function source
static demo_rotation_t stageSeventeenRotation(float pitch_degrees, float yaw_degrees,
float roll_degrees)
{
const float pitch = pitch_degrees * DEMO_PI / 180.0f;
const float yaw = yaw_degrees * DEMO_PI / 180.0f;
const float roll = roll_degrees * DEMO_PI / 180.0f;
return (demo_rotation_t) {
.pitch_cosine = cosf(pitch), .pitch_sine = sinf(pitch),
.yaw_cosine = cosf(yaw), .yaw_sine = sinf(yaw),
.roll_cosine = cosf(roll), .roll_sine = sinf(roll),
};
}stageSeventeenGearOrigin() · source line 3600
static demo_point_3d_t stageSeventeenGearOrigin(unsigned gear)
- Select a case
gearEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case 0U: - Exit this function
return (demo_point_3d_t) {.x = 0.0f, .y = DEMO_STAGE_SEVENTEEN_LEAD_Y, .z = 0.0f}; - Case / label
case 1U: - Exit this function
return (demo_point_3d_t) {.x = -DEMO_STAGE_SEVENTEEN_DRIVEN_X, .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f}; - Case / label
default: - Exit this function
return (demo_point_3d_t) {.x = DEMO_STAGE_SEVENTEEN_DRIVEN_X, .y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
Read original function source
static demo_point_3d_t stageSeventeenGearOrigin(unsigned gear)
{
switch (gear) {
case 0U: return (demo_point_3d_t) {.x = 0.0f, .y = DEMO_STAGE_SEVENTEEN_LEAD_Y,
.z = 0.0f};
case 1U: return (demo_point_3d_t) {.x = -DEMO_STAGE_SEVENTEEN_DRIVEN_X,
.y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
default: return (demo_point_3d_t) {.x = DEMO_STAGE_SEVENTEEN_DRIVEN_X,
.y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
}
}stageSeventeenGearAxisRotation() · source line 3612
static demo_rotation_t stageSeventeenGearAxisRotation(unsigned gear)
Calls into this documentation:
- Select a case
gearEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case 0U: - Exit this function
return stageSeventeenRotation(-22.5f, 0.0f, 0.0f); - Case / label
case 1U: - Exit this function
return stageSeventeenRotation(14.8f, 22.5f, 0.0f); - Case / label
default: - Exit this function
return stageSeventeenRotation(14.8f, -22.5f, 0.0f);
Read original function source
static demo_rotation_t stageSeventeenGearAxisRotation(unsigned gear)
{
// Each backward axis converges toward the triangular centre. The front
// centres therefore remain at the wide-rim mesh contacts while the narrow
// rear centres close by the same ratio as the tapering tooth radii.
switch (gear) {
case 0U: return stageSeventeenRotation(-22.5f, 0.0f, 0.0f);
case 1U: return stageSeventeenRotation(14.8f, 22.5f, 0.0f);
default: return stageSeventeenRotation(14.8f, -22.5f, 0.0f);
}
}stageSeventeenTransformPoint() · source line 3624
static demo_point_3d_t stageSeventeenTransformPoint(demo_point_3d_t point,
const demo_point_3d_t *origin,
const demo_rotation_t *axis_rotation,
const demo_rotation_t *rig_rotation)Calls into this documentation:
- Execute
demo_point_3d_t transformed = rotateMeshPoint(point, axis_rotation); - Execute
transformed.x += origin->x; - Execute
transformed.y += origin->y; - Execute
transformed.z += origin->z; - Exit this function
return rotateMeshPoint(transformed, rig_rotation);
Read original function source
static demo_point_3d_t stageSeventeenTransformPoint(demo_point_3d_t point,
const demo_point_3d_t *origin,
const demo_rotation_t *axis_rotation,
const demo_rotation_t *rig_rotation)
{
demo_point_3d_t transformed = rotateMeshPoint(point, axis_rotation);
transformed.x += origin->x;
transformed.y += origin->y;
transformed.z += origin->z;
return rotateMeshPoint(transformed, rig_rotation);
}stageSeventeenProjectPoint() · source line 3636
static graphics_point_t stageSeventeenProjectPoint(const demo_point_3d_t *point)
- Execute
const float perspective = DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE / (DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE + point->z); - Exit this function
return (graphics_point_t) { .x = DEMO_STAGE_SEVENTEEN_CENTRE_X + (int)lroundf(point->x * perspective), .y = DEMO_STAGE_SEVENTEEN_CENTRE_Y + (int)lroundf(point->y * perspective), };
Read original function source
static graphics_point_t stageSeventeenProjectPoint(const demo_point_3d_t *point)
{
const float perspective = DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE /
(DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE + point->z);
return (graphics_point_t) {
.x = DEMO_STAGE_SEVENTEEN_CENTRE_X + (int)lroundf(point->x * perspective),
.y = DEMO_STAGE_SEVENTEEN_CENTRE_Y + (int)lroundf(point->y * perspective),
};
}stageSeventeenFaceColour() · source line 3646
static uint16_t stageSeventeenFaceColour(void)
- Decision
stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLATTrue- Exit this function
return RGB565(230, 195, 30);
FalseContinue after this decision.
- Decision
stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLICTrue- Exit this function
return RGB565(255, 220, 70);
FalseContinue after this decision.
- Exit this function
return COLOR_BLACK;
Read original function source
static uint16_t stageSeventeenFaceColour(void)
{
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(230, 195, 30);
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(255, 220, 70);
return COLOR_BLACK;
}stageSeventeenRearColour() · source line 3653
static uint16_t stageSeventeenRearColour(void)
- Decision
stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLATTrue- Exit this function
return RGB565(170, 135, 10);
FalseContinue after this decision.
- Decision
stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLICTrue- Exit this function
return RGB565(210, 175, 30);
FalseContinue after this decision.
- Exit this function
return COLOR_BLACK;
Read original function source
static uint16_t stageSeventeenRearColour(void)
{
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(170, 135, 10);
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(210, 175, 30);
return COLOR_BLACK;
}stageSeventeenSideColour() · source line 3660
static uint16_t stageSeventeenSideColour(unsigned segment)
- Decision
stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLATTrue- Exit this function
return (segment & 2U) == 0U ? RGB565(160, 120, 8) : RGB565(90, 68, 2);
FalseContinue after this decision.
- Decision
stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLICTrue- Exit this function
return (segment & 2U) == 0U ? RGB565(195, 155, 18) : RGB565(115, 85, 3);
FalseContinue after this decision.
- Exit this function
return COLOR_BLACK;
Read original function source
static uint16_t stageSeventeenSideColour(unsigned segment)
{
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) {
return (segment & 2U) == 0U ? RGB565(160, 120, 8) : RGB565(90, 68, 2);
}
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) {
return (segment & 2U) == 0U ? RGB565(195, 155, 18) : RGB565(115, 85, 3);
}
return COLOR_BLACK;
}stageSeventeenEdgeColour() · source line 3671
static uint16_t stageSeventeenEdgeColour(void)
- Exit this function
return stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC ? RGB565(255, 245, 160) : RGB565(255, 220, 50);
Read original function source
static uint16_t stageSeventeenEdgeColour(void)
{
return stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC ? RGB565(255, 245, 160) :
RGB565(255, 220, 50);
}stageSeventeenAppendFace() · source line 3677
static void stageSeventeenAppendFace(const graphics_point_t *points, uint8_t point_count,
float depth, uint16_t colour)- Decision
stage_seventeen_draw_face_count >= DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT || point_count < 3U || point_count > 4UTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
stage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[stage_seventeen_draw_face_count++]; - FOR loop
uint8_t point = 0U; point < point_count; ++pointRepeat body while the loop condition permits; then continue below.
- Execute
face->points[point] = points[point];
- Execute
face->depth = depth; - Execute
face->colour = colour; - Execute
face->point_count = point_count;
Read original function source
static void stageSeventeenAppendFace(const graphics_point_t *points, uint8_t point_count,
float depth, uint16_t colour)
{
if (stage_seventeen_draw_face_count >= DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT ||
point_count < 3U || point_count > 4U) return;
stage_seventeen_draw_face_t *const face =
&stage_seventeen_draw_faces[stage_seventeen_draw_face_count++];
for (uint8_t point = 0U; point < point_count; ++point) face->points[point] = points[point];
face->depth = depth;
face->colour = colour;
face->point_count = point_count;
}stageSeventeenBuildGear() · source line 3690
static void stageSeventeenBuildGear(unsigned gear, const demo_rotation_t *rig_rotation)
Calls into this documentation:
- Execute
const unsigned point_count = DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U; - Execute
graphics_point_t front[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U]; - Execute
graphics_point_t rear[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U]; - Execute
demo_point_3d_t front_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U]; - Execute
demo_point_3d_t rear_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U]; - Execute
const demo_point_3d_t origin = stageSeventeenGearOrigin(gear); - Execute
const demo_rotation_t axis_rotation = stageSeventeenGearAxisRotation(gear); - Execute
const float spin = stage_seventeen_gear_degrees[gear] * DEMO_PI / 180.0f; - Execute
const float front_z = 0.0f; - Execute
const float rear_z = DEMO_STAGE_SEVENTEEN_CONE_DEPTH; - Execute
demo_point_3d_t front_centre = {.x = 0.0f, .y = 0.0f, .z = front_z}; - Execute
demo_point_3d_t rear_centre = {.x = 0.0f, .y = 0.0f, .z = rear_z}; - Execute
front_centre = stageSeventeenTransformPoint(front_centre, &origin, &axis_rotation, rig_rotation); - Execute
rear_centre = stageSeventeenTransformPoint(rear_centre, &origin, &axis_rotation, rig_rotation); - Execute
const graphics_point_t projected_front_centre = stageSeventeenProjectPoint(&front_centre); - Execute
const graphics_point_t projected_rear_centre = stageSeventeenProjectPoint(&rear_centre); - FOR loop
unsigned point = 0U; point < point_count; ++pointRepeat body while the loop condition permits; then continue below.
- Execute
const float angle = spin + (float)point * DEMO_PI / (float)DEMO_STAGE_SEVENTEEN_TOOTH_COUNT; - Execute
const float front_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_FRONT_RADIUS : DEMO_STAGE_SEVENTEEN_FRONT_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO; - Execute
const float rear_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_REAR_RADIUS : DEMO_STAGE_SEVENTEEN_REAR_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO; - Execute
demo_point_3d_t front_point = { .x = cosf(angle) * front_radius, .y = sinf(angle) * front_radius, .z = front_z, }; - Execute
demo_point_3d_t rear_point = { .x = cosf(angle) * rear_radius, .y = sinf(angle) * rear_radius, .z = rear_z, }; - Execute
front_points[point] = stageSeventeenTransformPoint(front_point, &origin, &axis_rotation, rig_rotation); - Execute
rear_points[point] = stageSeventeenTransformPoint(rear_point, &origin, &axis_rotation, rig_rotation); - Execute
front[point] = stageSeventeenProjectPoint(&front_points[point]); - Execute
rear[point] = stageSeventeenProjectPoint(&rear_points[point]);
- FOR loop
unsigned point = 0U; point < point_count; ++pointRepeat body while the loop condition permits; then continue below.
- Execute
const unsigned next = (point + 1U) % point_count; - Execute
const graphics_point_t side[] = {rear[point], rear[next], front[next], front[point]}; - Execute
stageSeventeenAppendFace(side, 4U, (rear_points[point].z + rear_points[next].z + front_points[next].z + front_points[point].z) * 0.25f, stageSeventeenSideColour(point)); - Execute
const graphics_point_t wedge[] = {projected_front_centre, front[point], front[next]}; - Execute
stageSeventeenAppendFace(wedge, 3U, (front_centre.z + front_points[point].z + front_points[next].z) / 3.0f, stageSeventeenFaceColour()); - Execute
const graphics_point_t rear_wedge[] = {projected_rear_centre, rear[next], rear[point]}; - Execute
stageSeventeenAppendFace(rear_wedge, 3U, (rear_centre.z + rear_points[point].z + rear_points[next].z) / 3.0f, stageSeventeenRearColour());
Read original function source
static void stageSeventeenBuildGear(unsigned gear, const demo_rotation_t *rig_rotation)
{
const unsigned point_count = DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U;
graphics_point_t front[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
graphics_point_t rear[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
demo_point_3d_t front_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
demo_point_3d_t rear_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
const demo_point_3d_t origin = stageSeventeenGearOrigin(gear);
const demo_rotation_t axis_rotation = stageSeventeenGearAxisRotation(gear);
const float spin = stage_seventeen_gear_degrees[gear] * DEMO_PI / 180.0f;
// Origins are the wide, front-face centres. Each gear then narrows and
// travels inward through its complete depth toward the shared rear mesh.
const float front_z = 0.0f;
const float rear_z = DEMO_STAGE_SEVENTEEN_CONE_DEPTH;
demo_point_3d_t front_centre = {.x = 0.0f, .y = 0.0f, .z = front_z};
demo_point_3d_t rear_centre = {.x = 0.0f, .y = 0.0f, .z = rear_z};
front_centre = stageSeventeenTransformPoint(front_centre, &origin, &axis_rotation, rig_rotation);
rear_centre = stageSeventeenTransformPoint(rear_centre, &origin, &axis_rotation, rig_rotation);
const graphics_point_t projected_front_centre = stageSeventeenProjectPoint(&front_centre);
const graphics_point_t projected_rear_centre = stageSeventeenProjectPoint(&rear_centre);
for (unsigned point = 0U; point < point_count; ++point) {
const float angle = spin + (float)point * DEMO_PI / (float)DEMO_STAGE_SEVENTEEN_TOOTH_COUNT;
const float front_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_FRONT_RADIUS :
DEMO_STAGE_SEVENTEEN_FRONT_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
// Preserve the same tooth/gap rhythm on the narrow end. Joining these
// matching corners makes every tooth a full tapered ridge, rather than
// letting the front cog collapse into a smooth conical rear hub.
const float rear_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_REAR_RADIUS :
DEMO_STAGE_SEVENTEEN_REAR_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
demo_point_3d_t front_point = {
.x = cosf(angle) * front_radius, .y = sinf(angle) * front_radius, .z = front_z,
};
demo_point_3d_t rear_point = {
.x = cosf(angle) * rear_radius,
.y = sinf(angle) * rear_radius, .z = rear_z,
};
front_points[point] =
stageSeventeenTransformPoint(front_point, &origin, &axis_rotation, rig_rotation);
rear_points[point] =
stageSeventeenTransformPoint(rear_point, &origin, &axis_rotation, rig_rotation);
front[point] = stageSeventeenProjectPoint(&front_points[point]);
rear[point] = stageSeventeenProjectPoint(&rear_points[point]);
}
for (unsigned point = 0U; point < point_count; ++point) {
const unsigned next = (point + 1U) % point_count;
const graphics_point_t side[] = {rear[point], rear[next], front[next], front[point]};
stageSeventeenAppendFace(side, 4U,
(rear_points[point].z + rear_points[next].z + front_points[next].z +
front_points[point].z) * 0.25f, stageSeventeenSideColour(point));
// A cog outline is concave at the tooth roots, so its front remains a
// set of separately sorted centre wedges rather than one polygon.
const graphics_point_t wedge[] = {projected_front_centre, front[point], front[next]};
stageSeventeenAppendFace(wedge, 3U,
(front_centre.z + front_points[point].z + front_points[next].z) / 3.0f,
stageSeventeenFaceColour());
// The rear is also concave at its tooth roots. Its individual wedges
// close the narrow end without crossing the adjacent tapered ridges.
const graphics_point_t rear_wedge[] = {projected_rear_centre, rear[next], rear[point]};
stageSeventeenAppendFace(rear_wedge, 3U,
(rear_centre.z + rear_points[point].z + rear_points[next].z) / 3.0f,
stageSeventeenRearColour());
}
}drawStageSeventeenBevelGears() · source line 3756
static void drawStageSeventeenBevelGears(uint16_t *frame)
Calls into this documentation:
- Execute
const demo_rotation_t rig_rotation = stageSeventeenRotation(stage_seventeen_rig_pitch_degrees, stage_seventeen_rig_yaw_degrees, stage_seventeen_rig_roll_degrees); - Execute
stage_seventeen_draw_face_count = 0U; - FOR loop
unsigned gear = 0U; gear < 3U; ++gearRepeat body while the loop condition permits; then continue below.
- Execute
stageSeventeenBuildGear(gear, &rig_rotation);
- FOR loop
size_t pass = 0U; pass + 1U < stage_seventeen_draw_face_count; ++passRepeat body while the loop condition permits; then continue below.
- FOR loop
size_t index = 0U; index + 1U < stage_seventeen_draw_face_count - pass; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
stage_seventeen_draw_faces[index].depth >= stage_seventeen_draw_faces[index + 1U].depthTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const stage_seventeen_draw_face_t temporary = stage_seventeen_draw_faces[index]; - Execute
stage_seventeen_draw_faces[index] = stage_seventeen_draw_faces[index + 1U]; - Execute
stage_seventeen_draw_faces[index + 1U] = temporary;
- FOR loop
size_t face_index = 0U; face_index < stage_seventeen_draw_face_count; ++face_indexRepeat body while the loop condition permits; then continue below.
- Execute
const stage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[face_index]; - Decision
stage_seventeen_style != STAGE_SEVENTEEN_STYLE_OUTLINETrue- Execute
fill_convex_polygon(frame, face->points, face->point_count, face->colour);
FalseContinue after this decision.
- FOR loop
uint8_t point = 0U; point < face->point_count; ++pointRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t next = (uint8_t)((point + 1U) % face->point_count); - Execute
draw_line(frame, face->points[point].x, face->points[point].y, face->points[next].x, face->points[next].y, stageSeventeenEdgeColour());
Read original function source
static void drawStageSeventeenBevelGears(uint16_t *frame)
{
const demo_rotation_t rig_rotation = stageSeventeenRotation(stage_seventeen_rig_pitch_degrees,
stage_seventeen_rig_yaw_degrees, stage_seventeen_rig_roll_degrees);
stage_seventeen_draw_face_count = 0U;
for (unsigned gear = 0U; gear < 3U; ++gear) stageSeventeenBuildGear(gear, &rig_rotation);
// Positive Z is farther from this camera. Sorting every tooth face, rather
// than three whole gears, preserves the interlock throughout the rig tumble.
for (size_t pass = 0U; pass + 1U < stage_seventeen_draw_face_count; ++pass) {
for (size_t index = 0U; index + 1U < stage_seventeen_draw_face_count - pass; ++index) {
if (stage_seventeen_draw_faces[index].depth >=
stage_seventeen_draw_faces[index + 1U].depth) continue;
const stage_seventeen_draw_face_t temporary = stage_seventeen_draw_faces[index];
stage_seventeen_draw_faces[index] = stage_seventeen_draw_faces[index + 1U];
stage_seventeen_draw_faces[index + 1U] = temporary;
}
}
for (size_t face_index = 0U; face_index < stage_seventeen_draw_face_count; ++face_index) {
const stage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[face_index];
if (stage_seventeen_style != STAGE_SEVENTEEN_STYLE_OUTLINE) {
fill_convex_polygon(frame, face->points, face->point_count, face->colour);
}
for (uint8_t point = 0U; point < face->point_count; ++point) {
const uint8_t next = (uint8_t)((point + 1U) % face->point_count);
draw_line(frame, face->points[point].x, face->points[point].y,
face->points[next].x, face->points[next].y, stageSeventeenEdgeColour());
}
}
}drawTextCornerIcons() · source line 3786
static void drawTextCornerIcons(uint16_t *frame)
Calls into this documentation:
- Execute
const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) / sizeof(demo_scroll_rainbow_colours[0]); - FOR loop
size_t corner = 0U; corner < sizeof(demo_text_corner_icon_slots) / sizeof(demo_text_corner_icon_slots[0]); ++cornerRepeat body while the loop condition permits; then continue below.
- Execute
const size_t slot = (size_t)demo_text_corner_icon_slots[corner] % DEMO_ICON_SLOT_COUNT; - Execute
const uint8_t *bitmap = demo_icon_slots[slot]; - Execute
const demo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner]; - Execute
const float rotation_degrees = cycle->rotation_start_degrees + (float)cycle->rotation_degrees_per_second * (float)(last_update_ms - cycle->rotation_started_at) / 1000.0f; - Execute
const float rotation_radians = rotation_degrees * DEMO_PI / 180.0f; - Execute
const demo_rotation_t rotation = { .pitch_cosine = 1.0f, .pitch_sine = 0.0f, .yaw_cosine = 1.0f, .yaw_sine = 0.0f, .roll_cosine = cosf(rotation_radians), .roll_sine = sinf(rotation_radians), }; - Execute
const float travel_phase = (float)(last_update_ms - cycle->cycle_started_at) / (float)cycle->cycle_duration_ms; - Execute
const float centre_progress = 0.5f - 0.5f * cosf(travel_phase * 2.0f * DEMO_PI); - Execute
const int centre_x = (int)lroundf((float)demo_text_corner_icon_x[corner] + ((float)(LCD_WIDTH / 2) - (float)demo_text_corner_icon_x[corner]) * centre_progress); - Execute
const uint32_t pulse_offset = (uint32_t)corner * DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS; - Execute
const float pulse_phase = (float)((last_update_ms + pulse_offset) % DEMO_TEXT_CORNER_ICON_PULSE_MS) / (float)DEMO_TEXT_CORNER_ICON_PULSE_MS; - Execute
const float pulse = 0.5f + 0.5f * sinf(pulse_phase * 2.0f * DEMO_PI); - Execute
const float scale = DEMO_TEXT_CORNER_ICON_SCALE_MIN + (DEMO_TEXT_CORNER_ICON_SCALE_MAX - DEMO_TEXT_CORNER_ICON_SCALE_MIN) * pulse; - Execute
const unsigned colour_index = (unsigned)(last_update_ms / DEMO_TEXT_CORNER_ICON_COLOUR_MS + corner * 2U) % colour_count; - Execute
const uint16_t colour = demo_scroll_rainbow_colours[colour_index]; - FOR loop
unsigned depth = DEMO_TEXT_CORNER_ICON_DEPTH; depth > 0U; depth -= DEMO_TEXT_CORNER_ICON_DEPTH_STEPRepeat body while the loop condition permits; then continue below.
- Execute
drawApproachLogoSlice(frame, bitmap, (float)depth, &rotation, scale, centre_x, demo_text_corner_icon_y[corner], C64_COLOR_DARK_GREY);
- Execute
drawApproachLogoSlice(frame, bitmap, 0.0f, &rotation, scale, centre_x, demo_text_corner_icon_y[corner], colour);
Read original function source
static void drawTextCornerIcons(uint16_t *frame)
{
// No value below comes from the main Demo rotation or either text effect.
// Every icon's travel, pulse, colour, and rotation come from its own state.
const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
sizeof(demo_scroll_rainbow_colours[0]);
for (size_t corner = 0U; corner < sizeof(demo_text_corner_icon_slots) /
sizeof(demo_text_corner_icon_slots[0]); ++corner) {
const size_t slot = (size_t)demo_text_corner_icon_slots[corner] % DEMO_ICON_SLOT_COUNT;
const uint8_t *bitmap = demo_icon_slots[slot];
const demo_text_corner_icon_cycle_t *const cycle =
&demo_text_corner_icon_cycles[corner];
const float rotation_degrees = cycle->rotation_start_degrees +
(float)cycle->rotation_degrees_per_second *
(float)(last_update_ms - cycle->rotation_started_at) / 1000.0f;
const float rotation_radians = rotation_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = 1.0f, .pitch_sine = 0.0f,
.yaw_cosine = 1.0f, .yaw_sine = 0.0f,
.roll_cosine = cosf(rotation_radians),
.roll_sine = sinf(rotation_radians),
};
// updateTextCornerIconCycles() restarts this cycle at every completed
// trip with a newly random duration. This fraction therefore describes
// one complete, independent edge -> centre -> edge journey.
const float travel_phase = (float)(last_update_ms - cycle->cycle_started_at) /
(float)cycle->cycle_duration_ms;
// 0.0 at the outer corner, 1.0 at the centre X line, then back out.
const float centre_progress = 0.5f - 0.5f * cosf(travel_phase * 2.0f * DEMO_PI);
const int centre_x = (int)lroundf((float)demo_text_corner_icon_x[corner] +
((float)(LCD_WIDTH / 2) -
(float)demo_text_corner_icon_x[corner]) *
centre_progress);
const uint32_t pulse_offset = (uint32_t)corner * DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS;
const float pulse_phase = (float)((last_update_ms + pulse_offset) %
DEMO_TEXT_CORNER_ICON_PULSE_MS) /
(float)DEMO_TEXT_CORNER_ICON_PULSE_MS;
const float pulse = 0.5f + 0.5f * sinf(pulse_phase * 2.0f * DEMO_PI);
const float scale = DEMO_TEXT_CORNER_ICON_SCALE_MIN +
(DEMO_TEXT_CORNER_ICON_SCALE_MAX -
DEMO_TEXT_CORNER_ICON_SCALE_MIN) * pulse;
const unsigned colour_index = (unsigned)(last_update_ms /
DEMO_TEXT_CORNER_ICON_COLOUR_MS + corner * 2U) % colour_count;
const uint16_t colour = demo_scroll_rainbow_colours[colour_index];
// Draw far slices first so the pulsing coloured face remains in front.
for (unsigned depth = DEMO_TEXT_CORNER_ICON_DEPTH; depth > 0U;
depth -= DEMO_TEXT_CORNER_ICON_DEPTH_STEP) {
drawApproachLogoSlice(frame, bitmap, (float)depth, &rotation, scale,
centre_x, demo_text_corner_icon_y[corner], C64_COLOR_DARK_GREY);
}
drawApproachLogoSlice(frame, bitmap, 0.0f, &rotation, scale,
centre_x, demo_text_corner_icon_y[corner], colour);
}
}drawStageFifteenBattleZoneBackground() · source line 3845
static void drawStageFifteenBattleZoneBackground(uint16_t *frame)
Calls into this documentation:
- Execute
const int source_top = BATTLE_ZONE_HORIZON_Y; - Execute
const int source_height = LCD_HEIGHT - source_top; - Execute
const int destination_top = DEMO_STAGE_FIFTEEN_HORIZON_Y; - Execute
const int destination_height = LCD_HEIGHT - destination_top; - Execute
const int forward_source_offset = (int)( (raster_band_effect_elapsed_ms % DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS) * (uint32_t)source_height / DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS); - Execute
drawBattleZoneLandscape(frame); - Decision
stage_fifteen_ground_source != NULLTrue- Execute
memcpy(stage_fifteen_ground_source, &frame[(size_t)source_top * LCD_WIDTH], (size_t)LCD_WIDTH * (size_t)source_height * sizeof(frame[0])); - FOR loop
int x = 0; x < LCD_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
stage_fifteen_ground_source[x] = COLOR_BLACK;
FalseContinue after this decision.
- Decision
stage_fifteen_ground_source == NULLTrue- FOR loop
int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top; --destination_yRepeat body while the loop condition permits; then continue below.
- Execute
const int source_y = source_top + (destination_y - destination_top) * (source_height - 1) / (destination_height - 1); - Execute
memcpy(&frame[(size_t)destination_y * LCD_WIDTH], &frame[(size_t)source_y * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
False- FOR loop
int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top; --destination_yRepeat body while the loop condition permits; then continue below.
- Execute
const int unscrolled_source_offset = (destination_y - destination_top) * (source_height - 1) / (destination_height - 1); - Execute
const int source_row = (unscrolled_source_offset - forward_source_offset + source_height) % source_height; - Execute
memcpy(&frame[(size_t)destination_y * LCD_WIDTH], &stage_fifteen_ground_source[(size_t)source_row * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
- Execute
fill_rect(frame, 0, 0, LCD_WIDTH, destination_top, COLOR_BLACK); - Execute
draw_line(frame, 0, destination_top, LCD_WIDTH - 1, destination_top, COLOR_LIME);
Read original function source
static void drawStageFifteenBattleZoneBackground(uint16_t *frame)
{
const int source_top = BATTLE_ZONE_HORIZON_Y;
const int source_height = LCD_HEIGHT - source_top;
const int destination_top = DEMO_STAGE_FIFTEEN_HORIZON_Y;
const int destination_height = LCD_HEIGHT - destination_top;
// Moving the source lookup upward makes terrain features travel down from
// the horizon toward the viewer. A wrapped source introduces the next
// ground pattern at the fixed horizon.
const int forward_source_offset = (int)(
(raster_band_effect_elapsed_ms % DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS) *
(uint32_t)source_height / DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS);
drawBattleZoneLandscape(frame);
if (stage_fifteen_ground_source != NULL) {
memcpy(stage_fifteen_ground_source, &frame[(size_t)source_top * LCD_WIDTH],
(size_t)LCD_WIDTH * (size_t)source_height * sizeof(frame[0]));
// The captured first ground row is Battle Zone's lime horizon. Segment
// 15 draws its own fixed horizon below, so remove this copy before it
// can wrap through the moving texture as an unwanted bright stripe.
for (int x = 0; x < LCD_WIDTH; ++x) {
stage_fifteen_ground_source[x] = COLOR_BLACK;
}
}
if (stage_fifteen_ground_source == NULL) {
// A PSRAM allocation failure leaves a static, safely compressed
// landscape instead of risking corrupt rows with wrapped in-place reads.
for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
--destination_y) {
const int source_y = source_top +
(destination_y - destination_top) * (source_height - 1) /
(destination_height - 1);
memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
&frame[(size_t)source_y * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
}
} else {
for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
--destination_y) {
const int unscrolled_source_offset =
(destination_y - destination_top) * (source_height - 1) /
(destination_height - 1);
const int source_row =
(unscrolled_source_offset - forward_source_offset + source_height) % source_height;
memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
&stage_fifteen_ground_source[(size_t)source_row * LCD_WIDTH],
LCD_WIDTH * sizeof(frame[0]));
}
}
// The original unscaled sky and terrain in the top half are no longer part
// of this segment, leaving the bar and horizon-anchored scroller clear.
fill_rect(frame, 0, 0, LCD_WIDTH, destination_top, COLOR_BLACK);
// The wrapped texture moves, but the bright green horizon remains a stable
// reference line exactly beneath the scroller.
draw_line(frame, 0, destination_top, LCD_WIDTH - 1, destination_top, COLOR_LIME);
}drawRasterBands() · source line 3905
static void drawRasterBands(uint16_t *frame)
Calls into this documentation:
- Execute
const bool lower_half_stage = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER; - Execute
const bool battle_zone_stage = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER; - Execute
const int block_height = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_HEIGHT : (battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT : DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT); - Execute
const uint32_t drift_ms = battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS : DEMO_STAGE_ELEVEN_BAND_DRIFT_MS; - Execute
const int travel_height = battle_zone_stage ? DEMO_STAGE_FIFTEEN_HORIZON_Y : LCD_HEIGHT; - Execute
const float drift_phase = (float)(raster_band_effect_elapsed_ms % drift_ms) / (float)drift_ms; - Execute
const int band_top = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_TOP : (int)lroundf((float)(travel_height - block_height) * (0.5f + 0.5f * sinf(drift_phase * 2.0f * DEMO_PI))); - Execute
const demo_raster_band_palette_t *palette = demo_raster_band_palettes[raster_band_palette_index]; - Execute
draw_rolling_raster_band(frame, band_top, block_height, DEMO_STAGE_ELEVEN_BAND_HEIGHT, raster_band_effect_elapsed_ms, DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS, palette);
Read original function source
static void drawRasterBands(uint16_t *frame)
{
const bool lower_half_stage = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
const bool battle_zone_stage = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
const int block_height = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_HEIGHT :
(battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT :
DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT);
const uint32_t drift_ms = battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS :
DEMO_STAGE_ELEVEN_BAND_DRIFT_MS;
// Segment 16 reserves its lower half for the scaled Battle Zone ground.
// Its bar may touch the halfway horizon but cannot enter that landscape.
const int travel_height = battle_zone_stage ? DEMO_STAGE_FIFTEEN_HORIZON_Y :
LCD_HEIGHT;
const float drift_phase = (float)(raster_band_effect_elapsed_ms % drift_ms) /
(float)drift_ms;
const int band_top = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_TOP :
(int)lroundf((float)(travel_height - block_height) *
(0.5f + 0.5f * sinf(drift_phase * 2.0f * DEMO_PI)));
const demo_raster_band_palette_t *palette =
demo_raster_band_palettes[raster_band_palette_index];
draw_rolling_raster_band(frame, band_top, block_height,
DEMO_STAGE_ELEVEN_BAND_HEIGHT,
raster_band_effect_elapsed_ms,
DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS, palette);
}drawStageTwelveSlideshowLens() · source line 3932
static void drawStageTwelveSlideshowLens(uint16_t *frame)
Calls into this documentation:
- Decision
!stage_twelve_image_ready || stage_twelve_image == NULLTrue- Execute
draw_text_centred(frame, LCD_WIDTH / 2, LCD_HEIGHT / 2, "NO SLIDESHOW IMAGE", 1, C64_COLOR_LIGHT_GREY); - Exit this function
return;
FalseContinue after this decision.
- Execute
const size_t frame_bytes = (size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(frame[0]); - Execute
memcpy(frame, stage_twelve_image, frame_bytes); - Execute
const int centre_x = (int)lroundf(stage_twelve_lens_x); - Execute
const int centre_y = (int)lroundf(stage_twelve_lens_y); - Execute
const int radius = DEMO_STAGE_TWELVE_LENS_RADIUS; - Execute
const int outer_radius_squared = radius * radius; - Execute
const int inner_radius = radius - DEMO_STAGE_TWELVE_LENS_RIM_WIDTH; - Execute
const int inner_radius_squared = inner_radius * inner_radius; - Execute
const int first_y = centre_y - radius < 0 ? 0 : centre_y - radius; - Execute
const int last_y = centre_y + radius >= LCD_HEIGHT ? LCD_HEIGHT - 1 : centre_y + radius; - Execute
const int first_x = centre_x - radius < 0 ? 0 : centre_x - radius; - Execute
const int last_x = centre_x + radius >= LCD_WIDTH ? LCD_WIDTH - 1 : centre_x + radius; - FOR loop
int y = first_y; y <= last_y; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
int x = first_x; x <= last_x; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const int dx = x - centre_x; - Execute
const int dy = y - centre_y; - Execute
const int distance_squared = dx * dx + dy * dy; - Decision
distance_squared > outer_radius_squaredTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
distance_squared > inner_radius_squaredTrue- Execute
frame[(size_t)y * LCD_WIDTH + (size_t)x] = DEMO_STAGE_TWELVE_LENS_RIM_COLOUR; - Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const int source_x = (int)lroundf((float)centre_x + (float)dx / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION); - Execute
const int source_y = (int)lroundf((float)centre_y + (float)dy / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION); - Execute
frame[(size_t)y * LCD_WIDTH + (size_t)x] = stage_twelve_image[(size_t)source_y * LCD_WIDTH + (size_t)source_x];
Read original function source
static void drawStageTwelveSlideshowLens(uint16_t *frame)
{
if (!stage_twelve_image_ready || stage_twelve_image == NULL) {
draw_text_centred(frame, LCD_WIDTH / 2, LCD_HEIGHT / 2,
"NO SLIDESHOW IMAGE", 1, C64_COLOR_LIGHT_GREY);
return;
}
const size_t frame_bytes = (size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(frame[0]);
memcpy(frame, stage_twelve_image, frame_bytes);
const int centre_x = (int)lroundf(stage_twelve_lens_x);
const int centre_y = (int)lroundf(stage_twelve_lens_y);
const int radius = DEMO_STAGE_TWELVE_LENS_RADIUS;
const int outer_radius_squared = radius * radius;
const int inner_radius = radius - DEMO_STAGE_TWELVE_LENS_RIM_WIDTH;
const int inner_radius_squared = inner_radius * inner_radius;
// Sample nearer to the lens centre inside the circle. Clamp the visible
// part to the framebuffer because edge destinations deliberately place
// part of the lens beyond the screen.
const int first_y = centre_y - radius < 0 ? 0 : centre_y - radius;
const int last_y = centre_y + radius >= LCD_HEIGHT ? LCD_HEIGHT - 1 :
centre_y + radius;
const int first_x = centre_x - radius < 0 ? 0 : centre_x - radius;
const int last_x = centre_x + radius >= LCD_WIDTH ? LCD_WIDTH - 1 :
centre_x + radius;
for (int y = first_y; y <= last_y; ++y) {
for (int x = first_x; x <= last_x; ++x) {
const int dx = x - centre_x;
const int dy = y - centre_y;
const int distance_squared = dx * dx + dy * dy;
if (distance_squared > outer_radius_squared) continue;
if (distance_squared > inner_radius_squared) {
frame[(size_t)y * LCD_WIDTH + (size_t)x] = DEMO_STAGE_TWELVE_LENS_RIM_COLOUR;
continue;
}
const int source_x = (int)lroundf((float)centre_x +
(float)dx / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
const int source_y = (int)lroundf((float)centre_y +
(float)dy / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
frame[(size_t)y * LCD_WIDTH + (size_t)x] =
stage_twelve_image[(size_t)source_y * LCD_WIDTH + (size_t)source_x];
}
}
}drawStageSevenGlyph() · source line 3982
static void drawStageSevenGlyph(uint16_t *frame, int column, int row, uint8_t glyph)
Calls into this documentation:
- Execute
const int display_row = row + DEMO_STAGE_SEVEN_ROW_OFFSET; - Decision
column < 0 || column >= 40 || display_row < 0 || display_row >= 21True- Exit this function
return;
FalseContinue after this decision.
- Execute
drawPet2001Glyph(frame, column * (int)PET2001_FONT_GLYPH_WIDTH, display_row * (int)PET2001_FONT_GLYPH_HEIGHT, glyph, false, DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
Read original function source
static void drawStageSevenGlyph(uint16_t *frame, int column, int row, uint8_t glyph)
{
const int display_row = row + DEMO_STAGE_SEVEN_ROW_OFFSET;
if (column < 0 || column >= 40 || display_row < 0 || display_row >= 21) return;
drawPet2001Glyph(frame, column * (int)PET2001_FONT_GLYPH_WIDTH,
display_row * (int)PET2001_FONT_GLYPH_HEIGHT, glyph, false,
DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
}drawStageSevenWalloon() · source line 3991
static void drawStageSevenWalloon(uint16_t *frame, int column, int row, bool walking)
Calls into this documentation:
- Execute
drawStageSevenGlyph(frame, column, row, walking ? 81U : 87U); - Execute
drawStageSevenGlyph(frame, column - 1, row + 1, 64U); - Execute
drawStageSevenGlyph(frame, column, row + 1, 71U); - Execute
drawStageSevenGlyph(frame, column + 1, row + 1, 64U); - Execute
drawStageSevenGlyph(frame, column, row + 2, 90U); - Execute
drawStageSevenGlyph(frame, column - 1, row + 3, 78U); - Execute
drawStageSevenGlyph(frame, column + 1, row + 3, 77U);
Read original function source
static void drawStageSevenWalloon(uint16_t *frame, int column, int row, bool walking)
{
// This is the original seven-cell Walloon from BASIC lines 1800-1820.
// The walking top cell alternates the original 87 and 81 glyphs, while the
// rest of the PET-character sprite remains exactly as the listing defines it.
drawStageSevenGlyph(frame, column, row, walking ? 81U : 87U);
drawStageSevenGlyph(frame, column - 1, row + 1, 64U);
drawStageSevenGlyph(frame, column, row + 1, 71U);
drawStageSevenGlyph(frame, column + 1, row + 1, 64U);
drawStageSevenGlyph(frame, column, row + 2, 90U);
drawStageSevenGlyph(frame, column - 1, row + 3, 78U);
drawStageSevenGlyph(frame, column + 1, row + 3, 77U);
}drawStageSevenPlayfield() · source line 4005
static void drawStageSevenPlayfield(uint16_t *frame)
Calls into this documentation:
- FOR loop
int column = 10; column <= 28; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
drawStageSevenGlyph(frame, column, 5, 127U);
- FOR loop
int row = 5; row <= 15; ++rowRepeat body while the loop condition permits; then continue below.
- Execute
drawStageSevenGlyph(frame, 28, row, 127U);
Read original function source
static void drawStageSevenPlayfield(uint16_t *frame)
{
// Retain the upper platform and right-hand climbing pole throughout the
// act. Their origin is BASIC lines 220-290, at PET cell (10, 5).
for (int column = 10; column <= 28; ++column) {
drawStageSevenGlyph(frame, column, 5, 127U);
}
for (int row = 5; row <= 15; ++row) {
drawStageSevenGlyph(frame, 28, row, 127U);
}
}drawStageSevenTitleBox() · source line 4017
static void drawStageSevenTitleBox(uint16_t *frame)
Calls into this documentation:
- Execute
drawStageSevenPlayfield(frame); - FOR loop
int row = 5; row <= 15; ++rowRepeat body while the loop condition permits; then continue below.
- Execute
drawStageSevenGlyph(frame, 10, row, 127U);
- FOR loop
int column = 10; column <= 25; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
drawStageSevenGlyph(frame, column, 15, 127U);
- Execute
drawPet2001Text(frame, 18 * (int)PET2001_FONT_GLYPH_WIDTH, 7 * (int)PET2001_FONT_GLYPH_HEIGHT, "THE", false, DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1); - Execute
drawPet2001Text(frame, 12 * (int)PET2001_FONT_GLYPH_WIDTH, 9 * (int)PET2001_FONT_GLYPH_HEIGHT, "FLYING WALLOONS", false, DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
Read original function source
static void drawStageSevenTitleBox(uint16_t *frame)
{
// The title-only left edge and lower edge make the temporary opening box.
// They deliberately disappear before the first Walloon begins walking.
drawStageSevenPlayfield(frame);
for (int row = 5; row <= 15; ++row) {
drawStageSevenGlyph(frame, 10, row, 127U);
}
for (int column = 10; column <= 25; ++column) {
drawStageSevenGlyph(frame, column, 15, 127U);
}
// The original presents its stunt as "THE FLYING WALLOONS". Split the
// title over two PET rows so it remains inside this 17-cell-wide frame.
drawPet2001Text(frame, 18 * (int)PET2001_FONT_GLYPH_WIDTH,
7 * (int)PET2001_FONT_GLYPH_HEIGHT, "THE", false,
DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
drawPet2001Text(frame, 12 * (int)PET2001_FONT_GLYPH_WIDTH,
9 * (int)PET2001_FONT_GLYPH_HEIGHT, "FLYING WALLOONS", false,
DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
}drawStageSevenSeesaw() · source line 4038
static void drawStageSevenSeesaw(uint16_t *frame, bool left_end_down)
Calls into this documentation:
- Execute
static const struct { int column; int row; } left_down_board[] = { {13, 19}, {14, 19}, {15, 19}, {16, 18}, {17, 18}, {18, 18}, {19, 17}, {20, 17}, {21, 17}, {22, 16}, {23, 16}, {24, 16}, }; - Execute
static const struct { int column; int row; } right_down_board[] = { {13, 16}, {14, 16}, {15, 16}, {16, 17}, {17, 17}, {18, 17}, {19, 18}, {20, 18}, {21, 18}, {22, 19}, {23, 19}, {24, 19}, }; - Decision
left_end_downTrue- FOR loop
size_t index = 0U; index < sizeof(left_down_board) / sizeof(left_down_board[0]); ++indexRepeat body while the loop condition permits; then continue below.
- Execute
drawStageSevenGlyph(frame, left_down_board[index].column, left_down_board[index].row, DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
False- FOR loop
size_t index = 0U; index < sizeof(right_down_board) / sizeof(right_down_board[0]); ++indexRepeat body while the loop condition permits; then continue below.
- Execute
drawStageSevenGlyph(frame, right_down_board[index].column, right_down_board[index].row, DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
- Execute
drawStageSevenGlyph(frame, 18, 20, 233U); - Execute
drawStageSevenGlyph(frame, 19, 20, 223U);
Read original function source
static void drawStageSevenSeesaw(uint16_t *frame, bool left_end_down)
{
// A single fixed PET-cell pivot replaces the listing's two absolute screen
// snapshots. The board never slides sideways: only its stepped slope swaps
// when the other Walloon becomes the one at the low end.
static const struct { int column; int row; } left_down_board[] = {
{13, 19}, {14, 19}, {15, 19}, {16, 18}, {17, 18}, {18, 18},
{19, 17}, {20, 17}, {21, 17}, {22, 16}, {23, 16}, {24, 16},
};
static const struct { int column; int row; } right_down_board[] = {
{13, 16}, {14, 16}, {15, 16}, {16, 17}, {17, 17}, {18, 17},
{19, 18}, {20, 18}, {21, 18}, {22, 19}, {23, 19}, {24, 19},
};
if (left_end_down) {
for (size_t index = 0U; index < sizeof(left_down_board) / sizeof(left_down_board[0]);
++index) {
drawStageSevenGlyph(frame, left_down_board[index].column,
left_down_board[index].row,
DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
}
} else {
for (size_t index = 0U; index < sizeof(right_down_board) / sizeof(right_down_board[0]);
++index) {
drawStageSevenGlyph(frame, right_down_board[index].column,
right_down_board[index].row,
DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
}
}
// The two original PET glyphs make a compact, fixed fulcrum below the board.
drawStageSevenGlyph(frame, 18, 20, 233U);
drawStageSevenGlyph(frame, 19, 20, 223U);
}drawStageSevenWalloons() · source line 4071
static void drawStageSevenWalloons(uint16_t *frame, uint32_t now_ms)
Calls into this documentation:
- Decision
stage_seven_phase == WALLOONS_TITLE_CARDTrue- Execute
drawStageSevenTitleBox(frame); - Exit this function
return;
FalseContinue after this decision.
- Execute
drawStageSevenPlayfield(frame); - Execute
const unsigned steps = walloonsPhaseSteps(now_ms, 14U); - Execute
const bool walking_pose = (steps & 1U) != 0U; - Select a case
stage_seven_phaseEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case WALLOONS_TITLE_CARD: - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_LEFT_WALK_IN: - Execute
drawStageSevenSeesaw(frame, true); - Execute
drawStageSevenWalloon(frame, 2 + (int)walloonsPhaseSteps(now_ms, 12U), 15, walking_pose); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_PAUSE_ON_SEESAW: - Execute
drawStageSevenSeesaw(frame, true); - Execute
drawStageSevenWalloon(frame, 14, 15, false); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_RIGHT_WALK_ON_PLATFORM: - Execute
drawStageSevenSeesaw(frame, true); - Execute
drawStageSevenWalloon(frame, 14, 15, false); - Execute
drawStageSevenWalloon(frame, 38 - (int)steps, 2, walking_pose); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_RIGHT_DROP: - Execute
drawStageSevenSeesaw(frame, true); - Execute
drawStageSevenWalloon(frame, 14, 15, false); - Execute
drawStageSevenWalloon(frame, 24, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_BOUNCE_RIGHT_UP: - Case / label
case WALLOONS_BOUNCE_RIGHT_DOWN: - Execute
drawStageSevenSeesaw(frame, true); - Execute
drawStageSevenWalloon(frame, 15, 15, false); - Execute
drawStageSevenWalloon(frame, 24, stage_seven_phase == WALLOONS_BOUNCE_RIGHT_UP ? 14 - (int)walloonsPhaseSteps(now_ms, 13U) : 2 + (int)walloonsPhaseSteps(now_ms, 13U), false); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_BOUNCE_LEFT_UP: - Execute
drawStageSevenSeesaw(frame, false); - Execute
drawStageSevenWalloon(frame, 24, 14, false); - Execute
drawStageSevenWalloon(frame, 15, 14 - (int)walloonsPhaseSteps(now_ms, 13U), false); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_BOUNCE_LEFT_DOWN: - Execute
drawStageSevenSeesaw(frame, false); - Execute
drawStageSevenWalloon(frame, 24, 14, false); - Execute
drawStageSevenWalloon(frame, 15, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_LEFT_LANDED: - Execute
drawStageSevenSeesaw(frame, true); - Execute
drawStageSevenWalloon(frame, 15, 15, false); - Exit enclosing loop or switch
break; - Case / label
case WALLOONS_FINISHED: - Execute
drawStageSevenSeesaw(frame, true); - Execute
drawStageSevenWalloon(frame, 15, 15, false); - Execute
drawStageSevenWalloon(frame, 24, 14, false); - Exit enclosing loop or switch
break;
Read original function source
static void drawStageSevenWalloons(uint16_t *frame, uint32_t now_ms)
{
if (stage_seven_phase == WALLOONS_TITLE_CARD) {
drawStageSevenTitleBox(frame);
return;
}
drawStageSevenPlayfield(frame);
const unsigned steps = walloonsPhaseSteps(now_ms, 14U);
const bool walking_pose = (steps & 1U) != 0U;
switch (stage_seven_phase) {
case WALLOONS_TITLE_CARD:
// Handled above so the title card contains no active Walloons.
break;
case WALLOONS_LEFT_WALK_IN:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 2 + (int)walloonsPhaseSteps(now_ms, 12U), 15,
walking_pose);
break;
case WALLOONS_PAUSE_ON_SEESAW:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 14, 15, false);
break;
case WALLOONS_RIGHT_WALK_ON_PLATFORM:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 14, 15, false);
drawStageSevenWalloon(frame, 38 - (int)steps, 2, walking_pose);
break;
case WALLOONS_RIGHT_DROP:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 14, 15, false);
drawStageSevenWalloon(frame, 24, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_BOUNCE_RIGHT_UP:
case WALLOONS_BOUNCE_RIGHT_DOWN:
// The left Walloon remains at the low end while the right Walloon is airborne.
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 15, 15, false);
drawStageSevenWalloon(frame, 24,
stage_seven_phase == WALLOONS_BOUNCE_RIGHT_UP
? 14 - (int)walloonsPhaseSteps(now_ms, 13U)
: 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_BOUNCE_LEFT_UP:
// The right Walloon remains at the low end while the left Walloon rises.
drawStageSevenSeesaw(frame, false);
drawStageSevenWalloon(frame, 24, 14, false);
drawStageSevenWalloon(frame, 15,
14 - (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_BOUNCE_LEFT_DOWN:
drawStageSevenSeesaw(frame, false);
drawStageSevenWalloon(frame, 24, 14, false);
drawStageSevenWalloon(frame, 15,
2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_LEFT_LANDED:
// The landing phase makes the left-down state visible before the
// next Walloon is launched from the other end.
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 15, 15, false);
break;
case WALLOONS_FINISHED:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 15, 15, false);
drawStageSevenWalloon(frame, 24, 14, false);
break;
}
}drawDemoInstructions() · source line 4143
static void drawDemoInstructions(uint16_t *frame)
Calls into this documentation:
- Execute
const uint32_t elapsed_ms = last_update_ms - demo_instructions_started_at; - Execute
const uint32_t remaining_ms = elapsed_ms >= DEMO_INSTRUCTIONS_DURATION_MS ? 0U : DEMO_INSTRUCTIONS_DURATION_MS - elapsed_ms; - Execute
const unsigned seconds_remaining = (unsigned)((remaining_ms + 999U) / 1000U); - Execute
char start_text[24]; - Execute
fill_rect(frame, 0, 0, LCD_WIDTH, LCD_HEIGHT, C64_COLOR_BLUE); - Execute
starfield_draw(frame, &demo_instruction_starfield); - Execute
drawPet2001Text(frame, 40, 8, "Old school Demo", false, C64_COLOR_CYAN, C64_COLOR_BLUE, 2); - Execute
drawPet2001Text(frame, 128, 32, "CONTROLS", false, C64_COLOR_LIGHT_GREY, C64_COLOR_BLUE, 1); - Execute
drawPet2001Text(frame, 56, 48, "C: NEXT SEGMENT", false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1); - Execute
drawPet2001Text(frame, 56, 64, "A: NEXT EFFECT (1)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1); - Execute
drawPet2001Text(frame, 56, 80, "B: ICON/SOLID/PALETTE", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1); - Execute
drawPet2001Text(frame, 56, 96, "UP/DOWN: APPROACH (2)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1); - Execute
drawPet2001Text(frame, 56, 112, "LEFT/RIGHT: TICKER SPEED", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1); - Execute
drawPet2001Text(frame, 56, 128, "BOOT: RETURN TO MENU", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1); - Execute
snprintf(start_text, sizeof(start_text), "C STARTS IN %u S", seconds_remaining); - Execute
drawPet2001Text(frame, 96, 152, start_text, false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
Read original function source
static void drawDemoInstructions(uint16_t *frame)
{
const uint32_t elapsed_ms = last_update_ms - demo_instructions_started_at;
const uint32_t remaining_ms = elapsed_ms >= DEMO_INSTRUCTIONS_DURATION_MS ? 0U :
DEMO_INSTRUCTIONS_DURATION_MS - elapsed_ms;
const unsigned seconds_remaining = (unsigned)((remaining_ms + 999U) / 1000U);
char start_text[24];
fill_rect(frame, 0, 0, LCD_WIDTH, LCD_HEIGHT, C64_COLOR_BLUE);
starfield_draw(frame, &demo_instruction_starfield);
drawPet2001Text(frame, 40, 8, "Old school Demo", false,
C64_COLOR_CYAN, C64_COLOR_BLUE, 2);
drawPet2001Text(frame, 128, 32, "CONTROLS", false, C64_COLOR_LIGHT_GREY, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 48, "C: NEXT SEGMENT", false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 64, "A: NEXT EFFECT (1)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 80, "B: ICON/SOLID/PALETTE", false,
C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 96, "UP/DOWN: APPROACH (2)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 112, "LEFT/RIGHT: TICKER SPEED", false,
C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 128, "BOOT: RETURN TO MENU", false,
C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
snprintf(start_text, sizeof(start_text), "C STARTS IN %u S", seconds_remaining);
drawPet2001Text(frame, 96, 152, start_text, false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
}drawDemoSelector() · source line 4169
static void drawDemoSelector(uint16_t *frame)
Calls into this documentation:
- Execute
clear(frame, COLOR_BLACK); - Execute
draw_text_centred(frame, LCD_WIDTH / 2, 14, "SELECT DEMO SEGMENT", 1, COLOR_ORANGE); - FOR loop
size_t item = 0U; item <= DEMO_STAGE_COUNT; ++itemRepeat body while the loop condition permits; then continue below.
- Execute
const int y = DEMO_SELECTOR_FIRST_ROW_Y + (int)item * DEMO_SELECTOR_ROW_HEIGHT; - Execute
const bool selected = item == demo_selector_item; - Decision
selectedTrue- Execute
fill_rect(frame, 0, y - 1, LCD_WIDTH, DEMO_SELECTOR_ROW_HEIGHT, COLOR_DARKBLUE);
FalseContinue after this decision.
- Execute
draw_text(frame, 8, y, item == 0U ? "RETURN CURRENT" : demoSelectorStageCodeName((demo_stage_t)(item - 1U)), 1, selected ? COLOR_WHITE : COLOR_LIGHTGREY);
- Execute
draw_text_centred(frame, LCD_WIDTH / 2, 160, "D/B OR UP/DOWN A/K SELECT", 1, COLOR_LIGHTGREY);
Read original function source
static void drawDemoSelector(uint16_t *frame)
{
clear(frame, COLOR_BLACK);
draw_text_centred(frame, LCD_WIDTH / 2, 14, "SELECT DEMO SEGMENT", 1, COLOR_ORANGE);
for (size_t item = 0U; item <= DEMO_STAGE_COUNT; ++item) {
const int y = DEMO_SELECTOR_FIRST_ROW_Y + (int)item * DEMO_SELECTOR_ROW_HEIGHT;
const bool selected = item == demo_selector_item;
if (selected) fill_rect(frame, 0, y - 1, LCD_WIDTH, DEMO_SELECTOR_ROW_HEIGHT,
COLOR_DARKBLUE);
draw_text(frame, 8, y, item == 0U ? "RETURN CURRENT" :
demoSelectorStageCodeName((demo_stage_t)(item - 1U)), 1,
selected ? COLOR_WHITE : COLOR_LIGHTGREY);
}
draw_text_centred(frame, LCD_WIDTH / 2, 160, "D/B OR UP/DOWN A/K SELECT", 1,
COLOR_LIGHTGREY);
}drawDemo() · source line 4186
void drawDemo(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
demo_selector_activeTrue- Execute
drawDemoSelector(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
demo_selector_clear_new_stageTrue- Execute
clear(frame, COLOR_BLACK); - Execute
demo_selector_clear_new_stage = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
demo_instructions_activeTrue- Execute
drawDemoInstructions(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
screenTransitionActive(&stage_transition)True- Execute
screenTransitionDraw(&stage_transition, frame, last_update_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
screenRevealActive(&stage_transition)True- Execute
screenRevealDraw(&stage_transition, frame, last_update_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
screenScrollActive(&stage_transition) && !stage_scroll_in_requestedTrue- Execute
screenScrollDraw(&stage_transition, frame, last_update_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
screenSliceActive(&stage_transition)True- Execute
screenSliceDraw(&stage_transition, frame, last_update_ms); - Exit this function
return;
FalseContinue after this decision.
- Execute
clear(frame, COLOR_BLACK); - Decision
selected_stage == DEMO_STAGE_APPROACHTrue- Execute
drawApproachStage(frame);
False- Decision
selected_stage == DEMO_STAGE_AMIGA_BALLTrue- Execute
drawStageThreeBall(frame);
False- Decision
selected_stage == DEMO_STAGE_SCROLLERTrueFalse- Decision
selected_stage == DEMO_STAGE_REVERSE_SCROLLERTrueFalse- Decision
selected_stage == DEMO_STAGE_GEOMETRIC_SOLIDTrue- Execute
drawStageSixSolid(frame);
False- Decision
selected_stage == DEMO_STAGE_WALLOONSTrue- Execute
demoStageWalloonsDraw(frame, last_update_ms);
False- Decision
selected_stage == DEMO_STAGE_KALEIDOTrue- Execute
demoStageKaleidoDraw(frame);
False- Decision
selected_stage == DEMO_STAGE_SPARKLETrue- Execute
demoStageSparkleDraw(frame);
False- Decision
selected_stage == DEMO_STAGE_SQUARESTrue- Execute
demoStageSquaresDraw(frame);
False- Decision
selected_stage == DEMO_STAGE_RASTER_BANDSTrue- Execute
drawRasterBands(frame);
False- Decision
selected_stage == DEMO_STAGE_SLIDESHOW_LENSTrue- Execute
demoStageSlideshowLensDraw(frame);
False- Decision
selected_stage == DEMO_STAGE_PLASMATrue- Execute
demoStagePlasmaDraw(frame);
False- Decision
selected_stage == DEMO_STAGE_GAME_OF_LIFETrue- Execute
demoStageGameOfLifeDraw(frame);
False- Decision
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLERTrue- Execute
drawRasterBands(frame);
False- Decision
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTERTrue- Execute
drawStageFifteenBattleZoneBackground(frame); - Execute
drawRasterBands(frame);
False- Decision
selected_stage == DEMO_STAGE_BEVEL_GEARSTrue- Execute
drawStageSeventeenBevelGears(frame);
False- Decision
selected_stage == DEMO_STAGE_MOUSE_MAZETrue- Execute
demoStageMouseMazeDraw(frame);
False- Decision
selected_effect == DEMO_EFFECT_DOT_GRID || selected_effect == DEMO_EFFECT_DOT_SPARKLE || selected_effect == DEMO_EFFECT_RADIAL_FLAMETrue- Execute
const bool sparkle_enabled = selected_effect == DEMO_EFFECT_DOT_SPARKLE; - Decision
selected_effect == DEMO_EFFECT_RADIAL_FLAMETrue- Execute
draw_radial_flame(frame, &demo_ring_flame, DEMO_CENTRE_X, DEMO_CENTRE_Y, demoRingInnerRadius(), yaw_degrees, demo_ring_colours);
FalseContinue after this decision.
- Execute
const dotted_bitmap_3d_params_t dotted_sprite = { .centre_x = DEMO_CENTRE_X, .centre_y = DEMO_CENTRE_Y, .scale = DEMO_FACE_SCALE, .pitch_degrees = pitch_degrees, .yaw_degrees = yaw_degrees, .roll_degrees = roll_degrees, .depth = (float)DEMO_EXTRUSION_DEPTH, .camera_distance = DEMO_CAMERA_DISTANCE, .depth_dot_spacing = DEMO_DOT_SPACING, .dot_radius = DEMO_DOT_RADIUS, .dot_density_percent = DEMO_DOT_DENSITY_PERCENT, .random_seed = DEMO_DOT_RANDOM_SEED, .sparkle_density_percent = sparkle_enabled ? DEMO_DOT_SPARKLE_DENSITY_PERCENT : 0U, .sparkle_phase = sparkle_phase, .sparkle_radius = DEMO_DOT_SPARKLE_RADIUS, .sparkle_color = DEMO_DOT_SPARKLE_COLOR, .front_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].front, .back_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].dotted_back, .side_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth, }; - Execute
draw_bitmap_dotted_3d(frame, active_logo_bitmap, DEMO_LOGO_WIDTH, DEMO_LOGO_HEIGHT, &dotted_sprite);
False- Execute
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f; - Execute
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f; - Execute
const float roll_radians = roll_degrees * DEMO_PI / 180.0f; - Execute
const demo_rotation_t rotation = { .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians), .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians), .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians), }; - Decision
rotation.pitch_cosine * rotation.yaw_cosine >= 0.0fTrue- FOR loop
unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U; depth -= DEMO_EXTRUSION_STEPRepeat body while the loop condition permits; then continue below.
- Execute
drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
- Execute
drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
False- Execute
drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].front); - FOR loop
unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH; depth += DEMO_EXTRUSION_STEPRepeat body while the loop condition permits; then continue below.
- Execute
drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
- Decision
selected_stage == DEMO_STAGE_SCROLLER || selected_stage == DEMO_STAGE_REVERSE_SCROLLERTrue- Execute
drawTextCornerIcons(frame);
FalseContinue after this decision.
- Execute
drawScrollText(frame); - Decision
stage_scroll_in_requestedTrue- Decision
screenScrollSetIncoming(&stage_transition, frame, stage_scroll_in_direction, DEMO_STAGE_SCROLL_GAP_PIXELS)True- Execute
screenScrollDraw(&stage_transition, frame, last_update_ms);
FalseContinue after this decision.
- Execute
stage_scroll_in_requested = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
stage_reveal_requestedTrue- Decision
screenRevealStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_REBUILD_MS, DEMO_STAGE_REBUILD_PERCENT)True- Execute
screenRevealDraw(&stage_transition, frame, last_update_ms);
FalseContinue after this decision.
- Execute
stage_reveal_requested = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
stage_zoom_in_requestedTrue- Decision
screenTransitionZoomInStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_ZOOM_IN_MS)True- Execute
stage_zooming_in = true; - Execute
screenTransitionDraw(&stage_transition, frame, last_update_ms);
FalseContinue after this decision.
- Execute
stage_zoom_in_requested = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
stage_change_requestedTrue- Select a case
selected_stage_transitionEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case DEMO_STAGE_TRANSITION_SHRINK_ZOOM: - Decision
!screenTransitionStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_TRANSITION_FREEZE_MS, DEMO_STAGE_TRANSITION_ZOOM_MS)True- Execute
advanceDemoStage(last_update_ms); - Execute
stage_zoom_in_requested = true;
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
case DEMO_STAGE_TRANSITION_REBUILD: - Execute
advanceDemoStage(last_update_ms); - Execute
stage_reveal_requested = true; - Exit enclosing loop or switch
break; - Case / label
case DEMO_STAGE_TRANSITION_SCROLL: - Decision
!screenScrollOutStart(&stage_transition, frame, last_update_ms, stage_scroll_out_direction, DEMO_STAGE_SCROLL_STEP_MS, DEMO_STAGE_SCROLL_PIXELS_PER_STEP)True- Execute
advanceDemoStage(last_update_ms); - Execute
stage_scroll_in_requested = true;
False- Execute
advanceDemoStage(last_update_ms); - Execute
stage_scroll_in_requested = true;
- Exit enclosing loop or switch
break; - Case / label
case DEMO_STAGE_TRANSITION_SLICES: - Decision
!screenSliceStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_SLICE_COUNT, DEMO_STAGE_SLICE_MS)True- Execute
advanceDemoStage(last_update_ms); - Execute
stage_zoom_in_requested = true;
FalseContinue after this decision.
- Exit enclosing loop or switch
break; - Case / label
default: - Exit enclosing loop or switch
break;
- Execute
stage_change_requested = false;
FalseContinue after this decision.
Read original function source
void drawDemo(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (demo_selector_active) {
drawDemoSelector(frame);
return;
}
if (demo_selector_clear_new_stage) {
clear(frame, COLOR_BLACK);
demo_selector_clear_new_stage = false;
return;
}
if (demo_instructions_active) {
drawDemoInstructions(frame);
return;
}
if (screenTransitionActive(&stage_transition)) {
screenTransitionDraw(&stage_transition, frame, last_update_ms);
return;
}
if (screenRevealActive(&stage_transition)) {
screenRevealDraw(&stage_transition, frame, last_update_ms);
return;
}
if (screenScrollActive(&stage_transition) && !stage_scroll_in_requested) {
screenScrollDraw(&stage_transition, frame, last_update_ms);
return;
}
if (screenSliceActive(&stage_transition)) {
screenSliceDraw(&stage_transition, frame, last_update_ms);
return;
}
clear(frame, COLOR_BLACK);
if (selected_stage == DEMO_STAGE_APPROACH) {
drawApproachStage(frame);
} else if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
drawStageThreeBall(frame);
} else if (selected_stage == DEMO_STAGE_SCROLLER) {
// The rainbow magnifier scroller is drawn below on this clear black background.
} else if (selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
// The inverse rainbow magnifier scroller is drawn below on this clear black background.
} else if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
drawStageSixSolid(frame);
} else if (selected_stage == DEMO_STAGE_WALLOONS) {
demoStageWalloonsDraw(frame, last_update_ms);
} else if (selected_stage == DEMO_STAGE_KALEIDO) {
demoStageKaleidoDraw(frame);
} else if (selected_stage == DEMO_STAGE_SPARKLE) {
demoStageSparkleDraw(frame);
} else if (selected_stage == DEMO_STAGE_SQUARES) {
demoStageSquaresDraw(frame);
} else if (selected_stage == DEMO_STAGE_RASTER_BANDS) {
drawRasterBands(frame);
} else if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
demoStageSlideshowLensDraw(frame);
} else if (selected_stage == DEMO_STAGE_PLASMA) {
demoStagePlasmaDraw(frame);
} else if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
demoStageGameOfLifeDraw(frame);
} else if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
drawRasterBands(frame);
} else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
// The real Battle Zone renderer is compressed into the lower half;
// the compact raster then travels only in the clear upper half.
drawStageFifteenBattleZoneBackground(frame);
drawRasterBands(frame);
} else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
drawStageSeventeenBevelGears(frame);
} else if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
demoStageMouseMazeDraw(frame);
} else if (selected_effect == DEMO_EFFECT_DOT_GRID ||
selected_effect == DEMO_EFFECT_DOT_SPARKLE ||
selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
const bool sparkle_enabled = selected_effect == DEMO_EFFECT_DOT_SPARKLE;
if (selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
draw_radial_flame(frame, &demo_ring_flame, DEMO_CENTRE_X, DEMO_CENTRE_Y,
demoRingInnerRadius(), yaw_degrees, demo_ring_colours);
}
const dotted_bitmap_3d_params_t dotted_sprite = {
.centre_x = DEMO_CENTRE_X,
.centre_y = DEMO_CENTRE_Y,
.scale = DEMO_FACE_SCALE,
.pitch_degrees = pitch_degrees,
.yaw_degrees = yaw_degrees,
.roll_degrees = roll_degrees,
.depth = (float)DEMO_EXTRUSION_DEPTH,
.camera_distance = DEMO_CAMERA_DISTANCE,
.depth_dot_spacing = DEMO_DOT_SPACING,
.dot_radius = DEMO_DOT_RADIUS,
.dot_density_percent = DEMO_DOT_DENSITY_PERCENT,
.random_seed = DEMO_DOT_RANDOM_SEED,
.sparkle_density_percent = sparkle_enabled ? DEMO_DOT_SPARKLE_DENSITY_PERCENT : 0U,
.sparkle_phase = sparkle_phase,
.sparkle_radius = DEMO_DOT_SPARKLE_RADIUS,
.sparkle_color = DEMO_DOT_SPARKLE_COLOR,
.front_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].front,
.back_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].dotted_back,
.side_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth,
};
draw_bitmap_dotted_3d(frame, active_logo_bitmap, DEMO_LOGO_WIDTH,
DEMO_LOGO_HEIGHT, &dotted_sprite);
} else {
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = cosf(pitch_radians),
.pitch_sine = sinf(pitch_radians),
.yaw_cosine = cosf(yaw_radians),
.yaw_sine = sinf(yaw_radians),
.roll_cosine = cosf(roll_radians),
.roll_sine = sinf(roll_radians),
};
// The body depth changes screen depth by pitch_cosine * yaw_cosine.
// Draw from far to near so the random front colour is never painted through a nearer slice.
if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
depth -= DEMO_EXTRUSION_STEP) {
drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
} else {
drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
depth += DEMO_EXTRUSION_STEP) {
drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
}
}
if (selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
drawTextCornerIcons(frame);
}
drawScrollText(frame);
if (stage_scroll_in_requested) {
// The new stage is complete in the shared frame but has not reached the LCD yet.
// Capture it as the second moving frame, then scroll both frames together.
if (screenScrollSetIncoming(&stage_transition, frame, stage_scroll_in_direction,
DEMO_STAGE_SCROLL_GAP_PIXELS)) {
screenScrollDraw(&stage_transition, frame, last_update_ms);
}
stage_scroll_in_requested = false;
return;
}
if (stage_reveal_requested) {
// Capture the completed destination before the reusable 8 x 8 rebuild effect hides it.
if (screenRevealStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_REBUILD_MS,
DEMO_STAGE_REBUILD_PERCENT)) {
screenRevealDraw(&stage_transition, frame, last_update_ms);
}
stage_reveal_requested = false;
return;
}
if (stage_zoom_in_requested) {
// This scene has been drawn into the shared frame but not presented yet: capture it
// and replace it with the first one-pixel frame of the incoming zoom.
if (screenTransitionZoomInStart(&stage_transition, frame, last_update_ms,
DEMO_STAGE_ZOOM_IN_MS)) {
stage_zooming_in = true;
screenTransitionDraw(&stage_transition, frame, last_update_ms);
}
stage_zoom_in_requested = false;
return;
}
if (stage_change_requested) {
switch (selected_stage_transition) {
case DEMO_STAGE_TRANSITION_SHRINK_ZOOM:
// Freeze the completed old stage before updateDemo selects and captures the new one.
if (!screenTransitionStart(&stage_transition, frame, last_update_ms,
DEMO_STAGE_TRANSITION_FREEZE_MS,
DEMO_STAGE_TRANSITION_ZOOM_MS)) {
advanceDemoStage(last_update_ms);
stage_zoom_in_requested = true;
}
break;
case DEMO_STAGE_TRANSITION_REBUILD:
// Select the destination now; drawDemo captures it next frame for its block reveal.
advanceDemoStage(last_update_ms);
stage_reveal_requested = true;
break;
case DEMO_STAGE_TRANSITION_SCROLL:
// Capture after the completed outgoing scene, then translate that exact image away.
if (!screenScrollOutStart(&stage_transition, frame, last_update_ms,
stage_scroll_out_direction,
DEMO_STAGE_SCROLL_STEP_MS,
DEMO_STAGE_SCROLL_PIXELS_PER_STEP)) {
advanceDemoStage(last_update_ms);
stage_scroll_in_requested = true;
} else {
// Select and draw the new stage next frame while the captured old one moves away.
advanceDemoStage(last_update_ms);
stage_scroll_in_requested = true;
}
break;
case DEMO_STAGE_TRANSITION_SLICES:
// Freeze the outgoing stage as alternating horizontal bands before entering the next.
if (!screenSliceStart(&stage_transition, frame, last_update_ms,
DEMO_STAGE_SLICE_COUNT, DEMO_STAGE_SLICE_MS)) {
advanceDemoStage(last_update_ms);
stage_zoom_in_requested = true;
}
break;
default:
break;
}
stage_change_requested = false;
}
}demoScreenshotLabel() · source line 4401
const char *demoScreenshotLabel(void)
Calls into this documentation:
- Exit this function
return demoSelectorStageScreenshotLabel(selected_stage);
Read original function source
const char *demoScreenshotLabel(void)
{
return demoSelectorStageScreenshotLabel(selected_stage);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "demo.h"
#include "PET2001_font.h"
#include "battle_zone.h"
#include "c64_colors.h"
#include "demo_selector.h"
#include "demo_stage_game_of_life.h"
#include "demo_stage_kaleido.h"
#include "demo_stage_mouse_maze.h"
#include "demo_stage_plasma.h"
#include "demo_stage_sparkle.h"
#include "demo_stage_slideshow_lens.h"
#include "demo_stage_squares.h"
#include "demo_stage_walloons.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp_random.h"
#include "game_graphics.h"
#include "image_decoder.h"
#include "screen_transition.h"
#include "starfield.h"
#include <dirent.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
// Write-permission check: added on 2026-10-08 at John's request.
// Second write-permission check: added after VS Code was opened.
// Segment 1 -- rotating Demo icon showcase. It automatically cycles through the
// same four visual effects that A selects manually: dot grid, solid 3D icon,
// sparkling dots, and the radial flame ring. The shared Demo timing and ticker
// controls come first; the Segment 1-specific effect controls are below.
// Visual tuning controls. These values can be changed without changing the renderer.
// Higher frame time reduces CPU work; lower values make motion update more often.
#define DEMO_FRAME_MS 20U
// The control reference appears before Segment 1. Change this one value to
// tune how long it remains visible when Demo starts.
#define DEMO_INSTRUCTIONS_DURATION_MS 20000U
// Rotation rates. Different values make the logo tumble rather than turn in lockstep.
#define DEMO_PITCH_DEGREES_PER_SECOND 37.0f
#define DEMO_YAW_DEGREES_PER_SECOND 60.0f
#define DEMO_ROLL_DEGREES_PER_SECOND 23.0f
// PET ticker controls. Positive speed scrolls left; negative speed scrolls right.
#define DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND 140.0f
#define DEMO_SCROLL_MAX_PIXELS_PER_SECOND 420.0f
// The stick must leave this centre zone before it changes the retained speed.
#define DEMO_SCROLL_JOYSTICK_DEAD_ZONE 250
// At this deflection the ticker reaches its full acceleration rate.
#define DEMO_SCROLL_JOYSTICK_FULL_TRAVEL 1900.0f
// Full left adds leftward speed; full right removes it and then adds rightward speed.
#define DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED 240.0f
// Scale 2 makes the PET ticker 16 pixels high; keep its baseline at the display bottom.
#define DEMO_SCROLL_SCALE 2
#define DEMO_SCROLL_Y (LCD_HEIGHT - PET2001_FONT_GLYPH_HEIGHT * DEMO_SCROLL_SCALE)
// The bottom PET ticker uses only this permanent per-character sine wave.
#define DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE 8 //12
// Rainbow ticker tuning. Lower milliseconds move the colour band faster;
// more glyphs per colour make each visible colour band wider.
#define DEMO_SCROLL_RAINBOW_MS_PER_COLOUR 90U // 75 ms per colour is 13.3 colours per second
#define DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR 4U // 4 glyphs per colour is 10 pixels per colour at scale 2
// Segment 4 -- centred rainbow magnifying scroller. The normal PET ticker moves
// through the middle of the screen; glyphs expand as they cross the centre.
// Segment 5 uses these same values in reverse: glyphs are smallest at centre
// and largest at the left and right edges.
#define DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X (LCD_WIDTH / 2)
#define DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y (LCD_HEIGHT / 2)
#define DEMO_STAGE_FOUR_MAGNIFIER_RADIUS 160.0f
#define DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE 6.5f
// Extra horizontal expansion near the lens centre. Keep this below 3.0 so the
// transformed glyph positions remain in their original left-to-right order.
#define DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH 2.75f
// Text-stage icon tuning. Slot numbers are zero-based: 0 is icon 1, 1 is
// icon 2, and 2 is the current icon-3 placeholder. Change this order to
// repeat one icon in every corner or arrange the available icons differently.
// Each icon stays on its original top/bottom row and independently glides from
// its outer corner to the screen-centre X line and back. These controls are
// deliberately independent of the Stage 4/5 text magnifier and scroll speed.
#define DEMO_TEXT_CORNER_ICON_SCALE_MIN 0.34f
#define DEMO_TEXT_CORNER_ICON_SCALE_MAX 0.48f
#define DEMO_TEXT_CORNER_ICON_DEPTH 12U
#define DEMO_TEXT_CORNER_ICON_DEPTH_STEP 3U
// Every icon chooses a new, independent duration in this inclusive range after
// completing edge -> centre X -> edge. Raise both values for slower travel.
#define DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS 4200U
#define DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS 9800U
// This only separates colour/size pulse timing. Travel itself is random per icon.
#define DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS 900U
#define DEMO_TEXT_CORNER_ICON_PULSE_MS 1500U
#define DEMO_TEXT_CORNER_ICON_COLOUR_MS 220U
// Each icon picks its own signed rotation speed when the text segment starts.
// A positive value turns clockwise; a negative value turns anti-clockwise.
#define DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND 18
#define DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND 52
// Segment 6 -- filled rotating geometric solids. A random catalogue mesh and a
// new colour scheme appear every five seconds; B chooses another mesh immediately.
// The mesh coordinates are scaled before projection;
// keep the radius below the camera distance so the solid cannot cross the camera.
#define DEMO_STAGE_SIX_SOLID_SCALE 56.0f
#define DEMO_STAGE_SIX_CAMERA_DISTANCE 260.0f
// This is the centre behind the bottom ticker. Change only these two values to
// reposition every generic mesh without editing its vertex data.
#define DEMO_STAGE_SIX_CENTRE_X (LCD_WIDTH / 2)
#define DEMO_STAGE_SIX_CENTRE_Y (LCD_HEIGHT / 2)
// Segment 7 -- a direct PET-character tribute to the 1978 "Walloons" listing.
// Every coordinate below is measured in PET cells: the Mini Arcade display is
// 40 cells wide by 21 cells high at the PET ROM's native 8 x 8 cell size.
// Increase the step interval to make the original stop-motion movement slower.
#define DEMO_STAGE_SEVEN_STEP_MS 120U
#define DEMO_STAGE_SEVEN_TITLE_MS 2000U
#define DEMO_STAGE_SEVEN_PAUSE_MS 650U
#define DEMO_STAGE_SEVEN_LANDING_MS 360U
#define DEMO_STAGE_SEVEN_BOUNCE_COUNT 3U
#define DEMO_STAGE_SEVEN_PET_COLOUR C64_COLOR_LIGHT_GREEN
// Raise the complete PET set by two 8-pixel rows so its lowest fulcrum cell
// remains above Demo's 16-pixel bottom ticker. Keep this negative while the
// Walloons segment shares the normal scroller.
#define DEMO_STAGE_SEVEN_ROW_OFFSET -2
// PET glyph 64 (0x40) is a one-pixel horizontal line. Do not use the frame's
// glyph 127 here: it is a block/checker pattern rather than a seesaw beam.
#define DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH 64U
// Segment 11 -- a C64-style rolling raster-band backdrop beneath a centred PET scroller.
// The scroller remains at normal size and keeps its per-glyph sine wave; move
// only this centre value to raise or lower the whole text line.
#define DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y (LCD_HEIGHT / 2)
#define DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE 8
// The full-width band block is one third of the display high and drifts from
// the top screen edge to the bottom edge. One-pixel bands recreate the fine
// metallic scanlines of the reference image.
#define DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT (LCD_HEIGHT / 3)
#define DEMO_STAGE_ELEVEN_BAND_HEIGHT 1
#define DEMO_STAGE_ELEVEN_BAND_DRIFT_MS 5200U
// The metallic profile shifts by one scanline at this interval, independently
// of the bar's physical travel. Lower values make its colours roll faster.
#define DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS 28U
// Segment 15 reuses the metallic rolling palette below, but keeps it fixed
// across the whole lower half while its larger rainbow text crosses the middle.
// The larger advance prevents the scale-3 PET letters from overlapping.
#define DEMO_STAGE_FOURTEEN_BAND_TOP (LCD_HEIGHT / 2)
#define DEMO_STAGE_FOURTEEN_BAND_HEIGHT (LCD_HEIGHT - DEMO_STAGE_FOURTEEN_BAND_TOP)
#define DEMO_STAGE_FOURTEEN_SCROLL_SCALE 3.0f
#define DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE 10
// Keep the lowest point of the sine wave this many pixels above the raster bar.
// The centre position is derived so changing the scale or wave amplitude keeps
// that clear gap automatically.
#define DEMO_STAGE_FOURTEEN_SCROLL_GAP_PIXELS 1
#define DEMO_STAGE_FOURTEEN_SCROLL_PIXEL_HEIGHT \
(PET2001_FONT_GLYPH_HEIGHT * (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE)
#define DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y \
(DEMO_STAGE_FOURTEEN_BAND_TOP - DEMO_STAGE_FOURTEEN_SCROLL_GAP_PIXELS - \
(DEMO_STAGE_FOURTEEN_SCROLL_PIXEL_HEIGHT / 2) - \
DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE)
// Segment 16 -- render the normal Battle Zone landscape, then scale its ground
// portion into the lower half of this Demo frame. The compact raster remains in
// the clear top half, and the scale-2 PET scroller ends at the halfway horizon.
#define DEMO_STAGE_FIFTEEN_HORIZON_Y (LCD_HEIGHT / 2)
#define DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT (LCD_HEIGHT / 6)
#define DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS 5200U
// One complete ground-texture journey from the horizon to the viewer. Lower
// values make the Battle Zone terrain appear to travel forward faster.
#define DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS 3000U
#define DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE 8
#define DEMO_STAGE_FIFTEEN_SCROLL_GAP_PIXELS 1
#define DEMO_STAGE_FIFTEEN_SCROLL_PIXEL_HEIGHT \
(PET2001_FONT_GLYPH_HEIGHT * DEMO_SCROLL_SCALE)
#define DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y \
(DEMO_STAGE_FIFTEEN_HORIZON_Y - DEMO_STAGE_FIFTEEN_SCROLL_GAP_PIXELS - \
(DEMO_STAGE_FIFTEEN_SCROLL_PIXEL_HEIGHT / 2) - \
DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE)
// Segment 17 -- a triangular bevel-gear mechanism. The three wide, toothed
// front rims taper into narrower rear tooth rings, so the assembly reads as
// machinery extending backward into the screen rather than flat linked circles.
// Visual tuning controls. Keep the tooth count even and the three gear centres
// close enough that the two driven rims overlap the lead gear at the mesh points.
#define DEMO_STAGE_SEVENTEEN_TOOTH_COUNT 12U
#define DEMO_STAGE_SEVENTEEN_FRONT_RADIUS 36.0f
#define DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO 0.76f
#define DEMO_STAGE_SEVENTEEN_REAR_RADIUS 15.0f
#define DEMO_STAGE_SEVENTEEN_CONE_DEPTH 50.0f
#define DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE 310.0f
#define DEMO_STAGE_SEVENTEEN_CENTRE_X (LCD_WIDTH / 2)
// Leave the lowest 16 pixels free for the permanent scale-2 PET ticker.
#define DEMO_STAGE_SEVENTEEN_CENTRE_Y 76
// The rig tumbles in place only; these rates deliberately do not move it toward
// the viewer. Individual gear rates stay linked but alternate direction.
#define DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND 12.0f
#define DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND 18.0f
#define DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND 7.0f
#define DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND 92.0f
// The upper lead gear meshes with both lower driven gears. The lower pair keeps
// a narrow deliberate gap: three external gears cannot all mesh pair-to-pair in
// a closed triangle while retaining their required alternating rotation.
#define DEMO_STAGE_SEVENTEEN_DRIVEN_X 32.0f
#define DEMO_STAGE_SEVENTEEN_LEAD_Y -33.0f
#define DEMO_STAGE_SEVENTEEN_DRIVEN_Y 22.0f
// Segment 12 -- one randomly selected image from the existing slideshow
// directory and moves a circular lens across it. The decoded source lives in
// PSRAM only while this stage is active.
#define DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY "/spiffs/slideshow"
#define DEMO_STAGE_TWELVE_IMAGE_PATH_MAX 128U
// Lens size in pixels. Edge-to-edge routes deliberately allow part of this
// circle to extend beyond the display, so it can travel across the full height.
#define DEMO_STAGE_TWELVE_LENS_RADIUS 63
// Image scale inside the lens. Values above 1.0 magnify; larger values show a
// smaller source area and make the effect stronger.
#define DEMO_STAGE_TWELVE_LENS_MAGNIFICATION 3.0f
// Every new random path lasts MIN through MIN + RANGE - 1 milliseconds. Lower
// both values for quicker movement while retaining the smooth glide between
// targets. A new target begins immediately after the prior one completes.
#define DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS 1400U
#define DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS 1200U
// Reject edge targets closer than this many pixels to the current lens centre.
// This prevents a random corner-to-corner route from looking like a small hop.
#define DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE 48.0f
#define DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS 8U
// While Segment 12 is visible, load a different random slideshow image at
// this interval. The same PSRAM buffer is reused; no second full-screen image
// allocation is needed for the timed picture change.
#define DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS 10000U
// Width and colour of the circle outline. Keep rim width below the radius.
#define DEMO_STAGE_TWELVE_LENS_RIM_WIDTH 2
#define DEMO_STAGE_TWELVE_LENS_RIM_COLOUR C64_COLOR_CYAN
// Segment selector controls. The selector is an internal Demo pause screen, so its
// joystick repeat is independent of both the menu and the active segment controls.
#define DEMO_SELECTOR_JOYSTICK_ENTER 600
#define DEMO_SELECTOR_JOYSTICK_RELEASE 350
#define DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS 400U
#define DEMO_SELECTOR_JOYSTICK_REPEAT_MS 140U
#define DEMO_SELECTOR_FIRST_ROW_Y 26
#define DEMO_SELECTOR_ROW_HEIGHT 7
// This is intentionally wider than the joystick control dead zones. It prevents a
// slightly off-centre resting stick from continuously resetting the Demo timeout.
// Lower it if a gentler stick movement should count as user interaction.
#define DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE 600
// Stage 6 catalogue solids still change independently; this does not reset the
// shared inactivity timer because it is an automatic, not user-triggered, change.
#define DEMO_STAGE_SIX_CHANGE_MS 5000U
// Move the whole effect. Keep the logo near the centre to avoid screen clipping.
#define DEMO_CENTRE_X (LCD_WIDTH / 2)
#define DEMO_CENTRE_Y (LCD_HEIGHT / 2)
#define DEMO_PI 3.14159265f
// Bitmap layout controls. Change these only when replacing both Demo bitmap assets.
#define DEMO_LOGO_WIDTH 64U
#define DEMO_LOGO_HEIGHT 64U
#define DEMO_LOGO_ROW_BYTES (DEMO_LOGO_WIDTH / 8U)
// Set to 1 to use the smaller 112 x 112 plane. This leaves margin during a full
// Z-axis turn. The default 128 x 128 plane is larger and may briefly clip at its corners.
#define DEMO_CONSTRAIN_TO_DISPLAY 0
#if DEMO_CONSTRAIN_TO_DISPLAY
#define DEMO_FACE_SCALE 1.75f
#else
#define DEMO_FACE_SCALE 2.0f
#endif
// 3D body width in projected source units. Keep depth an exact multiple of step.
#define DEMO_EXTRUSION_DEPTH 24U
// Distance between body slices. Smaller values look smoother but cost more drawing time.
#define DEMO_EXTRUSION_STEP 3U
// Perspective strength. Larger values flatten the 3D effect; smaller values exaggerate it.
// Keep this positive and high enough that the enlarged logo remains on screen.
#define DEMO_CAMERA_DISTANCE 320.0f
// Dotted-sprite tuning. Face dots use one bitmap cell (four screen pixels) between
// centres; this spacing controls only the dotted depth joins.
#define DEMO_DOT_SPACING 6U // distance in pixles between each dot "face"
#define DEMO_DOT_RADIUS 1
// Percentage of available dots shown on every letter row and depth join.
// 100 draws all dots; lower values create a stable random sparse pattern.
#define DEMO_DOT_DENSITY_PERCENT 50U
// Change this only to choose a different stable sparse-dot pattern.
#define DEMO_DOT_RANDOM_SEED 0x4A440001U
// Starfield-style sparkle tuning. A small subset of front dots grows brighter
// every phase; set density to zero to disable this optional extra.
#define DEMO_DOT_SPARKLE_DENSITY_PERCENT 10U
#define DEMO_DOT_SPARKLE_INTERVAL_MS 300U
#define DEMO_DOT_SPARKLE_COLOR COLOR_LIGHTYELLOW
#define DEMO_DOT_SPARKLE_RADIUS 2
// Radial flame tuning. The inner edge follows the projected icon and keeps this gap.
#define DEMO_RING_ICON_GAP 14.0f
#define DEMO_RING_SEGMENTS 72U
#define DEMO_RING_LAYERS 20U
#define DEMO_RING_LAYER_DEPTH 2.0f
// Wavy flame tuning. More crests make shorter repeating tongues; extra cooling
// makes individual flames die out earlier. These values affect only the ring.
#define DEMO_RING_WAVE_AMPLITUDE 55.0f
#define DEMO_RING_WAVE_COUNT 5.0f
#define DEMO_RING_WAVE_DEGREES_PER_SECOND 45.0f
#define DEMO_RING_EXTRA_COOLING 8U
// Segment 1 automatic effect timing. It selects the next A-button visual effect
// after this interval.
// Lower values make the opening sequence cycle faster; A remains available for manual changes.
#define DEMO_STAGE_ONE_EFFECT_CHANGE_MS 2000U
#define DEMO_EFFECT_COUNT 4U
// Keep this at 0U while B and completed ticker messages may cycle icon slots.
#define DEMO_LOCK_ICON_TO_PRIMARY 0U
// Segment 2 -- approaching 3D icon over a perspective grid. Forward reduces
// range and grows the icon; back increases range.
#define DEMO_STAGE_TWO_START_DISTANCE 130.0f
#define DEMO_STAGE_TWO_MIN_DISTANCE 16.0f
#define DEMO_STAGE_TWO_MAX_DISTANCE 260.0f
#define DEMO_STAGE_TWO_MAX_ICON_PIXELS 144
#define DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE 250
#define DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND 95.0f
// Segment 2 automatically travels between the distance limits while the vertical
// stick is centred. Lower values make its approach and retreat more leisurely.
#define DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND 35.0f
// Segment 3 -- the Demo-only Amiga ball. The private ball keeps changing to the next Demo icon
// at this interval until the shared Demo inactivity timer advances the segment.
#define DEMO_STAGE_THREE_ICON_CHANGE_MS 5000U
// Stage 3 ball tuning. These values are private to Demo and do not affect the
// standalone Boing Ball module. Radius must leave room for the bottom ticker.
#define DEMO_STAGE_THREE_BALL_RADIUS 32
#define DEMO_STAGE_THREE_BALL_DIAMETER (DEMO_STAGE_THREE_BALL_RADIUS * 2 + 1)
#define DEMO_STAGE_THREE_BALL_PIXEL_COUNT \
(DEMO_STAGE_THREE_BALL_DIAMETER * DEMO_STAGE_THREE_BALL_DIAMETER)
#define DEMO_STAGE_THREE_BALL_FLOOR_Y 145.0f
#define DEMO_STAGE_THREE_BALL_GRAVITY 380.0f
#define DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY -215.0f
#define DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY 82.0f
#define DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS 16U
#define DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND 12.0f
// Stage-transition tuning. The old frame pauses, then shrinks to its centre dot.
#define DEMO_STAGE_TRANSITION_FREEZE_MS 180U
#define DEMO_STAGE_TRANSITION_ZOOM_MS 620U
// Incoming-stage tuning. The captured destination frame expands from the centre dot.
#define DEMO_STAGE_ZOOM_IN_MS 620U
// Scroll-transition tuning. Use opposite directions on one axis: LEFT/RIGHT,
// RIGHT/LEFT, UP/DOWN, or DOWN/UP. A horizontal scroll has LCD_WIDTH steps;
// a vertical scroll has LCD_HEIGHT steps, each of the size below.
// Match Demo's 20 ms update cadence so each visible scroll update moves exactly this many pixels.
#define DEMO_STAGE_SCROLL_STEP_MS 20U
#define DEMO_STAGE_SCROLL_PIXELS_PER_STEP 4U
// Gap between the two moving frames. Set this above 0U to leave a black band.
#define DEMO_STAGE_SCROLL_GAP_PIXELS 0U
// Slice-transition tuning. Slice count controls how many horizontal bands split
// apart; duration controls how long all bands take to leave the display.
#define DEMO_STAGE_SLICE_COUNT 12U
#define DEMO_STAGE_SLICE_MS 620U
// Rebuild tuning. This is the percentage of the incoming scene exposed as shuffled blocks
// before its complete frame is shown.
#define DEMO_STAGE_REBUILD_PERCENT 25U
#define DEMO_STAGE_REBUILD_MS 420U
typedef enum {
DEMO_EFFECT_DOT_GRID,
DEMO_EFFECT_3D_JD,
DEMO_EFFECT_DOT_SPARKLE,
DEMO_EFFECT_RADIAL_FLAME,
} demo_effect_t;
typedef enum {
STAGE_SEVENTEEN_STYLE_OUTLINE,
STAGE_SEVENTEEN_STYLE_FLAT,
STAGE_SEVENTEEN_STYLE_METALLIC,
STAGE_SEVENTEEN_STYLE_COUNT,
} stage_seventeen_style_t;
// Every gear contributes one tapered side plus front and rear wedges per
// perimeter point. Keeping this list in PSRAM avoids a large temporary main-task stack.
#define DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT (3U * DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 6U)
typedef struct {
graphics_point_t points[4];
float depth;
uint16_t colour;
uint8_t point_count;
} stage_seventeen_draw_face_t;
typedef enum {
STAGE_TWELVE_LENS_EDGE_TOP,
STAGE_TWELVE_LENS_EDGE_RIGHT,
STAGE_TWELVE_LENS_EDGE_BOTTOM,
STAGE_TWELVE_LENS_EDGE_LEFT,
STAGE_TWELVE_LENS_EDGE_COUNT,
} stage_twelve_lens_edge_t;
// The original program moves character-cell address A by one to walk and by
// forty to rise or fall. These named phases retain that choreography while
// using elapsed milliseconds instead of BASIC FOR/NEXT delay loops.
typedef enum {
WALLOONS_TITLE_CARD,
WALLOONS_LEFT_WALK_IN,
WALLOONS_PAUSE_ON_SEESAW,
WALLOONS_RIGHT_WALK_ON_PLATFORM,
WALLOONS_RIGHT_DROP,
WALLOONS_BOUNCE_RIGHT_UP,
WALLOONS_BOUNCE_RIGHT_DOWN,
WALLOONS_BOUNCE_LEFT_UP,
WALLOONS_BOUNCE_LEFT_DOWN,
WALLOONS_LEFT_LANDED,
WALLOONS_FINISHED,
} walloons_phase_t;
typedef enum {
// Freeze the outgoing frame, shrink it to a dot, then grow the next scene from a dot.
DEMO_STAGE_TRANSITION_SHRINK_ZOOM,
// Reveal a partial random 8 x 8 block pattern before showing the complete next scene.
DEMO_STAGE_TRANSITION_REBUILD,
// Move the old and new frames together in a randomly selected cardinal direction.
DEMO_STAGE_TRANSITION_SCROLL,
// Split the outgoing image into horizontal bands that leave on alternating sides.
DEMO_STAGE_TRANSITION_SLICES,
DEMO_STAGE_TRANSITION_EFFECT_COUNT,
} demo_stage_transition_effect_t;
// Packed 64 x 64 JD-and-circle character. Each row has eight bytes and uses
// most-significant-bit-first pixel order. Set bits are the visible icon shape.
static const uint8_t demo_jd_bitmap[DEMO_LOGO_HEIGHT * DEMO_LOGO_ROW_BYTES] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x3F,0xFC,0x00,0x00,0x00,
0x00,0x00,0x03,0xFF,0xFF,0xC0,0x00,0x00,
0x00,0x00,0x0F,0xF0,0x07,0xF0,0x00,0x00,
0x00,0x00,0x7F,0x00,0x00,0xFE,0x00,0x00,
0x00,0x00,0xFC,0x00,0x00,0x3F,0x00,0x00,
0x00,0x01,0xE0,0x00,0x00,0x07,0x80,0x00,
0x00,0x07,0xC0,0x00,0x00,0x03,0xE0,0x00,
0x00,0x0F,0x00,0x00,0x00,0x00,0xF0,0x00,
0x00,0x1E,0x00,0x00,0x00,0x00,0x78,0x00,
0x00,0x3C,0x3F,0xFF,0xF8,0x00,0x3C,0x00,
0x00,0x78,0x3F,0xFF,0xFC,0x00,0x1E,0x00,
0x00,0x70,0x3F,0xFF,0xFC,0x00,0x0E,0x00,
0x00,0xE0,0x03,0xFF,0x00,0x00,0x07,0x00,
0x01,0xE0,0x00,0xFC,0x00,0x00,0x03,0x80,
0x01,0xC0,0x00,0x78,0x00,0x00,0x03,0x80,
0x03,0x80,0x00,0x78,0x00,0x00,0x01,0xC0,
0x03,0x80,0x00,0x78,0x00,0x00,0x01,0xC0,
0x07,0x00,0x00,0x79,0xFF,0xFC,0x00,0xE0,
0x07,0x00,0x00,0x79,0xFF,0xFF,0x00,0xE0,
0x0E,0x00,0x00,0x79,0xFF,0xFF,0x80,0x70,
0x0E,0x00,0x00,0x79,0xF0,0x3F,0xC0,0x70,
0x0C,0x00,0x00,0x79,0xE0,0x0F,0xE0,0x30,
0x1C,0x00,0x00,0x79,0xE0,0x03,0xF0,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x03,0xF8,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x01,0xF8,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0xFC,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0xF8,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0x7C,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0x7C,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x38,
0x1C,0x00,0x00,0x79,0xE0,0x00,0x3C,0x18,
0x1C,0x03,0x80,0x79,0xE0,0x00,0x3C,0x38,
0x1C,0x07,0xC0,0x79,0xE0,0x00,0x3C,0x38,
0x0C,0x0F,0xE0,0x79,0xE0,0x00,0x7C,0x30,
0x0E,0x0F,0xE0,0x79,0xE0,0x00,0x7C,0x70,
0x0E,0x1F,0xE0,0xF9,0xE0,0x00,0xF8,0x70,
0x07,0x1F,0xE0,0xF9,0xE0,0x00,0xF8,0xE0,
0x07,0x1F,0x80,0xF1,0xE0,0x01,0xF8,0xE0,
0x03,0x9E,0x01,0xF1,0xE0,0x03,0xF1,0xC0,
0x03,0x8F,0x07,0xE1,0xE0,0x07,0xE1,0xC0,
0x01,0xCF,0xFF,0xC3,0xE0,0x0F,0xC3,0x80,
0x01,0xE7,0xFF,0x87,0xE0,0x7F,0x87,0x80,
0x00,0xE1,0xFE,0x0F,0xFF,0xFE,0x0F,0x00,
0x00,0x70,0x78,0x1F,0xFF,0xFC,0x0E,0x00,
0x00,0x78,0x00,0x3F,0xFF,0xF0,0x1E,0x00,
0x00,0x3C,0x00,0xFF,0xFF,0xC0,0x3C,0x00,
0x00,0x1E,0x00,0x00,0x00,0x00,0xF8,0x00,
0x00,0x0F,0x00,0x00,0x00,0x00,0xF0,0x00,
0x00,0x07,0xC0,0x00,0x00,0x03,0xE0,0x00,
0x00,0x01,0xF0,0x00,0x00,0x0F,0x80,0x00,
0x00,0x00,0xFC,0x00,0x00,0x3F,0x00,0x00,
0x00,0x00,0x7F,0x00,0x00,0xFE,0x00,0x00,
0x00,0x00,0x1F,0xF0,0x0F,0xF8,0x00,0x00,
0x00,0x00,0x03,0xFF,0xFF,0xC0,0x00,0x00,
0x00,0x00,0x00,0x3F,0xFC,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
// Reserved packed 64 x 64 alternate asset. It shares the primary bitmap's
// dimensions and bit order, ready for a future replacement without renderer changes.
static const uint8_t demo_alternate_bitmap[DEMO_LOGO_HEIGHT * DEMO_LOGO_ROW_BYTES] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,
0x00,0x00,0x07,0xFF,0xFF,0xE0,0x00,0x00,
0x00,0x00,0x1F,0x80,0x01,0xF8,0x00,0x00,
0x00,0x00,0x7C,0x00,0x00,0x3E,0x00,0x00,
0x00,0x00,0xF0,0x00,0x00,0x0F,0x00,0x00,
0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
0x00,0x07,0x00,0x00,0x00,0x00,0xE0,0x00,
0x00,0x0E,0x00,0x00,0x00,0x00,0x70,0x00,
0x00,0x1C,0x00,0x00,0x00,0x00,0x38,0x00,
0x00,0x38,0x00,0x00,0x00,0x00,0x1C,0x00,
0x00,0x70,0x00,0x00,0x00,0x00,0x0E,0x00,
0x00,0xE0,0x00,0x00,0x00,0x00,0x07,0x00,
0x00,0xC0,0x00,0x00,0x00,0x00,0x03,0x00,
0x01,0x80,0x00,0x00,0x00,0x00,0x01,0x80,
0x03,0x80,0x00,0x00,0x00,0x00,0x01,0xC0,
0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,
0x07,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,
0x06,0x07,0xE0,0x0F,0xC1,0xFF,0xE0,0x60,
0x0E,0x07,0xE0,0x0F,0xC1,0xFF,0xE0,0x70,
0x0C,0x07,0x70,0x1D,0xC0,0x1E,0x00,0x30,
0x0C,0x07,0x70,0x1D,0xC0,0x1E,0x00,0x30,
0x1C,0x07,0x38,0x39,0xC0,0x1E,0x00,0x38,
0x18,0x07,0x38,0x39,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x1C,0x71,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x0E,0xE1,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x0E,0xE1,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x07,0xC1,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x07,0xC1,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x03,0x81,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x07,0xC1,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
0x18,0x07,0x00,0x01,0xC0,0x1E,0x00,0x18,
0x1C,0x07,0x00,0x01,0xC0,0x1E,0x00,0x38,
0x0C,0x07,0x00,0x01,0xC0,0x1E,0x00,0x30,
0x0C,0x07,0x00,0x01,0xC0,0x1E,0x00,0x30,
0x0E,0x07,0x00,0x01,0xC0,0x1E,0x00,0x70,
0x06,0x07,0x00,0x01,0xC1,0xFF,0xE0,0x60,
0x07,0x07,0x00,0x01,0xC1,0xFF,0xE0,0xE0,
0x03,0x07,0x00,0x01,0xC0,0x00,0x00,0xC0,
0x03,0x80,0x00,0x00,0x00,0x00,0x01,0xC0,
0x01,0x80,0x00,0x00,0x00,0x00,0x01,0x80,
0x00,0xC0,0x00,0x00,0x00,0x00,0x03,0x00,
0x00,0xE0,0x00,0x00,0x00,0x00,0x07,0x00,
0x00,0x70,0x00,0x00,0x00,0x00,0x0E,0x00,
0x00,0x38,0x00,0x00,0x00,0x00,0x1C,0x00,
0x00,0x1C,0x00,0x00,0x00,0x00,0x38,0x00,
0x00,0x0E,0x00,0x00,0x00,0x00,0x70,0x00,
0x00,0x07,0x00,0x00,0x00,0x00,0xE0,0x00,
0x00,0x03,0xC0,0x00,0x00,0x03,0xC0,0x00,
0x00,0x00,0xF0,0x00,0x00,0x0F,0x00,0x00,
0x00,0x00,0x7C,0x00,0x00,0x3E,0x00,0x00,
0x00,0x00,0x1F,0x80,0x01,0xF8,0x00,0x00,
0x00,0x00,0x07,0xFF,0xFF,0xE0,0x00,0x00,
0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
// Keep three icon slots. The new third slot begins as a duplicate of JD, ready
// to point at its own 64 x 64 bitmap when the next icon is supplied.
static const uint8_t *const demo_icon_slots[] = {
demo_jd_bitmap,
demo_alternate_bitmap,
demo_jd_bitmap,
};
#define DEMO_ICON_SLOT_COUNT (sizeof(demo_icon_slots) / sizeof(demo_icon_slots[0]))
// Top-left, top-right, bottom-left, bottom-right: user-selected order 1, 2, 3, 2.
static const uint8_t demo_text_corner_icon_slots[] = {0U, 1U, 2U, 1U};
static const int demo_text_corner_icon_x[] = {22, LCD_WIDTH - 23, 22, LCD_WIDTH - 23};
static const int demo_text_corner_icon_y[] = {22, 22, LCD_HEIGHT - 23, LCD_HEIGHT - 23};
#define DEMO_TEXT_CORNER_ICON_COUNT \
(sizeof(demo_text_corner_icon_slots) / sizeof(demo_text_corner_icon_slots[0]))
// PET-safe ASCII ticker messages. Curly quotes and the em dash from the source
// list use normal C punctuation so every character has a PET glyph.
static const char *const demo_scroll_messages[] = {
"All that we see or seem is but a dream within a dream. ",
"Deep into that darkness peering, long I stood there wondering, fearing. ",
"There is no exquisite beauty without some strangeness in the proportion. ",
"I became insane, with long intervals of horrible sanity. ",
"Words have no power to impress the mind without the exquisite horror of their reality. ",
"Believe nothing you hear, and only one half that you see. ",
"Those who dream by day are cognizant of many things which escape those who dream only by night. ",
"Nevermore. ",
"----I have always loved all of my children and always will,.-----------",
"And so being young and dipped in folly I fell in love with melancholy. ",
"The best things in life make you sweaty. ",
"A mystery and a dream should be enjoyed as such. ",
"Science has not yet taught us if madness is or is not the sublimity of the intelligence. ",
"The answer, is 42 ",
"And Darkness and Decay and the Red Death held illimitable dominion over all. ",
"I was never really insane except upon occasions when my heart was touched. ",
"From a proud tower in the town Death looks gigantically down. ",
"Sleep, those little slices of death, how I loathe them. ",
"And all my days are trances, And all my nightly dreams. ",
"I was never kinder to the old man than during the whole week before I killed him. ",
"The boundaries which divide Life from Death are at best shadowy and vague. ",
"There is no beauty without some strangeness. ",
"Years of love have been forgot, In the hatred of a minute. ",
"And now was acknowledged the presence of the Red Death. ",
"The rain came down upon my head , Unsheltered. And the wind rendered me mad. ",
"The fever called Living is conquered at last. ",
"Leave my loneliness unbroken. ",
"Take thy beak from out my heart, and take thy form from off my door! ",
"Quoth the Raven, Nevermore. ",
"Deep in earth my love is lying.",
"Thou wouldst be loved? then let thy heart From its present pathway part. ",
"Darkness there, and nothing more. ",
"From a proud tower in the town Death looks gigantically down. ",
"And the life of the ebony bird beguiling my sad fancy into smiling. ",
"All that we see or seem Is but a dream within a dream.",
"I fell in love with melancholy. ",
"The skies they were ashen and sober. ",
"And so being young and dipped in folly I fell in love with melancholy. ",
"The world is a great ocean, upon which we encounter more storms than calms. ",
"There are chords in the hearts of the most reckless which cannot be touched without emotion. ",
"To die laughing must be the most glorious of all glorious deaths! ",
"Stupidly staring at the floor. ",
"And each separate dying ember wrought its ghost upon the floor. ",
"Sorrow for the lost Lenore. ",
"And the silken sad uncertain rustling of each purple curtain. ",
"Once upon a midnight dreary, while I pondered, weak and weary. ",
"And my soul from out that shadow that lies floating on the floor, Shall be lifted—nevermore! ",
};
#define DEMO_SCROLL_MESSAGE_COUNT \
(sizeof(demo_scroll_messages) / sizeof(demo_scroll_messages[0]))
// Bright C64 colours ordered around the ticker's moving rainbow band.
static const uint16_t demo_scroll_rainbow_colours[] = {
C64_COLOR_RED,
C64_COLOR_ORANGE,
C64_COLOR_YELLOW,
C64_COLOR_LIGHT_GREEN,
C64_COLOR_CYAN,
C64_COLOR_LIGHT_PURPLE,
C64_COLOR_PURPLE,
};
// A palette stays in flash because it is tiny, read-only data. The full RGB565
// frame it colours is the large allocation and already lives in PSRAM.
typedef graphics_raster_band_palette_t demo_raster_band_palette_t;
// Stage 11's stationary-in-the-bar silver/lilac gradient. The block itself
// moves, but this profile does not cycle: its dark purple edges and broad white
// centre are the deliberate metallic appearance from the supplied reference.
static const uint16_t demo_stage_eleven_band_colours[] = {
C64_COLOR_LIGHT_GREY,
RGB565(30, 20, 58),
RGB565(46, 34, 82),
RGB565(64, 52, 108),
C64_COLOR_LIGHT_PURPLE,
RGB565(126, 122, 154),
C64_COLOR_GREY,
C64_COLOR_LIGHT_GREY,
RGB565(220, 220, 230),
C64_COLOR_WHITE,
RGB565(220, 220, 230),
C64_COLOR_LIGHT_GREY,
C64_COLOR_GREY,
RGB565(126, 122, 154),
C64_COLOR_LIGHT_PURPLE,
RGB565(64, 52, 108),
RGB565(46, 34, 82),
RGB565(30, 20, 58),
C64_COLOR_LIGHT_GREY,
};
// Segment 15's rolling flame gradient: dark embers at either edge, through
// red and orange, to a hot yellow-white centre. Add future palettes here, then
// give their colour table and length to drawRasterBandBlock().
static const uint16_t demo_raster_fire_band_colours[] = {
COLOR_BLACK,
RGB565(48, 0, 0),
RGB565(104, 0, 0),
C64_COLOR_RED,
RGB565(220, 48, 0),
C64_COLOR_ORANGE,
C64_COLOR_YELLOW,
RGB565(255, 232, 128),
C64_COLOR_WHITE,
RGB565(255, 232, 128),
C64_COLOR_YELLOW,
C64_COLOR_ORANGE,
RGB565(220, 48, 0),
C64_COLOR_RED,
RGB565(104, 0, 0),
RGB565(48, 0, 0),
COLOR_BLACK,
};
// Segment 16's Battle Zone palette: dark terrain green at both edges and a
// bright lime horizon glow through the centre.
static const uint16_t demo_raster_green_band_colours[] = {
COLOR_BLACK,
RGB565(0, 32, 0),
COLOR_DARKGREEN,
RGB565(0, 96, 0),
C64_COLOR_GREEN,
COLOR_LIME,
RGB565(176, 255, 176),
COLOR_LIME,
C64_COLOR_GREEN,
RGB565(0, 96, 0),
COLOR_DARKGREEN,
RGB565(0, 32, 0),
COLOR_BLACK,
};
static const demo_raster_band_palette_t demo_raster_metallic_palette = {
.colours = demo_stage_eleven_band_colours,
.colour_count = sizeof(demo_stage_eleven_band_colours) /
sizeof(demo_stage_eleven_band_colours[0]),
};
static const demo_raster_band_palette_t demo_raster_fire_palette = {
.colours = demo_raster_fire_band_colours,
.colour_count = sizeof(demo_raster_fire_band_colours) /
sizeof(demo_raster_fire_band_colours[0]),
};
static const demo_raster_band_palette_t demo_raster_green_palette = {
.colours = demo_raster_green_band_colours,
.colour_count = sizeof(demo_raster_green_band_colours) /
sizeof(demo_raster_green_band_colours[0]),
};
// Keep every selectable raster palette in this one order. B advances through
// the list, so adding a descriptor here automatically makes it selectable.
#define DEMO_RASTER_METALLIC_PALETTE_INDEX 0U
#define DEMO_RASTER_FIRE_PALETTE_INDEX 1U
#define DEMO_RASTER_GREEN_PALETTE_INDEX 2U
static const demo_raster_band_palette_t *const demo_raster_band_palettes[] = {
&demo_raster_metallic_palette,
&demo_raster_fire_palette,
&demo_raster_green_palette,
};
#define DEMO_RASTER_BAND_PALETTE_COUNT \
(sizeof(demo_raster_band_palettes) / sizeof(demo_raster_band_palettes[0]))
typedef struct {
uint16_t front;
uint16_t depth;
uint16_t dotted_back;
} demo_icon_colour_scheme_t;
typedef struct {
uint16_t face_shades[4];
uint16_t edge;
} demo_solid_colour_scheme_t;
// Demo icon colour themes. One is chosen at each Demo start and is shared by
// the stage-1 icon, stage-2 approach icon, dotted icon effects, and corner icons.
static const demo_icon_colour_scheme_t demo_icon_colour_schemes[] = {
{C64_COLOR_LIGHT_GREEN, C64_COLOR_GREEN, C64_COLOR_ORANGE_BROWN},
{C64_COLOR_YELLOW, C64_COLOR_ORANGE, C64_COLOR_ORANGE_BROWN},
{C64_COLOR_SALMON, C64_COLOR_RED, C64_COLOR_ORANGE_BROWN},
{C64_COLOR_LIGHT_PURPLE, C64_COLOR_PURPLE, C64_COLOR_BLUE},
{C64_COLOR_CYAN, COLOR_CYAN, COLOR_DARKCYAN},
};
#define DEMO_ICON_COLOUR_SCHEME_COUNT \
(sizeof(demo_icon_colour_schemes) / sizeof(demo_icon_colour_schemes[0]))
// Segment-6 solid palettes. Faces use the four levels from brightest to darkest;
// a different palette is selected whenever a new mesh is selected.
static const demo_solid_colour_scheme_t demo_solid_colour_schemes[] = {
{{C64_COLOR_LIGHT_GREEN, C64_COLOR_GREEN, RGB565(50, 105, 30), RGB565(18, 50, 14)},
RGB565(15, 65, 12)},
{{C64_COLOR_YELLOW, RGB565(180, 165, 35), C64_COLOR_ORANGE_BROWN, RGB565(52, 38, 0)},
RGB565(72, 48, 0)},
{{C64_COLOR_SALMON, C64_COLOR_RED, RGB565(105, 42, 35), RGB565(52, 18, 16)},
RGB565(72, 25, 20)},
{{C64_COLOR_LIGHT_PURPLE, C64_COLOR_PURPLE, RGB565(78, 40, 105), RGB565(38, 18, 55)},
RGB565(52, 25, 72)},
{{RGB565(125, 245, 255), RGB565(30, 185, 225), RGB565(12, 100, 170), RGB565(4, 42, 105)},
COLOR_DARKCYAN},
};
#define DEMO_SOLID_COLOUR_SCHEME_COUNT \
(sizeof(demo_solid_colour_schemes) / sizeof(demo_solid_colour_schemes[0]))
typedef struct {
float pitch_cosine;
float pitch_sine;
float yaw_cosine;
float yaw_sine;
float roll_cosine;
float roll_sine;
} demo_rotation_t;
// Generic filled-mesh limits. The largest selected catalogue solid has 60 vertices,
// 62 faces, and ten corners on a face. The renderer keeps extra headroom at 64.
#define DEMO_MESH_MAX_VERTICES 64U
#define DEMO_MESH_MAX_FACES 64U
#define DEMO_MESH_MAX_FACE_VERTICES 10U
typedef struct {
float x;
float y;
float z;
} demo_point_3d_t;
typedef struct {
uint8_t vertex_count;
const uint8_t *vertex_indices;
} demo_mesh_face_t;
typedef struct {
const demo_point_3d_t *vertices;
size_t vertex_count;
const demo_mesh_face_t *faces;
size_t face_count;
} demo_mesh_t;
typedef struct {
size_t face_index;
float depth;
float brightness;
} demo_mesh_face_draw_t;
// Stage 6 starts with this regular dodecahedron. Vertices use the standard
// golden-ratio layout: eight cube corners plus twelve axis-aligned points.
// Face indices stay separate from the renderer, so a later cube or another
// solid only needs its own vertex and face tables. ( i tried other combos,. but its not pretty..jd)
static const demo_point_3d_t demo_dodecahedron_vertices[] = {
{ 1.0f, 1.0f, 1.0f}, { 1.0f, 1.0f, -1.0f},
{ 1.0f, -1.0f, 1.0f}, { 1.0f, -1.0f, -1.0f},
{-1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, -1.0f},
{-1.0f, -1.0f, 1.0f}, {-1.0f, -1.0f, -1.0f},
{ 0.0f, 0.618034f, 1.618034f}, { 0.0f, 0.618034f, -1.618034f},
{ 0.0f, -0.618034f, 1.618034f}, { 0.0f, -0.618034f, -1.618034f},
{ 0.618034f, 1.618034f, 0.0f}, { 0.618034f, -1.618034f, 0.0f},
{-0.618034f, 1.618034f, 0.0f}, {-0.618034f, -1.618034f, 0.0f},
{ 1.618034f, 0.0f, 0.618034f}, { 1.618034f, 0.0f, -0.618034f},
{-1.618034f, 0.0f, 0.618034f}, {-1.618034f, 0.0f, -0.618034f},
};
static const uint8_t demo_dodecahedron_face_indices[][5] = {
{0U, 8U, 10U, 2U, 16U}, {0U, 16U, 17U, 1U, 12U},
{0U, 12U, 14U, 4U, 8U}, {8U, 4U, 18U, 6U, 10U},
{10U, 6U, 15U, 13U, 2U}, {2U, 13U, 3U, 17U, 16U},
{1U, 17U, 3U, 11U, 9U}, {1U, 9U, 5U, 14U, 12U},
{4U, 14U, 5U, 19U, 18U}, {6U, 18U, 19U, 7U, 15U},
{3U, 13U, 15U, 7U, 11U}, {5U, 9U, 11U, 7U, 19U},
};
static const demo_mesh_face_t demo_dodecahedron_faces[] = {
{5U, demo_dodecahedron_face_indices[0]}, {5U, demo_dodecahedron_face_indices[1]},
{5U, demo_dodecahedron_face_indices[2]}, {5U, demo_dodecahedron_face_indices[3]},
{5U, demo_dodecahedron_face_indices[4]}, {5U, demo_dodecahedron_face_indices[5]},
{5U, demo_dodecahedron_face_indices[6]}, {5U, demo_dodecahedron_face_indices[7]},
{5U, demo_dodecahedron_face_indices[8]}, {5U, demo_dodecahedron_face_indices[9]},
{5U, demo_dodecahedron_face_indices[10]}, {5U, demo_dodecahedron_face_indices[11]},
};
static const demo_mesh_t demo_dodecahedron_mesh = {
.vertices = demo_dodecahedron_vertices,
.vertex_count = sizeof(demo_dodecahedron_vertices) / sizeof(demo_dodecahedron_vertices[0]),
.faces = demo_dodecahedron_faces,
.face_count = sizeof(demo_dodecahedron_faces) / sizeof(demo_dodecahedron_faces[0]),
};
// The regular solids keep their familiar silhouette at their matching face totals.
static const demo_point_3d_t demo_cube_vertices[] = {
{-1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f},
{ 1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f},
{-1.0f, -1.0f, 1.0f}, { 1.0f, -1.0f, 1.0f},
{ 1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f},
};
static const uint8_t demo_cube_indices[][4] = {
{0U, 1U, 2U, 3U}, {4U, 7U, 6U, 5U}, {0U, 4U, 5U, 1U},
{1U, 5U, 6U, 2U}, {2U, 6U, 7U, 3U}, {3U, 7U, 4U, 0U},
};
static const demo_mesh_face_t demo_cube_faces[] = {
{4U, demo_cube_indices[0]}, {4U, demo_cube_indices[1]}, {4U, demo_cube_indices[2]},
{4U, demo_cube_indices[3]}, {4U, demo_cube_indices[4]}, {4U, demo_cube_indices[5]},
};
static const demo_mesh_t demo_cube_mesh = {
demo_cube_vertices, sizeof(demo_cube_vertices) / sizeof(demo_cube_vertices[0]),
demo_cube_faces, sizeof(demo_cube_faces) / sizeof(demo_cube_faces[0]),
};
static const demo_point_3d_t demo_octahedron_vertices[] = {
{ 0.0f, 1.414214f, 0.0f}, { 0.0f, -1.414214f, 0.0f},
{ 1.414214f, 0.0f, 0.0f}, {-1.414214f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 1.414214f}, { 0.0f, 0.0f, -1.414214f},
};
static const uint8_t demo_octahedron_indices[][3] = {
{0U, 2U, 4U}, {0U, 4U, 3U}, {0U, 3U, 5U}, {0U, 5U, 2U},
{1U, 4U, 2U}, {1U, 3U, 4U}, {1U, 5U, 3U}, {1U, 2U, 5U},
};
static const demo_mesh_face_t demo_octahedron_faces[] = {
{3U, demo_octahedron_indices[0]}, {3U, demo_octahedron_indices[1]},
{3U, demo_octahedron_indices[2]}, {3U, demo_octahedron_indices[3]},
{3U, demo_octahedron_indices[4]}, {3U, demo_octahedron_indices[5]},
{3U, demo_octahedron_indices[6]}, {3U, demo_octahedron_indices[7]},
};
static const demo_mesh_t demo_octahedron_mesh = {
demo_octahedron_vertices, sizeof(demo_octahedron_vertices) / sizeof(demo_octahedron_vertices[0]),
demo_octahedron_faces, sizeof(demo_octahedron_faces) / sizeof(demo_octahedron_faces[0]),
};
static const demo_point_3d_t demo_icosahedron_vertices[] = {
{-1.0f, 1.618034f, 0.0f}, { 1.0f, 1.618034f, 0.0f},
{-1.0f,-1.618034f, 0.0f}, { 1.0f,-1.618034f, 0.0f},
{ 0.0f,-1.0f, 1.618034f}, { 0.0f, 1.0f, 1.618034f},
{ 0.0f,-1.0f,-1.618034f}, { 0.0f, 1.0f,-1.618034f},
{ 1.618034f, 0.0f,-1.0f}, { 1.618034f, 0.0f, 1.0f},
{-1.618034f, 0.0f,-1.0f}, {-1.618034f, 0.0f, 1.0f},
};
static const uint8_t demo_icosahedron_indices[][3] = {
{0U,11U,5U}, {0U,5U,1U}, {0U,1U,7U}, {0U,7U,10U}, {0U,10U,11U},
{1U,5U,9U}, {5U,11U,4U}, {11U,10U,2U}, {10U,7U,6U}, {7U,1U,8U},
{3U,9U,4U}, {3U,4U,2U}, {3U,2U,6U}, {3U,6U,8U}, {3U,8U,9U},
{4U,9U,5U}, {2U,4U,11U}, {6U,2U,10U}, {8U,6U,7U}, {9U,8U,1U},
};
static const demo_mesh_face_t demo_icosahedron_faces[] = {
{3U, demo_icosahedron_indices[0]}, {3U, demo_icosahedron_indices[1]},
{3U, demo_icosahedron_indices[2]}, {3U, demo_icosahedron_indices[3]},
{3U, demo_icosahedron_indices[4]}, {3U, demo_icosahedron_indices[5]},
{3U, demo_icosahedron_indices[6]}, {3U, demo_icosahedron_indices[7]},
{3U, demo_icosahedron_indices[8]}, {3U, demo_icosahedron_indices[9]},
{3U, demo_icosahedron_indices[10]}, {3U, demo_icosahedron_indices[11]},
{3U, demo_icosahedron_indices[12]}, {3U, demo_icosahedron_indices[13]},
{3U, demo_icosahedron_indices[14]}, {3U, demo_icosahedron_indices[15]},
{3U, demo_icosahedron_indices[16]}, {3U, demo_icosahedron_indices[17]},
{3U, demo_icosahedron_indices[18]}, {3U, demo_icosahedron_indices[19]},
};
static const demo_mesh_t demo_icosahedron_mesh = {
demo_icosahedron_vertices, sizeof(demo_icosahedron_vertices) / sizeof(demo_icosahedron_vertices[0]),
demo_icosahedron_faces, sizeof(demo_icosahedron_faces) / sizeof(demo_icosahedron_faces[0]),
};
// Archimedean solid: truncated tetrahedron.
static const demo_point_3d_t demo_truncated_tetrahedron_vertices[] = {
{0.209877f, 0.749559f, -0.209877f},
{0.749559f, 0.209877f, -0.209877f},
{0.209877f, 0.209877f, -0.749559f},
{-0.209877f, 0.209877f, 0.749559f},
{-0.209877f, 0.749559f, 0.209877f},
{-0.749559f, 0.209877f, 0.209877f},
{0.749559f, -0.209877f, 0.209877f},
{0.209877f, -0.209877f, 0.749559f},
{0.209877f, -0.749559f, 0.209877f},
{-0.209877f, -0.209877f, -0.749559f},
{-0.209877f, -0.749559f, -0.209877f},
{-0.749559f, -0.209877f, -0.209877f},
};
static const uint8_t demo_truncated_tetrahedron_face_indices[][6] = {
{0U, 4U, 3U, 7U, 6U, 1U},
{1U, 6U, 8U, 10U, 9U, 2U},
{0U, 2U, 9U, 11U, 5U, 4U},
{3U, 5U, 11U, 10U, 8U, 7U},
{0U, 1U, 2U},
{3U, 4U, 5U},
{6U, 7U, 8U},
{9U, 10U, 11U},
};
static const demo_mesh_face_t demo_truncated_tetrahedron_faces[] = {
{6U, demo_truncated_tetrahedron_face_indices[0]},
{6U, demo_truncated_tetrahedron_face_indices[1]},
{6U, demo_truncated_tetrahedron_face_indices[2]},
{6U, demo_truncated_tetrahedron_face_indices[3]},
{3U, demo_truncated_tetrahedron_face_indices[4]},
{3U, demo_truncated_tetrahedron_face_indices[5]},
{3U, demo_truncated_tetrahedron_face_indices[6]},
{3U, demo_truncated_tetrahedron_face_indices[7]},
};
static const demo_mesh_t demo_truncated_tetrahedron_mesh = {
demo_truncated_tetrahedron_vertices, sizeof(demo_truncated_tetrahedron_vertices) / sizeof(demo_truncated_tetrahedron_vertices[0]),
demo_truncated_tetrahedron_faces, sizeof(demo_truncated_tetrahedron_faces) / sizeof(demo_truncated_tetrahedron_faces[0]),
};
// Archimedean solid: cuboctahedron.
static const demo_point_3d_t demo_cuboctahedron_vertices[] = {
{-0.707000f, 0.000000f, 0.707000f},
{0.000000f, -0.707000f, 0.707000f},
{0.000000f, 0.707000f, 0.707000f},
{0.707000f, 0.000000f, 0.707000f},
{0.707000f, -0.707000f, 0.000000f},
{0.707000f, 0.000000f, -0.707000f},
{0.707000f, 0.707000f, 0.000000f},
{0.000000f, 0.707000f, -0.707000f},
{-0.707000f, 0.707000f, 0.000000f},
{-0.707000f, 0.000000f, -0.707000f},
{-0.707000f, -0.707000f, 0.000000f},
{0.000000f, -0.707000f, -0.707000f},
};
static const uint8_t demo_cuboctahedron_face_indices[][4] = {
{0U, 1U, 3U, 2U},
{1U, 0U, 10U},
{3U, 1U, 4U},
{2U, 3U, 6U},
{0U, 2U, 8U},
{6U, 3U, 4U, 5U},
{5U, 4U, 11U},
{6U, 5U, 7U},
{8U, 2U, 6U, 7U},
{8U, 7U, 9U},
{10U, 0U, 8U, 9U},
{10U, 9U, 11U},
{4U, 1U, 10U, 11U},
{9U, 7U, 5U, 11U},
};
static const demo_mesh_face_t demo_cuboctahedron_faces[] = {
{4U, demo_cuboctahedron_face_indices[0]},
{3U, demo_cuboctahedron_face_indices[1]},
{3U, demo_cuboctahedron_face_indices[2]},
{3U, demo_cuboctahedron_face_indices[3]},
{3U, demo_cuboctahedron_face_indices[4]},
{4U, demo_cuboctahedron_face_indices[5]},
{3U, demo_cuboctahedron_face_indices[6]},
{3U, demo_cuboctahedron_face_indices[7]},
{4U, demo_cuboctahedron_face_indices[8]},
{3U, demo_cuboctahedron_face_indices[9]},
{4U, demo_cuboctahedron_face_indices[10]},
{3U, demo_cuboctahedron_face_indices[11]},
{4U, demo_cuboctahedron_face_indices[12]},
{4U, demo_cuboctahedron_face_indices[13]},
};
static const demo_mesh_t demo_cuboctahedron_mesh = {
demo_cuboctahedron_vertices, sizeof(demo_cuboctahedron_vertices) / sizeof(demo_cuboctahedron_vertices[0]),
demo_cuboctahedron_faces, sizeof(demo_cuboctahedron_faces) / sizeof(demo_cuboctahedron_faces[0]),
};
// Archimedean solid: truncated cube.
static const demo_point_3d_t demo_truncated_cube_vertices[] = {
{0.192025f, 0.624080f, 0.624080f},
{0.624080f, 0.192025f, 0.624080f},
{0.624080f, 0.624080f, 0.192025f},
{-0.624080f, 0.192025f, 0.624080f},
{-0.192025f, 0.624080f, 0.624080f},
{-0.624080f, 0.624080f, 0.192025f},
{-0.192025f, -0.624080f, 0.624080f},
{-0.624080f, -0.192025f, 0.624080f},
{-0.624080f, -0.624080f, 0.192025f},
{0.624080f, -0.192025f, 0.624080f},
{0.192025f, -0.624080f, 0.624080f},
{0.624080f, -0.624080f, 0.192025f},
{0.624080f, -0.192025f, -0.624080f},
{0.624080f, -0.624080f, -0.192025f},
{0.192025f, -0.624080f, -0.624080f},
{0.624080f, 0.624080f, -0.192025f},
{0.624080f, 0.192025f, -0.624080f},
{0.192025f, 0.624080f, -0.624080f},
{-0.624080f, 0.624080f, -0.192025f},
{-0.192025f, 0.624080f, -0.624080f},
{-0.624080f, 0.192025f, -0.624080f},
{-0.624080f, -0.624080f, -0.192025f},
{-0.624080f, -0.192025f, -0.624080f},
{-0.192025f, -0.624080f, -0.624080f},
};
static const uint8_t demo_truncated_cube_face_indices[][8] = {
{0U, 4U, 3U, 7U, 6U, 10U, 9U, 1U},
{1U, 9U, 11U, 13U, 12U, 16U, 15U, 2U},
{0U, 2U, 15U, 17U, 19U, 18U, 5U, 4U},
{3U, 5U, 18U, 20U, 22U, 21U, 8U, 7U},
{6U, 8U, 21U, 23U, 14U, 13U, 11U, 10U},
{12U, 14U, 23U, 22U, 20U, 19U, 17U, 16U},
{0U, 1U, 2U},
{3U, 4U, 5U},
{6U, 7U, 8U},
{9U, 10U, 11U},
{12U, 13U, 14U},
{15U, 16U, 17U},
{18U, 19U, 20U},
{21U, 22U, 23U},
};
static const demo_mesh_face_t demo_truncated_cube_faces[] = {
{8U, demo_truncated_cube_face_indices[0]},
{8U, demo_truncated_cube_face_indices[1]},
{8U, demo_truncated_cube_face_indices[2]},
{8U, demo_truncated_cube_face_indices[3]},
{8U, demo_truncated_cube_face_indices[4]},
{8U, demo_truncated_cube_face_indices[5]},
{3U, demo_truncated_cube_face_indices[6]},
{3U, demo_truncated_cube_face_indices[7]},
{3U, demo_truncated_cube_face_indices[8]},
{3U, demo_truncated_cube_face_indices[9]},
{3U, demo_truncated_cube_face_indices[10]},
{3U, demo_truncated_cube_face_indices[11]},
{3U, demo_truncated_cube_face_indices[12]},
{3U, demo_truncated_cube_face_indices[13]},
};
static const demo_mesh_t demo_truncated_cube_mesh = {
demo_truncated_cube_vertices, sizeof(demo_truncated_cube_vertices) / sizeof(demo_truncated_cube_vertices[0]),
demo_truncated_cube_faces, sizeof(demo_truncated_cube_faces) / sizeof(demo_truncated_cube_faces[0]),
};
// Archimedean solid: truncated octahedron.
static const demo_point_3d_t demo_truncated_octahedron_vertices[] = {
{0.000000f, -0.408489f, 0.748896f},
{0.408489f, 0.000000f, 0.748896f},
{0.000000f, 0.408489f, 0.748896f},
{-0.408489f, 0.000000f, 0.748896f},
{0.748896f, 0.000000f, 0.408489f},
{0.748896f, -0.408489f, 0.000000f},
{0.748896f, 0.000000f, -0.408489f},
{0.748896f, 0.408489f, 0.000000f},
{0.000000f, 0.748896f, 0.408489f},
{0.408489f, 0.748896f, 0.000000f},
{0.000000f, 0.748896f, -0.408489f},
{-0.408489f, 0.748896f, 0.000000f},
{-0.748896f, 0.000000f, 0.408489f},
{-0.748896f, 0.408489f, 0.000000f},
{-0.748896f, 0.000000f, -0.408489f},
{-0.748896f, -0.408489f, 0.000000f},
{0.000000f, -0.748896f, 0.408489f},
{-0.408489f, -0.748896f, 0.000000f},
{0.000000f, -0.748896f, -0.408489f},
{0.408489f, -0.748896f, 0.000000f},
{0.000000f, 0.408489f, -0.748896f},
{0.408489f, 0.000000f, -0.748896f},
{0.000000f, -0.408489f, -0.748896f},
{-0.408489f, 0.000000f, -0.748896f},
};
static const uint8_t demo_truncated_octahedron_face_indices[][6] = {
{1U, 4U, 7U, 9U, 8U, 2U},
{2U, 8U, 11U, 13U, 12U, 3U},
{0U, 3U, 12U, 15U, 17U, 16U},
{0U, 16U, 19U, 5U, 4U, 1U},
{5U, 19U, 18U, 22U, 21U, 6U},
{6U, 21U, 20U, 10U, 9U, 7U},
{10U, 20U, 23U, 14U, 13U, 11U},
{14U, 23U, 22U, 18U, 17U, 15U},
{0U, 1U, 2U, 3U},
{4U, 5U, 6U, 7U},
{8U, 9U, 10U, 11U},
{12U, 13U, 14U, 15U},
{16U, 17U, 18U, 19U},
{20U, 21U, 22U, 23U},
};
static const demo_mesh_face_t demo_truncated_octahedron_faces[] = {
{6U, demo_truncated_octahedron_face_indices[0]},
{6U, demo_truncated_octahedron_face_indices[1]},
{6U, demo_truncated_octahedron_face_indices[2]},
{6U, demo_truncated_octahedron_face_indices[3]},
{6U, demo_truncated_octahedron_face_indices[4]},
{6U, demo_truncated_octahedron_face_indices[5]},
{6U, demo_truncated_octahedron_face_indices[6]},
{6U, demo_truncated_octahedron_face_indices[7]},
{4U, demo_truncated_octahedron_face_indices[8]},
{4U, demo_truncated_octahedron_face_indices[9]},
{4U, demo_truncated_octahedron_face_indices[10]},
{4U, demo_truncated_octahedron_face_indices[11]},
{4U, demo_truncated_octahedron_face_indices[12]},
{4U, demo_truncated_octahedron_face_indices[13]},
};
static const demo_mesh_t demo_truncated_octahedron_mesh = {
demo_truncated_octahedron_vertices, sizeof(demo_truncated_octahedron_vertices) / sizeof(demo_truncated_octahedron_vertices[0]),
demo_truncated_octahedron_faces, sizeof(demo_truncated_octahedron_faces) / sizeof(demo_truncated_octahedron_faces[0]),
};
// Archimedean solid: rhombicuboctahedron.
static const demo_point_3d_t demo_rhombicuboctahedron_vertices[] = {
{-0.306915f, -0.306915f, 0.745364f},
{0.306915f, -0.306915f, 0.745364f},
{-0.745364f, -0.306915f, 0.306915f},
{0.306915f, -0.745364f, 0.306915f},
{0.745364f, 0.306915f, 0.306915f},
{0.306915f, 0.306915f, 0.745364f},
{-0.306915f, 0.745364f, 0.306915f},
{-0.306915f, 0.306915f, 0.745364f},
{0.745364f, -0.306915f, -0.306915f},
{0.745364f, 0.306915f, -0.306915f},
{0.745364f, -0.306915f, 0.306915f},
{0.306915f, -0.745364f, -0.306915f},
{0.306915f, 0.306915f, -0.745364f},
{0.306915f, 0.745364f, -0.306915f},
{-0.306915f, 0.745364f, -0.306915f},
{0.306915f, 0.745364f, 0.306915f},
{-0.306915f, 0.306915f, -0.745364f},
{-0.745364f, 0.306915f, -0.306915f},
{-0.745364f, -0.306915f, -0.306915f},
{-0.745364f, 0.306915f, 0.306915f},
{-0.306915f, -0.306915f, -0.745364f},
{-0.306915f, -0.745364f, -0.306915f},
{-0.306915f, -0.745364f, 0.306915f},
{0.306915f, -0.306915f, -0.745364f},
};
static const uint8_t demo_rhombicuboctahedron_face_indices[][4] = {
{5U, 7U, 0U, 1U},
{7U, 5U, 15U, 6U},
{0U, 7U, 19U, 2U},
{1U, 0U, 22U, 3U},
{5U, 1U, 10U, 4U},
{2U, 22U, 0U},
{22U, 2U, 18U, 21U},
{3U, 10U, 1U},
{10U, 3U, 11U, 8U},
{4U, 15U, 5U},
{15U, 4U, 9U, 13U},
{6U, 19U, 7U},
{19U, 6U, 14U, 17U},
{8U, 9U, 4U, 10U},
{9U, 8U, 23U, 12U},
{11U, 23U, 8U},
{23U, 11U, 21U, 20U},
{12U, 13U, 9U},
{13U, 12U, 16U, 14U},
{13U, 14U, 6U, 15U},
{16U, 17U, 14U},
{17U, 16U, 20U, 18U},
{17U, 18U, 2U, 19U},
{20U, 21U, 18U},
{21U, 11U, 3U, 22U},
{23U, 20U, 16U, 12U},
};
static const demo_mesh_face_t demo_rhombicuboctahedron_faces[] = {
{4U, demo_rhombicuboctahedron_face_indices[0]},
{4U, demo_rhombicuboctahedron_face_indices[1]},
{4U, demo_rhombicuboctahedron_face_indices[2]},
{4U, demo_rhombicuboctahedron_face_indices[3]},
{4U, demo_rhombicuboctahedron_face_indices[4]},
{3U, demo_rhombicuboctahedron_face_indices[5]},
{4U, demo_rhombicuboctahedron_face_indices[6]},
{3U, demo_rhombicuboctahedron_face_indices[7]},
{4U, demo_rhombicuboctahedron_face_indices[8]},
{3U, demo_rhombicuboctahedron_face_indices[9]},
{4U, demo_rhombicuboctahedron_face_indices[10]},
{3U, demo_rhombicuboctahedron_face_indices[11]},
{4U, demo_rhombicuboctahedron_face_indices[12]},
{4U, demo_rhombicuboctahedron_face_indices[13]},
{4U, demo_rhombicuboctahedron_face_indices[14]},
{3U, demo_rhombicuboctahedron_face_indices[15]},
{4U, demo_rhombicuboctahedron_face_indices[16]},
{3U, demo_rhombicuboctahedron_face_indices[17]},
{4U, demo_rhombicuboctahedron_face_indices[18]},
{4U, demo_rhombicuboctahedron_face_indices[19]},
{3U, demo_rhombicuboctahedron_face_indices[20]},
{4U, demo_rhombicuboctahedron_face_indices[21]},
{4U, demo_rhombicuboctahedron_face_indices[22]},
{3U, demo_rhombicuboctahedron_face_indices[23]},
{4U, demo_rhombicuboctahedron_face_indices[24]},
{4U, demo_rhombicuboctahedron_face_indices[25]},
};
static const demo_mesh_t demo_rhombicuboctahedron_mesh = {
demo_rhombicuboctahedron_vertices, sizeof(demo_rhombicuboctahedron_vertices) / sizeof(demo_rhombicuboctahedron_vertices[0]),
demo_rhombicuboctahedron_faces, sizeof(demo_rhombicuboctahedron_faces) / sizeof(demo_rhombicuboctahedron_faces[0]),
};
// Archimedean solid: truncated cuboctahedron.
static const demo_point_3d_t demo_truncated_cuboctahedron_vertices[] = {
{-0.291952f, -0.133283f, 0.538772f},
{-0.291952f, 0.133283f, 0.538772f},
{-0.538772f, 0.133283f, 0.291952f},
{-0.538772f, -0.133283f, 0.291952f},
{0.133283f, -0.291952f, 0.538772f},
{-0.133283f, -0.291952f, 0.538772f},
{-0.133283f, -0.538772f, 0.291952f},
{0.133283f, -0.538772f, 0.291952f},
{-0.133283f, 0.291952f, 0.538772f},
{0.133283f, 0.291952f, 0.538772f},
{0.133283f, 0.538772f, 0.291952f},
{-0.133283f, 0.538772f, 0.291952f},
{0.291952f, 0.133283f, 0.538772f},
{0.291952f, -0.133283f, 0.538772f},
{0.538772f, -0.133283f, 0.291952f},
{0.538772f, 0.133283f, 0.291952f},
{0.538772f, -0.291952f, 0.133283f},
{0.291952f, -0.538772f, 0.133283f},
{0.291952f, -0.538772f, -0.133283f},
{0.538772f, -0.291952f, -0.133283f},
{0.538772f, 0.133283f, -0.291952f},
{0.538772f, -0.133283f, -0.291952f},
{0.291952f, -0.133283f, -0.538772f},
{0.291952f, 0.133283f, -0.538772f},
{0.291952f, 0.538772f, 0.133283f},
{0.538772f, 0.291952f, 0.133283f},
{0.538772f, 0.291952f, -0.133283f},
{0.291952f, 0.538772f, -0.133283f},
{0.133283f, 0.538772f, -0.291952f},
{0.133283f, 0.291952f, -0.538772f},
{-0.133283f, 0.291952f, -0.538772f},
{-0.133283f, 0.538772f, -0.291952f},
{-0.538772f, 0.291952f, 0.133283f},
{-0.291952f, 0.538772f, 0.133283f},
{-0.291952f, 0.538772f, -0.133283f},
{-0.538772f, 0.291952f, -0.133283f},
{-0.538772f, 0.133283f, -0.291952f},
{-0.291952f, 0.133283f, -0.538772f},
{-0.291952f, -0.133283f, -0.538772f},
{-0.538772f, -0.133283f, -0.291952f},
{-0.291952f, -0.538772f, 0.133283f},
{-0.538772f, -0.291952f, 0.133283f},
{-0.538772f, -0.291952f, -0.133283f},
{-0.291952f, -0.538772f, -0.133283f},
{0.133283f, -0.291952f, -0.538772f},
{0.133283f, -0.538772f, -0.291952f},
{-0.133283f, -0.538772f, -0.291952f},
{-0.133283f, -0.291952f, -0.538772f},
};
static const uint8_t demo_truncated_cuboctahedron_face_indices[][8] = {
{0U, 5U, 4U, 13U, 12U, 9U, 8U, 1U},
{0U, 3U, 41U, 40U, 6U, 5U},
{4U, 7U, 17U, 16U, 14U, 13U},
{9U, 12U, 15U, 25U, 24U, 10U},
{1U, 8U, 11U, 33U, 32U, 2U},
{14U, 16U, 19U, 21U, 20U, 26U, 25U, 15U},
{18U, 45U, 44U, 22U, 21U, 19U},
{20U, 23U, 29U, 28U, 27U, 26U},
{10U, 24U, 27U, 28U, 31U, 34U, 33U, 11U},
{30U, 37U, 36U, 35U, 34U, 31U},
{2U, 32U, 35U, 36U, 39U, 42U, 41U, 3U},
{38U, 47U, 46U, 43U, 42U, 39U},
{6U, 40U, 43U, 46U, 45U, 18U, 17U, 7U},
{22U, 44U, 47U, 38U, 37U, 30U, 29U, 23U},
{0U, 1U, 2U, 3U},
{4U, 5U, 6U, 7U},
{8U, 9U, 10U, 11U},
{12U, 13U, 14U, 15U},
{16U, 17U, 18U, 19U},
{20U, 21U, 22U, 23U},
{24U, 25U, 26U, 27U},
{28U, 29U, 30U, 31U},
{32U, 33U, 34U, 35U},
{36U, 37U, 38U, 39U},
{40U, 41U, 42U, 43U},
{44U, 45U, 46U, 47U},
};
static const demo_mesh_face_t demo_truncated_cuboctahedron_faces[] = {
{8U, demo_truncated_cuboctahedron_face_indices[0]},
{6U, demo_truncated_cuboctahedron_face_indices[1]},
{6U, demo_truncated_cuboctahedron_face_indices[2]},
{6U, demo_truncated_cuboctahedron_face_indices[3]},
{6U, demo_truncated_cuboctahedron_face_indices[4]},
{8U, demo_truncated_cuboctahedron_face_indices[5]},
{6U, demo_truncated_cuboctahedron_face_indices[6]},
{6U, demo_truncated_cuboctahedron_face_indices[7]},
{8U, demo_truncated_cuboctahedron_face_indices[8]},
{6U, demo_truncated_cuboctahedron_face_indices[9]},
{8U, demo_truncated_cuboctahedron_face_indices[10]},
{6U, demo_truncated_cuboctahedron_face_indices[11]},
{8U, demo_truncated_cuboctahedron_face_indices[12]},
{8U, demo_truncated_cuboctahedron_face_indices[13]},
{4U, demo_truncated_cuboctahedron_face_indices[14]},
{4U, demo_truncated_cuboctahedron_face_indices[15]},
{4U, demo_truncated_cuboctahedron_face_indices[16]},
{4U, demo_truncated_cuboctahedron_face_indices[17]},
{4U, demo_truncated_cuboctahedron_face_indices[18]},
{4U, demo_truncated_cuboctahedron_face_indices[19]},
{4U, demo_truncated_cuboctahedron_face_indices[20]},
{4U, demo_truncated_cuboctahedron_face_indices[21]},
{4U, demo_truncated_cuboctahedron_face_indices[22]},
{4U, demo_truncated_cuboctahedron_face_indices[23]},
{4U, demo_truncated_cuboctahedron_face_indices[24]},
{4U, demo_truncated_cuboctahedron_face_indices[25]},
};
static const demo_mesh_t demo_truncated_cuboctahedron_mesh = {
demo_truncated_cuboctahedron_vertices, sizeof(demo_truncated_cuboctahedron_vertices) / sizeof(demo_truncated_cuboctahedron_vertices[0]),
demo_truncated_cuboctahedron_faces, sizeof(demo_truncated_cuboctahedron_faces) / sizeof(demo_truncated_cuboctahedron_faces[0]),
};
// Archimedean solid: icosidodecahedron.
static const demo_point_3d_t demo_icosidodecahedron_vertices[] = {
{0.356820f, 0.000003f, 0.934167f},
{0.755758f, 0.309018f, 0.577349f},
{0.467086f, 0.809013f, 0.356822f},
{-0.178410f, 0.309011f, 0.934169f},
{-0.645495f, 0.499990f, 0.577348f},
{-0.934169f, -0.000004f, 0.356821f},
{-0.178409f, -0.309009f, 0.934169f},
{-0.110264f, -0.809007f, 0.577354f},
{0.755759f, -0.309006f, 0.577350f},
{0.866024f, -0.499989f, -0.000000f},
{-0.110267f, 0.809008f, 0.577352f},
{-0.000002f, 0.999993f, -0.000000f},
{-0.645495f, -0.499993f, 0.577350f},
{-0.866022f, -0.499998f, 0.000001f},
{0.866022f, 0.499998f, -0.000001f},
{0.645495f, 0.499993f, -0.577350f},
{0.467089f, -0.809005f, 0.356823f},
{0.000002f, -0.999993f, 0.000000f},
{0.110267f, -0.809008f, -0.577352f},
{-0.866024f, 0.499989f, 0.000000f},
{-0.755759f, 0.309006f, -0.577350f},
{0.934169f, 0.000004f, -0.356821f},
{0.645495f, -0.499990f, -0.577348f},
{0.178410f, -0.309011f, -0.934169f},
{-0.467089f, 0.809005f, -0.356823f},
{0.110264f, 0.809007f, -0.577354f},
{0.178409f, 0.309009f, -0.934169f},
{-0.467086f, -0.809013f, -0.356822f},
{-0.755758f, -0.309018f, -0.577349f},
{-0.356820f, -0.000003f, -0.934167f},
};
static const uint8_t demo_icosidodecahedron_face_indices[][5] = {
{10U, 3U, 0U, 1U, 2U},
{3U, 10U, 4U},
{0U, 3U, 6U},
{1U, 0U, 8U},
{2U, 1U, 14U},
{10U, 2U, 11U},
{12U, 6U, 3U, 4U, 5U},
{6U, 12U, 7U},
{5U, 4U, 19U},
{12U, 5U, 13U},
{8U, 0U, 6U, 7U, 16U},
{16U, 7U, 17U},
{8U, 16U, 9U},
{14U, 1U, 8U, 9U, 21U},
{21U, 9U, 22U},
{14U, 21U, 15U},
{19U, 4U, 10U, 11U, 24U},
{24U, 11U, 25U},
{19U, 24U, 20U},
{17U, 7U, 12U, 13U, 27U},
{27U, 13U, 28U},
{17U, 27U, 18U},
{11U, 2U, 14U, 15U, 25U},
{25U, 15U, 26U},
{22U, 9U, 16U, 17U, 18U},
{22U, 18U, 23U},
{13U, 5U, 19U, 20U, 28U},
{28U, 20U, 29U},
{26U, 15U, 21U, 22U, 23U},
{26U, 23U, 29U},
{29U, 20U, 24U, 25U, 26U},
{23U, 18U, 27U, 28U, 29U},
};
static const demo_mesh_face_t demo_icosidodecahedron_faces[] = {
{5U, demo_icosidodecahedron_face_indices[0]},
{3U, demo_icosidodecahedron_face_indices[1]},
{3U, demo_icosidodecahedron_face_indices[2]},
{3U, demo_icosidodecahedron_face_indices[3]},
{3U, demo_icosidodecahedron_face_indices[4]},
{3U, demo_icosidodecahedron_face_indices[5]},
{5U, demo_icosidodecahedron_face_indices[6]},
{3U, demo_icosidodecahedron_face_indices[7]},
{3U, demo_icosidodecahedron_face_indices[8]},
{3U, demo_icosidodecahedron_face_indices[9]},
{5U, demo_icosidodecahedron_face_indices[10]},
{3U, demo_icosidodecahedron_face_indices[11]},
{3U, demo_icosidodecahedron_face_indices[12]},
{5U, demo_icosidodecahedron_face_indices[13]},
{3U, demo_icosidodecahedron_face_indices[14]},
{3U, demo_icosidodecahedron_face_indices[15]},
{5U, demo_icosidodecahedron_face_indices[16]},
{3U, demo_icosidodecahedron_face_indices[17]},
{3U, demo_icosidodecahedron_face_indices[18]},
{5U, demo_icosidodecahedron_face_indices[19]},
{3U, demo_icosidodecahedron_face_indices[20]},
{3U, demo_icosidodecahedron_face_indices[21]},
{5U, demo_icosidodecahedron_face_indices[22]},
{3U, demo_icosidodecahedron_face_indices[23]},
{5U, demo_icosidodecahedron_face_indices[24]},
{3U, demo_icosidodecahedron_face_indices[25]},
{5U, demo_icosidodecahedron_face_indices[26]},
{3U, demo_icosidodecahedron_face_indices[27]},
{5U, demo_icosidodecahedron_face_indices[28]},
{3U, demo_icosidodecahedron_face_indices[29]},
{5U, demo_icosidodecahedron_face_indices[30]},
{5U, demo_icosidodecahedron_face_indices[31]},
};
static const demo_mesh_t demo_icosidodecahedron_mesh = {
demo_icosidodecahedron_vertices, sizeof(demo_icosidodecahedron_vertices) / sizeof(demo_icosidodecahedron_vertices[0]),
demo_icosidodecahedron_faces, sizeof(demo_icosidodecahedron_faces) / sizeof(demo_icosidodecahedron_faces[0]),
};
// Archimedean solid: truncated dodecahedron.
static const demo_point_3d_t demo_truncated_dodecahedron_vertices[] = {
{0.267614f, 0.441939f, 0.813805f},
{0.502744f, 0.118442f, 0.813928f},
{0.607784f, 0.441949f, 0.603615f},
{-0.337643f, 0.391405f, 0.813832f},
{0.042581f, 0.515040f, 0.813805f},
{-0.232604f, 0.714912f, 0.603519f},
{-0.476665f, -0.199892f, 0.814011f},
{-0.476674f, 0.199962f, 0.813946f},
{-0.751850f, -0.000020f, 0.603725f},
{0.042818f, -0.514910f, 0.814050f},
{-0.337533f, -0.391332f, 0.814013f},
{-0.232358f, -0.714892f, 0.603828f},
{0.502851f, -0.118211f, 0.814033f},
{0.267856f, -0.441782f, 0.814039f},
{0.608026f, -0.441772f, 0.603849f},
{0.878844f, -0.391315f, 0.062268f},
{0.708719f, -0.514894f, 0.402495f},
{0.643850f, -0.714886f, 0.062274f},
{0.941012f, 0.199998f, -0.062204f},
{0.941075f, -0.199882f, -0.062184f},
{0.876143f, 0.000005f, -0.402425f},
{0.708468f, 0.515057f, 0.402268f},
{0.878666f, 0.391447f, 0.062151f},
{0.643536f, 0.714944f, 0.062027f},
{0.100405f, 0.956756f, -0.062493f},
{0.480726f, 0.833243f, -0.062453f},
{0.270603f, 0.833146f, -0.402610f},
{-0.271066f, 0.833211f, 0.402147f},
{-0.100964f, 0.956749f, 0.061963f},
{-0.481189f, 0.833114f, 0.061990f},
{-0.878883f, 0.391350f, -0.062352f},
{-0.643890f, 0.714806f, -0.062362f},
{-0.708781f, 0.514889f, -0.402536f},
{-0.876215f, -0.000020f, 0.402340f},
{-0.941115f, 0.199917f, 0.062101f},
{-0.941106f, -0.199937f, 0.062166f},
{-0.643850f, -0.714886f, -0.062274f},
{-0.878874f, -0.391370f, -0.062287f},
{-0.708750f, -0.514949f, -0.402513f},
{-0.270813f, -0.833220f, 0.402459f},
{-0.481040f, -0.833220f, 0.062196f},
{-0.100688f, -0.956799f, 0.062233f},
{0.481040f, -0.833220f, -0.062196f},
{0.100688f, -0.956799f, -0.062233f},
{0.270813f, -0.833220f, -0.402459f},
{0.476570f, 0.199942f, -0.814066f},
{0.751779f, 0.000005f, -0.603810f},
{0.476633f, -0.199937f, -0.814046f},
{0.232263f, 0.714837f, -0.603871f},
{0.337438f, 0.391382f, -0.814067f},
{-0.042882f, 0.514895f, -0.814107f},
{0.337501f, -0.391376f, -0.814048f},
{0.232358f, -0.714892f, -0.603828f},
{-0.042850f, -0.514955f, -0.814085f},
{-0.608088f, 0.441767f, -0.603890f},
{-0.267920f, 0.441767f, -0.814096f},
{-0.502913f, 0.118311f, -0.814086f},
{-0.608056f, -0.441827f, -0.603867f},
{-0.502913f, -0.118311f, -0.814086f},
{-0.267888f, -0.441827f, -0.814074f},
};
static const uint8_t demo_truncated_dodecahedron_face_indices[][10] = {
{0U, 4U, 3U, 7U, 6U, 10U, 9U, 13U, 12U, 1U},
{1U, 12U, 14U, 16U, 15U, 19U, 18U, 22U, 21U, 2U},
{0U, 2U, 21U, 23U, 25U, 24U, 28U, 27U, 5U, 4U},
{3U, 5U, 27U, 29U, 31U, 30U, 34U, 33U, 8U, 7U},
{6U, 8U, 33U, 35U, 37U, 36U, 40U, 39U, 11U, 10U},
{9U, 11U, 39U, 41U, 43U, 42U, 17U, 16U, 14U, 13U},
{18U, 20U, 46U, 45U, 49U, 48U, 26U, 25U, 23U, 22U},
{15U, 17U, 42U, 44U, 52U, 51U, 47U, 46U, 20U, 19U},
{24U, 26U, 48U, 50U, 55U, 54U, 32U, 31U, 29U, 28U},
{30U, 32U, 54U, 56U, 58U, 57U, 38U, 37U, 35U, 34U},
{36U, 38U, 57U, 59U, 53U, 52U, 44U, 43U, 41U, 40U},
{45U, 47U, 51U, 53U, 59U, 58U, 56U, 55U, 50U, 49U},
{0U, 1U, 2U},
{3U, 4U, 5U},
{6U, 7U, 8U},
{9U, 10U, 11U},
{12U, 13U, 14U},
{15U, 16U, 17U},
{18U, 19U, 20U},
{21U, 22U, 23U},
{24U, 25U, 26U},
{27U, 28U, 29U},
{30U, 31U, 32U},
{33U, 34U, 35U},
{36U, 37U, 38U},
{39U, 40U, 41U},
{42U, 43U, 44U},
{45U, 46U, 47U},
{48U, 49U, 50U},
{51U, 52U, 53U},
{54U, 55U, 56U},
{57U, 58U, 59U},
};
static const demo_mesh_face_t demo_truncated_dodecahedron_faces[] = {
{10U, demo_truncated_dodecahedron_face_indices[0]},
{10U, demo_truncated_dodecahedron_face_indices[1]},
{10U, demo_truncated_dodecahedron_face_indices[2]},
{10U, demo_truncated_dodecahedron_face_indices[3]},
{10U, demo_truncated_dodecahedron_face_indices[4]},
{10U, demo_truncated_dodecahedron_face_indices[5]},
{10U, demo_truncated_dodecahedron_face_indices[6]},
{10U, demo_truncated_dodecahedron_face_indices[7]},
{10U, demo_truncated_dodecahedron_face_indices[8]},
{10U, demo_truncated_dodecahedron_face_indices[9]},
{10U, demo_truncated_dodecahedron_face_indices[10]},
{10U, demo_truncated_dodecahedron_face_indices[11]},
{3U, demo_truncated_dodecahedron_face_indices[12]},
{3U, demo_truncated_dodecahedron_face_indices[13]},
{3U, demo_truncated_dodecahedron_face_indices[14]},
{3U, demo_truncated_dodecahedron_face_indices[15]},
{3U, demo_truncated_dodecahedron_face_indices[16]},
{3U, demo_truncated_dodecahedron_face_indices[17]},
{3U, demo_truncated_dodecahedron_face_indices[18]},
{3U, demo_truncated_dodecahedron_face_indices[19]},
{3U, demo_truncated_dodecahedron_face_indices[20]},
{3U, demo_truncated_dodecahedron_face_indices[21]},
{3U, demo_truncated_dodecahedron_face_indices[22]},
{3U, demo_truncated_dodecahedron_face_indices[23]},
{3U, demo_truncated_dodecahedron_face_indices[24]},
{3U, demo_truncated_dodecahedron_face_indices[25]},
{3U, demo_truncated_dodecahedron_face_indices[26]},
{3U, demo_truncated_dodecahedron_face_indices[27]},
{3U, demo_truncated_dodecahedron_face_indices[28]},
{3U, demo_truncated_dodecahedron_face_indices[29]},
{3U, demo_truncated_dodecahedron_face_indices[30]},
{3U, demo_truncated_dodecahedron_face_indices[31]},
};
static const demo_mesh_t demo_truncated_dodecahedron_mesh = {
demo_truncated_dodecahedron_vertices, sizeof(demo_truncated_dodecahedron_vertices) / sizeof(demo_truncated_dodecahedron_vertices[0]),
demo_truncated_dodecahedron_faces, sizeof(demo_truncated_dodecahedron_faces) / sizeof(demo_truncated_dodecahedron_faces[0]),
};
// Archimedean solid: truncated icosahedron.
static const demo_point_3d_t demo_truncated_icosahedron_vertices[] = {
{-0.010218f, 0.366446f, 0.844223f},
{0.322463f, 0.174378f, 0.844222f},
{0.570430f, 0.366451f, 0.622436f},
{0.391000f, 0.677230f, 0.485365f},
{0.032140f, 0.677226f, 0.622438f},
{-0.312247f, -0.192069f, 0.844221f},
{-0.312247f, 0.192070f, 0.844221f},
{-0.602570f, 0.310775f, 0.622435f},
{-0.781999f, -0.000002f, 0.485362f},
{-0.602570f, -0.310777f, 0.622436f},
{0.322464f, -0.174372f, 0.844221f},
{-0.010217f, -0.366443f, 0.844223f},
{0.032141f, -0.677222f, 0.622438f},
{0.391002f, -0.677222f, 0.485365f},
{0.570431f, -0.366441f, 0.622436f},
{0.795550f, 0.192078f, 0.421081f},
{0.795551f, -0.192065f, 0.421081f},
{0.864087f, -0.310772f, 0.062222f},
{0.906443f, 0.000004f, -0.159566f},
{0.864086f, 0.310783f, 0.062221f},
{-0.564117f, 0.592920f, 0.421082f},
{-0.231437f, 0.784995f, 0.421083f},
{-0.162901f, 0.903704f, 0.062222f},
{-0.453225f, 0.784993f, -0.159567f},
{-0.701189f, 0.592919f, 0.062222f},
{-0.231435f, -0.784996f, 0.421085f},
{-0.564116f, -0.592924f, 0.421084f},
{-0.701188f, -0.592928f, 0.062223f},
{-0.453222f, -0.785001f, -0.159566f},
{-0.162898f, -0.903706f, 0.062223f},
{0.453222f, 0.785001f, 0.159566f},
{0.701188f, 0.592928f, -0.062223f},
{0.564116f, 0.592924f, -0.421084f},
{0.231435f, 0.784996f, -0.421085f},
{0.162898f, 0.903706f, -0.062223f},
{0.701189f, -0.592919f, -0.062222f},
{0.453225f, -0.784993f, 0.159567f},
{0.162901f, -0.903704f, -0.062222f},
{0.231437f, -0.784995f, -0.421083f},
{0.564117f, -0.592920f, -0.421082f},
{-0.906443f, -0.000004f, 0.159566f},
{-0.864087f, 0.310772f, -0.062222f},
{-0.795551f, 0.192065f, -0.421081f},
{-0.795550f, -0.192078f, -0.421081f},
{-0.864086f, -0.310783f, -0.062221f},
{0.602570f, 0.310777f, -0.622436f},
{0.781999f, 0.000002f, -0.485362f},
{0.602570f, -0.310775f, -0.622435f},
{0.312247f, -0.192070f, -0.844221f},
{0.312247f, 0.192069f, -0.844221f},
{-0.570431f, 0.366441f, -0.622436f},
{-0.391002f, 0.677222f, -0.485365f},
{-0.032141f, 0.677222f, -0.622438f},
{0.010217f, 0.366443f, -0.844223f},
{-0.322464f, 0.174372f, -0.844221f},
{-0.032140f, -0.677226f, -0.622438f},
{-0.391000f, -0.677230f, -0.485365f},
{-0.570430f, -0.366451f, -0.622436f},
{-0.322463f, -0.174378f, -0.844222f},
{0.010218f, -0.366446f, -0.844223f},
};
static const uint8_t demo_truncated_icosahedron_face_indices[][6] = {
{0U, 6U, 5U, 11U, 10U, 1U},
{1U, 10U, 14U, 16U, 15U, 2U},
{0U, 4U, 21U, 20U, 7U, 6U},
{5U, 9U, 26U, 25U, 12U, 11U},
{2U, 15U, 19U, 31U, 30U, 3U},
{13U, 36U, 35U, 17U, 16U, 14U},
{7U, 20U, 24U, 41U, 40U, 8U},
{3U, 30U, 34U, 22U, 21U, 4U},
{12U, 25U, 29U, 37U, 36U, 13U},
{8U, 40U, 44U, 27U, 26U, 9U},
{18U, 46U, 45U, 32U, 31U, 19U},
{17U, 35U, 39U, 47U, 46U, 18U},
{23U, 51U, 50U, 42U, 41U, 24U},
{22U, 34U, 33U, 52U, 51U, 23U},
{28U, 56U, 55U, 38U, 37U, 29U},
{27U, 44U, 43U, 57U, 56U, 28U},
{32U, 45U, 49U, 53U, 52U, 33U},
{38U, 55U, 59U, 48U, 47U, 39U},
{42U, 50U, 54U, 58U, 57U, 43U},
{48U, 59U, 58U, 54U, 53U, 49U},
{0U, 1U, 2U, 3U, 4U},
{5U, 6U, 7U, 8U, 9U},
{10U, 11U, 12U, 13U, 14U},
{15U, 16U, 17U, 18U, 19U},
{20U, 21U, 22U, 23U, 24U},
{25U, 26U, 27U, 28U, 29U},
{30U, 31U, 32U, 33U, 34U},
{35U, 36U, 37U, 38U, 39U},
{40U, 41U, 42U, 43U, 44U},
{45U, 46U, 47U, 48U, 49U},
{50U, 51U, 52U, 53U, 54U},
{55U, 56U, 57U, 58U, 59U},
};
static const demo_mesh_face_t demo_truncated_icosahedron_faces[] = {
{6U, demo_truncated_icosahedron_face_indices[0]},
{6U, demo_truncated_icosahedron_face_indices[1]},
{6U, demo_truncated_icosahedron_face_indices[2]},
{6U, demo_truncated_icosahedron_face_indices[3]},
{6U, demo_truncated_icosahedron_face_indices[4]},
{6U, demo_truncated_icosahedron_face_indices[5]},
{6U, demo_truncated_icosahedron_face_indices[6]},
{6U, demo_truncated_icosahedron_face_indices[7]},
{6U, demo_truncated_icosahedron_face_indices[8]},
{6U, demo_truncated_icosahedron_face_indices[9]},
{6U, demo_truncated_icosahedron_face_indices[10]},
{6U, demo_truncated_icosahedron_face_indices[11]},
{6U, demo_truncated_icosahedron_face_indices[12]},
{6U, demo_truncated_icosahedron_face_indices[13]},
{6U, demo_truncated_icosahedron_face_indices[14]},
{6U, demo_truncated_icosahedron_face_indices[15]},
{6U, demo_truncated_icosahedron_face_indices[16]},
{6U, demo_truncated_icosahedron_face_indices[17]},
{6U, demo_truncated_icosahedron_face_indices[18]},
{6U, demo_truncated_icosahedron_face_indices[19]},
{5U, demo_truncated_icosahedron_face_indices[20]},
{5U, demo_truncated_icosahedron_face_indices[21]},
{5U, demo_truncated_icosahedron_face_indices[22]},
{5U, demo_truncated_icosahedron_face_indices[23]},
{5U, demo_truncated_icosahedron_face_indices[24]},
{5U, demo_truncated_icosahedron_face_indices[25]},
{5U, demo_truncated_icosahedron_face_indices[26]},
{5U, demo_truncated_icosahedron_face_indices[27]},
{5U, demo_truncated_icosahedron_face_indices[28]},
{5U, demo_truncated_icosahedron_face_indices[29]},
{5U, demo_truncated_icosahedron_face_indices[30]},
{5U, demo_truncated_icosahedron_face_indices[31]},
};
static const demo_mesh_t demo_truncated_icosahedron_mesh = {
demo_truncated_icosahedron_vertices, sizeof(demo_truncated_icosahedron_vertices) / sizeof(demo_truncated_icosahedron_vertices[0]),
demo_truncated_icosahedron_faces, sizeof(demo_truncated_icosahedron_faces) / sizeof(demo_truncated_icosahedron_faces[0]),
};
// Archimedean solid: rhombicosidodecahedron.
static const demo_point_3d_t demo_rhombicosidodecahedron_vertices[] = {
{0.563455f, 0.555699f, 0.522353f},
{0.199519f, 0.765814f, 0.522354f},
{0.517118f, 0.215722f, 0.764976f},
{-0.071742f, 0.555694f, 0.764977f},
{-0.249092f, 0.000001f, 0.914925f},
{0.124546f, 0.215720f, 0.914925f},
{0.517119f, -0.215714f, 0.764976f},
{0.841949f, 0.339980f, 0.273260f},
{0.276494f, 0.899137f, 0.119313f},
{-0.762977f, 0.210114f, 0.522352f},
{-0.762976f, -0.210118f, 0.522353f},
{-0.445379f, 0.339974f, 0.764976f},
{-0.445378f, -0.339974f, 0.764976f},
{-0.715406f, 0.559154f, 0.273260f},
{-0.916924f, -0.210120f, 0.119313f},
{0.199523f, -0.765810f, 0.522355f},
{0.124547f, -0.215716f, 0.914925f},
{-0.071740f, -0.555692f, 0.764978f},
{-0.126544f, -0.899135f, 0.273263f},
{0.563458f, -0.555690f, 0.522354f},
{0.916925f, -0.210112f, -0.119313f},
{0.795612f, 0.000005f, 0.515884f},
{0.841951f, -0.339970f, 0.273261f},
{0.715406f, -0.559154f, -0.273260f},
{0.916924f, 0.210120f, -0.119313f},
{-0.276498f, 0.899134f, -0.119314f},
{-0.397809f, 0.689015f, 0.515885f},
{-0.126548f, 0.899135f, 0.273261f},
{0.126544f, 0.899135f, -0.273263f},
{-0.640433f, 0.689013f, -0.119314f},
{-0.640430f, -0.689020f, -0.119312f},
{-0.397806f, -0.689016f, 0.515886f},
{-0.715404f, -0.559159f, 0.273262f},
{-0.841949f, -0.339980f, -0.273260f},
{-0.276494f, -0.899137f, -0.119313f},
{0.397806f, 0.689016f, -0.515886f},
{0.640430f, 0.689020f, 0.119312f},
{0.715404f, 0.559159f, -0.273262f},
{0.445378f, 0.339974f, -0.764976f},
{0.126548f, -0.899135f, -0.273261f},
{0.397809f, -0.689015f, -0.515885f},
{0.640433f, -0.689013f, 0.119314f},
{0.276498f, -0.899134f, 0.119314f},
{0.071742f, -0.555694f, -0.764977f},
{-0.795612f, -0.000005f, -0.515884f},
{-0.916925f, 0.210112f, 0.119313f},
{-0.841951f, 0.339970f, -0.273261f},
{-0.517119f, 0.215714f, -0.764976f},
{0.445379f, -0.339974f, -0.764976f},
{0.249092f, -0.000001f, -0.914925f},
{0.762976f, 0.210118f, -0.522353f},
{0.762977f, -0.210114f, -0.522352f},
{-0.124546f, -0.215720f, -0.914925f},
{0.071740f, 0.555692f, -0.764978f},
{-0.124547f, 0.215716f, -0.914925f},
{-0.563458f, 0.555690f, -0.522354f},
{-0.199523f, 0.765810f, -0.522355f},
{-0.517118f, -0.215722f, -0.764976f},
{-0.199519f, -0.765814f, -0.522354f},
{-0.563455f, -0.555699f, -0.522353f},
};
static const uint8_t demo_rhombicosidodecahedron_face_indices[][5] = {
{0U, 1U, 3U, 5U, 2U},
{1U, 0U, 36U, 8U},
{3U, 1U, 27U, 26U},
{5U, 3U, 11U, 4U},
{2U, 5U, 16U, 6U},
{0U, 2U, 21U, 7U},
{26U, 11U, 3U},
{11U, 26U, 13U, 9U},
{4U, 16U, 5U},
{16U, 4U, 12U, 17U},
{6U, 21U, 2U},
{21U, 6U, 19U, 22U},
{7U, 36U, 0U},
{36U, 7U, 24U, 37U},
{8U, 27U, 1U},
{27U, 8U, 28U, 25U},
{9U, 10U, 12U, 4U, 11U},
{10U, 9U, 45U, 14U},
{12U, 10U, 32U, 31U},
{31U, 17U, 12U},
{17U, 31U, 18U, 15U},
{13U, 45U, 9U},
{45U, 13U, 29U, 46U},
{14U, 32U, 10U},
{32U, 14U, 33U, 30U},
{15U, 19U, 6U, 16U, 17U},
{19U, 15U, 42U, 41U},
{18U, 42U, 15U},
{42U, 18U, 34U, 39U},
{41U, 22U, 19U},
{22U, 41U, 23U, 20U},
{20U, 24U, 7U, 21U, 22U},
{24U, 20U, 51U, 50U},
{23U, 51U, 20U},
{51U, 23U, 40U, 48U},
{50U, 37U, 24U},
{37U, 50U, 38U, 35U},
{25U, 29U, 13U, 26U, 27U},
{29U, 25U, 56U, 55U},
{28U, 56U, 25U},
{56U, 28U, 35U, 53U},
{55U, 46U, 29U},
{46U, 55U, 47U, 44U},
{30U, 34U, 18U, 31U, 32U},
{34U, 30U, 59U, 58U},
{33U, 59U, 30U},
{59U, 33U, 44U, 57U},
{58U, 39U, 34U},
{39U, 58U, 43U, 40U},
{35U, 28U, 8U, 36U, 37U},
{38U, 53U, 35U},
{53U, 38U, 49U, 54U},
{39U, 40U, 23U, 41U, 42U},
{43U, 48U, 40U},
{48U, 43U, 52U, 49U},
{44U, 33U, 14U, 45U, 46U},
{47U, 57U, 44U},
{57U, 47U, 54U, 52U},
{48U, 49U, 38U, 50U, 51U},
{52U, 54U, 49U},
{53U, 54U, 47U, 55U, 56U},
{57U, 52U, 43U, 58U, 59U},
};
static const demo_mesh_face_t demo_rhombicosidodecahedron_faces[] = {
{5U, demo_rhombicosidodecahedron_face_indices[0]},
{4U, demo_rhombicosidodecahedron_face_indices[1]},
{4U, demo_rhombicosidodecahedron_face_indices[2]},
{4U, demo_rhombicosidodecahedron_face_indices[3]},
{4U, demo_rhombicosidodecahedron_face_indices[4]},
{4U, demo_rhombicosidodecahedron_face_indices[5]},
{3U, demo_rhombicosidodecahedron_face_indices[6]},
{4U, demo_rhombicosidodecahedron_face_indices[7]},
{3U, demo_rhombicosidodecahedron_face_indices[8]},
{4U, demo_rhombicosidodecahedron_face_indices[9]},
{3U, demo_rhombicosidodecahedron_face_indices[10]},
{4U, demo_rhombicosidodecahedron_face_indices[11]},
{3U, demo_rhombicosidodecahedron_face_indices[12]},
{4U, demo_rhombicosidodecahedron_face_indices[13]},
{3U, demo_rhombicosidodecahedron_face_indices[14]},
{4U, demo_rhombicosidodecahedron_face_indices[15]},
{5U, demo_rhombicosidodecahedron_face_indices[16]},
{4U, demo_rhombicosidodecahedron_face_indices[17]},
{4U, demo_rhombicosidodecahedron_face_indices[18]},
{3U, demo_rhombicosidodecahedron_face_indices[19]},
{4U, demo_rhombicosidodecahedron_face_indices[20]},
{3U, demo_rhombicosidodecahedron_face_indices[21]},
{4U, demo_rhombicosidodecahedron_face_indices[22]},
{3U, demo_rhombicosidodecahedron_face_indices[23]},
{4U, demo_rhombicosidodecahedron_face_indices[24]},
{5U, demo_rhombicosidodecahedron_face_indices[25]},
{4U, demo_rhombicosidodecahedron_face_indices[26]},
{3U, demo_rhombicosidodecahedron_face_indices[27]},
{4U, demo_rhombicosidodecahedron_face_indices[28]},
{3U, demo_rhombicosidodecahedron_face_indices[29]},
{4U, demo_rhombicosidodecahedron_face_indices[30]},
{5U, demo_rhombicosidodecahedron_face_indices[31]},
{4U, demo_rhombicosidodecahedron_face_indices[32]},
{3U, demo_rhombicosidodecahedron_face_indices[33]},
{4U, demo_rhombicosidodecahedron_face_indices[34]},
{3U, demo_rhombicosidodecahedron_face_indices[35]},
{4U, demo_rhombicosidodecahedron_face_indices[36]},
{5U, demo_rhombicosidodecahedron_face_indices[37]},
{4U, demo_rhombicosidodecahedron_face_indices[38]},
{3U, demo_rhombicosidodecahedron_face_indices[39]},
{4U, demo_rhombicosidodecahedron_face_indices[40]},
{3U, demo_rhombicosidodecahedron_face_indices[41]},
{4U, demo_rhombicosidodecahedron_face_indices[42]},
{5U, demo_rhombicosidodecahedron_face_indices[43]},
{4U, demo_rhombicosidodecahedron_face_indices[44]},
{3U, demo_rhombicosidodecahedron_face_indices[45]},
{4U, demo_rhombicosidodecahedron_face_indices[46]},
{3U, demo_rhombicosidodecahedron_face_indices[47]},
{4U, demo_rhombicosidodecahedron_face_indices[48]},
{5U, demo_rhombicosidodecahedron_face_indices[49]},
{3U, demo_rhombicosidodecahedron_face_indices[50]},
{4U, demo_rhombicosidodecahedron_face_indices[51]},
{5U, demo_rhombicosidodecahedron_face_indices[52]},
{3U, demo_rhombicosidodecahedron_face_indices[53]},
{4U, demo_rhombicosidodecahedron_face_indices[54]},
{5U, demo_rhombicosidodecahedron_face_indices[55]},
{3U, demo_rhombicosidodecahedron_face_indices[56]},
{4U, demo_rhombicosidodecahedron_face_indices[57]},
{5U, demo_rhombicosidodecahedron_face_indices[58]},
{3U, demo_rhombicosidodecahedron_face_indices[59]},
{5U, demo_rhombicosidodecahedron_face_indices[60]},
{5U, demo_rhombicosidodecahedron_face_indices[61]},
};
static const demo_mesh_t demo_rhombicosidodecahedron_mesh = {
demo_rhombicosidodecahedron_vertices, sizeof(demo_rhombicosidodecahedron_vertices) / sizeof(demo_rhombicosidodecahedron_vertices[0]),
demo_rhombicosidodecahedron_faces, sizeof(demo_rhombicosidodecahedron_faces) / sizeof(demo_rhombicosidodecahedron_faces[0]),
};
static const demo_point_3d_t demo_tetrahedron_vertices[] = {
{ 1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f},
{-1.0f, 1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f},
};
static const uint8_t demo_tetrahedron_face_indices[][3] = {
{0U, 1U, 2U}, {0U, 3U, 1U}, {0U, 2U, 3U}, {1U, 3U, 2U},
};
static const demo_mesh_face_t demo_tetrahedron_faces[] = {
{3U, demo_tetrahedron_face_indices[0]}, {3U, demo_tetrahedron_face_indices[1]},
{3U, demo_tetrahedron_face_indices[2]}, {3U, demo_tetrahedron_face_indices[3]},
};
static const demo_mesh_t demo_tetrahedron_mesh = {
demo_tetrahedron_vertices, sizeof(demo_tetrahedron_vertices) / sizeof(demo_tetrahedron_vertices[0]),
demo_tetrahedron_faces, sizeof(demo_tetrahedron_faces) / sizeof(demo_tetrahedron_faces[0]),
};
// These five Platonic plus ten display-friendly Archimedean solids replace the
// old variable-sided pyramids. Every listed mesh is a real closed solid.
static const demo_mesh_t *const demo_stage_six_mesh_catalog[] = {
&demo_tetrahedron_mesh, &demo_cube_mesh, &demo_octahedron_mesh,
&demo_dodecahedron_mesh, &demo_icosahedron_mesh,
&demo_truncated_tetrahedron_mesh, &demo_cuboctahedron_mesh,
&demo_truncated_cube_mesh, &demo_truncated_octahedron_mesh,
&demo_rhombicuboctahedron_mesh, &demo_truncated_cuboctahedron_mesh,
&demo_icosidodecahedron_mesh, &demo_truncated_dodecahedron_mesh,
&demo_truncated_icosahedron_mesh, &demo_rhombicosidodecahedron_mesh,
};
#define DEMO_STAGE_SIX_MESH_CATALOG_COUNT \
(sizeof(demo_stage_six_mesh_catalog) / sizeof(demo_stage_six_mesh_catalog[0]))
static float pitch_degrees;
static float yaw_degrees;
static float roll_degrees;
static float scroll_text_x;
static float scroll_speed_pixels_per_second;
static const char *scroll_text;
static size_t scroll_text_width;
static size_t scroll_message_index = DEMO_SCROLL_MESSAGE_COUNT;
static size_t active_icon_slot;
static const uint8_t *active_logo_bitmap;
static size_t demo_icon_colour_scheme_index;
static uint32_t scroll_text_effect_elapsed_ms;
// Segments 11, 14, and 15 share this independent raster/text clock. It is separate
// from ticker messages, so neither a new message nor its width can make the
// metallic colour roll or text wave visibly hop.
static uint32_t raster_band_effect_elapsed_ms;
// B selects an entry from the shared raster palette list while either raster
// segment is active. Each segment restores its own default on entry.
static size_t raster_band_palette_index;
static uint32_t last_update_ms;
static uint32_t sparkle_phase;
static uint32_t stage_one_next_effect_change_at;
static uint32_t demo_last_interaction_at;
static uint32_t demo_instructions_started_at;
// USER pauses Demo by freezing this virtual clock. On return, real elapsed time
// is added here so every segment timer resumes from its exact previous instant.
static uint32_t demo_paused_total_ms;
static uint32_t demo_selector_started_at;
static uint32_t demo_selector_last_move_at;
static size_t demo_selector_item;
static int demo_selector_joystick_direction;
static bool demo_selector_repeating;
static bool demo_selector_active;
static bool demo_selector_clear_new_stage;
static demo_effect_t selected_effect;
static demo_stage_t selected_stage;
static float stage_two_distance;
static bool stage_two_auto_approaching;
static uint32_t stage_three_next_icon_change_at;
static size_t stage_three_icon_slot;
static const uint8_t *stage_three_icon_bitmap;
static float stage_three_ball_x;
static float stage_three_ball_y;
static float stage_three_ball_velocity_x;
static float stage_three_ball_velocity_y;
static float stage_three_ball_rotation_phase;
static bool stage_three_ball_map_ready;
// Cached sphere geometry keeps the stage responsive: rendering uses these maps
// rather than recalculating trigonometry for every ball pixel on every frame.
EXT_RAM_BSS_ATTR static uint8_t stage_three_ball_longitude_map[DEMO_STAGE_THREE_BALL_PIXEL_COUNT];
EXT_RAM_BSS_ATTR static uint8_t stage_three_ball_latitude_map[DEMO_STAGE_THREE_BALL_PIXEL_COUNT];
EXT_RAM_BSS_ATTR static uint8_t stage_three_ball_shade_map[DEMO_STAGE_THREE_BALL_PIXEL_COUNT];
static const demo_mesh_t *stage_six_current_mesh;
static uint32_t stage_six_next_change_at;
static size_t stage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT;
// Each text-stage icon owns one record. These records are deliberately in
// PSRAM, as requested, so this optional animation state does not consume
// internal RAM if more icon slots or per-icon tuning are added later.
typedef struct {
uint32_t cycle_started_at;
uint32_t cycle_duration_ms;
uint32_t rotation_started_at;
float rotation_start_degrees;
int16_t rotation_degrees_per_second;
} demo_text_corner_icon_cycle_t;
EXT_RAM_BSS_ATTR static demo_text_corner_icon_cycle_t
demo_text_corner_icon_cycles[DEMO_TEXT_CORNER_ICON_COUNT];
static walloons_phase_t stage_seven_phase;
static uint32_t stage_seven_phase_started_at;
static uint8_t stage_seven_completed_bounces;
#if 0 // Moved to demo_stage_slideshow_lens.c with its complete PSRAM lifecycle.
// The lens source is deliberately allocated when Stage 12 opens and released
// when it closes. It must not become a permanent full-screen global allocation.
static uint16_t *stage_twelve_image;
static bool stage_twelve_image_ready;
static char stage_twelve_image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
static float stage_twelve_lens_x;
static float stage_twelve_lens_y;
static float stage_twelve_lens_start_x;
static float stage_twelve_lens_start_y;
static float stage_twelve_lens_target_x;
static float stage_twelve_lens_target_y;
static stage_twelve_lens_edge_t stage_twelve_lens_current_edge;
static stage_twelve_lens_edge_t stage_twelve_lens_target_edge;
static uint32_t stage_twelve_next_image_change_at;
static uint32_t stage_twelve_lens_glide_started_at;
static uint32_t stage_twelve_lens_glide_duration_ms;
#endif
// Segment 16 keeps a ground-only PSRAM scratch source while active. Battle Zone
// renders afresh every Demo frame, then the scaler reads the fresh ground rows
// from this buffer so wrapped movement cannot corrupt its source.
static uint16_t *stage_fifteen_ground_source;
// Stage 17 owns only compact animation state. Its conical gear geometry is
// generated directly into the existing RGB565 frame, so it needs no PSRAM buffer.
static float stage_seventeen_rig_pitch_degrees;
static float stage_seventeen_rig_yaw_degrees;
static float stage_seventeen_rig_roll_degrees;
static float stage_seventeen_gear_degrees[3];
static stage_seventeen_style_t stage_seventeen_style;
EXT_RAM_BSS_ATTR static stage_seventeen_draw_face_t
stage_seventeen_draw_faces[DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT];
static size_t stage_seventeen_draw_face_count;
// Three lighting levels preserve the round form while the logo replaces the red checks.
static const uint16_t stage_three_red_shades[] = {
RGB565(105, 10, 10), RGB565(190, 22, 20), RGB565(250, 70, 60),
};
static const uint16_t stage_three_white_shades[] = {
RGB565(105, 115, 135), RGB565(210, 220, 235), RGB565(255, 255, 255),
};
static screen_transition_t stage_transition;
static bool stage_change_requested;
static bool stage_zoom_in_requested;
static bool stage_zooming_in;
static bool stage_scroll_in_requested;
static bool stage_reveal_requested;
static demo_stage_transition_effect_t selected_stage_transition;
static screen_transition_scroll_direction_t stage_scroll_out_direction;
static screen_transition_scroll_direction_t stage_scroll_in_direction;
static bool running;
static bool demo_instructions_active;
// The instruction page owns this reusable colour starfield. It is deliberately
// separate from the visual effects used by the active Demo segments.
static starfield_t demo_instruction_starfield;
static radial_flame_t demo_ring_flame;
static uint8_t demo_ring_heat[DEMO_RING_SEGMENTS * DEMO_RING_LAYERS];
static const uint16_t demo_ring_colours[5] = {
COLOR_DARKRED, COLOR_RED, COLOR_ORANGE, COLOR_YELLOW, COLOR_WHITE,
};
static bool bitmapPixelSet(const uint8_t *bitmap, unsigned x, unsigned y)
{
const size_t byte_index = (size_t)y * DEMO_LOGO_ROW_BYTES + x / 8U;
const uint8_t bit = (uint8_t)(7U - x % 8U);
return (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
}
static void advanceIconSlot(void)
{
active_icon_slot = (active_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
active_logo_bitmap = demo_icon_slots[active_icon_slot];
}
static void selectDemoIconColourScheme(void)
{
demo_icon_colour_scheme_index =
(size_t)esp_random() % DEMO_ICON_COLOUR_SCHEME_COUNT;
}
static uint32_t demoStageInactivityTimeout(void)
{
return demoSelectorStageInactivityTimeout(selected_stage);
}
static void buildStageThreeBallMap(void)
{
if (stage_three_ball_map_ready) return;
for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
const float normal_x = (float)(x - DEMO_STAGE_THREE_BALL_RADIUS) /
(float)DEMO_STAGE_THREE_BALL_RADIUS;
const float normal_y = (float)(y - DEMO_STAGE_THREE_BALL_RADIUS) /
(float)DEMO_STAGE_THREE_BALL_RADIUS;
const float radius_squared = normal_x * normal_x + normal_y * normal_y;
if (radius_squared > 1.0f) {
stage_three_ball_shade_map[index] = 0U;
continue;
}
const float depth = sqrtf(1.0f - radius_squared);
const float longitude = (atan2f(normal_x, depth) + DEMO_PI) / (2.0f * DEMO_PI);
const float latitude = (asinf(normal_y) + DEMO_PI * 0.5f) / DEMO_PI;
stage_three_ball_longitude_map[index] = (uint8_t)(longitude * 255.0f);
stage_three_ball_latitude_map[index] = (uint8_t)(latitude * 255.0f);
stage_three_ball_shade_map[index] = depth > 0.78f ? 3U : depth > 0.42f ? 2U : 1U;
}
}
stage_three_ball_map_ready = true;
}
// Segment 3 setup: begin the icon-textured ball at its initial bounce position.
static void startStageThreeBall(uint32_t now_ms)
{
buildStageThreeBallMap();
stage_three_next_icon_change_at = now_ms + DEMO_STAGE_THREE_ICON_CHANGE_MS;
stage_three_icon_slot = 0U;
stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
stage_three_ball_x = 82.0f;
stage_three_ball_y = 52.0f;
stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
stage_three_ball_velocity_y = 0.0f;
stage_three_ball_rotation_phase = 0.0f;
}
static void selectStageSixMesh(void)
{
const demo_mesh_t *next_mesh;
do {
next_mesh = demo_stage_six_mesh_catalog[esp_random() % DEMO_STAGE_SIX_MESH_CATALOG_COUNT];
} while (next_mesh == stage_six_current_mesh);
stage_six_current_mesh = next_mesh;
size_t next_colour_scheme =
(size_t)esp_random() % DEMO_SOLID_COLOUR_SCHEME_COUNT;
if (stage_six_colour_scheme_index < DEMO_SOLID_COLOUR_SCHEME_COUNT &&
DEMO_SOLID_COLOUR_SCHEME_COUNT > 1U) {
next_colour_scheme = (size_t)esp_random() % (DEMO_SOLID_COLOUR_SCHEME_COUNT - 1U);
if (next_colour_scheme >= stage_six_colour_scheme_index) ++next_colour_scheme;
}
stage_six_colour_scheme_index = next_colour_scheme;
}
// Segment 6 setup: select the first random filled solid and start its five-second cycle.
static void startStageSixSolid(uint32_t now_ms)
{
stage_six_next_change_at = now_ms + DEMO_STAGE_SIX_CHANGE_MS;
stage_six_current_mesh = NULL;
selectStageSixMesh();
}
static uint32_t randomTextCornerIconTravelDuration(void)
{
// Keep the range inclusive so setting MIN and MAX equal later creates a
// fixed, repeatable period without changing the animation code.
const uint32_t range = DEMO_TEXT_CORNER_ICON_TRAVEL_MAX_MS -
DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + 1U;
return DEMO_TEXT_CORNER_ICON_TRAVEL_MIN_MS + esp_random() % range;
}
static int16_t randomTextCornerIconRotationSpeed(void)
{
const uint32_t range = (uint32_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MAX_DEGREES_PER_SECOND -
DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND + 1);
const int16_t speed = (int16_t)(DEMO_TEXT_CORNER_ICON_ROTATION_MIN_DEGREES_PER_SECOND +
(int)(esp_random() % range));
// Give neighbouring icons visibly different movement by independently
// choosing their clockwise or anti-clockwise direction as well as speed.
return (esp_random() & 1U) != 0U ? speed : (int16_t)-speed;
}
static void startTextCornerIconCycles(uint32_t now_ms)
{
// A text segment always starts with every icon at its own outer corner.
// The first random duration then separates the icons naturally as they
// travel toward the centre X line at different rates.
for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
demo_text_corner_icon_cycles[corner].cycle_started_at = now_ms;
demo_text_corner_icon_cycles[corner].cycle_duration_ms =
randomTextCornerIconTravelDuration();
// Rotation does not restart with every travel cycle: it stays smooth
// while movement chooses a new random duration at each outer edge.
demo_text_corner_icon_cycles[corner].rotation_started_at = now_ms;
demo_text_corner_icon_cycles[corner].rotation_start_degrees =
(float)(esp_random() % 360U);
demo_text_corner_icon_cycles[corner].rotation_degrees_per_second =
randomTextCornerIconRotationSpeed();
}
}
static void updateTextCornerIconCycles(uint32_t now_ms)
{
for (size_t corner = 0U; corner < DEMO_TEXT_CORNER_ICON_COUNT; ++corner) {
demo_text_corner_icon_cycle_t *const cycle = &demo_text_corner_icon_cycles[corner];
// A late frame can cross more than one complete cycle. Preserve the
// remaining time by advancing from the old start, rather than snapping
// the icon back to its corner at the current frame time.
while ((uint32_t)(now_ms - cycle->cycle_started_at) >= cycle->cycle_duration_ms) {
cycle->cycle_started_at += cycle->cycle_duration_ms;
cycle->cycle_duration_ms = randomTextCornerIconTravelDuration();
}
}
}
static void startStageSevenWalloons(uint32_t now_ms)
{
// Begin with the boxed PET title. After the short title interval, the box
// disappears and the original action begins with the left Walloon walking
// from PET cell (2, 15) to (14, 15).
stage_seven_phase = WALLOONS_TITLE_CARD;
stage_seven_phase_started_at = now_ms;
stage_seven_completed_bounces = 0U;
}
#if 0 // Moved to demo_stage_slideshow_lens.c.
static void releaseStageTwelveImage(void)
{
heap_caps_free(stage_twelve_image);
stage_twelve_image = NULL;
stage_twelve_image_ready = false;
stage_twelve_image_path[0] = '\0';
stage_twelve_next_image_change_at = 0U;
}
#endif
static void releaseStageFifteenGroundSource(void)
{
heap_caps_free(stage_fifteen_ground_source);
stage_fifteen_ground_source = NULL;
}
static void startStageFifteenBattleZoneRaster(void)
{
releaseStageFifteenGroundSource();
const size_t ground_pixels = (size_t)LCD_WIDTH *
(LCD_HEIGHT - BATTLE_ZONE_HORIZON_Y);
stage_fifteen_ground_source = heap_caps_malloc(ground_pixels *
sizeof(stage_fifteen_ground_source[0]),
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
}
#if 0 // Moved to demo_stage_slideshow_lens.c.
static void chooseStageTwelveLensEdgePoint(stage_twelve_lens_edge_t edge,
float *x, float *y)
{
// The centre is always on one screen edge. The lens radius can therefore
// extend outside the display, while every sampled source pixel stays valid.
switch (edge) {
case STAGE_TWELVE_LENS_EDGE_TOP:
*x = (float)(esp_random() % LCD_WIDTH);
*y = 0.0f;
break;
case STAGE_TWELVE_LENS_EDGE_RIGHT:
*x = (float)(LCD_WIDTH - 1);
*y = (float)(esp_random() % LCD_HEIGHT);
break;
case STAGE_TWELVE_LENS_EDGE_BOTTOM:
*x = (float)(esp_random() % LCD_WIDTH);
*y = (float)(LCD_HEIGHT - 1);
break;
case STAGE_TWELVE_LENS_EDGE_LEFT:
default:
*x = 0.0f;
*y = (float)(esp_random() % LCD_HEIGHT);
break;
}
}
static void chooseStageTwelveLensTarget(void)
{
// Pick a different edge and a visibly long route. The bounded retry count
// guarantees progress even if future tuning makes the distance too strict.
for (unsigned attempt = 0U; attempt < DEMO_STAGE_TWELVE_LENS_TARGET_ATTEMPTS; ++attempt) {
do {
stage_twelve_lens_target_edge =
(stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
} while (stage_twelve_lens_target_edge == stage_twelve_lens_current_edge);
chooseStageTwelveLensEdgePoint(stage_twelve_lens_target_edge,
&stage_twelve_lens_target_x,
&stage_twelve_lens_target_y);
const float dx = stage_twelve_lens_target_x - stage_twelve_lens_x;
const float dy = stage_twelve_lens_target_y - stage_twelve_lens_y;
const float distance_squared = dx * dx + dy * dy;
const float minimum_squared = DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE *
DEMO_STAGE_TWELVE_LENS_MIN_TARGET_DISTANCE;
if (distance_squared >= minimum_squared) return;
}
}
static bool selectStageTwelveSlideshowImage(char *path, size_t path_size,
const char *excluded_path)
{
DIR *const directory = opendir(DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY);
if (directory == NULL) return false;
// Reservoir sampling gives every supported file an equal chance without
// storing the slideshow directory's full filename list in RAM.
char selected_name[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX] = {0};
size_t supported_count = 0U;
struct dirent *entry;
while ((entry = readdir(directory)) != NULL) {
if (!imageIsSupportedName(entry->d_name)) continue;
char candidate_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
const int candidate_written = snprintf(candidate_path, sizeof(candidate_path), "%s/%s",
DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, entry->d_name);
if (candidate_written < 0 || (size_t)candidate_written >= sizeof(candidate_path)) continue;
// Do not immediately repeat the current picture when the directory
// contains another supported file to choose from.
if (excluded_path != NULL && strcmp(candidate_path, excluded_path) == 0) continue;
++supported_count;
if (esp_random() % supported_count != 0U) continue;
const int written = snprintf(selected_name, sizeof(selected_name), "%s", entry->d_name);
if (written < 0 || (size_t)written >= sizeof(selected_name)) {
closedir(directory);
return false;
}
}
closedir(directory);
if (supported_count == 0U) return false;
const int written = snprintf(path, path_size, "%s/%s",
DEMO_STAGE_TWELVE_SLIDESHOW_DIRECTORY, selected_name);
return written >= 0 && (size_t)written < path_size;
}
static void beginStageTwelveLensGlide(uint32_t now_ms)
{
stage_twelve_lens_start_x = stage_twelve_lens_x;
stage_twelve_lens_start_y = stage_twelve_lens_y;
chooseStageTwelveLensTarget();
stage_twelve_lens_glide_started_at = now_ms;
stage_twelve_lens_glide_duration_ms = DEMO_STAGE_TWELVE_LENS_GLIDE_MIN_MS +
(esp_random() % DEMO_STAGE_TWELVE_LENS_GLIDE_RANGE_MS);
}
static void startStageTwelveSlideshowLens(uint32_t now_ms)
{
releaseStageTwelveImage();
stage_twelve_image = heap_caps_malloc((size_t)LCD_WIDTH * LCD_HEIGHT *
sizeof(stage_twelve_image[0]),
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (stage_twelve_image == NULL) return;
char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
if (!selectStageTwelveSlideshowImage(image_path, sizeof(image_path), NULL) ||
!imageDecodeFile(image_path, stage_twelve_image)) {
releaseStageTwelveImage();
return;
}
stage_twelve_image_ready = true;
snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
stage_twelve_lens_current_edge =
(stage_twelve_lens_edge_t)(esp_random() % STAGE_TWELVE_LENS_EDGE_COUNT);
chooseStageTwelveLensEdgePoint(stage_twelve_lens_current_edge,
&stage_twelve_lens_x, &stage_twelve_lens_y);
beginStageTwelveLensGlide(now_ms);
}
static void updateStageTwelveSlideshowLens(uint32_t now_ms)
{
if (!stage_twelve_image_ready) return;
if ((int32_t)(now_ms - stage_twelve_next_image_change_at) >= 0) {
char image_path[DEMO_STAGE_TWELVE_IMAGE_PATH_MAX];
// Reuse the existing PSRAM image buffer. If a new file cannot be
// selected or decoded, leave the already visible picture in place.
if (selectStageTwelveSlideshowImage(image_path, sizeof(image_path),
stage_twelve_image_path) &&
imageDecodeFile(image_path, stage_twelve_image)) {
snprintf(stage_twelve_image_path, sizeof(stage_twelve_image_path), "%s", image_path);
}
stage_twelve_next_image_change_at = now_ms + DEMO_STAGE_TWELVE_IMAGE_CHANGE_MS;
}
const uint32_t elapsed_ms = now_ms - stage_twelve_lens_glide_started_at;
if (elapsed_ms >= stage_twelve_lens_glide_duration_ms) {
stage_twelve_lens_x = stage_twelve_lens_target_x;
stage_twelve_lens_y = stage_twelve_lens_target_y;
stage_twelve_lens_current_edge = stage_twelve_lens_target_edge;
beginStageTwelveLensGlide(now_ms);
return;
}
const float progress = (float)elapsed_ms / (float)stage_twelve_lens_glide_duration_ms;
// Use constant-speed interpolation rather than smoothstep. Smoothstep
// reaches zero speed at every target, which made the lens visibly pause.
stage_twelve_lens_x = stage_twelve_lens_start_x +
(stage_twelve_lens_target_x - stage_twelve_lens_start_x) * progress;
stage_twelve_lens_y = stage_twelve_lens_start_y +
(stage_twelve_lens_target_y - stage_twelve_lens_start_y) * progress;
}
#endif
static unsigned walloonsPhaseSteps(uint32_t now_ms, unsigned maximum_steps)
{
const unsigned elapsed_steps = (now_ms - stage_seven_phase_started_at) /
DEMO_STAGE_SEVEN_STEP_MS;
return elapsed_steps < maximum_steps ? elapsed_steps : maximum_steps;
}
static void setWalloonsPhase(walloons_phase_t phase, uint32_t now_ms)
{
stage_seven_phase = phase;
stage_seven_phase_started_at = now_ms;
}
static void updateStageSevenWalloons(uint32_t now_ms)
{
const uint32_t elapsed = now_ms - stage_seven_phase_started_at;
const uint32_t walk_duration = 12U * DEMO_STAGE_SEVEN_STEP_MS;
const uint32_t platform_duration = 14U * DEMO_STAGE_SEVEN_STEP_MS;
// There are twelve row moves. Hold the twelfth (landing) cell for one full
// step interval instead of changing phase before drawDemo can show it.
const uint32_t vertical_duration = 13U * DEMO_STAGE_SEVEN_STEP_MS;
switch (stage_seven_phase) {
case WALLOONS_TITLE_CARD:
if (elapsed >= DEMO_STAGE_SEVEN_TITLE_MS) {
setWalloonsPhase(WALLOONS_LEFT_WALK_IN, now_ms);
}
break;
case WALLOONS_LEFT_WALK_IN:
if (elapsed >= walk_duration) {
setWalloonsPhase(WALLOONS_PAUSE_ON_SEESAW, now_ms);
}
break;
case WALLOONS_PAUSE_ON_SEESAW:
if (elapsed >= DEMO_STAGE_SEVEN_PAUSE_MS) {
setWalloonsPhase(WALLOONS_RIGHT_WALK_ON_PLATFORM, now_ms);
}
break;
case WALLOONS_RIGHT_WALK_ON_PLATFORM:
if (elapsed >= platform_duration) {
setWalloonsPhase(WALLOONS_RIGHT_DROP, now_ms);
}
break;
case WALLOONS_RIGHT_DROP:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
}
break;
case WALLOONS_BOUNCE_RIGHT_UP:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_DOWN, now_ms);
}
break;
case WALLOONS_BOUNCE_RIGHT_DOWN:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_LEFT_UP, now_ms);
}
break;
case WALLOONS_BOUNCE_LEFT_UP:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_BOUNCE_LEFT_DOWN, now_ms);
}
break;
case WALLOONS_BOUNCE_LEFT_DOWN:
if (elapsed >= vertical_duration) {
setWalloonsPhase(WALLOONS_LEFT_LANDED, now_ms);
}
break;
case WALLOONS_LEFT_LANDED:
// Keep the left Walloon visibly on the new low end before launching
// the right Walloon. This is the original stunt's missing landing beat.
if (elapsed >= DEMO_STAGE_SEVEN_LANDING_MS) {
++stage_seven_completed_bounces;
if (stage_seven_completed_bounces >= DEMO_STAGE_SEVEN_BOUNCE_COUNT) {
setWalloonsPhase(WALLOONS_FINISHED, now_ms);
} else {
setWalloonsPhase(WALLOONS_BOUNCE_RIGHT_UP, now_ms);
}
}
break;
case WALLOONS_FINISHED:
// Hold the completed stunt until C or the shared inactivity timer advances Demo.
break;
}
}
static uint32_t demoTimeNow(uint32_t now_ms)
{
return now_ms - demo_paused_total_ms;
}
static void enterDemoStage(demo_stage_t stage, uint32_t now_ms)
{
// Stage 12 owns a full-screen PSRAM source only while it is visible.
if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStop();
if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStop();
if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) releaseStageFifteenGroundSource();
selected_stage = stage;
// Entering any segment begins a fresh shared inactivity interval.
demo_last_interaction_at = now_ms;
if (selected_stage == DEMO_STAGE_ICON) {
// A full Demo loop begins a fresh automatic Stage-1 effect interval.
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
}
if (selected_stage == DEMO_STAGE_AMIGA_BALL) startStageThreeBall(now_ms);
if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) startStageSixSolid(now_ms);
if (selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER) startTextCornerIconCycles(now_ms);
if (selected_stage == DEMO_STAGE_WALLOONS) demoStageWalloonsStart(now_ms);
if (selected_stage == DEMO_STAGE_KALEIDO) demoStageKaleidoStart(now_ms);
if (selected_stage == DEMO_STAGE_SPARKLE) demoStageSparkleStart(now_ms);
if (selected_stage == DEMO_STAGE_SQUARES) demoStageSquaresStart(now_ms);
if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) startStageFifteenBattleZoneRaster();
if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
stage_seventeen_rig_pitch_degrees = -12.0f;
stage_seventeen_rig_yaw_degrees = 18.0f;
stage_seventeen_rig_roll_degrees = 0.0f;
stage_seventeen_gear_degrees[0] = 0.0f;
// Each driven gear begins half a tooth out of phase with the lead gear
// at its fixed mesh contact. Their later angles are derived by motion.
stage_seventeen_gear_degrees[1] = 286.0f;
stage_seventeen_gear_degrees[2] = 226.0f;
// Start with the clearest filled treatment; B cycles outline, flat-retro,
// and shaded-metal versions without restarting the rig.
stage_seventeen_style = STAGE_SEVENTEEN_STYLE_FLAT;
}
if (selected_stage == DEMO_STAGE_MOUSE_MAZE) demoStageMouseMazeStart(now_ms);
if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
raster_band_effect_elapsed_ms = 0U;
// Each raster segment starts on its own fitting palette. B can then
// cycle every palette from the shared list without interrupting motion.
if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
raster_band_palette_index = DEMO_RASTER_FIRE_PALETTE_INDEX;
} else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
raster_band_palette_index = DEMO_RASTER_GREEN_PALETTE_INDEX;
} else {
raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
}
}
if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER && scroll_text != NULL) {
// This segment is wider than the shared scale-2 ticker. Restart the
// current message just outside its travel edge using its scale-3 width.
scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH *
(size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE;
scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
? (float)LCD_WIDTH : -(float)scroll_text_width;
}
if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) demoStageSlideshowLensStart(now_ms);
if (selected_stage == DEMO_STAGE_PLASMA) demoStagePlasmaStart();
if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) demoStageGameOfLifeStart(now_ms);
}
static void advanceDemoStage(uint32_t now_ms)
{
enterDemoStage(demoSelectorNextAutomaticStage(selected_stage), now_ms);
}
static void updateStageThreeBall(float elapsed_seconds)
{
stage_three_ball_x += stage_three_ball_velocity_x * elapsed_seconds;
stage_three_ball_y += stage_three_ball_velocity_y * elapsed_seconds;
stage_three_ball_velocity_y += DEMO_STAGE_THREE_BALL_GRAVITY * elapsed_seconds;
stage_three_ball_rotation_phase +=
DEMO_STAGE_THREE_BALL_ROTATION_BANDS_PER_SECOND * elapsed_seconds;
if (stage_three_ball_rotation_phase >= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) {
stage_three_ball_rotation_phase -= (float)DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS;
}
if (stage_three_ball_x < (float)DEMO_STAGE_THREE_BALL_RADIUS) {
stage_three_ball_x = (float)DEMO_STAGE_THREE_BALL_RADIUS;
stage_three_ball_velocity_x = DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
} else if (stage_three_ball_x >
(float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS)) {
stage_three_ball_x = (float)(LCD_WIDTH - 1 - DEMO_STAGE_THREE_BALL_RADIUS);
stage_three_ball_velocity_x = -DEMO_STAGE_THREE_BALL_HORIZONTAL_VELOCITY;
}
if (stage_three_ball_y > DEMO_STAGE_THREE_BALL_FLOOR_Y -
(float)DEMO_STAGE_THREE_BALL_RADIUS) {
stage_three_ball_y = DEMO_STAGE_THREE_BALL_FLOOR_Y -
(float)DEMO_STAGE_THREE_BALL_RADIUS;
stage_three_ball_velocity_y = DEMO_STAGE_THREE_BALL_BOUNCE_VELOCITY;
}
}
static void selectRandomStageTransition(void)
{
selected_stage_transition = (demo_stage_transition_effect_t)
(esp_random() % DEMO_STAGE_TRANSITION_EFFECT_COUNT);
if (selected_stage_transition != DEMO_STAGE_TRANSITION_SCROLL) return;
// Each pair keeps both frames on the same axis, travelling in opposite directions.
// The index is random so each scroll transition can go left, right, up, or down.
switch (esp_random() % 4U) {
case 0U:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
break;
case 1U:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_LEFT;
break;
case 2U:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_UP;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_DOWN;
break;
default:
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_DOWN;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_UP;
break;
}
}
static void selectScrollMessage(bool switch_logo)
{
size_t next = (size_t)esp_random() % DEMO_SCROLL_MESSAGE_COUNT;
if (scroll_message_index < DEMO_SCROLL_MESSAGE_COUNT) {
next = (size_t)esp_random() % (DEMO_SCROLL_MESSAGE_COUNT - 1U);
if (next >= scroll_message_index) ++next;
}
scroll_message_index = next;
scroll_text = demo_scroll_messages[scroll_message_index];
const size_t scroll_scale = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER
? (size_t)DEMO_STAGE_FOURTEEN_SCROLL_SCALE
: DEMO_SCROLL_SCALE;
scroll_text_width = strlen(scroll_text) * PET2001_FONT_GLYPH_WIDTH * scroll_scale;
scroll_text_x = scroll_speed_pixels_per_second >= 0.0f
? (float)LCD_WIDTH : -(float)scroll_text_width;
if (switch_logo && !DEMO_LOCK_ICON_TO_PRIMARY) {
advanceIconSlot();
}
scroll_text_effect_elapsed_ms = 0U;
}
static bool dimFlamesBehindTickerGlyph(uint16_t *frame, int centre_x, int centre_y,
float glyph_scale)
{
if (selected_effect != DEMO_EFFECT_RADIAL_FLAME || glyph_scale <= 0.0f) return false;
const int half_width = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale * 0.5f);
const int half_height = (int)ceilf((float)PET2001_FONT_GLYPH_HEIGHT * glyph_scale * 0.5f);
bool touches_flame = false;
for (int y = centre_y - half_height; y <= centre_y + half_height; ++y) {
if (y < 0 || y >= LCD_HEIGHT) continue;
for (int x = centre_x - half_width; x <= centre_x + half_width; ++x) {
if (x < 0 || x >= LCD_WIDTH) continue;
uint16_t *pixel = &frame[(size_t)y * LCD_WIDTH + (size_t)x];
for (size_t colour = 0U; colour < sizeof(demo_ring_colours) /
sizeof(demo_ring_colours[0]); ++colour) {
if (*pixel != demo_ring_colours[colour]) continue;
// Retain the moving fire shape but step it down for white PET text.
*pixel = colour == 0U ? COLOR_BLACK : demo_ring_colours[colour - 1U];
touches_flame = true;
break;
}
}
}
return touches_flame;
}
static uint16_t demoScrollRainbowColour(unsigned glyph_index)
{
const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
sizeof(demo_scroll_rainbow_colours[0]);
const unsigned time_step = scroll_text_effect_elapsed_ms /
DEMO_SCROLL_RAINBOW_MS_PER_COLOUR;
const unsigned glyph_step = glyph_index / DEMO_SCROLL_RAINBOW_GLYPHS_PER_COLOUR;
return demo_scroll_rainbow_colours[(glyph_step + time_step) % colour_count];
}
// Segment 4 renderer helpers: enlarge and spread glyphs around the centre lens.
static float stageFourMagnifierScale(int glyph_centre_x)
{
const float distance = fabsf((float)glyph_centre_x -
(float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X);
float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
if (influence < 0.0f) influence = 0.0f;
// Squaring concentrates the largest letters around the exact screen centre.
influence *= influence;
return (float)DEMO_SCROLL_SCALE +
(DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) * influence;
}
static float stageFourMagnifierPosition(int glyph_centre_x)
{
const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
const float offset = (float)glyph_centre_x - centre;
const float distance = fabsf(offset);
if (distance >= DEMO_STAGE_FOUR_MAGNIFIER_RADIUS) return (float)glyph_centre_x;
const float influence = 1.0f - distance / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
// Spread the glyph centres with the lens so expanded neighbouring letters do not overlap.
return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
influence * influence);
}
// Segment 5 renderer helpers: the same scroller in reverse, shrinking at centre
// and expanding toward both edges.
static float stageFiveMagnifierScale(int glyph_centre_x)
{
float influence = fabsf((float)glyph_centre_x -
(float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X) /
DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
if (influence > 1.0f) influence = 1.0f;
// Inverse lens: smallest at centre, largest at the screen edges and beyond.
return (float)DEMO_SCROLL_SCALE +
(DEMO_STAGE_FOUR_MAGNIFIER_MAX_SCALE - (float)DEMO_SCROLL_SCALE) *
influence * influence;
}
static float stageFiveMagnifierPosition(int glyph_centre_x)
{
const float centre = (float)DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_X;
const float offset = (float)glyph_centre_x - centre;
float influence = fabsf(offset) / DEMO_STAGE_FOUR_MAGNIFIER_RADIUS;
if (influence > 1.0f) influence = 1.0f;
// Keep the maximum expansion beyond both screen edges so large letters enter
// and leave cleanly instead of snapping back to their ordinary spacing.
return centre + offset * (1.0f + DEMO_STAGE_FOUR_MAGNIFIER_SPACING_STRENGTH *
influence * influence);
}
// Shared ticker renderer. Segments 4, 5, 11, 14, and 15 select their own positioning
// or magnification above; every other segment receives the normal bottom ticker.
static void drawScrollText(uint16_t *frame)
{
if (scroll_text == NULL) return;
const unsigned text_length = (unsigned)strlen(scroll_text);
const int base_x = (int)lroundf(scroll_text_x);
const bool magnifier_active = selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
const bool raster_band_scroller = selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
const bool lower_raster_band_scroller =
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
const bool battle_zone_raster_scroller =
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
const int advance = PET2001_FONT_GLYPH_WIDTH *
(lower_raster_band_scroller ? (int)DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
DEMO_SCROLL_SCALE);
const uint32_t text_wave_elapsed_ms = raster_band_scroller ?
raster_band_effect_elapsed_ms : scroll_text_effect_elapsed_ms;
const bool reverse_magnifier = selected_stage == DEMO_STAGE_REVERSE_SCROLLER;
for (unsigned index = 0U; index < text_length; ++index) {
const int unscaled_centre_x = base_x + (int)index * advance + advance / 2;
const int centre_x = !magnifier_active ? unscaled_centre_x :
(int)lroundf(reverse_magnifier ? stageFiveMagnifierPosition(unscaled_centre_x) :
stageFourMagnifierPosition(unscaled_centre_x));
const float glyph_scale = !magnifier_active ?
(lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_SCALE :
(float)DEMO_SCROLL_SCALE) :
(reverse_magnifier ? stageFiveMagnifierScale(unscaled_centre_x) :
stageFourMagnifierScale(unscaled_centre_x));
const int glyph_margin = (int)ceilf((float)PET2001_FONT_GLYPH_WIDTH * glyph_scale);
const int centre_y = magnifier_active ? DEMO_STAGE_FOUR_MAGNIFIER_CENTRE_Y :
(lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_CENTRE_Y :
(battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_CENTRE_Y :
(raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_CENTRE_Y : DEMO_SCROLL_Y))) +
graphics_text_effect_wave_offset(text_wave_elapsed_ms, index,
lower_raster_band_scroller ? DEMO_STAGE_FOURTEEN_SCROLL_WAVE_AMPLITUDE :
(battle_zone_raster_scroller ? DEMO_STAGE_FIFTEEN_SCROLL_WAVE_AMPLITUDE :
(raster_band_scroller ? DEMO_STAGE_ELEVEN_SCROLL_WAVE_AMPLITUDE :
DEMO_SCROLL_EFFECT_WAVE_AMPLITUDE))) +
(raster_band_scroller ? 0 : advance / 2);
// Long messages contain many off-screen glyphs; skip their transform work.
if (centre_x + glyph_margin < 0 || centre_x - glyph_margin >= LCD_WIDTH ||
centre_y + glyph_margin < 0 || centre_y - glyph_margin >= LCD_HEIGHT) {
continue;
}
const uint16_t glyph_color = dimFlamesBehindTickerGlyph(frame, centre_x, centre_y,
glyph_scale)
? COLOR_WHITE : demoScrollRainbowColour(index);
drawPet2001AsciiCharTransformed(frame, centre_x, centre_y, scroll_text[index],
glyph_scale, 0.0f, glyph_color);
}
}
static graphics_point_t projectPoint(float source_x, float source_y, float depth,
const demo_rotation_t *rotation)
{
const float scaled_x = source_x * DEMO_FACE_SCALE;
const float scaled_y = source_y * DEMO_FACE_SCALE;
const float pitched_y = scaled_y * rotation->pitch_cosine -
depth * rotation->pitch_sine;
const float pitched_z = scaled_y * rotation->pitch_sine +
depth * rotation->pitch_cosine;
const float yawed_x = scaled_x * rotation->yaw_cosine +
pitched_z * rotation->yaw_sine;
const float yawed_z = -scaled_x * rotation->yaw_sine +
pitched_z * rotation->yaw_cosine;
const float rotated_x = yawed_x * rotation->roll_cosine -
pitched_y * rotation->roll_sine;
const float rotated_y = yawed_x * rotation->roll_sine +
pitched_y * rotation->roll_cosine;
const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
const graphics_point_t result = {
.x = DEMO_CENTRE_X + (int)lroundf(rotated_x * perspective),
.y = DEMO_CENTRE_Y + (int)lroundf(rotated_y * perspective),
};
return result;
}
static float demoRingInnerRadius(void)
{
const float pitch = pitch_degrees * DEMO_PI / 180.0f;
const float yaw = yaw_degrees * DEMO_PI / 180.0f;
const float roll = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
cosf(pitch), sinf(pitch), cosf(yaw), sinf(yaw), cosf(roll), sinf(roll),
};
float outer_radius = 0.0f;
for (unsigned sample = 0U; sample < 16U; ++sample) {
const float angle = (float)sample * DEMO_PI * 2.0f / 16.0f;
for (unsigned depth_index = 0U; depth_index < 2U; ++depth_index) {
const graphics_point_t point = projectPoint(cosf(angle) * 30.0f,
sinf(angle) * 30.0f, depth_index == 0U ? 0.0f : DEMO_EXTRUSION_DEPTH,
&rotation);
const float dx = (float)point.x - DEMO_CENTRE_X;
const float dy = (float)point.y - DEMO_CENTRE_Y;
const float radius = sqrtf(dx * dx + dy * dy);
if (radius > outer_radius) outer_radius = radius;
}
}
return outer_radius + DEMO_RING_ICON_GAP;
}
static void drawLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
const demo_rotation_t *rotation, uint16_t color)
{
for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
unsigned run_start = 0U;
while (run_start < DEMO_LOGO_WIDTH) {
while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
++run_start;
}
if (run_start == DEMO_LOGO_WIDTH) break;
unsigned run_end = run_start + 1U;
while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
++run_end;
}
const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
const graphics_point_t run[4] = {
projectPoint(left, top, depth, rotation),
projectPoint(right, top, depth, rotation),
projectPoint(right, top + 1.0f, depth, rotation),
projectPoint(left, top + 1.0f, depth, rotation),
};
fill_convex_polygon(frame, run, 4U, color);
run_start = run_end;
}
}
}
static graphics_point_t projectApproachPoint(float source_x, float source_y, float depth,
const demo_rotation_t *rotation, float scale,
int centre_x, int centre_y)
{
const float scaled_x = source_x * scale;
const float scaled_y = source_y * scale;
const float scaled_depth = depth * scale;
const float pitched_y = scaled_y * rotation->pitch_cosine -
scaled_depth * rotation->pitch_sine;
const float pitched_z = scaled_y * rotation->pitch_sine +
scaled_depth * rotation->pitch_cosine;
const float yawed_x = scaled_x * rotation->yaw_cosine +
pitched_z * rotation->yaw_sine;
const float yawed_z = -scaled_x * rotation->yaw_sine +
pitched_z * rotation->yaw_cosine;
const float rotated_x = yawed_x * rotation->roll_cosine -
pitched_y * rotation->roll_sine;
const float rotated_y = yawed_x * rotation->roll_sine +
pitched_y * rotation->roll_cosine;
const float perspective = DEMO_CAMERA_DISTANCE / (DEMO_CAMERA_DISTANCE + yawed_z);
const graphics_point_t result = {
.x = centre_x + (int)lroundf(rotated_x * perspective),
.y = centre_y + (int)lroundf(rotated_y * perspective),
};
return result;
}
static void drawApproachLogoSlice(uint16_t *frame, const uint8_t *bitmap, float depth,
const demo_rotation_t *rotation, float scale,
int centre_x, int centre_y, uint16_t color)
{
for (unsigned y = 0U; y < DEMO_LOGO_HEIGHT; ++y) {
unsigned run_start = 0U;
while (run_start < DEMO_LOGO_WIDTH) {
while (run_start < DEMO_LOGO_WIDTH && !bitmapPixelSet(bitmap, run_start, y)) {
++run_start;
}
if (run_start == DEMO_LOGO_WIDTH) break;
unsigned run_end = run_start + 1U;
while (run_end < DEMO_LOGO_WIDTH && bitmapPixelSet(bitmap, run_end, y)) {
++run_end;
}
const float left = (float)run_start - (float)DEMO_LOGO_WIDTH * 0.5f;
const float right = (float)run_end - (float)DEMO_LOGO_WIDTH * 0.5f;
const float top = (float)y - (float)DEMO_LOGO_HEIGHT * 0.5f;
const graphics_point_t run[4] = {
projectApproachPoint(left, top, depth, rotation, scale, centre_x, centre_y),
projectApproachPoint(right, top, depth, rotation, scale, centre_x, centre_y),
projectApproachPoint(right, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
projectApproachPoint(left, top + 1.0f, depth, rotation, scale, centre_x, centre_y),
};
fill_convex_polygon(frame, run, 4U, color);
run_start = run_end;
}
}
}
// Leave the pre-Demo control page and begin Segment 1 with a fresh timeout and
// automatic-effect schedule. This deliberately does not use a stage transition:
// the instruction page is not one of the sixteen Demo segments.
static void beginDemoSegments(uint32_t now_ms)
{
demo_instructions_active = false;
demo_last_interaction_at = now_ms;
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
}
void startDemo(uint32_t now_ms)
{
demoStageSlideshowLensStop();
demoStageGameOfLifeStop();
releaseStageFifteenGroundSource();
demoStagePlasmaInitialize();
pitch_degrees = 0.0f;
yaw_degrees = 0.0f;
roll_degrees = 0.0f;
active_icon_slot = 0U;
active_logo_bitmap = demo_icon_slots[active_icon_slot];
selectDemoIconColourScheme();
scroll_speed_pixels_per_second = DEMO_SCROLL_INITIAL_PIXELS_PER_SECOND;
selected_stage = DEMO_STAGE_ICON;
stage_two_distance = DEMO_STAGE_TWO_START_DISTANCE;
stage_two_auto_approaching = true;
stage_three_next_icon_change_at = 0U;
stage_three_icon_slot = 0U;
stage_three_icon_bitmap = NULL;
stage_six_next_change_at = 0U;
stage_six_current_mesh = NULL;
stage_six_colour_scheme_index = DEMO_SOLID_COLOUR_SCHEME_COUNT;
raster_band_palette_index = DEMO_RASTER_METALLIC_PALETTE_INDEX;
demo_paused_total_ms = 0U;
demo_selector_active = false;
demo_selector_clear_new_stage = false;
demo_selector_item = 0U;
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
demo_instructions_active = true;
demo_instructions_started_at = now_ms;
starfield_initialize(&demo_instruction_starfield, 60U, 0, 0, LCD_WIDTH, LCD_HEIGHT,
STARFIELD_COLOUR);
stage_one_next_effect_change_at = 0U;
demo_last_interaction_at = now_ms;
screenTransitionReset(&stage_transition);
stage_change_requested = false;
stage_zoom_in_requested = false;
stage_zooming_in = false;
stage_scroll_in_requested = false;
stage_reveal_requested = false;
selected_stage_transition = DEMO_STAGE_TRANSITION_SHRINK_ZOOM;
stage_scroll_out_direction = SCREEN_TRANSITION_SCROLL_LEFT;
stage_scroll_in_direction = SCREEN_TRANSITION_SCROLL_RIGHT;
selectScrollMessage(false);
last_update_ms = now_ms;
sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
radial_flame_initialize(&demo_ring_flame, demo_ring_heat, DEMO_RING_SEGMENTS,
DEMO_RING_LAYERS, DEMO_RING_LAYER_DEPTH, esp_random());
// Start with the outward fire ring; A/K then selects the remaining effects.
selected_effect = DEMO_EFFECT_RADIAL_FLAME;
running = true;
}
void stopDemo(void)
{
screenTransitionDeinitialize(&stage_transition);
demoStageSlideshowLensStop();
demoStageGameOfLifeStop();
releaseStageFifteenGroundSource();
running = false;
}
static void moveDemoSelector(int direction, uint32_t now_ms)
{
const size_t item_count = DEMO_STAGE_COUNT + 1U;
if (direction < 0) {
demo_selector_item = demo_selector_item == 0U ? item_count - 1U :
demo_selector_item - 1U;
} else if (direction > 0) {
demo_selector_item = (demo_selector_item + 1U) % item_count;
}
demo_selector_last_move_at = now_ms;
}
static void closeDemoSelector(uint32_t now_ms)
{
demo_paused_total_ms += now_ms - demo_selector_started_at;
demo_selector_active = false;
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
last_update_ms = demoTimeNow(now_ms);
}
static void openDemoSelector(uint32_t now_ms)
{
demo_selector_active = true;
demo_selector_started_at = now_ms;
demo_selector_item = 0U;
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
}
void demoAction(bool next_effect, bool toggle_icon, bool next_stage, bool user_long_pressed,
bool selector_previous, bool selector_next, uint32_t now_ms)
{
if (!running) return;
if (demo_selector_active) {
if (selector_previous) {
moveDemoSelector(-1, now_ms);
} else if (selector_next) {
moveDemoSelector(1, now_ms);
} else if (next_effect) {
const size_t selected_item = demo_selector_item;
closeDemoSelector(now_ms);
if (selected_item != 0U) {
screenTransitionReset(&stage_transition);
stage_change_requested = false;
stage_zoom_in_requested = false;
stage_zooming_in = false;
stage_scroll_in_requested = false;
stage_reveal_requested = false;
demo_instructions_active = false;
enterDemoStage((demo_stage_t)(selected_item - 1U), demoTimeNow(now_ms));
// Present one clean black frame before the newly initialized segment draws.
demo_selector_clear_new_stage = true;
}
}
return;
}
if (user_long_pressed) {
openDemoSelector(now_ms);
return;
}
now_ms = demoTimeNow(now_ms);
if (demo_instructions_active) {
// C skips the control page and starts Segment 1. A, B, and the stick
// remain inactive here so the Demo always begins from its known state.
if (next_stage) beginDemoSegments(now_ms);
return;
}
if (toggle_icon) {
if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
// B previews another catalogue solid and restarts the shared inactivity timer.
// Deliberately leave the independent five-second automatic change deadline alone.
selectStageSixMesh();
demo_last_interaction_at = now_ms;
} else if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
// B cycles the shared palette list without disturbing the bar's
// travel, colour-roll phase, or the scroller's wave.
raster_band_palette_index =
(raster_band_palette_index + 1U) % DEMO_RASTER_BAND_PALETTE_COUNT;
demo_last_interaction_at = now_ms;
} else if (selected_stage == DEMO_STAGE_PLASMA) {
// B changes only the plasma ramp; wave phases and ticker movement continue.
demoStagePlasmaNextPalette();
demo_last_interaction_at = now_ms;
} else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
// B changes only the presentation treatment; gear and rig phases stay continuous.
stage_seventeen_style = (stage_seventeen_style_t)
(((unsigned)stage_seventeen_style + 1U) % STAGE_SEVENTEEN_STYLE_COUNT);
demo_last_interaction_at = now_ms;
} else if (selected_stage != DEMO_STAGE_AMIGA_BALL &&
selected_stage != DEMO_STAGE_SCROLLER &&
selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
selected_stage != DEMO_STAGE_WALLOONS &&
selected_stage != DEMO_STAGE_KALEIDO &&
selected_stage != DEMO_STAGE_SPARKLE &&
selected_stage != DEMO_STAGE_SQUARES &&
selected_stage != DEMO_STAGE_SLIDESHOW_LENS &&
!DEMO_LOCK_ICON_TO_PRIMARY) {
advanceIconSlot();
demo_last_interaction_at = now_ms;
}
}
if (next_stage && !stage_change_requested && !stage_zoom_in_requested &&
!stage_scroll_in_requested && !stage_reveal_requested &&
!screenTransitionActive(&stage_transition) && !screenRevealActive(&stage_transition) &&
!screenScrollActive(&stage_transition) && !screenSliceActive(&stage_transition)) {
// Capture happens after this update, once drawDemo has completed the outgoing frame.
selectRandomStageTransition();
stage_change_requested = true;
} else if (next_effect && selected_stage == DEMO_STAGE_ICON) {
selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) %
DEMO_EFFECT_COUNT);
demo_last_interaction_at = now_ms;
}
}
void updateDemo(int joystick_x, int joystick_y, bool joystick_valid, uint32_t now_ms)
{
if (demo_selector_active) {
int direction = 0;
if (joystick_valid) {
if (joystick_y > DEMO_SELECTOR_JOYSTICK_ENTER) direction = -1;
else if (joystick_y < -DEMO_SELECTOR_JOYSTICK_ENTER) direction = 1;
}
if (direction == 0) {
if (joystick_y >= -DEMO_SELECTOR_JOYSTICK_RELEASE &&
joystick_y <= DEMO_SELECTOR_JOYSTICK_RELEASE) {
demo_selector_joystick_direction = 0;
demo_selector_repeating = false;
}
return;
}
if (direction != demo_selector_joystick_direction) {
demo_selector_joystick_direction = direction;
demo_selector_repeating = false;
moveDemoSelector(direction, now_ms);
} else if (now_ms - demo_selector_last_move_at >=
(demo_selector_repeating ? DEMO_SELECTOR_JOYSTICK_REPEAT_MS :
DEMO_SELECTOR_JOYSTICK_REPEAT_START_MS)) {
demo_selector_repeating = true;
moveDemoSelector(direction, now_ms);
}
return;
}
now_ms = demoTimeNow(now_ms);
if (!running || now_ms - last_update_ms < DEMO_FRAME_MS) return;
uint32_t elapsed_ms = now_ms - last_update_ms;
if (elapsed_ms > 50U) elapsed_ms = 50U;
last_update_ms = now_ms;
if (demo_instructions_active) {
// The control page has its own fixed duration and does not consume any
// of Segment 1's normal inactivity timeout.
starfield_update(&demo_instruction_starfield, elapsed_ms);
if ((uint32_t)(now_ms - demo_instructions_started_at) >=
DEMO_INSTRUCTIONS_DURATION_MS) {
beginDemoSegments(now_ms);
}
return;
}
if (screenTransitionActive(&stage_transition)) {
if (screenTransitionComplete(&stage_transition)) {
// The transition's finished frame was shown. Either resume the new stage or
// select it and request its off-screen capture for the incoming zoom.
screenTransitionReset(&stage_transition);
if (stage_zooming_in) {
stage_zooming_in = false;
} else {
advanceDemoStage(now_ms);
stage_zoom_in_requested = true;
}
}
return;
}
if (screenRevealActive(&stage_transition)) {
if (screenRevealComplete(&stage_transition)) {
screenRevealReset(&stage_transition);
}
return;
}
if (screenScrollActive(&stage_transition)) {
if (screenScrollComplete(&stage_transition)) {
screenScrollReset(&stage_transition);
}
return;
}
if (screenSliceActive(&stage_transition)) {
if (screenSliceComplete(&stage_transition)) {
screenSliceReset(&stage_transition);
advanceDemoStage(now_ms);
stage_zoom_in_requested = true;
}
return;
}
pitch_degrees += DEMO_PITCH_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
yaw_degrees += DEMO_YAW_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
roll_degrees += DEMO_ROLL_DEGREES_PER_SECOND * (float)elapsed_ms / 1000.0f;
const float elapsed_seconds = (float)elapsed_ms / 1000.0f;
if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
updateStageThreeBall(elapsed_seconds);
// This signed comparison continues to work if the millisecond counter wraps.
if ((int32_t)(now_ms - stage_three_next_icon_change_at) >= 0) {
stage_three_icon_slot = (stage_three_icon_slot + 1U) % DEMO_ICON_SLOT_COUNT;
stage_three_icon_bitmap = demo_icon_slots[stage_three_icon_slot];
stage_three_next_icon_change_at += DEMO_STAGE_THREE_ICON_CHANGE_MS;
}
}
if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
if ((int32_t)(now_ms - stage_six_next_change_at) >= 0) {
selectStageSixMesh();
stage_six_next_change_at += DEMO_STAGE_SIX_CHANGE_MS;
}
}
if (selected_stage == DEMO_STAGE_WALLOONS) {
demoStageWalloonsUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_KALEIDO) {
demoStageKaleidoUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_SPARKLE) {
demoStageSparkleUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_SQUARES) {
demoStageSquaresUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_RASTER_BANDS ||
selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER ||
selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
raster_band_effect_elapsed_ms += elapsed_ms;
}
if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
demoStageSlideshowLensUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_PLASMA) {
demoStagePlasmaUpdate(elapsed_ms);
}
if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
demoStageGameOfLifeUpdate(elapsed_ms);
}
if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
const float angle_step = DEMO_STAGE_SEVENTEEN_GEAR_DEGREES_PER_SECOND *
elapsed_seconds;
stage_seventeen_rig_pitch_degrees +=
DEMO_STAGE_SEVENTEEN_RIG_PITCH_DEGREES_PER_SECOND * elapsed_seconds;
stage_seventeen_rig_yaw_degrees +=
DEMO_STAGE_SEVENTEEN_RIG_YAW_DEGREES_PER_SECOND * elapsed_seconds;
stage_seventeen_rig_roll_degrees +=
DEMO_STAGE_SEVENTEEN_RIG_ROLL_DEGREES_PER_SECOND * elapsed_seconds;
// The two driven gears reverse from the common lead phase, preserving
// their fixed tooth-to-gap relationship at both mesh contacts.
stage_seventeen_gear_degrees[0] += angle_step;
stage_seventeen_gear_degrees[1] -= angle_step;
stage_seventeen_gear_degrees[2] -= angle_step;
}
if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
demoStageMouseMazeUpdate(now_ms);
}
if (selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
updateTextCornerIconCycles(now_ms);
}
if (selected_stage == DEMO_STAGE_ICON &&
(int32_t)(now_ms - stage_one_next_effect_change_at) >= 0) {
// Keep the automatic sequence in the same enum order as a manual A press.
selected_effect = (demo_effect_t)(((unsigned)selected_effect + 1U) % DEMO_EFFECT_COUNT);
stage_one_next_effect_change_at = now_ms + DEMO_STAGE_ONE_EFFECT_CHANGE_MS;
}
if (joystick_valid &&
(joystick_x < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
joystick_x > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
(selected_stage == DEMO_STAGE_APPROACH &&
(joystick_y < -DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE ||
joystick_y > DEMO_INACTIVITY_JOYSTICK_DEAD_ZONE)))) {
demo_last_interaction_at = now_ms;
}
if (joystick_valid && (joystick_x < -DEMO_SCROLL_JOYSTICK_DEAD_ZONE ||
joystick_x > DEMO_SCROLL_JOYSTICK_DEAD_ZONE)) {
float deflection = (float)(joystick_x < 0 ? -joystick_x : joystick_x) -
(float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
(float)DEMO_SCROLL_JOYSTICK_DEAD_ZONE;
if (deflection > 1.0f) deflection = 1.0f;
const float direction = joystick_x < 0 ? 1.0f : -1.0f;
scroll_speed_pixels_per_second += direction *
DEMO_SCROLL_ACCELERATION_PIXELS_PER_SECOND_SQUARED *
deflection * elapsed_seconds;
if (scroll_speed_pixels_per_second > DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
scroll_speed_pixels_per_second = DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
} else if (scroll_speed_pixels_per_second < -DEMO_SCROLL_MAX_PIXELS_PER_SECOND) {
scroll_speed_pixels_per_second = -DEMO_SCROLL_MAX_PIXELS_PER_SECOND;
}
}
if (selected_stage == DEMO_STAGE_APPROACH && joystick_valid &&
(joystick_y < -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE ||
joystick_y > DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE)) {
float deflection = (float)(joystick_y < 0 ? -joystick_y : joystick_y) -
(float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
deflection /= DEMO_SCROLL_JOYSTICK_FULL_TRAVEL -
(float)DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE;
if (deflection > 1.0f) deflection = 1.0f;
const float direction = joystick_y > 0 ? 1.0f : -1.0f;
stage_two_distance -= direction * DEMO_STAGE_TWO_APPROACH_UNITS_PER_SECOND *
deflection * elapsed_seconds;
if (stage_two_distance < DEMO_STAGE_TWO_MIN_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
} else if (stage_two_distance > DEMO_STAGE_TWO_MAX_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
}
}
if (selected_stage == DEMO_STAGE_APPROACH &&
(!joystick_valid ||
(joystick_y >= -DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE &&
joystick_y <= DEMO_STAGE_TWO_JOYSTICK_DEAD_ZONE))) {
// With no vertical-stick input, travel toward the current end and reverse there.
const float auto_distance_step = DEMO_STAGE_TWO_AUTO_APPROACH_UNITS_PER_SECOND *
elapsed_seconds;
if (stage_two_auto_approaching) {
stage_two_distance -= auto_distance_step;
if (stage_two_distance <= DEMO_STAGE_TWO_MIN_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MIN_DISTANCE;
stage_two_auto_approaching = false;
}
} else {
stage_two_distance += auto_distance_step;
if (stage_two_distance >= DEMO_STAGE_TWO_MAX_DISTANCE) {
stage_two_distance = DEMO_STAGE_TWO_MAX_DISTANCE;
stage_two_auto_approaching = true;
}
}
}
if (now_ms - demo_last_interaction_at >= demoStageInactivityTimeout() &&
!stage_change_requested) {
// Match C's normal route so every automatic advance uses a regular transition.
selectRandomStageTransition();
stage_change_requested = true;
}
scroll_text_x -= scroll_speed_pixels_per_second * elapsed_seconds;
scroll_text_effect_elapsed_ms += elapsed_ms;
if (pitch_degrees >= 360.0f) pitch_degrees -= 360.0f;
if (yaw_degrees >= 360.0f) yaw_degrees -= 360.0f;
if (roll_degrees >= 360.0f) roll_degrees -= 360.0f;
if (stage_seventeen_rig_pitch_degrees >= 360.0f) stage_seventeen_rig_pitch_degrees -= 360.0f;
if (stage_seventeen_rig_yaw_degrees >= 360.0f) stage_seventeen_rig_yaw_degrees -= 360.0f;
if (stage_seventeen_rig_roll_degrees >= 360.0f) stage_seventeen_rig_roll_degrees -= 360.0f;
for (size_t gear = 0U; gear < 3U; ++gear) {
while (stage_seventeen_gear_degrees[gear] >= 360.0f) {
stage_seventeen_gear_degrees[gear] -= 360.0f;
}
while (stage_seventeen_gear_degrees[gear] < 0.0f) {
stage_seventeen_gear_degrees[gear] += 360.0f;
}
}
if ((scroll_speed_pixels_per_second > 0.0f &&
scroll_text_x <= -(float)scroll_text_width) ||
(scroll_speed_pixels_per_second < 0.0f && scroll_text_x >= (float)LCD_WIDTH)) {
selectScrollMessage(selected_stage != DEMO_STAGE_SCROLLER &&
selected_stage != DEMO_STAGE_REVERSE_SCROLLER &&
selected_stage != DEMO_STAGE_RASTER_BANDS &&
selected_stage != DEMO_STAGE_BATTLE_ZONE_RASTER);
}
sparkle_phase = now_ms / DEMO_DOT_SPARKLE_INTERVAL_MS;
radial_flame_update_wavy(&demo_ring_flame, DEMO_RING_WAVE_AMPLITUDE,
DEMO_RING_WAVE_COUNT,
(float)now_ms * DEMO_RING_WAVE_DEGREES_PER_SECOND / 1000.0f,
DEMO_RING_EXTRA_COOLING);
}
// Segment 2 renderer: a rotating icon approaches and retreats over a perspective grid.
static void drawApproachStage(uint16_t *frame)
{
static const int terrain_bands[] = {58, 76, 101, 134};
const int horizon = 42;
for (size_t index = 0U; index < sizeof(terrain_bands) / sizeof(terrain_bands[0]); ++index) {
draw_line(frame, 0, terrain_bands[index], LCD_WIDTH - 1, terrain_bands[index],
COLOR_DARKGREEN);
}
for (int lane = -3; lane <= 3; ++lane) {
draw_line(frame, DEMO_CENTRE_X + lane * 10, horizon,
DEMO_CENTRE_X + lane * 50, LCD_HEIGHT - 1, COLOR_DARKGREEN);
}
draw_line(frame, 0, horizon, LCD_WIDTH - 1, horizon, COLOR_LIME);
int icon_size = (int)lroundf(2400.0f / stage_two_distance);
if (icon_size < 4) icon_size = 4;
if (icon_size > DEMO_STAGE_TWO_MAX_ICON_PIXELS) icon_size = DEMO_STAGE_TWO_MAX_ICON_PIXELS;
const int icon_base_y = horizon + (int)lroundf(2000.0f / stage_two_distance);
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = cosf(pitch_radians),
.pitch_sine = sinf(pitch_radians),
.yaw_cosine = cosf(yaw_radians),
.yaw_sine = sinf(yaw_radians),
.roll_cosine = cosf(roll_radians),
.roll_sine = sinf(roll_radians),
};
const float scale = (float)icon_size / (float)DEMO_LOGO_WIDTH;
const int centre_y = icon_base_y - icon_size / 2;
if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
depth -= DEMO_EXTRUSION_STEP) {
drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
} else {
drawApproachLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
depth += DEMO_EXTRUSION_STEP) {
drawApproachLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation, scale,
DEMO_CENTRE_X, centre_y,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
}
}
static void drawStageThreeBall(uint16_t *frame)
{
// Keep the classic blue field, while the scrolling PET text is layered later by drawDemo.
clear(frame, RGB565(0, 32, 175));
const int centre_x = (int)lroundf(stage_three_ball_x);
const int centre_y = (int)lroundf(stage_three_ball_y);
const uint8_t rotation = (uint8_t)stage_three_ball_rotation_phase;
const uint16_t shadow = RGB565(0, 0, 80);
fill_rect(frame, centre_x - 13, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y, 27, 1, shadow);
fill_rect(frame, centre_x - 19, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 1, 39, 2, shadow);
fill_rect(frame, centre_x - 15, (int)DEMO_STAGE_THREE_BALL_FLOOR_Y + 3, 31, 1, shadow);
const uint8_t *bitmap = stage_three_icon_bitmap != NULL ? stage_three_icon_bitmap :
demo_jd_bitmap;
for (int y = 0; y < DEMO_STAGE_THREE_BALL_DIAMETER; ++y) {
const int screen_y = centre_y - DEMO_STAGE_THREE_BALL_RADIUS + y;
if (screen_y < 0 || screen_y >= LCD_HEIGHT) continue;
for (int x = 0; x < DEMO_STAGE_THREE_BALL_DIAMETER; ++x) {
const int index = y * DEMO_STAGE_THREE_BALL_DIAMETER + x;
const uint8_t shade = stage_three_ball_shade_map[index];
if (shade == 0U) continue;
const int screen_x = centre_x - DEMO_STAGE_THREE_BALL_RADIUS + x;
if (screen_x < 0 || screen_x >= LCD_WIDTH) continue;
// The packed Demo icon is a rotating spherical texture. Set pixels replace
// the Amiga ball's red checks; clear pixels retain the shaded white surface.
const unsigned texture_x = ((((unsigned)stage_three_ball_longitude_map[index] *
DEMO_LOGO_WIDTH) >> 8U) +
(unsigned)rotation * DEMO_LOGO_WIDTH /
DEMO_STAGE_THREE_BALL_LONGITUDE_BANDS) % DEMO_LOGO_WIDTH;
const unsigned texture_y = ((unsigned)stage_three_ball_latitude_map[index] *
DEMO_LOGO_HEIGHT) >> 8U;
const size_t byte_index = (size_t)texture_y * DEMO_LOGO_ROW_BYTES + texture_x / 8U;
const uint8_t bit = (uint8_t)(7U - texture_x % 8U);
const bool icon_pixel = (bitmap[byte_index] & (uint8_t)(1U << bit)) != 0U;
frame[(size_t)screen_y * LCD_WIDTH + (size_t)screen_x] = icon_pixel
? stage_three_red_shades[shade - 1U] : stage_three_white_shades[shade - 1U];
}
}
}
static demo_point_3d_t rotateMeshPoint(demo_point_3d_t point, const demo_rotation_t *rotation)
{
const float pitched_y = point.y * rotation->pitch_cosine - point.z * rotation->pitch_sine;
const float pitched_z = point.y * rotation->pitch_sine + point.z * rotation->pitch_cosine;
const float yawed_x = point.x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
const float yawed_z = -point.x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
const demo_point_3d_t result = {
.x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine,
.y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine,
.z = yawed_z,
};
return result;
}
static graphics_point_t projectMeshPoint(const demo_point_3d_t *point)
{
const float perspective = DEMO_STAGE_SIX_CAMERA_DISTANCE /
(DEMO_STAGE_SIX_CAMERA_DISTANCE + point->z);
const graphics_point_t result = {
.x = DEMO_STAGE_SIX_CENTRE_X + (int)lroundf(point->x * perspective),
.y = DEMO_STAGE_SIX_CENTRE_Y + (int)lroundf(point->y * perspective),
};
return result;
}
static uint16_t meshFaceColour(float brightness)
{
const demo_solid_colour_scheme_t *scheme =
&demo_solid_colour_schemes[stage_six_colour_scheme_index];
if (brightness > 0.76f) return scheme->face_shades[0];
if (brightness > 0.52f) return scheme->face_shades[1];
if (brightness > 0.28f) return scheme->face_shades[2];
return scheme->face_shades[3];
}
static float meshFaceBrightness(const demo_point_3d_t *first, const demo_point_3d_t *second,
const demo_point_3d_t *third)
{
const demo_point_3d_t first_edge = {
.x = second->x - first->x, .y = second->y - first->y, .z = second->z - first->z,
};
const demo_point_3d_t second_edge = {
.x = third->x - first->x, .y = third->y - first->y, .z = third->z - first->z,
};
const demo_point_3d_t normal = {
.x = first_edge.y * second_edge.z - first_edge.z * second_edge.y,
.y = first_edge.z * second_edge.x - first_edge.x * second_edge.z,
.z = first_edge.x * second_edge.y - first_edge.y * second_edge.x,
};
const float length = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
if (length <= 0.0f) return 0.0f;
// Both sides are shaded because depth sorting, rather than culling, is what lets
// one generic face table render solids with either winding direction.
const float light = fabsf((normal.x * 0.35f - normal.y * 0.45f + normal.z * 0.82f) / length);
return light > 1.0f ? 1.0f : light;
}
static void drawFilledMesh(uint16_t *frame, const demo_mesh_t *mesh,
const demo_rotation_t *rotation)
{
if (mesh == NULL || mesh->vertices == NULL || mesh->faces == NULL ||
mesh->vertex_count > DEMO_MESH_MAX_VERTICES || mesh->face_count > DEMO_MESH_MAX_FACES) {
return;
}
demo_point_3d_t rotated_vertices[DEMO_MESH_MAX_VERTICES];
demo_mesh_face_draw_t draw_order[DEMO_MESH_MAX_FACES];
for (size_t vertex = 0U; vertex < mesh->vertex_count; ++vertex) {
demo_point_3d_t scaled = mesh->vertices[vertex];
scaled.x *= DEMO_STAGE_SIX_SOLID_SCALE;
scaled.y *= DEMO_STAGE_SIX_SOLID_SCALE;
scaled.z *= DEMO_STAGE_SIX_SOLID_SCALE;
rotated_vertices[vertex] = rotateMeshPoint(scaled, rotation);
}
for (size_t face = 0U; face < mesh->face_count; ++face) {
const demo_mesh_face_t *source_face = &mesh->faces[face];
if (source_face->vertex_indices == NULL || source_face->vertex_count < 3U ||
source_face->vertex_count > DEMO_MESH_MAX_FACE_VERTICES) {
return;
}
float total_depth = 0.0f;
for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
const uint8_t vertex = source_face->vertex_indices[corner];
if (vertex >= mesh->vertex_count) return;
total_depth += rotated_vertices[vertex].z;
}
draw_order[face] = (demo_mesh_face_draw_t) {
.face_index = face,
.depth = total_depth / (float)source_face->vertex_count,
.brightness = meshFaceBrightness(&rotated_vertices[source_face->vertex_indices[0]],
&rotated_vertices[source_face->vertex_indices[1]],
&rotated_vertices[source_face->vertex_indices[2]]),
};
}
// Painter's order draws the farthest polygon first, allowing nearer filled faces
// to cover it without a full per-pixel depth buffer.
for (size_t pass = 0U; pass + 1U < mesh->face_count; ++pass) {
for (size_t index = 0U; index + 1U < mesh->face_count - pass; ++index) {
if (draw_order[index].depth <= draw_order[index + 1U].depth) continue;
const demo_mesh_face_draw_t temporary = draw_order[index];
draw_order[index] = draw_order[index + 1U];
draw_order[index + 1U] = temporary;
}
}
for (size_t order = 0U; order < mesh->face_count; ++order) {
const demo_mesh_face_t *source_face = &mesh->faces[draw_order[order].face_index];
graphics_point_t polygon[DEMO_MESH_MAX_FACE_VERTICES];
for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
polygon[corner] = projectMeshPoint(&rotated_vertices[source_face->vertex_indices[corner]]);
}
fill_convex_polygon(frame, polygon, source_face->vertex_count,
meshFaceColour(draw_order[order].brightness));
for (size_t corner = 0U; corner < source_face->vertex_count; ++corner) {
const size_t next_corner = (corner + 1U) % source_face->vertex_count;
draw_line(frame, polygon[corner].x, polygon[corner].y,
polygon[next_corner].x, polygon[next_corner].y,
demo_solid_colour_schemes[stage_six_colour_scheme_index].edge);
}
}
}
static void drawStageSixSolid(uint16_t *frame)
{
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians),
.yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians),
.roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians),
};
drawFilledMesh(frame, stage_six_current_mesh, &rotation);
}
static demo_rotation_t stageSeventeenRotation(float pitch_degrees, float yaw_degrees,
float roll_degrees)
{
const float pitch = pitch_degrees * DEMO_PI / 180.0f;
const float yaw = yaw_degrees * DEMO_PI / 180.0f;
const float roll = roll_degrees * DEMO_PI / 180.0f;
return (demo_rotation_t) {
.pitch_cosine = cosf(pitch), .pitch_sine = sinf(pitch),
.yaw_cosine = cosf(yaw), .yaw_sine = sinf(yaw),
.roll_cosine = cosf(roll), .roll_sine = sinf(roll),
};
}
static demo_point_3d_t stageSeventeenGearOrigin(unsigned gear)
{
switch (gear) {
case 0U: return (demo_point_3d_t) {.x = 0.0f, .y = DEMO_STAGE_SEVENTEEN_LEAD_Y,
.z = 0.0f};
case 1U: return (demo_point_3d_t) {.x = -DEMO_STAGE_SEVENTEEN_DRIVEN_X,
.y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
default: return (demo_point_3d_t) {.x = DEMO_STAGE_SEVENTEEN_DRIVEN_X,
.y = DEMO_STAGE_SEVENTEEN_DRIVEN_Y, .z = 0.0f};
}
}
static demo_rotation_t stageSeventeenGearAxisRotation(unsigned gear)
{
// Each backward axis converges toward the triangular centre. The front
// centres therefore remain at the wide-rim mesh contacts while the narrow
// rear centres close by the same ratio as the tapering tooth radii.
switch (gear) {
case 0U: return stageSeventeenRotation(-22.5f, 0.0f, 0.0f);
case 1U: return stageSeventeenRotation(14.8f, 22.5f, 0.0f);
default: return stageSeventeenRotation(14.8f, -22.5f, 0.0f);
}
}
static demo_point_3d_t stageSeventeenTransformPoint(demo_point_3d_t point,
const demo_point_3d_t *origin,
const demo_rotation_t *axis_rotation,
const demo_rotation_t *rig_rotation)
{
demo_point_3d_t transformed = rotateMeshPoint(point, axis_rotation);
transformed.x += origin->x;
transformed.y += origin->y;
transformed.z += origin->z;
return rotateMeshPoint(transformed, rig_rotation);
}
static graphics_point_t stageSeventeenProjectPoint(const demo_point_3d_t *point)
{
const float perspective = DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE /
(DEMO_STAGE_SEVENTEEN_CAMERA_DISTANCE + point->z);
return (graphics_point_t) {
.x = DEMO_STAGE_SEVENTEEN_CENTRE_X + (int)lroundf(point->x * perspective),
.y = DEMO_STAGE_SEVENTEEN_CENTRE_Y + (int)lroundf(point->y * perspective),
};
}
static uint16_t stageSeventeenFaceColour(void)
{
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(230, 195, 30);
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(255, 220, 70);
return COLOR_BLACK;
}
static uint16_t stageSeventeenRearColour(void)
{
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) return RGB565(170, 135, 10);
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) return RGB565(210, 175, 30);
return COLOR_BLACK;
}
static uint16_t stageSeventeenSideColour(unsigned segment)
{
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_FLAT) {
return (segment & 2U) == 0U ? RGB565(160, 120, 8) : RGB565(90, 68, 2);
}
if (stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC) {
return (segment & 2U) == 0U ? RGB565(195, 155, 18) : RGB565(115, 85, 3);
}
return COLOR_BLACK;
}
static uint16_t stageSeventeenEdgeColour(void)
{
return stage_seventeen_style == STAGE_SEVENTEEN_STYLE_METALLIC ? RGB565(255, 245, 160) :
RGB565(255, 220, 50);
}
static void stageSeventeenAppendFace(const graphics_point_t *points, uint8_t point_count,
float depth, uint16_t colour)
{
if (stage_seventeen_draw_face_count >= DEMO_STAGE_SEVENTEEN_DRAW_FACE_COUNT ||
point_count < 3U || point_count > 4U) return;
stage_seventeen_draw_face_t *const face =
&stage_seventeen_draw_faces[stage_seventeen_draw_face_count++];
for (uint8_t point = 0U; point < point_count; ++point) face->points[point] = points[point];
face->depth = depth;
face->colour = colour;
face->point_count = point_count;
}
static void stageSeventeenBuildGear(unsigned gear, const demo_rotation_t *rig_rotation)
{
const unsigned point_count = DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U;
graphics_point_t front[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
graphics_point_t rear[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
demo_point_3d_t front_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
demo_point_3d_t rear_points[DEMO_STAGE_SEVENTEEN_TOOTH_COUNT * 2U];
const demo_point_3d_t origin = stageSeventeenGearOrigin(gear);
const demo_rotation_t axis_rotation = stageSeventeenGearAxisRotation(gear);
const float spin = stage_seventeen_gear_degrees[gear] * DEMO_PI / 180.0f;
// Origins are the wide, front-face centres. Each gear then narrows and
// travels inward through its complete depth toward the shared rear mesh.
const float front_z = 0.0f;
const float rear_z = DEMO_STAGE_SEVENTEEN_CONE_DEPTH;
demo_point_3d_t front_centre = {.x = 0.0f, .y = 0.0f, .z = front_z};
demo_point_3d_t rear_centre = {.x = 0.0f, .y = 0.0f, .z = rear_z};
front_centre = stageSeventeenTransformPoint(front_centre, &origin, &axis_rotation, rig_rotation);
rear_centre = stageSeventeenTransformPoint(rear_centre, &origin, &axis_rotation, rig_rotation);
const graphics_point_t projected_front_centre = stageSeventeenProjectPoint(&front_centre);
const graphics_point_t projected_rear_centre = stageSeventeenProjectPoint(&rear_centre);
for (unsigned point = 0U; point < point_count; ++point) {
const float angle = spin + (float)point * DEMO_PI / (float)DEMO_STAGE_SEVENTEEN_TOOTH_COUNT;
const float front_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_FRONT_RADIUS :
DEMO_STAGE_SEVENTEEN_FRONT_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
// Preserve the same tooth/gap rhythm on the narrow end. Joining these
// matching corners makes every tooth a full tapered ridge, rather than
// letting the front cog collapse into a smooth conical rear hub.
const float rear_radius = (point & 1U) == 0U ? DEMO_STAGE_SEVENTEEN_REAR_RADIUS :
DEMO_STAGE_SEVENTEEN_REAR_RADIUS * DEMO_STAGE_SEVENTEEN_TOOTH_ROOT_RATIO;
demo_point_3d_t front_point = {
.x = cosf(angle) * front_radius, .y = sinf(angle) * front_radius, .z = front_z,
};
demo_point_3d_t rear_point = {
.x = cosf(angle) * rear_radius,
.y = sinf(angle) * rear_radius, .z = rear_z,
};
front_points[point] =
stageSeventeenTransformPoint(front_point, &origin, &axis_rotation, rig_rotation);
rear_points[point] =
stageSeventeenTransformPoint(rear_point, &origin, &axis_rotation, rig_rotation);
front[point] = stageSeventeenProjectPoint(&front_points[point]);
rear[point] = stageSeventeenProjectPoint(&rear_points[point]);
}
for (unsigned point = 0U; point < point_count; ++point) {
const unsigned next = (point + 1U) % point_count;
const graphics_point_t side[] = {rear[point], rear[next], front[next], front[point]};
stageSeventeenAppendFace(side, 4U,
(rear_points[point].z + rear_points[next].z + front_points[next].z +
front_points[point].z) * 0.25f, stageSeventeenSideColour(point));
// A cog outline is concave at the tooth roots, so its front remains a
// set of separately sorted centre wedges rather than one polygon.
const graphics_point_t wedge[] = {projected_front_centre, front[point], front[next]};
stageSeventeenAppendFace(wedge, 3U,
(front_centre.z + front_points[point].z + front_points[next].z) / 3.0f,
stageSeventeenFaceColour());
// The rear is also concave at its tooth roots. Its individual wedges
// close the narrow end without crossing the adjacent tapered ridges.
const graphics_point_t rear_wedge[] = {projected_rear_centre, rear[next], rear[point]};
stageSeventeenAppendFace(rear_wedge, 3U,
(rear_centre.z + rear_points[point].z + rear_points[next].z) / 3.0f,
stageSeventeenRearColour());
}
}
static void drawStageSeventeenBevelGears(uint16_t *frame)
{
const demo_rotation_t rig_rotation = stageSeventeenRotation(stage_seventeen_rig_pitch_degrees,
stage_seventeen_rig_yaw_degrees, stage_seventeen_rig_roll_degrees);
stage_seventeen_draw_face_count = 0U;
for (unsigned gear = 0U; gear < 3U; ++gear) stageSeventeenBuildGear(gear, &rig_rotation);
// Positive Z is farther from this camera. Sorting every tooth face, rather
// than three whole gears, preserves the interlock throughout the rig tumble.
for (size_t pass = 0U; pass + 1U < stage_seventeen_draw_face_count; ++pass) {
for (size_t index = 0U; index + 1U < stage_seventeen_draw_face_count - pass; ++index) {
if (stage_seventeen_draw_faces[index].depth >=
stage_seventeen_draw_faces[index + 1U].depth) continue;
const stage_seventeen_draw_face_t temporary = stage_seventeen_draw_faces[index];
stage_seventeen_draw_faces[index] = stage_seventeen_draw_faces[index + 1U];
stage_seventeen_draw_faces[index + 1U] = temporary;
}
}
for (size_t face_index = 0U; face_index < stage_seventeen_draw_face_count; ++face_index) {
const stage_seventeen_draw_face_t *const face = &stage_seventeen_draw_faces[face_index];
if (stage_seventeen_style != STAGE_SEVENTEEN_STYLE_OUTLINE) {
fill_convex_polygon(frame, face->points, face->point_count, face->colour);
}
for (uint8_t point = 0U; point < face->point_count; ++point) {
const uint8_t next = (uint8_t)((point + 1U) % face->point_count);
draw_line(frame, face->points[point].x, face->points[point].y,
face->points[next].x, face->points[next].y, stageSeventeenEdgeColour());
}
}
}
static void drawTextCornerIcons(uint16_t *frame)
{
// No value below comes from the main Demo rotation or either text effect.
// Every icon's travel, pulse, colour, and rotation come from its own state.
const unsigned colour_count = sizeof(demo_scroll_rainbow_colours) /
sizeof(demo_scroll_rainbow_colours[0]);
for (size_t corner = 0U; corner < sizeof(demo_text_corner_icon_slots) /
sizeof(demo_text_corner_icon_slots[0]); ++corner) {
const size_t slot = (size_t)demo_text_corner_icon_slots[corner] % DEMO_ICON_SLOT_COUNT;
const uint8_t *bitmap = demo_icon_slots[slot];
const demo_text_corner_icon_cycle_t *const cycle =
&demo_text_corner_icon_cycles[corner];
const float rotation_degrees = cycle->rotation_start_degrees +
(float)cycle->rotation_degrees_per_second *
(float)(last_update_ms - cycle->rotation_started_at) / 1000.0f;
const float rotation_radians = rotation_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = 1.0f, .pitch_sine = 0.0f,
.yaw_cosine = 1.0f, .yaw_sine = 0.0f,
.roll_cosine = cosf(rotation_radians),
.roll_sine = sinf(rotation_radians),
};
// updateTextCornerIconCycles() restarts this cycle at every completed
// trip with a newly random duration. This fraction therefore describes
// one complete, independent edge -> centre -> edge journey.
const float travel_phase = (float)(last_update_ms - cycle->cycle_started_at) /
(float)cycle->cycle_duration_ms;
// 0.0 at the outer corner, 1.0 at the centre X line, then back out.
const float centre_progress = 0.5f - 0.5f * cosf(travel_phase * 2.0f * DEMO_PI);
const int centre_x = (int)lroundf((float)demo_text_corner_icon_x[corner] +
((float)(LCD_WIDTH / 2) -
(float)demo_text_corner_icon_x[corner]) *
centre_progress);
const uint32_t pulse_offset = (uint32_t)corner * DEMO_TEXT_CORNER_ICON_PULSE_STAGGER_MS;
const float pulse_phase = (float)((last_update_ms + pulse_offset) %
DEMO_TEXT_CORNER_ICON_PULSE_MS) /
(float)DEMO_TEXT_CORNER_ICON_PULSE_MS;
const float pulse = 0.5f + 0.5f * sinf(pulse_phase * 2.0f * DEMO_PI);
const float scale = DEMO_TEXT_CORNER_ICON_SCALE_MIN +
(DEMO_TEXT_CORNER_ICON_SCALE_MAX -
DEMO_TEXT_CORNER_ICON_SCALE_MIN) * pulse;
const unsigned colour_index = (unsigned)(last_update_ms /
DEMO_TEXT_CORNER_ICON_COLOUR_MS + corner * 2U) % colour_count;
const uint16_t colour = demo_scroll_rainbow_colours[colour_index];
// Draw far slices first so the pulsing coloured face remains in front.
for (unsigned depth = DEMO_TEXT_CORNER_ICON_DEPTH; depth > 0U;
depth -= DEMO_TEXT_CORNER_ICON_DEPTH_STEP) {
drawApproachLogoSlice(frame, bitmap, (float)depth, &rotation, scale,
centre_x, demo_text_corner_icon_y[corner], C64_COLOR_DARK_GREY);
}
drawApproachLogoSlice(frame, bitmap, 0.0f, &rotation, scale,
centre_x, demo_text_corner_icon_y[corner], colour);
}
}
// Render Battle Zone normally every frame, copy its fresh ground rows to PSRAM,
// then scale and wrap that current source into Demo's lower half. This avoids a
// full-screen scratch allocation while keeping forward motion safe.
static void drawStageFifteenBattleZoneBackground(uint16_t *frame)
{
const int source_top = BATTLE_ZONE_HORIZON_Y;
const int source_height = LCD_HEIGHT - source_top;
const int destination_top = DEMO_STAGE_FIFTEEN_HORIZON_Y;
const int destination_height = LCD_HEIGHT - destination_top;
// Moving the source lookup upward makes terrain features travel down from
// the horizon toward the viewer. A wrapped source introduces the next
// ground pattern at the fixed horizon.
const int forward_source_offset = (int)(
(raster_band_effect_elapsed_ms % DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS) *
(uint32_t)source_height / DEMO_STAGE_FIFTEEN_FORWARD_CYCLE_MS);
drawBattleZoneLandscape(frame);
if (stage_fifteen_ground_source != NULL) {
memcpy(stage_fifteen_ground_source, &frame[(size_t)source_top * LCD_WIDTH],
(size_t)LCD_WIDTH * (size_t)source_height * sizeof(frame[0]));
// The captured first ground row is Battle Zone's lime horizon. Segment
// 15 draws its own fixed horizon below, so remove this copy before it
// can wrap through the moving texture as an unwanted bright stripe.
for (int x = 0; x < LCD_WIDTH; ++x) {
stage_fifteen_ground_source[x] = COLOR_BLACK;
}
}
if (stage_fifteen_ground_source == NULL) {
// A PSRAM allocation failure leaves a static, safely compressed
// landscape instead of risking corrupt rows with wrapped in-place reads.
for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
--destination_y) {
const int source_y = source_top +
(destination_y - destination_top) * (source_height - 1) /
(destination_height - 1);
memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
&frame[(size_t)source_y * LCD_WIDTH], LCD_WIDTH * sizeof(frame[0]));
}
} else {
for (int destination_y = LCD_HEIGHT - 1; destination_y >= destination_top;
--destination_y) {
const int unscrolled_source_offset =
(destination_y - destination_top) * (source_height - 1) /
(destination_height - 1);
const int source_row =
(unscrolled_source_offset - forward_source_offset + source_height) % source_height;
memcpy(&frame[(size_t)destination_y * LCD_WIDTH],
&stage_fifteen_ground_source[(size_t)source_row * LCD_WIDTH],
LCD_WIDTH * sizeof(frame[0]));
}
}
// The original unscaled sky and terrain in the top half are no longer part
// of this segment, leaving the bar and horizon-anchored scroller clear.
fill_rect(frame, 0, 0, LCD_WIDTH, destination_top, COLOR_BLACK);
// The wrapped texture moves, but the bright green horizon remains a stable
// reference line exactly beneath the scroller.
draw_line(frame, 0, destination_top, LCD_WIDTH - 1, destination_top, COLOR_LIME);
}
// Segment wrapper: preserves the shared motion timing and chooses each raster
// segment's geometry. Segment 11 starts metallic, Segment 15 fire, and Segment
// 16 green; B chooses another shared palette without interrupting that motion.
static void drawRasterBands(uint16_t *frame)
{
const bool lower_half_stage = selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER;
const bool battle_zone_stage = selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER;
const int block_height = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_HEIGHT :
(battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_BLOCK_HEIGHT :
DEMO_STAGE_ELEVEN_BAND_BLOCK_HEIGHT);
const uint32_t drift_ms = battle_zone_stage ? DEMO_STAGE_FIFTEEN_BAND_DRIFT_MS :
DEMO_STAGE_ELEVEN_BAND_DRIFT_MS;
// Segment 16 reserves its lower half for the scaled Battle Zone ground.
// Its bar may touch the halfway horizon but cannot enter that landscape.
const int travel_height = battle_zone_stage ? DEMO_STAGE_FIFTEEN_HORIZON_Y :
LCD_HEIGHT;
const float drift_phase = (float)(raster_band_effect_elapsed_ms % drift_ms) /
(float)drift_ms;
const int band_top = lower_half_stage ? DEMO_STAGE_FOURTEEN_BAND_TOP :
(int)lroundf((float)(travel_height - block_height) *
(0.5f + 0.5f * sinf(drift_phase * 2.0f * DEMO_PI)));
const demo_raster_band_palette_t *palette =
demo_raster_band_palettes[raster_band_palette_index];
draw_rolling_raster_band(frame, band_top, block_height,
DEMO_STAGE_ELEVEN_BAND_HEIGHT,
raster_band_effect_elapsed_ms,
DEMO_STAGE_ELEVEN_COLOUR_ROLL_MS, palette);
}
#if 0 // Moved to demo_stage_slideshow_lens.c.
static void drawStageTwelveSlideshowLens(uint16_t *frame)
{
if (!stage_twelve_image_ready || stage_twelve_image == NULL) {
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, LCD_HEIGHT / 2,
"NO SLIDESHOW IMAGE", C64_COLOR_LIGHT_GREY, 1);
return;
}
const size_t frame_bytes = (size_t)LCD_WIDTH * LCD_HEIGHT * sizeof(frame[0]);
memcpy(frame, stage_twelve_image, frame_bytes);
const int centre_x = (int)lroundf(stage_twelve_lens_x);
const int centre_y = (int)lroundf(stage_twelve_lens_y);
const int radius = DEMO_STAGE_TWELVE_LENS_RADIUS;
const int outer_radius_squared = radius * radius;
const int inner_radius = radius - DEMO_STAGE_TWELVE_LENS_RIM_WIDTH;
const int inner_radius_squared = inner_radius * inner_radius;
// Sample nearer to the lens centre inside the circle. Clamp the visible
// part to the framebuffer because edge destinations deliberately place
// part of the lens beyond the screen.
const int first_y = centre_y - radius < 0 ? 0 : centre_y - radius;
const int last_y = centre_y + radius >= LCD_HEIGHT ? LCD_HEIGHT - 1 :
centre_y + radius;
const int first_x = centre_x - radius < 0 ? 0 : centre_x - radius;
const int last_x = centre_x + radius >= LCD_WIDTH ? LCD_WIDTH - 1 :
centre_x + radius;
for (int y = first_y; y <= last_y; ++y) {
for (int x = first_x; x <= last_x; ++x) {
const int dx = x - centre_x;
const int dy = y - centre_y;
const int distance_squared = dx * dx + dy * dy;
if (distance_squared > outer_radius_squared) continue;
if (distance_squared > inner_radius_squared) {
frame[(size_t)y * LCD_WIDTH + (size_t)x] = DEMO_STAGE_TWELVE_LENS_RIM_COLOUR;
continue;
}
const int source_x = (int)lroundf((float)centre_x +
(float)dx / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
const int source_y = (int)lroundf((float)centre_y +
(float)dy / DEMO_STAGE_TWELVE_LENS_MAGNIFICATION);
frame[(size_t)y * LCD_WIDTH + (size_t)x] =
stage_twelve_image[(size_t)source_y * LCD_WIDTH + (size_t)source_x];
}
}
}
#endif
static void drawStageSevenGlyph(uint16_t *frame, int column, int row, uint8_t glyph)
{
const int display_row = row + DEMO_STAGE_SEVEN_ROW_OFFSET;
if (column < 0 || column >= 40 || display_row < 0 || display_row >= 21) return;
drawPet2001Glyph(frame, column * (int)PET2001_FONT_GLYPH_WIDTH,
display_row * (int)PET2001_FONT_GLYPH_HEIGHT, glyph, false,
DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
}
static void drawStageSevenWalloon(uint16_t *frame, int column, int row, bool walking)
{
// This is the original seven-cell Walloon from BASIC lines 1800-1820.
// The walking top cell alternates the original 87 and 81 glyphs, while the
// rest of the PET-character sprite remains exactly as the listing defines it.
drawStageSevenGlyph(frame, column, row, walking ? 81U : 87U);
drawStageSevenGlyph(frame, column - 1, row + 1, 64U);
drawStageSevenGlyph(frame, column, row + 1, 71U);
drawStageSevenGlyph(frame, column + 1, row + 1, 64U);
drawStageSevenGlyph(frame, column, row + 2, 90U);
drawStageSevenGlyph(frame, column - 1, row + 3, 78U);
drawStageSevenGlyph(frame, column + 1, row + 3, 77U);
}
static void drawStageSevenPlayfield(uint16_t *frame)
{
// Retain the upper platform and right-hand climbing pole throughout the
// act. Their origin is BASIC lines 220-290, at PET cell (10, 5).
for (int column = 10; column <= 28; ++column) {
drawStageSevenGlyph(frame, column, 5, 127U);
}
for (int row = 5; row <= 15; ++row) {
drawStageSevenGlyph(frame, 28, row, 127U);
}
}
static void drawStageSevenTitleBox(uint16_t *frame)
{
// The title-only left edge and lower edge make the temporary opening box.
// They deliberately disappear before the first Walloon begins walking.
drawStageSevenPlayfield(frame);
for (int row = 5; row <= 15; ++row) {
drawStageSevenGlyph(frame, 10, row, 127U);
}
for (int column = 10; column <= 25; ++column) {
drawStageSevenGlyph(frame, column, 15, 127U);
}
// The original presents its stunt as "THE FLYING WALLOONS". Split the
// title over two PET rows so it remains inside this 17-cell-wide frame.
drawPet2001Text(frame, 18 * (int)PET2001_FONT_GLYPH_WIDTH,
7 * (int)PET2001_FONT_GLYPH_HEIGHT, "THE", false,
DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
drawPet2001Text(frame, 12 * (int)PET2001_FONT_GLYPH_WIDTH,
9 * (int)PET2001_FONT_GLYPH_HEIGHT, "FLYING WALLOONS", false,
DEMO_STAGE_SEVEN_PET_COLOUR, COLOR_BLACK, 1);
}
static void drawStageSevenSeesaw(uint16_t *frame, bool left_end_down)
{
// A single fixed PET-cell pivot replaces the listing's two absolute screen
// snapshots. The board never slides sideways: only its stepped slope swaps
// when the other Walloon becomes the one at the low end.
static const struct { int column; int row; } left_down_board[] = {
{13, 19}, {14, 19}, {15, 19}, {16, 18}, {17, 18}, {18, 18},
{19, 17}, {20, 17}, {21, 17}, {22, 16}, {23, 16}, {24, 16},
};
static const struct { int column; int row; } right_down_board[] = {
{13, 16}, {14, 16}, {15, 16}, {16, 17}, {17, 17}, {18, 17},
{19, 18}, {20, 18}, {21, 18}, {22, 19}, {23, 19}, {24, 19},
};
if (left_end_down) {
for (size_t index = 0U; index < sizeof(left_down_board) / sizeof(left_down_board[0]);
++index) {
drawStageSevenGlyph(frame, left_down_board[index].column,
left_down_board[index].row,
DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
}
} else {
for (size_t index = 0U; index < sizeof(right_down_board) / sizeof(right_down_board[0]);
++index) {
drawStageSevenGlyph(frame, right_down_board[index].column,
right_down_board[index].row,
DEMO_STAGE_SEVEN_SEESAW_LINE_GLYPH);
}
}
// The two original PET glyphs make a compact, fixed fulcrum below the board.
drawStageSevenGlyph(frame, 18, 20, 233U);
drawStageSevenGlyph(frame, 19, 20, 223U);
}
static void drawStageSevenWalloons(uint16_t *frame, uint32_t now_ms)
{
if (stage_seven_phase == WALLOONS_TITLE_CARD) {
drawStageSevenTitleBox(frame);
return;
}
drawStageSevenPlayfield(frame);
const unsigned steps = walloonsPhaseSteps(now_ms, 14U);
const bool walking_pose = (steps & 1U) != 0U;
switch (stage_seven_phase) {
case WALLOONS_TITLE_CARD:
// Handled above so the title card contains no active Walloons.
break;
case WALLOONS_LEFT_WALK_IN:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 2 + (int)walloonsPhaseSteps(now_ms, 12U), 15,
walking_pose);
break;
case WALLOONS_PAUSE_ON_SEESAW:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 14, 15, false);
break;
case WALLOONS_RIGHT_WALK_ON_PLATFORM:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 14, 15, false);
drawStageSevenWalloon(frame, 38 - (int)steps, 2, walking_pose);
break;
case WALLOONS_RIGHT_DROP:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 14, 15, false);
drawStageSevenWalloon(frame, 24, 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_BOUNCE_RIGHT_UP:
case WALLOONS_BOUNCE_RIGHT_DOWN:
// The left Walloon remains at the low end while the right Walloon is airborne.
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 15, 15, false);
drawStageSevenWalloon(frame, 24,
stage_seven_phase == WALLOONS_BOUNCE_RIGHT_UP
? 14 - (int)walloonsPhaseSteps(now_ms, 13U)
: 2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_BOUNCE_LEFT_UP:
// The right Walloon remains at the low end while the left Walloon rises.
drawStageSevenSeesaw(frame, false);
drawStageSevenWalloon(frame, 24, 14, false);
drawStageSevenWalloon(frame, 15,
14 - (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_BOUNCE_LEFT_DOWN:
drawStageSevenSeesaw(frame, false);
drawStageSevenWalloon(frame, 24, 14, false);
drawStageSevenWalloon(frame, 15,
2 + (int)walloonsPhaseSteps(now_ms, 13U), false);
break;
case WALLOONS_LEFT_LANDED:
// The landing phase makes the left-down state visible before the
// next Walloon is launched from the other end.
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 15, 15, false);
break;
case WALLOONS_FINISHED:
drawStageSevenSeesaw(frame, true);
drawStageSevenWalloon(frame, 15, 15, false);
drawStageSevenWalloon(frame, 24, 14, false);
break;
}
}
// Pre-Demo control reference. PET characters and the blue C64-style background
// keep this separate page visually consistent with the retro Demo segments.
static void drawDemoInstructions(uint16_t *frame)
{
const uint32_t elapsed_ms = last_update_ms - demo_instructions_started_at;
const uint32_t remaining_ms = elapsed_ms >= DEMO_INSTRUCTIONS_DURATION_MS ? 0U :
DEMO_INSTRUCTIONS_DURATION_MS - elapsed_ms;
const unsigned seconds_remaining = (unsigned)((remaining_ms + 999U) / 1000U);
char start_text[24];
fill_rect(frame, 0, 0, LCD_WIDTH, LCD_HEIGHT, C64_COLOR_BLUE);
starfield_draw(frame, &demo_instruction_starfield);
drawPet2001Text(frame, 40, 8, "Old school Demo", false,
C64_COLOR_CYAN, C64_COLOR_BLUE, 2);
drawPet2001Text(frame, 128, 32, "CONTROLS", false, C64_COLOR_LIGHT_GREY, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 48, "C: NEXT SEGMENT", false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 64, "A: NEXT EFFECT (1)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 80, "B: ICON/SOLID/PALETTE", false,
C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 96, "UP/DOWN: APPROACH (2)", false, C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 112, "LEFT/RIGHT: TICKER SPEED", false,
C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
drawPet2001Text(frame, 56, 128, "BOOT: RETURN TO MENU", false,
C64_COLOR_WHITE, C64_COLOR_BLUE, 1);
snprintf(start_text, sizeof(start_text), "C STARTS IN %u S", seconds_remaining);
drawPet2001Text(frame, 96, 152, start_text, false, C64_COLOR_YELLOW, C64_COLOR_BLUE, 1);
}
static void drawDemoSelector(uint16_t *frame)
{
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 14,
"SELECT DEMO SEGMENT", COLOR_ORANGE, 1);
for (size_t item = 0U; item <= DEMO_STAGE_COUNT; ++item) {
const int y = DEMO_SELECTOR_FIRST_ROW_Y + (int)item * DEMO_SELECTOR_ROW_HEIGHT;
const bool selected = item == demo_selector_item;
if (selected) fill_rect(frame, 0, y - 1, LCD_WIDTH, DEMO_SELECTOR_ROW_HEIGHT,
COLOR_DARKBLUE);
drawPet2001TextTransparent(frame, 8, y, item == 0U ? "RETURN CURRENT" :
demoSelectorStageCodeName((demo_stage_t)(item - 1U)),
selected ? COLOR_WHITE : COLOR_LIGHTGREY, 1);
}
drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 160,
"D/B OR UP/DOWN A/K SELECT", COLOR_LIGHTGREY, 1);
}
void drawDemo(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (demo_selector_active) {
drawDemoSelector(frame);
return;
}
if (demo_selector_clear_new_stage) {
clear(frame, COLOR_BLACK);
demo_selector_clear_new_stage = false;
return;
}
if (demo_instructions_active) {
drawDemoInstructions(frame);
return;
}
if (screenTransitionActive(&stage_transition)) {
screenTransitionDraw(&stage_transition, frame, last_update_ms);
return;
}
if (screenRevealActive(&stage_transition)) {
screenRevealDraw(&stage_transition, frame, last_update_ms);
return;
}
if (screenScrollActive(&stage_transition) && !stage_scroll_in_requested) {
screenScrollDraw(&stage_transition, frame, last_update_ms);
return;
}
if (screenSliceActive(&stage_transition)) {
screenSliceDraw(&stage_transition, frame, last_update_ms);
return;
}
clear(frame, COLOR_BLACK);
if (selected_stage == DEMO_STAGE_APPROACH) {
drawApproachStage(frame);
} else if (selected_stage == DEMO_STAGE_AMIGA_BALL) {
drawStageThreeBall(frame);
} else if (selected_stage == DEMO_STAGE_SCROLLER) {
// The rainbow magnifier scroller is drawn below on this clear black background.
} else if (selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
// The inverse rainbow magnifier scroller is drawn below on this clear black background.
} else if (selected_stage == DEMO_STAGE_GEOMETRIC_SOLID) {
drawStageSixSolid(frame);
} else if (selected_stage == DEMO_STAGE_WALLOONS) {
demoStageWalloonsDraw(frame, last_update_ms);
} else if (selected_stage == DEMO_STAGE_KALEIDO) {
demoStageKaleidoDraw(frame);
} else if (selected_stage == DEMO_STAGE_SPARKLE) {
demoStageSparkleDraw(frame);
} else if (selected_stage == DEMO_STAGE_SQUARES) {
demoStageSquaresDraw(frame);
} else if (selected_stage == DEMO_STAGE_RASTER_BANDS) {
drawRasterBands(frame);
} else if (selected_stage == DEMO_STAGE_SLIDESHOW_LENS) {
demoStageSlideshowLensDraw(frame);
} else if (selected_stage == DEMO_STAGE_PLASMA) {
demoStagePlasmaDraw(frame);
} else if (selected_stage == DEMO_STAGE_GAME_OF_LIFE) {
demoStageGameOfLifeDraw(frame);
} else if (selected_stage == DEMO_STAGE_LOWER_RASTER_SCROLLER) {
drawRasterBands(frame);
} else if (selected_stage == DEMO_STAGE_BATTLE_ZONE_RASTER) {
// The real Battle Zone renderer is compressed into the lower half;
// the compact raster then travels only in the clear upper half.
drawStageFifteenBattleZoneBackground(frame);
drawRasterBands(frame);
} else if (selected_stage == DEMO_STAGE_BEVEL_GEARS) {
drawStageSeventeenBevelGears(frame);
} else if (selected_stage == DEMO_STAGE_MOUSE_MAZE) {
demoStageMouseMazeDraw(frame);
} else if (selected_effect == DEMO_EFFECT_DOT_GRID ||
selected_effect == DEMO_EFFECT_DOT_SPARKLE ||
selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
const bool sparkle_enabled = selected_effect == DEMO_EFFECT_DOT_SPARKLE;
if (selected_effect == DEMO_EFFECT_RADIAL_FLAME) {
draw_radial_flame(frame, &demo_ring_flame, DEMO_CENTRE_X, DEMO_CENTRE_Y,
demoRingInnerRadius(), yaw_degrees, demo_ring_colours);
}
const dotted_bitmap_3d_params_t dotted_sprite = {
.centre_x = DEMO_CENTRE_X,
.centre_y = DEMO_CENTRE_Y,
.scale = DEMO_FACE_SCALE,
.pitch_degrees = pitch_degrees,
.yaw_degrees = yaw_degrees,
.roll_degrees = roll_degrees,
.depth = (float)DEMO_EXTRUSION_DEPTH,
.camera_distance = DEMO_CAMERA_DISTANCE,
.depth_dot_spacing = DEMO_DOT_SPACING,
.dot_radius = DEMO_DOT_RADIUS,
.dot_density_percent = DEMO_DOT_DENSITY_PERCENT,
.random_seed = DEMO_DOT_RANDOM_SEED,
.sparkle_density_percent = sparkle_enabled ? DEMO_DOT_SPARKLE_DENSITY_PERCENT : 0U,
.sparkle_phase = sparkle_phase,
.sparkle_radius = DEMO_DOT_SPARKLE_RADIUS,
.sparkle_color = DEMO_DOT_SPARKLE_COLOR,
.front_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].front,
.back_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].dotted_back,
.side_color = demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth,
};
draw_bitmap_dotted_3d(frame, active_logo_bitmap, DEMO_LOGO_WIDTH,
DEMO_LOGO_HEIGHT, &dotted_sprite);
} else {
const float pitch_radians = pitch_degrees * DEMO_PI / 180.0f;
const float yaw_radians = yaw_degrees * DEMO_PI / 180.0f;
const float roll_radians = roll_degrees * DEMO_PI / 180.0f;
const demo_rotation_t rotation = {
.pitch_cosine = cosf(pitch_radians),
.pitch_sine = sinf(pitch_radians),
.yaw_cosine = cosf(yaw_radians),
.yaw_sine = sinf(yaw_radians),
.roll_cosine = cosf(roll_radians),
.roll_sine = sinf(roll_radians),
};
// The body depth changes screen depth by pitch_cosine * yaw_cosine.
// Draw from far to near so the random front colour is never painted through a nearer slice.
if (rotation.pitch_cosine * rotation.yaw_cosine >= 0.0f) {
for (unsigned depth = DEMO_EXTRUSION_DEPTH; depth > 0U;
depth -= DEMO_EXTRUSION_STEP) {
drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
} else {
drawLogoSlice(frame, active_logo_bitmap, 0.0f, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].front);
for (unsigned depth = DEMO_EXTRUSION_STEP; depth <= DEMO_EXTRUSION_DEPTH;
depth += DEMO_EXTRUSION_STEP) {
drawLogoSlice(frame, active_logo_bitmap, (float)depth, &rotation,
demo_icon_colour_schemes[demo_icon_colour_scheme_index].depth);
}
}
}
if (selected_stage == DEMO_STAGE_SCROLLER ||
selected_stage == DEMO_STAGE_REVERSE_SCROLLER) {
drawTextCornerIcons(frame);
}
drawScrollText(frame);
if (stage_scroll_in_requested) {
// The new stage is complete in the shared frame but has not reached the LCD yet.
// Capture it as the second moving frame, then scroll both frames together.
if (screenScrollSetIncoming(&stage_transition, frame, stage_scroll_in_direction,
DEMO_STAGE_SCROLL_GAP_PIXELS)) {
screenScrollDraw(&stage_transition, frame, last_update_ms);
}
stage_scroll_in_requested = false;
return;
}
if (stage_reveal_requested) {
// Capture the completed destination before the reusable 8 x 8 rebuild effect hides it.
if (screenRevealStart(&stage_transition, frame, last_update_ms, DEMO_STAGE_REBUILD_MS,
DEMO_STAGE_REBUILD_PERCENT)) {
screenRevealDraw(&stage_transition, frame, last_update_ms);
}
stage_reveal_requested = false;
return;
}
if (stage_zoom_in_requested) {
// This scene has been drawn into the shared frame but not presented yet: capture it
// and replace it with the first one-pixel frame of the incoming zoom.
if (screenTransitionZoomInStart(&stage_transition, frame, last_update_ms,
DEMO_STAGE_ZOOM_IN_MS)) {
stage_zooming_in = true;
screenTransitionDraw(&stage_transition, frame, last_update_ms);
}
stage_zoom_in_requested = false;
return;
}
if (stage_change_requested) {
switch (selected_stage_transition) {
case DEMO_STAGE_TRANSITION_SHRINK_ZOOM:
// Freeze the completed old stage before updateDemo selects and captures the new one.
if (!screenTransitionStart(&stage_transition, frame, last_update_ms,
DEMO_STAGE_TRANSITION_FREEZE_MS,
DEMO_STAGE_TRANSITION_ZOOM_MS)) {
advanceDemoStage(last_update_ms);
stage_zoom_in_requested = true;
}
break;
case DEMO_STAGE_TRANSITION_REBUILD:
// Select the destination now; drawDemo captures it next frame for its block reveal.
advanceDemoStage(last_update_ms);
stage_reveal_requested = true;
break;
case DEMO_STAGE_TRANSITION_SCROLL:
// Capture after the completed outgoing scene, then translate that exact image away.
if (!screenScrollOutStart(&stage_transition, frame, last_update_ms,
stage_scroll_out_direction,
DEMO_STAGE_SCROLL_STEP_MS,
DEMO_STAGE_SCROLL_PIXELS_PER_STEP)) {
advanceDemoStage(last_update_ms);
stage_scroll_in_requested = true;
} else {
// Select and draw the new stage next frame while the captured old one moves away.
advanceDemoStage(last_update_ms);
stage_scroll_in_requested = true;
}
break;
case DEMO_STAGE_TRANSITION_SLICES:
// Freeze the outgoing stage as alternating horizontal bands before entering the next.
if (!screenSliceStart(&stage_transition, frame, last_update_ms,
DEMO_STAGE_SLICE_COUNT, DEMO_STAGE_SLICE_MS)) {
advanceDemoStage(last_update_ms);
stage_zoom_in_requested = true;
}
break;
default:
break;
}
stage_change_requested = false;
}
}
const char *demoScreenshotLabel(void)
{
return demoSelectorStageScreenshotLabel(selected_stage);
}