feat(intro): port 3D FX phases B/C/D
Rotozoom, dot sphere et ray corridor portes depuis le FxEngine Arduino (boucles de rendu seules, sans CapsAllocator ni timelines). Rendu 240x160 RGB565 en PSRAM double en pixels vers un lv_canvas plein ecran derriere logo+scroller ; phases A 7s puis 3x3s ; fallback starfield si l'alloc PSRAM echoue. Build vert (35% libre).
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "display_ui.cpp"
|
||||
"intro_fx3d.cpp"
|
||||
"buttons_input.c"
|
||||
"fonts/lv_font_orbitron_40.c"
|
||||
"fonts/lv_font_ibmplexmono_18.c"
|
||||
|
||||
@@ -250,15 +250,20 @@ static volatile uint8_t s_browser_sel = 0;
|
||||
static volatile bool s_browser_open = false;
|
||||
static volatile bool s_browser_reload = false;
|
||||
|
||||
// ─── Intro — faithful port of the original cracktro phase A ──────────────────
|
||||
// Algorithms from ui_manager_intro.cpp: 3-layer parallax starfield in Q8.8
|
||||
// ─── Intro — faithful port of the original cracktro ──────────────────────────
|
||||
// Phase A from ui_manager_intro.cpp: 3-layer parallax starfield in Q8.8
|
||||
// fixed point (layer split 50/30/20%, speeds {54,116,198} px/s, sizes 1-3 px,
|
||||
// opacities 30/60/100%, 1-in-8 twinkle), logo + drop shadow (offset 1,23),
|
||||
// bottom sine-wave scrolltext, copper bars. The 3D FX modes of phases B/C
|
||||
// (dot sphere, ray corridor, rotozoom) are NOT ported yet.
|
||||
// bottom sine-wave scrolltext, copper bars. Phases B/C/D are the original 3D
|
||||
// FX (rotozoom, dot sphere, ray corridor) ported in intro_fx3d.cpp, rendered
|
||||
// into a full-screen LVGL canvas behind the logo + scroller. If the canvas
|
||||
// PSRAM allocation fails, the intro gracefully stays on phase A throughout.
|
||||
#include "intro_fx3d.h"
|
||||
#define INTRO_STARS 48
|
||||
#define INTRO_BARS 4
|
||||
#define INTRO_DURATION_MS 8000
|
||||
#define INTRO_PHASE_A_MS 7000
|
||||
#define INTRO_FX_MS 3000
|
||||
#define INTRO_DURATION_MS (INTRO_PHASE_A_MS + 3 * INTRO_FX_MS)
|
||||
typedef struct { int32_t x_q8, y_q8; uint8_t layer; int16_t speed; } intro_star_t;
|
||||
static lv_obj_t *s_scr_intro;
|
||||
static lv_obj_t *s_intro_logo, *s_intro_logo_shadow;
|
||||
@@ -269,6 +274,9 @@ static lv_obj_t *s_intro_bars[INTRO_BARS];
|
||||
static int32_t s_scroll_x_q8; // scrolltext x in Q8.8
|
||||
static uint32_t s_intro_t_ms; // elapsed intro time
|
||||
static volatile bool s_intro_done = false;
|
||||
static lv_obj_t *s_fx_canvas; // phases B/C/D render target
|
||||
static lv_color_t *s_fx_canvas_buf; // 480x320 RGB565, PSRAM
|
||||
static bool s_fx_tried, s_fx_ok; // lazy one-shot setup at phase B
|
||||
// Original kIntro defaults: scroll 90 px/s, sine amp 14 px, period 120 px.
|
||||
#define INTRO_SCROLL_PXS 90
|
||||
#define INTRO_SINE_AMP 14
|
||||
@@ -660,10 +668,52 @@ static void build_intro_screen(void) {
|
||||
lv_obj_set_pos(s_intro_scroll, DUI_HOR_RES, DUI_VER_RES - 60);
|
||||
}
|
||||
|
||||
// Phase B entry: allocate the FX module + a full-screen canvas behind the
|
||||
// logo/scroller, hide the phase-A starfield + bars. One-shot; on failure the
|
||||
// intro simply keeps the starfield (s_fx_ok stays false). Display task only.
|
||||
static bool intro_fx_setup(void) {
|
||||
if (!fx3d_init()) return false;
|
||||
s_fx_canvas_buf = (lv_color_t *) heap_caps_malloc(
|
||||
(size_t) DUI_HOR_RES * DUI_VER_RES * sizeof(lv_color_t),
|
||||
MALLOC_CAP_SPIRAM);
|
||||
if (!s_fx_canvas_buf) return false;
|
||||
s_fx_canvas = lv_canvas_create(s_scr_intro);
|
||||
lv_canvas_set_buffer(s_fx_canvas, s_fx_canvas_buf,
|
||||
DUI_HOR_RES, DUI_VER_RES, LV_IMG_CF_TRUE_COLOR);
|
||||
lv_obj_set_pos(s_fx_canvas, 0, 0);
|
||||
lv_obj_move_background(s_fx_canvas);
|
||||
for (int i = 0; i < INTRO_STARS; i++)
|
||||
lv_obj_add_flag(s_star_objs[i], LV_OBJ_FLAG_HIDDEN);
|
||||
for (int i = 0; i < INTRO_BARS; i++)
|
||||
lv_obj_add_flag(s_intro_bars[i], LV_OBJ_FLAG_HIDDEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Per-tick intro animation (display task only; dt in ms). Faithful Q8.8 math.
|
||||
static void update_intro(uint32_t dt_ms) {
|
||||
s_intro_t_ms += dt_ms;
|
||||
|
||||
// Phase sequencing: A starfield, then B/C/D = rotozoom, dot sphere,
|
||||
// ray corridor (original fx_mode order) on the FX canvas.
|
||||
if (s_intro_t_ms >= INTRO_PHASE_A_MS && !s_fx_tried) {
|
||||
s_fx_tried = true;
|
||||
s_fx_ok = intro_fx_setup();
|
||||
}
|
||||
if (s_fx_ok && s_intro_t_ms >= INTRO_PHASE_A_MS) {
|
||||
int fx = (int) ((s_intro_t_ms - INTRO_PHASE_A_MS) / INTRO_FX_MS);
|
||||
if (fx > 2) fx = 2;
|
||||
// Render every other tick (20 ms): the SPI flush of a full-screen
|
||||
// canvas dominates anyway, no point racing it.
|
||||
static bool skip;
|
||||
skip = !skip;
|
||||
if (!skip) {
|
||||
fx3d_render((fx3d_mode_t) fx, s_intro_t_ms,
|
||||
(uint16_t *) s_fx_canvas_buf, DUI_HOR_RES, DUI_VER_RES);
|
||||
lv_obj_invalidate(s_fx_canvas);
|
||||
}
|
||||
goto scroller; // stars/bars hidden — skip their updates
|
||||
}
|
||||
|
||||
// Starfield: fall + wrap + twinkle (original update loop).
|
||||
for (int i = 0; i < INTRO_STARS; i++) {
|
||||
intro_star_t *st = &s_stars[i];
|
||||
@@ -685,6 +735,7 @@ static void update_intro(uint32_t dt_ms) {
|
||||
lv_obj_set_y(s_intro_bars[i], 60 + i * 14 + (int) (sinf(ph) * 18.0f));
|
||||
}
|
||||
|
||||
scroller:
|
||||
// Sine scroller: leftward at INTRO_SCROLL_PXS, y rides a sine of the
|
||||
// x position (original sine_amp/sine_period defaults).
|
||||
s_scroll_x_q8 -= (int32_t) ((INTRO_SCROLL_PXS * dt_ms * 256u) / 1000u);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Intro cracktro — 3D FX phases B/C/D (rotozoom, dot sphere, ray corridor).
|
||||
// Faithful per-pixel ports of ui_freenove_allinone/src/ui/fx/fx_engine.cpp
|
||||
// (renderMidRotoZoom, renderDotSphere3D, renderRayCorridor) rendered at
|
||||
// 240x160 in a PSRAM buffer then pixel-doubled to the 480x320 LVGL canvas.
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FX3D_W 240
|
||||
#define FX3D_H 160
|
||||
|
||||
typedef enum {
|
||||
FX3D_ROTOZOOM = 0,
|
||||
FX3D_DOTSPHERE = 1,
|
||||
FX3D_CORRIDOR = 2,
|
||||
} fx3d_mode_t;
|
||||
|
||||
// Allocate the low-res buffer + LUTs/textures (PSRAM). Idempotent.
|
||||
bool fx3d_init(void);
|
||||
|
||||
// Render `mode` at time t_ms into dst (RGB565, dst_w x dst_h) with 2x pixel
|
||||
// doubling. dst must be exactly FX3D_W*2 x FX3D_H*2; anything else is a no-op.
|
||||
void fx3d_render(fx3d_mode_t mode, uint32_t t_ms, uint16_t *dst,
|
||||
int dst_w, int dst_h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,334 @@
|
||||
// Intro cracktro 3D FX — ports of the original Arduino FxEngine renderers
|
||||
// (ui_freenove_allinone/src/ui/fx/fx_engine.cpp): renderMidRotoZoom,
|
||||
// renderDotSphere3D (+ kDotSphere3D init), renderRayCorridor (+ kRayCorridor
|
||||
// init). Only the render loops are ported — no CapsAllocator, timelines or
|
||||
// DMA line buffers. Everything renders into a 240x160 RGB565 low-res buffer
|
||||
// (PSRAM) then gets pixel-doubled into the caller's 480x320 canvas.
|
||||
#include "intro_fx3d.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kRotoTexSize = 128; // FxEngine kRotoTexSize
|
||||
constexpr int kRayTexSize = 64; // FxEngine kRayTexSize
|
||||
constexpr int kDotCount = 360; // ~W*H/75 clamped (original formula)
|
||||
constexpr int kDotRadius = 72; // min_dim/2 - 8 clamped to [24,72]
|
||||
constexpr int kDotBlobR = 2;
|
||||
|
||||
struct DotPt { int16_t x, y, z; };
|
||||
|
||||
uint16_t *s_lowres; // FX3D_W * FX3D_H
|
||||
uint16_t *s_roto_tex; // 128 * 128
|
||||
uint16_t *s_ray_tex; // 64 * 64
|
||||
DotPt *s_dots;
|
||||
uint16_t s_dot_shade[256];
|
||||
int8_t s_ray_col_off[FX3D_W];
|
||||
uint16_t s_ray_floor_q12[FX3D_H];
|
||||
int16_t s_sin_q15[256];
|
||||
bool s_ready = false;
|
||||
|
||||
// ---- helpers (FxEngine::rgb565 / mul565_u8 / addSat565 / sin8) ----
|
||||
|
||||
uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return (uint16_t) (((r & 0xF8u) << 8) | ((g & 0xFCu) << 3) | (b >> 3));
|
||||
}
|
||||
|
||||
uint16_t mul565_u8(uint16_t c, uint8_t v) {
|
||||
uint16_t r = (uint16_t) ((c >> 11) & 31u);
|
||||
uint16_t g = (uint16_t) ((c >> 5) & 63u);
|
||||
uint16_t b = (uint16_t) (c & 31u);
|
||||
r = (uint16_t) ((r * v + 128u) >> 8);
|
||||
g = (uint16_t) ((g * v + 128u) >> 8);
|
||||
b = (uint16_t) ((b * v + 128u) >> 8);
|
||||
return (uint16_t) ((r << 11) | (g << 5) | b);
|
||||
}
|
||||
|
||||
uint16_t add_sat565(uint16_t a, uint16_t b) {
|
||||
uint16_t ar = (uint16_t) ((a >> 11) & 31u), ag = (uint16_t) ((a >> 5) & 63u), ab = (uint16_t) (a & 31u);
|
||||
uint16_t br = (uint16_t) ((b >> 11) & 31u), bg = (uint16_t) ((b >> 5) & 63u), bb = (uint16_t) (b & 31u);
|
||||
const uint16_t rr = (uint16_t) ((ar + br > 31u) ? 31u : (ar + br));
|
||||
const uint16_t gg = (uint16_t) ((ag + bg > 63u) ? 63u : (ag + bg));
|
||||
const uint16_t b2 = (uint16_t) ((ab + bb > 31u) ? 31u : (ab + bb));
|
||||
return (uint16_t) ((rr << 11) | (gg << 5) | b2);
|
||||
}
|
||||
|
||||
int16_t sin_q15(uint8_t a) { return s_sin_q15[a]; }
|
||||
int16_t cos_q15(uint8_t a) { return s_sin_q15[(uint8_t) (a + 64u)]; }
|
||||
// fx_sin8/fx_cos8 equivalents: amplitude -128..127.
|
||||
int16_t sin8(uint8_t a) { return (int16_t) (s_sin_q15[a] >> 8); }
|
||||
int16_t cos8(uint8_t a) { return (int16_t) (s_sin_q15[(uint8_t) (a + 64u)] >> 8); }
|
||||
|
||||
template <typename T>
|
||||
T clampv(T v, T lo, T hi) { return (v < lo) ? lo : (v > hi) ? hi : v; }
|
||||
|
||||
void fill_lowres(uint16_t color) {
|
||||
// 32-bit fill (buffer is 4-byte aligned, FX3D_W*FX3D_H even)
|
||||
const uint32_t packed = (uint32_t) color | ((uint32_t) color << 16);
|
||||
uint32_t *dst32 = (uint32_t *) s_lowres;
|
||||
for (size_t i = 0; i < (size_t) FX3D_W * FX3D_H / 2; i++) dst32[i] = packed;
|
||||
}
|
||||
|
||||
void add_pixel(int x, int y, uint16_t color) {
|
||||
if (x < 0 || y < 0 || x >= FX3D_W || y >= FX3D_H) return;
|
||||
const size_t idx = (size_t) y * FX3D_W + (size_t) x;
|
||||
s_lowres[idx] = add_sat565(s_lowres[idx], color);
|
||||
}
|
||||
|
||||
void *psram_alloc(size_t bytes) {
|
||||
void *p = heap_caps_malloc(bytes, MALLOC_CAP_SPIRAM);
|
||||
if (!p) p = heap_caps_malloc(bytes, MALLOC_CAP_DEFAULT);
|
||||
return p;
|
||||
}
|
||||
|
||||
// ---- renderers (faithful ports) ----
|
||||
|
||||
// FxEngine::renderMidRotoZoom — additive rotozoom of the radial-checker
|
||||
// texture generated in FxEngine::begin().
|
||||
void render_rotozoom(uint32_t now_ms) {
|
||||
fill_lowres(rgb565(4u, 8u, 16u));
|
||||
const int cx = FX3D_W / 2, cy = FX3D_H / 2;
|
||||
const uint8_t phase = (uint8_t) ((now_ms / 10u) & 0xFFu);
|
||||
const int16_t s = sin8(phase);
|
||||
const int16_t c = cos8(phase);
|
||||
const int16_t pulse = (int16_t) (sin8((uint8_t) (phase * 2u)) >> 1);
|
||||
const int16_t zoom_q8 = (int16_t) (256 + pulse);
|
||||
|
||||
for (int y = 0; y < FX3D_H; y++) {
|
||||
const int16_t dy = (int16_t) (y - cy);
|
||||
const size_t row = (size_t) y * FX3D_W;
|
||||
for (int x = 0; x < FX3D_W; x++) {
|
||||
const int16_t dx = (int16_t) (x - cx);
|
||||
int32_t u = (c * dx - s * dy);
|
||||
int32_t v = (s * dx + c * dy);
|
||||
u = (u * zoom_q8) >> 8;
|
||||
v = (v * zoom_q8) >> 8;
|
||||
const int tx = (int) ((u + kRotoTexSize / 2) & (kRotoTexSize - 1));
|
||||
const int ty = (int) ((v + kRotoTexSize / 2) & (kRotoTexSize - 1));
|
||||
const uint16_t tex = s_roto_tex[(size_t) ty * kRotoTexSize + (size_t) tx];
|
||||
s_lowres[row + x] = add_sat565(s_lowres[row + x], mul565_u8(tex, 180u));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FxEngine::renderDotSphere3D — lit point sphere, Q15 LUT rotations.
|
||||
void render_dotsphere(uint32_t now_ms) {
|
||||
fill_lowres(0x0000u);
|
||||
const uint8_t ax = (uint8_t) (now_ms >> 4);
|
||||
const uint8_t ay = (uint8_t) (now_ms >> 5);
|
||||
const uint8_t az = (uint8_t) (now_ms >> 6);
|
||||
const int16_t cx_r = cos_q15(ax), sx_r = sin_q15(ax);
|
||||
const int16_t cy_r = cos_q15(ay), sy_r = sin_q15(ay);
|
||||
const int16_t cz_r = cos_q15(az), sz_r = sin_q15(az);
|
||||
const int16_t lx = (int16_t) (0.30f * 32767.0f);
|
||||
const int16_t ly = (int16_t) (-0.20f * 32767.0f);
|
||||
const int16_t lz = (int16_t) (0.93f * 32767.0f);
|
||||
const int center_x = FX3D_W / 2, center_y = FX3D_H / 2;
|
||||
const int fov = (FX3D_W < FX3D_H) ? FX3D_W : FX3D_H;
|
||||
|
||||
for (int i = 0; i < kDotCount; i++) {
|
||||
const DotPt &dot = s_dots[i];
|
||||
const int32_t x = ((int32_t) dot.x * kDotRadius) >> 7;
|
||||
const int32_t y = ((int32_t) dot.y * kDotRadius) >> 7;
|
||||
const int32_t z = ((int32_t) dot.z * kDotRadius) >> 7;
|
||||
|
||||
const int32_t y1 = (y * cx_r - z * sx_r) >> 15;
|
||||
const int32_t z1 = (y * sx_r + z * cx_r) >> 15;
|
||||
const int32_t x2 = (x * cy_r + z1 * sy_r) >> 15;
|
||||
const int32_t z2 = (-x * sy_r + z1 * cy_r) >> 15;
|
||||
const int32_t x3 = (x2 * cz_r - y1 * sz_r) >> 15;
|
||||
const int32_t y3 = (x2 * sz_r + y1 * cz_r) >> 15;
|
||||
|
||||
const int32_t depth = z2 + (kDotRadius * 3);
|
||||
if (depth <= 1) continue;
|
||||
|
||||
const int sxp = center_x + (int) ((x3 * fov) / depth);
|
||||
const int syp = center_y + (int) ((y3 * fov) / depth);
|
||||
if (sxp < 0 || syp < 0 || sxp >= FX3D_W || syp >= FX3D_H) continue;
|
||||
|
||||
const int32_t nd = (x3 * lx + y3 * ly + z2 * lz) >> 15;
|
||||
const int32_t ndotl = clampv<int32_t>((nd * 128) / kDotRadius + 128, 0, 255);
|
||||
const uint16_t base = s_dot_shade[ndotl];
|
||||
|
||||
for (int yy = -kDotBlobR; yy <= kDotBlobR; yy++) {
|
||||
for (int xx = -kDotBlobR; xx <= kDotBlobR; xx++) {
|
||||
const int d2 = xx * xx + yy * yy;
|
||||
if (d2 > kDotBlobR * kDotBlobR) continue;
|
||||
const int atten = clampv<int>(255 - d2 * 28, 0, 255);
|
||||
add_pixel(sxp + xx, syp + yy, mul565_u8(base, (uint8_t) atten));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FxEngine::renderRayCorridor — textured tunnel walls + scrolling floor.
|
||||
void render_corridor(uint32_t now_ms) {
|
||||
fill_lowres(0x0000u);
|
||||
const int horizon = FX3D_H / 2;
|
||||
const uint32_t zscroll = now_ms >> 3;
|
||||
const uint8_t camera_angle = (uint8_t) (now_ms >> 6);
|
||||
|
||||
for (int x = 0; x < FX3D_W; x++) {
|
||||
const int8_t off = s_ray_col_off[x];
|
||||
const uint8_t ray_angle = (uint8_t) (camera_angle + (uint8_t) off);
|
||||
const int16_t dir_x = sin_q15(ray_angle);
|
||||
const int16_t dir_z = cos_q15(ray_angle);
|
||||
const int16_t abs_dir_x = (int16_t) ((dir_x < 0) ? -dir_x : dir_x);
|
||||
if (abs_dir_x < 64) {
|
||||
for (int y = horizon; y < FX3D_H; y++) {
|
||||
const int dy = y - horizon;
|
||||
const uint8_t shade = (uint8_t) (120 + dy * 2);
|
||||
s_lowres[(size_t) y * FX3D_W + x] = mul565_u8(rgb565(6u, 5u, 2u), shade);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t t_q15 = (1ul << 30) / (uint32_t) abs_dir_x;
|
||||
const uint16_t corr = (uint16_t) cos_q15((uint8_t) off);
|
||||
uint32_t dist_q15 = (uint32_t) (((uint64_t) t_q15 * corr) >> 15);
|
||||
if (dist_q15 == 0u) dist_q15 = 1u;
|
||||
int slice = (int) (((uint32_t) FX3D_H << 15) / dist_q15);
|
||||
slice = clampv<int>(slice, 1, FX3D_H);
|
||||
int y0 = horizon - slice / 2;
|
||||
int y1 = y0 + slice - 1;
|
||||
y0 = clampv<int>(y0, 0, FX3D_H - 1);
|
||||
y1 = clampv<int>(y1, 0, FX3D_H - 1);
|
||||
|
||||
const int32_t zhit_q15 = (int32_t) (((int64_t) dir_z * (int64_t) t_q15) >> 15);
|
||||
int u = (int) (((zhit_q15 >> 9) + (int32_t) zscroll) & 63);
|
||||
if (dir_x < 0) u ^= 63;
|
||||
const int shade = clampv<int>(255 - (int) (dist_q15 >> 9), 0, 255);
|
||||
for (int y = y0; y <= y1; y++) {
|
||||
const int v = ((y - y0) * 64) / ((slice == 0) ? 1 : slice);
|
||||
uint16_t color = s_ray_tex[(size_t) (v & 63) * kRayTexSize + (size_t) (u & 63)];
|
||||
s_lowres[(size_t) y * FX3D_W + x] = mul565_u8(color, (uint8_t) shade);
|
||||
}
|
||||
for (int y = y1 + 1; y < FX3D_H; y++) {
|
||||
const uint16_t k = s_ray_floor_q12[y];
|
||||
if (k == 0u) continue;
|
||||
const int32_t uu_q12 = (int32_t) (((int64_t) dir_x * k) >> 15);
|
||||
const int32_t vv_q12 = (int32_t) (((int64_t) dir_z * k) >> 15);
|
||||
const int uf = (int) (((uu_q12 >> 6) + (int32_t) zscroll) & 63);
|
||||
const int vf = (int) (((vv_q12 >> 6) + (int32_t) (zscroll >> 1)) & 63);
|
||||
uint16_t color = s_ray_tex[(size_t) (vf & 63) * kRayTexSize + (size_t) (uf & 63)];
|
||||
const int dy = y - horizon;
|
||||
const int fade = clampv<int>(255 - dy * 2, 0, 255);
|
||||
s_lowres[(size_t) y * FX3D_W + x] = mul565_u8(color, (uint8_t) fade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" bool fx3d_init(void) {
|
||||
if (s_ready) return true;
|
||||
|
||||
s_lowres = (uint16_t *) psram_alloc((size_t) FX3D_W * FX3D_H * sizeof(uint16_t));
|
||||
s_roto_tex = (uint16_t *) psram_alloc((size_t) kRotoTexSize * kRotoTexSize * sizeof(uint16_t));
|
||||
s_ray_tex = (uint16_t *) psram_alloc((size_t) kRayTexSize * kRayTexSize * sizeof(uint16_t));
|
||||
s_dots = (DotPt *) psram_alloc((size_t) kDotCount * sizeof(DotPt));
|
||||
if (!s_lowres || !s_roto_tex || !s_ray_tex || !s_dots) {
|
||||
heap_caps_free(s_lowres); s_lowres = nullptr;
|
||||
heap_caps_free(s_roto_tex); s_roto_tex = nullptr;
|
||||
heap_caps_free(s_ray_tex); s_ray_tex = nullptr;
|
||||
heap_caps_free(s_dots); s_dots = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
s_sin_q15[i] = (int16_t) (sinf((float) i * (6.28318530f / 256.0f)) * 32767.0f);
|
||||
}
|
||||
|
||||
// Rotozoom texture — radial-shaded checker (FxEngine::begin).
|
||||
for (int y = 0; y < kRotoTexSize; y++) {
|
||||
for (int x = 0; x < kRotoTexSize; x++) {
|
||||
const float cx = (float) x - kRotoTexSize * 0.5f;
|
||||
const float cy = (float) y - kRotoTexSize * 0.5f;
|
||||
float rr = sqrtf(cx * cx + cy * cy) / (kRotoTexSize * 0.5f);
|
||||
if (rr > 1.0f) rr = 1.0f;
|
||||
const bool checker = (((x >> 4) ^ (y >> 4)) & 1) != 0;
|
||||
const uint8_t r = checker ? (uint8_t) (40 + (uint8_t) (200.0f * (1.0f - rr))) : 200u;
|
||||
const uint8_t g = checker ? (uint8_t) (60 + (uint8_t) (160.0f * (1.0f - rr)))
|
||||
: (uint8_t) (50 + (uint8_t) (120.0f * (1.0f - rr)));
|
||||
const uint8_t b = checker ? 200u : (uint8_t) (60 + (uint8_t) (120.0f * (1.0f - rr)));
|
||||
s_roto_tex[(size_t) y * kRotoTexSize + x] = rgb565(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
// Dot sphere — shade LUT + random unit sphere (initModeState, same seed).
|
||||
{
|
||||
const uint16_t base = rgb565(40u, 80u, 240u);
|
||||
const uint16_t high = rgb565(255u, 255u, 255u);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
const uint8_t diffuse = (uint8_t) i;
|
||||
const int spec_i = (i > 220) ? (i - 220) * 7 : 0;
|
||||
const uint8_t spec = (uint8_t) clampv<int>(spec_i, 0, 255);
|
||||
s_dot_shade[i] = add_sat565(mul565_u8(base, diffuse), mul565_u8(high, spec));
|
||||
}
|
||||
uint32_t rng = 0xBADC0FFEul ^ (uint32_t) FX3D_W ^ ((uint32_t) FX3D_H << 16);
|
||||
for (int i = 0; i < kDotCount; i++) {
|
||||
rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5;
|
||||
const uint8_t a = (uint8_t) (rng & 0xFFu);
|
||||
rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5;
|
||||
const uint8_t b = (uint8_t) ((rng >> 8) & 0xFFu);
|
||||
const int16_t ca = cos_q15(a), sa = sin_q15(a);
|
||||
const int16_t cb = cos_q15(b), sb = sin_q15(b);
|
||||
s_dots[i].x = (int16_t) ((((int32_t) ca * cb) >> 15) >> 8);
|
||||
s_dots[i].y = (int16_t) ((int32_t) sb >> 8);
|
||||
s_dots[i].z = (int16_t) ((((int32_t) sa * cb) >> 15) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Ray corridor — wall/floor texture, per-column angle offsets, floor LUT
|
||||
// (initModeState kRayCorridor).
|
||||
for (int y = 0; y < kRayTexSize; y++) {
|
||||
for (int x = 0; x < kRayTexSize; x++) {
|
||||
const bool checker = (((x >> 3) ^ (y >> 3)) & 1) != 0;
|
||||
int c = checker ? 190 : 70;
|
||||
if ((y & 7) == 0) c = 40;
|
||||
s_ray_tex[(size_t) y * kRayTexSize + x] =
|
||||
rgb565((uint8_t) c, (uint8_t) (c / 2), (uint8_t) (c / 3));
|
||||
}
|
||||
}
|
||||
for (int x = 0; x < FX3D_W; x++) {
|
||||
const int dx = x - FX3D_W / 2;
|
||||
s_ray_col_off[x] = (int8_t) clampv<int>((dx * 24) / (FX3D_W / 2), -64, 64);
|
||||
}
|
||||
for (int y = 0; y < FX3D_H; y++) {
|
||||
const int dy = y - FX3D_H / 2;
|
||||
if (dy <= 0) { s_ray_floor_q12[y] = 0u; continue; }
|
||||
uint32_t value = (64ul << 12) / (uint32_t) dy;
|
||||
if (value > 65535ul) value = 65535ul;
|
||||
s_ray_floor_q12[y] = (uint16_t) value;
|
||||
}
|
||||
|
||||
s_ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" void fx3d_render(fx3d_mode_t mode, uint32_t t_ms, uint16_t *dst,
|
||||
int dst_w, int dst_h) {
|
||||
if (!s_ready || dst == nullptr || dst_w != FX3D_W * 2 || dst_h != FX3D_H * 2) {
|
||||
return;
|
||||
}
|
||||
switch (mode) {
|
||||
case FX3D_ROTOZOOM: render_rotozoom(t_ms); break;
|
||||
case FX3D_DOTSPHERE: render_dotsphere(t_ms); break;
|
||||
case FX3D_CORRIDOR: render_corridor(t_ms); break;
|
||||
default: return;
|
||||
}
|
||||
// 2x pixel doubling, two 32-bit writes per source pixel, row duplicated.
|
||||
for (int y = 0; y < FX3D_H; y++) {
|
||||
const uint16_t *src = &s_lowres[(size_t) y * FX3D_W];
|
||||
uint32_t *out0 = (uint32_t *) (dst + (size_t) (y * 2) * dst_w);
|
||||
uint32_t *out1 = (uint32_t *) (dst + (size_t) (y * 2 + 1) * dst_w);
|
||||
for (int x = 0; x < FX3D_W; x++) {
|
||||
const uint32_t px = (uint32_t) src[x] | ((uint32_t) src[x] << 16);
|
||||
out0[x] = px;
|
||||
out1[x] = px;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user