358 lines
9.8 KiB
C++
358 lines
9.8 KiB
C++
#include <azur/gint/render.h>
|
|
#include <gint/rtc.h>
|
|
#include <num/num.h>
|
|
#include <num/vec.h>
|
|
#include <random>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "settings.h"
|
|
#include "render.h"
|
|
#include "util.h"
|
|
using namespace libnum;
|
|
|
|
uint8_t BOSONX_SHADER_PTRACE = -1;
|
|
static azrp_shader_t bosonx_shader_ptrace;
|
|
|
|
__attribute__((constructor))
|
|
static void register_shader(void)
|
|
{
|
|
BOSONX_SHADER_PTRACE = azrp_register_shader(bosonx_shader_ptrace, NULL);
|
|
}
|
|
|
|
//---
|
|
|
|
struct command {
|
|
uint8_t shader_id;
|
|
uint8_t y;
|
|
int16_t x;
|
|
int16_t pty, height;
|
|
uint16_t bgcolor;
|
|
num t;
|
|
};
|
|
|
|
/* Nested circles */
|
|
#define CLIGHT_R1 10
|
|
#define CLIGHT_R2 15
|
|
#define CDARK_R1 36
|
|
#define CBEAMS1_R 41
|
|
#define CDARK_R2 45
|
|
#define CMAIN_R 48
|
|
#define CBEAMS2_R 52
|
|
#define CMAX_R 55
|
|
|
|
/* Ticks */
|
|
#define TICK_SIZE 7
|
|
#define TICK_COUNT 17 /* > 1 */
|
|
|
|
/* Traces */
|
|
#define TRACES_R CDARK_R1
|
|
#define TRACES_NUM_THREADS 24 /* < 255*/
|
|
#define TRACES_PALETTE_SIZE (TRACES_NUM_THREADS+1)
|
|
#define TRACES_TURN_MIN num(0.02)
|
|
#define TRACES_TURN_RATIO 5
|
|
#define TRACES_TURN_MAX (TRACES_TURN_RATIO * TRACES_TURN_MIN)
|
|
|
|
/* Precomputed and preallocated data */
|
|
|
|
/* Concentric circles pattern */
|
|
static uint8_t circles[CMAIN_R+1][CMAIN_R+1];
|
|
/* Trace image palette */
|
|
static uint16_t palette[TRACES_PALETTE_SIZE] = { 0 };
|
|
/* Trace turn vectors */
|
|
static vec2 trace_turns[TRACES_TURN_RATIO * TRACES_R];
|
|
/* Ticks tileset */
|
|
static image_t *img_ticks = NULL;
|
|
/* Trace image (regenerated every frame */
|
|
static image_t *img_trace = NULL;
|
|
/* Random seed for the entire animation */
|
|
static int seed = 0;
|
|
|
|
static void add_circle(int r, int value)
|
|
{
|
|
int x = -r;
|
|
int y = 0;
|
|
int err = 2 - 2 * r;
|
|
|
|
do {
|
|
for(int i = x + CMAIN_R; i <= CMAIN_R; i++)
|
|
circles[CMAIN_R - y][i] = value;
|
|
r = err;
|
|
if(r <= y)
|
|
err += ++y * 2 + 1;
|
|
if(r > x || err > y)
|
|
err += ++x * 2 + 1;
|
|
}
|
|
while(x <= 0);
|
|
}
|
|
|
|
__attribute__((constructor))
|
|
static void precompute_circles(void)
|
|
{
|
|
memset(circles, 0, sizeof circles);
|
|
add_circle(CMAIN_R, 1);
|
|
add_circle(CDARK_R2, 2);
|
|
add_circle(CDARK_R1, 1);
|
|
add_circle(CLIGHT_R2, 3);
|
|
add_circle(CLIGHT_R1, 1);
|
|
}
|
|
|
|
__attribute__((constructor))
|
|
static void precompute_ticks(void)
|
|
{
|
|
img_ticks = image_alloc(TICK_SIZE*TICK_COUNT, TICK_SIZE, IMAGE_P8_RGB565A);
|
|
if(!img_ticks)
|
|
return;
|
|
|
|
static uint16_t img_ticks_palette[2] = { 0x5555, C_WHITE };
|
|
image_set_palette(img_ticks, img_ticks_palette, 2, false);
|
|
|
|
num angle_step = num(1.57080) / (TICK_COUNT - 1);
|
|
|
|
for(int i = 0; i < TICK_COUNT; i++) {
|
|
num alpha = i * angle_step;
|
|
vec2 D(num_cos(alpha), num_sin(alpha));
|
|
|
|
/* num_cos / num_sin rounding... */
|
|
if(i == 0)
|
|
D = vec2(1, 0);
|
|
else if(i == TICK_COUNT - 1)
|
|
D = vec2(0, 1);
|
|
|
|
vec2 norm_D(-D.y, D.x);
|
|
|
|
/* Render the segment that goes through the center with slope alpha,
|
|
rendering with width 1 and length TICK_SIZE / 2 on both sides. */
|
|
|
|
for(int y = 0; y < TICK_SIZE; y++)
|
|
for(int x = 0; x < TICK_SIZE; x++) {
|
|
vec2 p(+num(x) - num(TICK_SIZE - 1) / 2,
|
|
-num(y) + num(TICK_SIZE - 1) / 2);
|
|
|
|
if(D.dot(p).abs() <= TICK_SIZE / 2 && norm_D.dot(p).abs() <= 1)
|
|
image_set_pixel(img_ticks, i * TICK_SIZE + x, y, 0x81);
|
|
else
|
|
image_set_pixel(img_ticks, i * TICK_SIZE + x, y, 0x80);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void render_tick(int cx, int cy, int radius, num angle)
|
|
{
|
|
num c = num_cos(angle);
|
|
num s = num_sin(angle);
|
|
|
|
bool flip = false;
|
|
|
|
/* Normalize to 0..π/2 */
|
|
if(angle < 0) {
|
|
angle = -angle;
|
|
flip = !flip;
|
|
}
|
|
angle = angle % num(3.14159);
|
|
if(angle > num(1.57080)) {
|
|
angle = num(3.14159) - angle;
|
|
flip = !flip;
|
|
}
|
|
|
|
int n = (angle * TICK_COUNT / num(1.57080)).ifloor();
|
|
if(n < 0)
|
|
n = 0;
|
|
if(n >= TICK_COUNT)
|
|
n = TICK_COUNT - 1;
|
|
azrp_subimage(
|
|
cx + int(c * radius) - TICK_SIZE / 2,
|
|
cy - int(s * radius) - TICK_SIZE / 2,
|
|
img_ticks, n * TICK_SIZE, 0, TICK_SIZE, TICK_SIZE,
|
|
flip ? IMAGE_HFLIP : 0);
|
|
}
|
|
|
|
static void bosonx_shader_ptrace(void *, void *cmd0, void *frag0)
|
|
{
|
|
struct command *cmd = (struct command *)cmd0;
|
|
uint16_t *frag = (uint16_t *)frag0;
|
|
frag += azrp_width * cmd->y + cmd->x;
|
|
|
|
int pty = cmd->pty;
|
|
int h = azrp_frag_height - cmd->y;
|
|
if(h > cmd->height)
|
|
h = cmd->height;
|
|
|
|
cmd->height -= h;
|
|
cmd->y = 0;
|
|
cmd->pty += h;
|
|
|
|
uint16_t palette[4] = { 0,
|
|
render_blend(cmd->bgcolor, C_BLACK, 10),
|
|
render_blend(cmd->bgcolor, C_BLACK, 16),
|
|
render_blend(cmd->bgcolor, C_BLACK, 4),
|
|
};
|
|
|
|
do {
|
|
int ly = pty - CMAX_R;
|
|
uint16_t *fragc = frag + CMAX_R;
|
|
|
|
if(ly >= -CMAIN_R && ly <= CMAIN_R) {
|
|
int data_y = (ly <= 0) ? ly + CMAIN_R : CMAIN_R - ly;
|
|
|
|
for(int lx = -CMAIN_R; lx <= 0; lx++) {
|
|
int8_t c = circles[data_y][lx + CMAIN_R];
|
|
if(c)
|
|
fragc[lx] = palette[c];
|
|
}
|
|
for(int lx = 1; lx <= CMAIN_R; lx++) {
|
|
int8_t c = circles[data_y][CMAIN_R - lx];
|
|
if(c)
|
|
fragc[lx] = palette[c];
|
|
}
|
|
}
|
|
|
|
frag += azrp_width;
|
|
pty++;
|
|
}
|
|
while(--h > 0);
|
|
}
|
|
|
|
/* Cannot use a constructor here because compiler schedules the global object
|
|
constructor (which sets everything to 0) to run after global constructors */
|
|
static void precompute_turn_vectors(void)
|
|
{
|
|
static bool computed = false;
|
|
if(computed)
|
|
return;
|
|
|
|
for(int i = 0; i < TRACES_TURN_RATIO * TRACES_R; i++) {
|
|
trace_turns[i].x = num_cos(i * TRACES_TURN_MIN);
|
|
trace_turns[i].y = num_sin(i * TRACES_TURN_MIN);
|
|
}
|
|
computed = true;
|
|
}
|
|
|
|
void generate_traces(uint16_t color, bool successful, num t)
|
|
{
|
|
int const N = 2 * TRACES_R;
|
|
precompute_turn_vectors();
|
|
|
|
if(!img_trace) {
|
|
img_trace = image_alloc(N, N, IMAGE_P8_RGB565A);
|
|
if(!img_trace)
|
|
return;
|
|
image_set_palette(img_trace, palette, TRACES_PALETTE_SIZE, false);
|
|
}
|
|
|
|
/* Clear out the entire image */
|
|
uint8_t *px = (uint8_t *)img_trace->data;
|
|
memset(px, 0x80, N * N);
|
|
|
|
/* Generate the palette */
|
|
int R5 = (color >> 11);
|
|
int G6 = (color >> 5) & 0x3f;
|
|
int B5 = (color & 0x1f);
|
|
|
|
for(int i = 1; i < TRACES_PALETTE_SIZE; i++) {
|
|
/* alpha=31: color, alpha=0: white */
|
|
int alpha = (i >= TRACES_NUM_THREADS / 4) ? 0 :
|
|
31 - (i * 31) / (TRACES_NUM_THREADS / 4);
|
|
int R = ((alpha * R5) + (31 - alpha) * 0x1f) / 31;
|
|
int G = ((alpha * G6) + (31 - alpha) * 0x3f) / 31;
|
|
int B = ((alpha * B5) + (31 - alpha) * 0x1f) / 31;
|
|
palette[i] = (R << 11) | (G << 5) | B;
|
|
}
|
|
|
|
std::minstd_rand r(seed);
|
|
std::minstd_rand r2(seed+1);
|
|
|
|
int steps = (t * TRACES_R / TIME_PTRACE_ANIMATION).ifloor();
|
|
if(steps > TRACES_R)
|
|
steps = TRACES_R;
|
|
|
|
for(int i = 0; i < TRACES_NUM_THREADS; i++) {
|
|
vec2 dir = num_rand_vec2_std(r);
|
|
int turn_factor = 1 + (r2() % 4);
|
|
|
|
for(int s = 0; s < steps; s++) {
|
|
vec2 straight = dir * s;
|
|
vec2 rotated = straight;
|
|
|
|
if(successful) {
|
|
vec2 const &turn = trace_turns[s * turn_factor];
|
|
rotated.x = straight.x * turn.x - straight.y * turn.y;
|
|
rotated.y = straight.x * turn.y + straight.y * turn.x;
|
|
}
|
|
|
|
int x = (N / 2 + rotated.x.ifloor());
|
|
int y = (N / 2 - rotated.y.ifloor());
|
|
|
|
/* "Increase" the color of the pixel. The palette is a gradient so
|
|
this will imitate an overlay effect */
|
|
if((unsigned)y < N) {
|
|
if((unsigned)x < N)
|
|
px[N * y + x]++;
|
|
if((unsigned)x+1 < N)
|
|
px[N * y + x+1]++;
|
|
}
|
|
if((unsigned)y+1 < N) {
|
|
if((unsigned)x < N)
|
|
px[N * (y+1) + x]++;
|
|
if((unsigned)x+1 < N)
|
|
px[N * (y+1) + x+1]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Make sure every value is within the palette range */
|
|
for(int i = 0; i < N * N; i++) {
|
|
if((uint8_t)(px[i] - 0x80) > TRACES_PALETTE_SIZE)
|
|
px[i] = 0x80 + TRACES_PALETTE_SIZE - 1;
|
|
}
|
|
}
|
|
|
|
void shader_ptrace_reset(void)
|
|
{
|
|
seed = rtc_ticks();
|
|
}
|
|
|
|
void shader_ptrace(int x, int y, uint16_t color, uint16_t bgcolor,
|
|
bool successful, num t)
|
|
{
|
|
prof_enter(azrp_perf_cmdgen);
|
|
|
|
t = num_clamp(t, 0, TIME_PTRACE_ANIMATION);
|
|
|
|
int frag_first, frag_offset, frag_count;
|
|
int height = 2 * CMAX_R + 1;
|
|
azrp_config_get_lines(y, height, &frag_first, &frag_offset, &frag_count);
|
|
|
|
struct command *cmd = (struct command *)
|
|
azrp_new_command(sizeof *cmd, frag_first, frag_count);
|
|
if(cmd) {
|
|
cmd->shader_id = BOSONX_SHADER_PTRACE;
|
|
cmd->y = frag_offset;
|
|
cmd->x = x;
|
|
cmd->pty = 0;
|
|
cmd->height = height;
|
|
cmd->bgcolor = bgcolor;
|
|
cmd->t = t;
|
|
}
|
|
|
|
prof_leave(azrp_perf_cmdgen);
|
|
|
|
/* Start with 4/8 ticks and go up to 12/20 if un/successful */
|
|
int N1 = successful ? 8 : 4, N2 = successful ? 20 : 12;
|
|
int N = N1 +(N2 * t / TIME_PTRACE_ANIMATION).ifloor();
|
|
|
|
std::minstd_rand r(seed);
|
|
for(int i = 0; i < N; i++) {
|
|
num speed = num_rand_between_std(r, -1, 1);
|
|
num orig = num_rand_between_std(r, 0, num(6.28));
|
|
render_tick(x + CMAX_R, y + CMAX_R, CBEAMS1_R, orig + t * speed);
|
|
render_tick(x + CMAX_R, y + CMAX_R, CBEAMS2_R, -orig - t * speed);
|
|
}
|
|
|
|
generate_traces(color, successful, t);
|
|
if(img_trace) {
|
|
azrp_image(x + CMAX_R - img_trace->width / 2,
|
|
y + CMAX_R - img_trace->height / 2,
|
|
img_trace);
|
|
}
|
|
}
|