starfields

master
ugrnm 5 years ago
parent 2241a36aed
commit 6b57938794

@ -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.h>
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();
}

@ -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.h>
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();
}

@ -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.h>
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();
}
Loading…
Cancel
Save