From 6b57938794ea2900a05da00bf7b51053a8b0f125 Mon Sep 17 00:00:00 2001 From: ugrnm Date: Mon, 28 Oct 2019 22:43:07 +0100 Subject: [PATCH] starfields --- basics/starfield25/starfield25.ino | 137 +++++++++++++++++++ basics/starfield51+pots/starfield51+pots.ino | 107 +++++++++++++++ basics/starfield51/starfield51.ino | 112 +++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 basics/starfield25/starfield25.ino create mode 100644 basics/starfield51+pots/starfield51+pots.ino create mode 100644 basics/starfield51/starfield51.ino diff --git a/basics/starfield25/starfield25.ino b/basics/starfield25/starfield25.ino new file mode 100644 index 0000000..155f3ad --- /dev/null +++ b/basics/starfield25/starfield25.ino @@ -0,0 +1,137 @@ +/* + * good old starfield + * + * The way this one works is by first filling the screen with pixels at + * random coordinates. Then at each frame we calculate the new position of + * each pixel as if it was going away from the center of the screen. When a + * pixel is going off screen, it is reassigned at a new random position. + * To simulate a bit of depth, some pixels move twice as fast. + * + * Limitations: all the pixels/stars coordinates are stored in float arrays. + * Each float is 4 bytes in size (32 bits) and the Nano has 2 KB of SRAM. + * For each pixel/star we need to store 4 floats: x, y, vx, vy. That's + * 16 bytes per pixel/star, so we should be able to store quite few. + * However, the SRAM is already used by other parts of the code, and TVout. + * In practice with the current implementation, there's just enough free + * memory left for 25 pixels/stars :( + * + * See starfield51 for a modification that allows to have 51 stars :) + */ + +#include + +TVout TV; + +#define STARS 25 + +float x[STARS]; // x coordinates of all the stars +float y[STARS]; // y coordinates of all the stars +float vx[STARS]; // vx and vy are the increments that we add to x +float vy[STARS]; // and y at each frame to get the x,y position + +/* + * new_star(i) generates the x, y, vx, and vy values for star i + * x is generated at random x-axis position around the centre + * y is generated at random y-axis position around the centre + * we calculate the distances dx and dy from the screen center ox,oy + * (we cheat a bit to make sure no 0 are left in a division later on) + * we use dx and dy to calculte the distance vl between the star + * and the center, ie the magnitude of the vector (center, star) + * we use the magnitude to normalize the vector, namely calculating + * the normalized components vx and vy + */ +void +new_star(uint8_t i) +{ + uint8_t ox = 60; + uint8_t oy = 48; + float dx, dy, vl; + + x[i] = 30 + random(60); + y[i] = 24 + random(48); + + dx = x[i] - ox; + dy = y[i] - oy; + + switch(int(dx)) + case 0: dx = 1; // CHEAT + + vl = sqrt(dx*dx + dy*dy); + vx[i] = dx / vl; + vy[i] = dy / vl; +} + +/* + * new_stars() creates as many stars as set by the STARS constant + */ +void +new_stars() +{ + for (uint8_t i = 0; i < STARS; i++) + new_star(i); +} + +/* + * Nothing exciting here, we initialize the PRNG with some noise, + * configure TV out, and generate coordinates for all stars. + */ +void +setup() +{ + randomSeed(analogRead(6)); // effective? + TV.begin(NTSC,120,96); + new_stars(); +} + +/* + * update_stars() goes through all the x and y values and update + * them in such a way that: + * new x = current x + vx + * new y = current y + vy + * we also test if the new coordinates of the star are still in the + * visible part of the screen, and if not, we call new_star() to + * generate new x, y, vx, vy all over again + * Note: fancy effect, the first 20 stars are updated with regular + * vx and vy values, while the last 5 are a updated with 2 times vx and vy, + * which means that these 5 last stars will move twice the speed. + */ +void +update_stars() +{ + for (uint8_t i = 0; i < 20; i++) { + x[i] = x[i] + vx[i]; + y[i] = y[i] + vy[i]; + if (x[i] > 120 || x[i] < 0 || y[i] < 0 || y[i] > 96) + new_star(i); + } + for (uint8_t i = 20; i < STARS; i++) { + x[i] = x[i] + vx[i] * 2; + y[i] = y[i] + vy[i] * 2; + if (x[i] > 120 || x[i] < 0 || y[i] < 0 || y[i] > 96) + new_star(i); + } +} + +/* + * draw_stars() walks through the x and y coordinates arrays and + * uses the value to draw a pixel for each star + */ +void +draw_stars() +{ + for (uint8_t i = 0; i < STARS; i++) + TV.set_pixel(x[i], y[i], 1); +} + +/* + * loop() is stuck doing the same stuff forever because it dared to trick + * the gods... + */ +void +loop() +{ + TV.delay_frame(1); + update_stars(); + TV.clear_screen(); + draw_stars(); +} diff --git a/basics/starfield51+pots/starfield51+pots.ino b/basics/starfield51+pots/starfield51+pots.ino new file mode 100644 index 0000000..85998e4 --- /dev/null +++ b/basics/starfield51+pots/starfield51+pots.ino @@ -0,0 +1,107 @@ +/* + * good old starfield - PASTIS 51 EDITION + CAPTAIN MOD + * + * Same as starfield51 but this time we can control the origin with POT 1 and + * POT2, which affects all the newly created stars, allowing for deep space + * travel. + * + * Really not optimised in current state, even though I tried to simplify things. + * But it starts to push the chip beyond its capacity, so it feels a bit sluggish now. + * More changes would start to make the thing even less readable so... + */ +#include + +TVout TV; + +#define STARS 51 +#define POT_1 0 +#define POT_2 1 +#define POT_1_MOD 11 // 120/1024 ish int(0.1171875 * 100) +#define POT_2_MOD 9 // 96/1024 ish +#define RND_1_MOD 5 // 60/1024 ish +#define RND_2_MOD 4 // 48/1024 ish + +int x[STARS]; +int y[STARS]; +int vx[STARS]; +int vy[STARS]; +uint8_t ox; +uint8_t oy; + +void +setup() +{ + randomSeed(analogRead(6)); // effective? + TV.begin(NTSC,120,96); + new_stars(); +} + +void +new_star(uint8_t i) +{ + uint8_t x_tmp, y_tmp; + int dx, dy, pot1, pot2; + float vl; + + pot1 = analogRead(POT_1); + pot2 = analogRead(POT_2); + + ox = pot1 * POT_1_MOD / 100 ; + oy = pot2 * POT_2_MOD / 100 ; + + x_tmp = pot1 * RND_1_MOD / 100 + random(60); + y_tmp = pot2 * RND_2_MOD / 100 + random(48); + + dx = x_tmp - ox; + dy = y_tmp - oy; + + switch(dx) + case 0: dx = 1; // CHEAT + + vl = 1 / sqrt(dx*dx + dy*dy); + vx[i] = int(dx * vl * 100); + vy[i] = int(dy * vl * 100); + + x[i] = x_tmp * 100; + y[i] = y_tmp * 100; +} + +void +new_stars() +{ + for (uint8_t i = 0; i < STARS; i++) + new_star(i); +} + +void +update_stars() +{ + for (uint8_t i = 0; i < 40; i++) { + x[i] = x[i] + vx[i]; + y[i] = y[i] + vy[i]; + if (x[i] > 12000 || x[i] < 0 || y[i] < 0 || y[i] > 9600) + new_star(i); + } + for (uint8_t i = 40; i < STARS; i++) { + x[i] = x[i] + (vx[i] << 1); + y[i] = y[i] + (vy[i] << 1); + if (x[i] > 12000 || x[i] < 0 || y[i] < 0 || y[i] > 9600) + new_star(i); + } +} + +void +draw_stars() +{ + for (uint8_t i = 0; i < STARS; i++) + TV.set_pixel(x[i]/100, y[i]/100, 1); +} + +void +loop() +{ + TV.delay_frame(1); + update_stars(); + TV.clear_screen(); + draw_stars(); +} diff --git a/basics/starfield51/starfield51.ino b/basics/starfield51/starfield51.ino new file mode 100644 index 0000000..8734320 --- /dev/null +++ b/basics/starfield51/starfield51.ino @@ -0,0 +1,112 @@ +/* + * good old starfield - PASTIS 51 EDITION + * + * See starfield25 for context and more comments, otherwise it may not make much + * sense... + * + * So basically the trick used to get more stars here, is not to free memory, + * because that's not possible, but instead it's to make the stars use less memory. + * To do that we change the type from float (4 bytes) to int (2 bytes). + * The tradeoff is of course that now we have integers and not floating point numbers, + * and that prevents us to have any meaningful results when we calculate vector related + * stuff. But there's a workaround to this! + * The workaround is very simple, we use int as floats without floating points, which + * means that everytime we read from the array we convert the int to float and divide + * it by an arbitrary precision value (here we use 100), and when we want to store a + * float we multiply it by the same precision value and convert to int. + * We loose in accuray, but that does not matter here at all. + * + * ex: we want to store 12,345678 + * 12,3456 * 100 = 1234,56 + * int(1234,56) = 1234 + * and converted back + * float(1234 / 100) = 12,34 + * in the process with this trick we lost 0,005678 (who cares?) + * + * Note: most of the changes are in new_star(). There are some minor changes elsewhere, + * mostly to try optimise things cheaply, ignore. + */ +#include + +TVout TV; + +#define STARS 51 + +int x[STARS]; +int y[STARS]; +int vx[STARS]; +int vy[STARS]; + +void +setup() +{ + randomSeed(analogRead(6)); // effective? + TV.begin(NTSC,120,96); + new_stars(); +} + +void +new_star(uint8_t i) +{ + uint8_t x_tmp, y_tmp; + uint8_t ox = 60; + uint8_t oy = 48; + int dx, dy; + float vl; + + x_tmp = 30 + random(60); + y_tmp = 24 + random(48); + + dx = x_tmp - ox; + dy = y_tmp - oy; + + switch(dx) + case 0: dx = 1; // CHEAT + + vl = 1 / sqrt(dx*dx + dy*dy); + vx[i] = int(dx * vl * 100); + vy[i] = int(dy * vl * 100); + + x[i] = x_tmp * 100; + y[i] = y_tmp * 100; +} + +void +new_stars() +{ + for (uint8_t i = 0; i < STARS; i++) + new_star(i); +} + +void +update_stars() +{ + for (uint8_t i = 0; i < 40; i++) { + x[i] = x[i] + vx[i]; + y[i] = y[i] + vy[i]; + if (x[i] > 12000 || x[i] < 0 || y[i] < 0 || y[i] > 9600) + new_star(i); + } + for (uint8_t i = 40; i < STARS; i++) { + x[i] = x[i] + (vx[i] << 1); + y[i] = y[i] + (vy[i] << 1); + if (x[i] > 12000 || x[i] < 0 || y[i] < 0 || y[i] > 9600) + new_star(i); + } +} + +void +draw_stars() +{ + for (uint8_t i = 0; i < STARS; i++) + TV.set_pixel(x[i]/100, y[i]/100, 1); +} + +void +loop() +{ + TV.delay_frame(1); + update_stars(); + TV.clear_screen(); + draw_stars(); +}