Files
kichikuou_xsystem35-sdl2/modules/dDemo/dDemo.c
T
kichikuou a627a8ea3d Add VM variable storage type
Define a dedicated `vmvar_t` type for values stored in variable pages
and propagate it through interfaces that expose those values. Keep the
type as int for now so this commit does not change runtime behavior.
This separates VM storage from ordinary int buffers and makes a later
conversion to 16-bit storage mechanically checkable.
2026-07-17 08:48:10 +09:00

964 lines
32 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "config.h"
#include <stdio.h>
#include <math.h>
#include <SDL.h>
#include "portab.h"
#include "system.h"
#include "xsystem35.h"
#include "modules.h"
#include "nact.h"
#include "input.h"
#include "music.h"
#include "alk.h"
#include "cg.h"
#include "gfx.h"
#include "gfx_private.h"
// Perspective projection parameters
#define PROJ_SCALE_X 1.2993f // cot(30°) / (4/3)
#define PROJ_SCALE_Y 1.7321f // cot(30°) = √3
#define PROJ_W_DENOM 0.5000625f
static inline float lerpf(float a, float b, float t) { return a + (b - a) * t; }
static void render_quad(SDL_Texture *tex, SDL_Vertex verts[4]) {
static const int indices[] = {0, 1, 2, 0, 2, 3};
SDL_RenderGeometry(gfx_renderer, tex, verts, 4, indices, 6);
}
static void fade_overlay(Uint8 r, Uint8 g, Uint8 b, Uint8 alpha) {
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(gfx_renderer, r, g, b, alpha);
SDL_RenderFillRect(gfx_renderer, NULL);
}
#define NR_SCENES 13
static const int scene_duration[] = {
5400,
4752,
24963,
4000,
20000,
4000,
21000,
4000,
6402,
4000,
10370,
31613,
10338,
};
#define NR_TEXTURES 86
static struct {
bool loop;
bool key_cancel;
SDL_Texture *images[NR_TEXTURES];
} dd;
// For each corner, if a coordinate is out of [0, view_w-1] x [0, view_h-1],
// shift all other corners by the overshoot amount, then hard-clamp everything
// to the screen boundary.
static void clamp_rect_to_screen(int xs[4], int ys[4]) {
for (int i = 0; i < 4; i++) {
if (xs[i] < 0) {
for (int j = 0; j < 4; j++) if (j != i) xs[j] -= xs[i];
xs[i] = 0;
}
if (ys[i] < 0) {
for (int j = 0; j < 4; j++) if (j != i) ys[j] -= ys[i];
ys[i] = 0;
}
if (xs[i] >= view_w - 1) {
for (int j = 0; j < 4; j++) if (j != i) xs[j] += (view_w - 1 - xs[i]);
xs[i] = view_w - 1;
}
if (ys[i] >= view_h - 1) {
for (int j = 0; j < 4; j++) if (j != i) ys[j] += (view_h - 1 - ys[i]);
ys[i] = view_h - 1;
}
}
for (int i = 0; i < 4; i++) {
if (xs[i] < 0) xs[i] = 0;
if (xs[i] >= view_w) xs[i] = view_w - 1;
if (ys[i] < 0) ys[i] = 0;
if (ys[i] >= view_h) ys[i] = view_h - 1;
}
}
static void Init() {
int p1 = getCaliValue();
int p2 = getCaliValue();
int p3 = getCaliValue();
vmvar_t *var = getCaliVariable();
if (!nact->files.alk[0]) {
WARNING("dDEMO.alk not found");
return;
}
alk_t *alk = alk_new(nact->files.alk[0]); // dDEMO.alk
if (!alk) {
WARNING("Cannot open %s", nact->files.alk[0]);
*var = 0;
return;
}
if (alk->datanum < NR_TEXTURES) {
WARNING("dDEMO.alk has %d entries, expected %d", alk->datanum, NR_TEXTURES);
alk_free(alk);
*var = 0;
return;
}
for (int i = 0; i < NR_TEXTURES; i++) {
bool load_as_alpha_map = i < 18 || i > 60;
SDL_Surface *sf = cg_load_as_sdlsurface_from_data(alk->entries[i].data, alk->entries[i].size, false, load_as_alpha_map);
if (!sf) {
WARNING("failed to load image %d in dDEMO.alk", i);
dd.images[i] = NULL;
continue;
}
dd.images[i] = SDL_CreateTextureFromSurface(gfx_renderer, sf);
SDL_FreeSurface(sf);
}
*var = 1;
TRACE("dDemo.Init %d,%d,%d,%p:", p1, p2, p3, var);
}
static void SetKeyCancelFlag() {
int cancelflag = getCaliValue();
dd.key_cancel = cancelflag != 0;
TRACE("dDemo.SetKeyCancelFlag %d:", cancelflag);
}
static void SetLoopFlag() {
int loopflag = getCaliValue();
dd.loop = loopflag != 0;
TRACE("dDemo.SetLoopFlag %d:", loopflag);
}
static void render_affine_bg(SDL_Texture *scroll_bg, int elapsed, int duration,
float angle_deg, float scale, int offset_x, int offset_y) {
if (!scroll_bg) return;
int bg_w, bg_h;
SDL_QueryTexture(scroll_bg, NULL, NULL, &bg_w, &bg_h);
int xs[4], ys[4];
const float angle = angle_deg * (float)M_PI / 180.0f;
const float hw = view_w * 0.5f, hh = view_h * 0.5f;
const float cosA = cosf(angle), sinA = sinf(angle);
const float cxs[4] = {-hw, hw, hw, -hw};
const float cys[4] = {-hh, -hh, hh, hh};
for (int i = 0; i < 4; i++) {
xs[i] = (int)((cosA * cxs[i] - sinA * cys[i]) * scale + hw) + offset_x;
ys[i] = (int)((sinA * cxs[i] + cosA * cys[i]) * scale + hh) + offset_y;
}
clamp_rect_to_screen(xs, ys);
float t = (float)elapsed / (float)duration;
float w = (float)view_w, h = (float)view_h;
float uv[4][2];
uv[0][0] = 0.0f;
uv[0][1] = lerpf(0.0f, (float)ys[0], t);
uv[1][0] = lerpf(w - 1.0f, (float)xs[1], t);
uv[1][1] = lerpf(0.0f, (float)ys[1], t);
uv[2][0] = lerpf(w - 1.0f, (float)xs[2], t);
uv[2][1] = lerpf(h - 1.0f, (float)ys[2], t);
uv[3][0] = lerpf(0.0f, (float)xs[3], t);
uv[3][1] = lerpf(h - 1.0f, (float)ys[3], t);
for (int i = 0; i < 4; i++) {
uv[i][0] /= (float)bg_w;
uv[i][1] /= (float)bg_h;
}
SDL_Vertex verts[4];
static int y_toggle = 0;
// Pass 1: Forward mapping layer
for (int i = 0; i < 4; i++) {
verts[i].position.x = (i == 1 || i == 2) ? w : 0;
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
verts[i].color = (SDL_Color){255, 255, 255, 255};
verts[i].tex_coord.x = uv[i][0];
verts[i].tex_coord.y = uv[i][1];
}
SDL_SetTextureBlendMode(scroll_bg, SDL_BLENDMODE_NONE);
render_quad(scroll_bg, verts);
// Pass 2: 180° rotated mapping layer
y_toggle = (y_toggle + 1) & 1;
for (int i = 0; i < 4; i++) {
verts[i].position.y = (i == 2 || i == 3) ? h : (float)y_toggle;
verts[i].color = (SDL_Color){255, 255, 255, 128};
}
// Swap mapping: TL<->BR, TR<->BL for 180° rotation effect
verts[0].tex_coord.x = uv[2][0]; verts[0].tex_coord.y = uv[2][1];
verts[1].tex_coord.x = uv[3][0]; verts[1].tex_coord.y = uv[3][1];
verts[2].tex_coord.x = uv[0][0]; verts[2].tex_coord.y = uv[0][1];
verts[3].tex_coord.x = uv[1][0]; verts[3].tex_coord.y = uv[1][1];
SDL_SetTextureBlendMode(scroll_bg, SDL_BLENDMODE_BLEND);
render_quad(scroll_bg, verts);
}
static void render_scene0(int elapsed) {
SDL_Texture *scroll_bg = dd.images[79];
SDL_Texture *sprite = dd.images[77];
if (!sprite) return;
int spr_w, spr_h;
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
const int slide_end = 4700;
const int duration = scene_duration[0]; // 5400
// --- Background: DrawAffineSurfaceAlpha50 x2 (alpha=50%) ---
// Source coordinates are linear-interpolated between identity and a transformed state.
// Two layers (identity and 180° rotated) are blended together.
render_affine_bg(scroll_bg, elapsed, duration, 40.0f, 0.55f, -20, 100);
// --- Sprite and UI elements ---
// 2. Sprite (ALK 77) with 3-echo horizontal slide-in.
// Each copy starts wider/further left and converges to the final position.
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
int dest_y = (view_h - spr_h) / 2;
if (elapsed < slide_end) {
SDL_SetTextureAlphaMod(sprite, 0x80);
const int a0_vals[] = {640, 480, 320};
for (int i = 0; i < 3; i++) {
int a0 = a0_vals[i];
int dest_w = a0 - ((a0 - spr_w) * elapsed) / slide_end;
if (dest_w <= 0) continue;
SDL_Rect dst = {view_w - 2 * dest_w, dest_y, dest_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
} else {
SDL_SetTextureAlphaMod(sprite, 0xd0);
SDL_Rect dst = {view_w - 2 * spr_w, dest_y, spr_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
// 3. Horizontal accent line (grows rightward over 4700ms)
int line_w = elapsed < slide_end ? 639 * elapsed / slide_end + 1 : 640;
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(gfx_renderer, 0xc0, 0xc0, 0xc0, 0xff);
SDL_Rect hline = {0, (spr_h + view_h) / 2 - 4, line_w, 2};
SDL_RenderFillRect(gfx_renderer, &hline);
// 4. Vertical accent line (right edge of sprite, grows downward)
int line_h = elapsed < slide_end ? 479 * elapsed / slide_end + 1 : 480;
SDL_Rect vline = {view_w - spr_w, 0, 2, line_h};
SDL_RenderFillRect(gfx_renderer, &vline);
}
static void render_scene1(int elapsed) {
SDL_Texture *scroll_bg = dd.images[78];
SDL_Texture *sprite = dd.images[76];
if (!sprite) return;
int spr_w, spr_h;
SDL_QueryTexture(sprite, NULL, NULL, &spr_w, &spr_h);
const int duration = scene_duration[1]; // 4752
// --- Background: DrawAffineSurfaceAlpha50 x2 (alpha=50%) ---
render_affine_bg(scroll_bg, elapsed, duration, -45.0f, 0.1f, 0, 200);
// Foreground (ALK 76): centered, fades in over first 3000ms.
// graph_BlendAlphaMapColorAlpha (alpha 0->255) for elapsed < 3000,
// graph_BlendAlphaMapColor (full opacity) for elapsed >= 3000.
{
int alpha = elapsed < 3000 ? (elapsed * 255) / 3000 : 255;
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(sprite, (Uint8)alpha);
SDL_Rect dst = {(view_w - spr_w) / 2, (view_h - spr_h) / 2, spr_w, spr_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
// Fade to black: graph_FillAlphaColor(0,0,0), starts at 4000ms.
if (elapsed > 4000) {
int fade_alpha = ((elapsed - 4000) * 255) / (duration - 4000);
fade_overlay(0, 0, 0, (Uint8)SDL_min(fade_alpha, 255));
}
}
// 3D rendering for render_scene2_4_6.
//
// Background quad (model space, XY plane):
// v0=(-1, -0.73446, 0) v1=(+1, -0.73446, 0)
// v2=(+1, +0.73446, 0) v3=(-1, +0.73446, 0)
// Object border quad (slightly oversized):
// v0=(-1.00885, -0.74655, 0) ... v3=(-1.00885, +0.74655, 0)
//
// Animation phases (phaseDuration = (sceneDuration - 1000) / 8):
// Phase 0 [0..1pd): approach from Z=-9 to Z=-3.75, Y-rot -90°→0°, tex1
// Phase 1 [1..3pd): static at Z=-3.75, rot=0°, tex1
// Phase 2 [3..4pd): recede from Z=-3.75 to Z=-9, Y-rot 0°→90°, tex1
// Phase 3 [4..5pd): approach from Z=-9 to Z=-3.75, Y-rot -90°→0°, tex2
// Phase 4 [5..7pd): static at Z=-3.75, rot=0°, tex2
// Phase 5 [7..8pd): recede from Z=-3.75 to Z=-9, Y-rot 0°→90°, tex2
//
// All phases also have an X-swing: tx = sin(π*t) * swing_amp
// approach swing_amp = -1.5, recede = +1.5
//
// Projection: same parameters as render_scene12_3d.
static void render_scene_2_4_6_3d(int elapsed, int phase_dur, SDL_Texture *tex1, SDL_Texture *tex2) {
static const float bg_mv[4][3] = {
{-1.00000f, -0.73446f, 0.0f},
{+1.00000f, -0.73446f, 0.0f},
{+1.00000f, +0.73446f, 0.0f},
{-1.00000f, +0.73446f, 0.0f},
};
static const float obj_mv[4][3] = {
{-1.00885f, -0.74655f, 0.0f},
{+1.00885f, -0.74655f, 0.0f},
{+1.00885f, +0.74655f, 0.0f},
{-1.00885f, +0.74655f, 0.0f},
};
static const float muv[4][2] = {
{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f},
};
float tx, tz, rot_y_deg;
SDL_Texture *tex;
if (elapsed < phase_dur) {
// Phase 0: approach with X-swing, tex1
float t = (float)elapsed / (float)phase_dur;
tx = sinf(t * (float)M_PI) * (-1.5f);
tz = lerpf(-9.0f, -3.75f, t);
rot_y_deg = lerpf(-90.0f, 0.0f, t);
tex = tex1;
} else if (elapsed < phase_dur * 3) {
// Phase 1: static, tex1
tx = 0.0f; tz = -3.75f; rot_y_deg = 0.0f;
tex = tex1;
} else if (elapsed < phase_dur * 4) {
// Phase 2: recede with X-swing, tex1
float t = (float)(elapsed - phase_dur * 3) / (float)phase_dur;
tx = sinf(t * (float)M_PI) * 1.5f;
tz = lerpf(-3.75f, -9.0f, t);
rot_y_deg = lerpf(0.0f, 90.0f, t);
tex = tex1;
} else if (elapsed < phase_dur * 5) {
// Phase 3: approach with X-swing, tex2
float t = (float)(elapsed - phase_dur * 4) / (float)phase_dur;
tx = sinf(t * (float)M_PI) * (-1.5f);
tz = lerpf(-9.0f, -3.75f, t);
rot_y_deg = lerpf(-90.0f, 0.0f, t);
tex = tex2;
} else if (elapsed < phase_dur * 7) {
// Phase 4: static, tex2
tx = 0.0f; tz = -3.75f; rot_y_deg = 0.0f;
tex = tex2;
} else if (elapsed < phase_dur * 8) {
// Phase 5: recede with X-swing, tex2
float t = (float)(elapsed - phase_dur * 7) / (float)phase_dur;
tx = sinf(t * (float)M_PI) * 1.5f;
tz = lerpf(-3.75f, -9.0f, t);
rot_y_deg = lerpf(0.0f, 90.0f, t);
tex = tex2;
} else {
return;
}
if (!tex) return;
float angle_rad = rot_y_deg * (float)M_PI / 180.0f;
float cos_a = cosf(angle_rad);
float sin_a = sinf(angle_rad);
const float half_w = (float)view_w * 0.5f;
const float half_h = (float)view_h * 0.5f;
SDL_Vertex bg_verts[4], obj_verts[4];
for (int i = 0; i < 4; i++) {
for (int pass = 0; pass < 2; pass++) {
const float (*mv)[3] = (pass == 0) ? bg_mv : obj_mv;
SDL_Vertex *v = (pass == 0) ? &bg_verts[i] : &obj_verts[i];
float rx = cos_a * mv[i][0] + sin_a * mv[i][2];
float ry = mv[i][1];
float rz = -sin_a * mv[i][0] + cos_a * mv[i][2];
float wx = rx + tx;
float wy = ry;
float wz = rz + tz;
float w = -wz * PROJ_W_DENOM;
if (w <= 0.0f) w = 1e-5f;
v->position.x = (wx * PROJ_SCALE_X / w + 1.0f) * half_w;
v->position.y = (wy * PROJ_SCALE_Y / w + 1.0f) * half_h;
v->tex_coord.x = muv[i][0];
v->tex_coord.y = muv[i][1];
}
}
// Object border quad: solid color RGB(244,221,181) = BGR(0xb5,0xdd,0xf4),
// drawn first as a backdrop. The bg quad drawn on top leaves only the thin
// border ring visible.
for (int i = 0; i < 4; i++) obj_verts[i].color = (SDL_Color){0xf4, 0xdd, 0xb5, 0xff};
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
render_quad(NULL, obj_verts);
// Background quad: textured, opaque, drawn on top (covers center of border quad)
for (int i = 0; i < 4; i++) bg_verts[i].color = (SDL_Color){255, 255, 255, 255};
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
render_quad(tex, bg_verts);
}
// Scenes 2, 4, and 6 share the same structure:
// - Fixed background (ALK[9]) full-screen blit
// - 3D quad animation (see render_scene_2_4_6_3d above)
// - Sprite + shadow scroll linearly left-to-right over the full scene duration
//
// sprite_idx: ALK index of the scrolling sprite (shadow is at sprite_idx + 73)
// tex1_idx: ALK index of the 3D quad texture for phases 0-2
// tex2_idx: ALK index of the 3D quad texture for phases 3-5
static void render_scene_2_4_6(int elapsed, int scene_idx, int sprite_idx, int tex1_idx, int tex2_idx) {
const int duration = scene_duration[scene_idx];
SDL_Texture *bg = dd.images[9];
SDL_Texture *sprite = dd.images[sprite_idx];
SDL_Texture *sprite_shadow = dd.images[sprite_idx + 73];
// 1. Background: full-screen blit, no blending
if (bg) {
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
SDL_SetTextureColorMod(bg, 0xff, 0xff, 0xff);
SDL_RenderCopy(gfx_renderer, bg, NULL, NULL);
}
// 2. 3D rotating quad animation (8-phase timing)
{
int phase_dur = (duration - 1000) / 8;
SDL_Texture *tex1 = dd.images[tex1_idx];
SDL_Texture *tex2 = dd.images[tex2_idx];
render_scene_2_4_6_3d(elapsed, phase_dur, tex1, tex2);
}
// 3. Scrolling sprite: x moves from -sprite_w (off-screen left) to view_w (off-screen right)
int sprite_w = 0, sprite_h = 0;
if (sprite)
SDL_QueryTexture(sprite, NULL, NULL, &sprite_w, &sprite_h);
int x = sprite_w ? ((sprite_w + view_w) * elapsed) / duration - sprite_w : 0;
// Shadow drawn first (color-modded to black), sprite on top (full color)
if (sprite_shadow) {
int sw, sh;
SDL_QueryTexture(sprite_shadow, NULL, NULL, &sw, &sh);
SDL_SetTextureBlendMode(sprite_shadow, SDL_BLENDMODE_BLEND);
SDL_SetTextureColorMod(sprite_shadow, 0, 0, 0);
SDL_Rect dst = {x, 0, sw, sh};
SDL_RenderCopy(gfx_renderer, sprite_shadow, NULL, &dst);
}
if (sprite) {
SDL_SetTextureBlendMode(sprite, SDL_BLENDMODE_BLEND);
SDL_SetTextureColorMod(sprite, 0xff, 0xff, 0xff);
SDL_Rect dst = {x, 0, sprite_w, sprite_h};
SDL_RenderCopy(gfx_renderer, sprite, NULL, &dst);
}
}
// Scenes 3, 5, 7, and 9 share the same structure:
// - Main image (image_idx) fades in and out over 4 seconds
// - ALK[9] overlay drawn at 50% alpha with an animated quad-warp throughout
//
// Timing of main image:
// 0250ms: black (no image)
// 2501000ms: fade in (alpha 0→255)
// 10003000ms: full opacity
// 30004000ms: fade out (alpha 255→0)
//
// Overlay quad-warp: source corners rotate clockwise along image edges over the full duration.
// At t=0: source = full image (no warp).
// At t=1: each corner has shifted 1/3 of its edge length, creating a rotation-like distortion.
static void render_scene_3_5_7_9(int elapsed, int image_idx) {
SDL_Texture *main_img = dd.images[image_idx];
SDL_Texture *overlay = dd.images[9];
if (main_img) {
int alpha;
if (elapsed < 250)
alpha = 0;
else if (elapsed < 1000)
alpha = ((elapsed - 250) * 255) / 750;
else if (elapsed < 3000)
alpha = 255;
else
alpha = ((4000 - elapsed) * 255) / 1000;
alpha = SDL_max(0, SDL_min(255, alpha));
if (alpha > 0) {
SDL_SetTextureBlendMode(main_img, alpha < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(main_img, (Uint8)alpha);
SDL_RenderCopy(gfx_renderer, main_img, NULL, NULL);
}
}
// Overlay: 50% alpha blend with animated quad-warp.
// UV corner positions (normalized): corners shift by t/3 along each edge.
if (overlay) {
float t3 = (float)elapsed / (4000.0f * 3.0f); // 0 → 1/3
float w = (float)view_w, h = (float)view_h;
SDL_Vertex verts[4] = {
/* TL */ {{0.f, 0.f}, {255, 255, 255, 128}, {t3, 0.f }},
/* TR */ {{w, 0.f}, {255, 255, 255, 128}, {1.0f, t3 }},
/* BR */ {{w, h }, {255, 255, 255, 128}, {1.0f - t3, 1.0f }},
/* BL */ {{0.f, h }, {255, 255, 255, 128}, {0.f, 1.0f-t3}},
};
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
render_quad(overlay, verts);
}
}
// Scene 8 is a slideshow of 6 images (ALK 8085) with crossfade transitions.
// Total duration (scene_duration[8] = 6402ms) is divided into 7 equal segments:
// Segment 1: fade in image[0] (black → full)
// Segments 26: crossfade image[i-1] → image[i]
// Segment 7: hold image[5] at full opacity
static void render_scene8(int elapsed) {
const int duration = scene_duration[8];
const int seg = duration / 7;
SDL_Texture **imgs = dd.images + 80; // ALK[80..85]
if (elapsed < seg) {
// Segment 1: fade in image[0] from black
int alpha = seg > 0 ? (elapsed * 255) / seg : 255;
if (imgs[0]) {
SDL_SetTextureBlendMode(imgs[0], SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(imgs[0], (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, imgs[0], NULL, NULL);
}
} else if (elapsed < 6 * seg) {
// Segments 26: crossfade image[idx] → image[idx+1]
int idx = elapsed / seg - 1; // 0..4
int alpha = ((elapsed - (idx + 1) * seg) * 255) / seg;
if (imgs[idx]) {
SDL_SetTextureBlendMode(imgs[idx], SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(imgs[idx], 255);
SDL_RenderCopy(gfx_renderer, imgs[idx], NULL, NULL);
}
if (imgs[idx + 1]) {
SDL_SetTextureBlendMode(imgs[idx + 1], SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(imgs[idx + 1], (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, imgs[idx + 1], NULL, NULL);
}
} else {
// Segment 7: hold image[5] at full opacity
if (imgs[5]) {
SDL_SetTextureBlendMode(imgs[5], SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(imgs[5], 255);
SDL_RenderCopy(gfx_renderer, imgs[5], NULL, NULL);
}
}
}
// Scene 10 is a 10.37s animation with 3 phases:
// images[18..31]: surfaces1, 14 animation frames (type A)
// images[32..59]: surfaces2, 28 animation frames (type B/C pairs per slot)
// images[10]: surface3, static full-screen image for zoom/fade
//
// Phase 1 (06453ms): slideshow of 14 frames × 461ms each.
// Within each frame, 3 sub-images are shown in sequence:
// 0152ms: images[18+frame] (surfaces1[frame])
// 153306ms: images[32+frame*2] (surfaces2[frame*2])
// 307460ms: images[32+frame*2+1] (surfaces2[frame*2+1])
//
// Phase 2 (64549609ms): ZoomBlend images[10] zooms in from a tiny
// bottom-centered region (16 × view_h/4) to full screen with pow(4) ease-in.
//
// Phase 3 (961010370ms): images[10] fades out to black (alpha 255→0).
static void render_scene10(int elapsed) {
const int duration = scene_duration[10];
SDL_Texture *surface3 = dd.images[10];
if (elapsed < 6454) {
// Phase 1: frame animation
int frame = elapsed / 461;
int phase = elapsed % 461;
int idx;
if (phase < 153)
idx = 18 + frame;
else if (phase < 307)
idx = 32 + frame * 2;
else
idx = 32 + frame * 2 + 1;
SDL_Texture *tex = dd.images[idx];
if (tex) {
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(tex, 255);
SDL_SetTextureColorMod(tex, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < 9610) {
// Phase 2: zoom in
int h4 = view_h / 4;
float progress = 1.0f - (float)(elapsed - 6454) / (float)(9610 - 6454);
float ease = powf(progress, 4.0f);
float src_w = lerpf((float)view_w, 16.0f, ease);
float src_h = lerpf((float)view_h, (float)h4, ease);
SDL_Rect src_rect = {
(int)((view_w - src_w) * 0.5f),
(int)(view_h - src_h),
(int)src_w,
(int)src_h,
};
if (surface3) {
SDL_SetTextureBlendMode(surface3, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(surface3, 255);
SDL_SetTextureColorMod(surface3, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, surface3, &src_rect, NULL);
}
} else {
// Phase 3: fade out to black
int blend_alpha = (elapsed - 9610) * 255 / (duration - 9610);
int combined = 255 - SDL_min(blend_alpha, 255);
if (surface3) {
SDL_SetTextureBlendMode(surface3,
combined < 255 ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE);
SDL_SetTextureAlphaMod(surface3, (Uint8)combined);
SDL_SetTextureColorMod(surface3, 255, 255, 255);
SDL_RenderCopy(gfx_renderer, surface3, NULL, NULL);
}
}
}
// Scene 11 is a complex layered animation with 8 segments over 31.6s:
// Background (ALK 60) scrolls upward over the full scene duration.
// The scene duration (31613ms) is divided into 8 equal segments (~3951ms each):
// Segment 0: background only
// Segments 1, 3, 5: one of 3 images (ALK 63, 62, 61) slides top-to-bottom at 50% alpha
// Segments 2, 4, 6: one of 3 overlay images (ALK 69, 70, 71) pingpong-fades in/out
// Segment 7: white fade out
static void render_scene11(int elapsed) {
const int duration = scene_duration[11]; // 31613
const int unit = duration / 8; // ~3951
SDL_Texture *bg = dd.images[60];
// Slide images (alpha maps, drawn at 50% alpha, pass top-to-bottom)
SDL_Texture **slide = dd.images + 61; // ALK[61..63]
// Overlay images (alpha maps, pingpong blend)
SDL_Texture **overlay = dd.images + 69; // ALK[69..71]
// Background: scrolls from bottom to top of source image over full duration.
// srcY = (bg_h - view_h) * (duration - elapsed) / duration
if (bg) {
int bg_w, bg_h;
SDL_QueryTexture(bg, NULL, NULL, &bg_w, &bg_h);
int scroll = (int)((long long)(bg_h - view_h) * elapsed / duration);
int src_y = SDL_max(0, (bg_h - view_h) - scroll);
SDL_Rect src = {0, src_y, SDL_min(view_w, bg_w), view_h};
SDL_SetTextureBlendMode(bg, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg, &src, NULL);
}
if (elapsed < unit) {
// Segment 0: background only, nothing extra
} else if (elapsed < unit * 2) {
// Segment 1: slide[2] (ALK 63) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[2];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 3) {
// Segment 2: overlay[0] (ALK 69) pingpong fade
SDL_Texture *tex = overlay[0];
if (tex) {
int t2 = elapsed % unit;
if (t2 >= unit / 2) t2 = unit - t2;
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < unit * 4) {
// Segment 3: slide[1] (ALK 62) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[1];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit * 3) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 5) {
// Segment 4: overlay[1] (ALK 70) pingpong fade
SDL_Texture *tex = overlay[1];
if (tex) {
int t2 = elapsed % unit;
if (t2 >= unit / 2) t2 = unit - t2;
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else if (elapsed < unit * 6) {
// Segment 5: slide[0] (ALK 61) passes top-to-bottom at 50% alpha
SDL_Texture *tex = slide[0];
if (tex) {
int tw, th;
SDL_QueryTexture(tex, NULL, NULL, &tw, &th);
int y = (int)((long long)(th + view_h) * (elapsed - unit * 5) / unit) - th;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, 0x80);
SDL_Rect dst = {(view_w - tw) / 2, y, tw, th};
SDL_RenderCopy(gfx_renderer, tex, NULL, &dst);
}
} else if (elapsed < unit * 7) {
// Segment 6: overlay[2] (ALK 71) pingpong fade
SDL_Texture *tex = overlay[2];
if (tex) {
int t2 = elapsed % unit;
if (t2 >= unit / 2) t2 = unit - t2;
int alpha = (t2 * 255) / (unit / 2);
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(tex, (Uint8)SDL_min(alpha, 255));
SDL_RenderCopy(gfx_renderer, tex, NULL, NULL);
}
} else {
// Segment 7: white fade out
int fade_alpha = (duration > unit * 7)
? ((elapsed - unit * 7) * 255) / (duration - unit * 7)
: 255;
fade_overlay(0xff, 0xff, 0xff, (Uint8)SDL_min(fade_alpha, 255));
}
}
// Scene 12 is a complex 10.37s animation with multiple layers and a 3D spinning quad:
// ALK[64]: background image A
// ALK[65]: background image B
// ALK[66]: overlay image
// ALK[67]: 3D quad texture
//
// Timeline (relative to scene start):
// 02076ms: fade in bg_a from white (white overlay 255→0)
// 20762226ms: bg_a static
// 22263826ms: crossfade bg_a → bg_b
// 38263976ms: bg_b static
// 39767803ms: bg_b wave distortion (TODO) + bg_b at α=0xB0 + fade to white
// 78039397ms: white bg + 3D rotating quad + overlay fade in (0→255)
// 939710338ms: overlay static
//
// 3D rendering (78039397ms):
// A rhombus-shaped textured quad in the XY plane at z=-2.4 (world space),
// spinning around the Y axis (2 full rotations over 1594ms).
// Projection: vertical FoV=60°, aspect=4:3, near=0.25, far=2000.
// Vertex positions (model space):
// v0=(0, -0.51524, 0) UV:(0,0)
// v1=(0.28806, 0.22720, 0) UV:(1,0)
// v2=(0, 0.51524, 0) UV:(1,1)
// v3=(-0.28806, -0.22720, 0) UV:(0,1)
static void render_scene12_3d(int elapsed) {
SDL_Texture *tex = dd.images[67];
if (!tex) return;
// Model-space vertices (x, y, z) forming a rhombus in the XY plane.
static const float mv[4][3] = {
{ 0.00000f, -0.51524f, 0.0f }, // v0
{ 0.28806f, 0.22720f, 0.0f }, // v1
{ 0.00000f, 0.51524f, 0.0f }, // v2
{ -0.28806f, -0.22720f, 0.0f }, // v3
};
// Normalized texture UV per vertex
static const float muv[4][2] = {
{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f},
};
// Y-rotation: 2 full rotations over the 1594ms 3D phase.
float t = (float)(elapsed - 7803);
float angle = t * (4.0f * (float)M_PI / 1594.0f);
float cos_a = cosf(angle);
float sin_a = sinf(angle);
// Perspective projection parameters: see PROJ_SCALE_X/Y, PROJ_W_DENOM
const float half_w = (float)view_w * 0.5f;
const float half_h = (float)view_h * 0.5f;
SDL_Vertex verts[4];
for (int i = 0; i < 4; i++) {
// World transform: Y-rotate first, then translate(+0.01, 0, -2.4).
float rx = cos_a * mv[i][0] + sin_a * mv[i][2];
float ry = mv[i][1];
float rz = -sin_a * mv[i][0] + cos_a * mv[i][2];
float wx = rx + 0.01f;
float wy = ry;
float wz = rz - 2.4f;
// Perspective divide: w = -wz * PROJ_W_DENOM (wz is negative → w positive)
float w = -wz * PROJ_W_DENOM;
if (w <= 0.0f) w = 1e-5f;
// NDC → screen
verts[i].position.x = (wx * PROJ_SCALE_X / w + 1.0f) * half_w;
verts[i].position.y = (wy * PROJ_SCALE_Y / w + 1.0f) * half_h;
verts[i].color = (SDL_Color){255, 255, 255, 255};
verts[i].tex_coord.x = muv[i][0];
verts[i].tex_coord.y = muv[i][1];
}
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
render_quad(tex, verts);
}
static void render_scene12(int elapsed) {
SDL_Texture *bg_a = dd.images[64];
SDL_Texture *bg_b = dd.images[65];
SDL_Texture *overlay = dd.images[66];
if (elapsed < 2076) {
// 02076ms: bg_a with white fade-in overlay
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
}
int white_alpha = 0xff - (elapsed * 0xff) / 2076;
fade_overlay(0xff, 0xff, 0xff, (Uint8)white_alpha);
} else if (elapsed < 2226) {
// 20762226ms: bg_a static
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
}
} else if (elapsed < 3826) {
// 22263826ms: crossfade bg_a → bg_b
if (bg_a) {
SDL_SetTextureBlendMode(bg_a, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_a, NULL, NULL);
}
if (bg_b) {
int alpha = ((elapsed - 2226) * 0xff) / 1600;
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(bg_b, (Uint8)SDL_min(alpha, 0xff));
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
}
} else if (elapsed < 3976) {
// 38263976ms: bg_b static
if (bg_b) {
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
}
} else if (elapsed < 7803) {
// 39767803ms: TODO wave distortion on bg_b; for now just show bg_b,
// then fade to white
if (bg_b) {
SDL_SetTextureBlendMode(bg_b, SDL_BLENDMODE_BLEND);
SDL_RenderCopy(gfx_renderer, bg_b, NULL, NULL);
}
int white_alpha = ((elapsed - 3976) * 0xff) / 3827;
fade_overlay(0xff, 0xff, 0xff, (Uint8)SDL_min(white_alpha, 0xff));
} else if (elapsed < 9397) {
// 78039397ms: white bg + 3D rotating quad + overlay fade-in
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderDrawColor(gfx_renderer, 0xff, 0xff, 0xff, 0xff);
SDL_RenderFillRect(gfx_renderer, NULL);
render_scene12_3d(elapsed);
if (overlay) {
int alpha = ((elapsed - 7803) * 0xff) / 1594;
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(overlay, (Uint8)SDL_min(alpha, 0xff));
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
}
} else {
// 939710338ms: overlay static
if (overlay) {
SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_NONE);
SDL_RenderCopy(gfx_renderer, overlay, NULL, NULL);
}
}
}
static void Run() {
TRACE("dDemo.Run:");
muscd_start(13, 1);
uint32_t start = sys_get_ticks();
int current_scene = 0;
while (!nact->is_quit && !(sys_getInputInfo() && dd.key_cancel)) {
uint32_t now = sys_get_ticks();
if (current_scene < NR_SCENES && now - start >= (uint32_t)scene_duration[current_scene]) {
current_scene++;
if (current_scene < NR_SCENES) {
start = now;
} else if (dd.loop) {
muscd_stop();
current_scene = 0;
start = now;
muscd_start(13, 1);
}
}
if (current_scene >= NR_SCENES) {
break;
}
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, 255);
SDL_RenderClear(gfx_renderer);
switch (current_scene) {
case 0:
render_scene0(now - start);
break;
case 1:
render_scene1(now - start);
break;
case 2:
render_scene_2_4_6(now - start, 2, 0, 3, 4);
break;
case 3:
render_scene_3_5_7_9(now - start, 11);
break;
case 4:
render_scene_2_4_6(now - start, 4, 1, 5, 6);
break;
case 5:
render_scene_3_5_7_9(now - start, 12);
break;
case 6:
render_scene_2_4_6(now - start, 6, 2, 7, 8);
break;
case 7:
render_scene_3_5_7_9(now - start, 13);
break;
case 8:
render_scene8(now - start);
break;
case 9:
render_scene_3_5_7_9(now - start, 14);
break;
case 10:
render_scene10(now - start);
break;
case 11:
render_scene11(now - start);
break;
case 12:
render_scene12(now - start);
break;
}
SDL_RenderPresent(gfx_renderer);
sys_wait_vsync();
}
muscd_stop();
for (int i = 0; i < NR_TEXTURES; i++) {
if (dd.images[i])
SDL_DestroyTexture(dd.images[i]);
dd.images[i] = NULL;
}
}
static const ModuleFunc functions[] = {
{"Init", Init},
{"Run", Run},
{"SetKeyCancelFlag", SetKeyCancelFlag},
{"SetLoopFlag", SetLoopFlag},
};
const Module module_dDemo = {"dDemo", functions, sizeof(functions) / sizeof(ModuleFunc)};