Once you see the source, you'll understand why I felt the need to explain the algorithm. My favorite line in the code is:
// kill time for early frames which draw too fast
wait_ms(4*(MAX_LINES - n_lines));
When I was initially writing the code, I wasn't thinking animation at all - I figured it would be at least a few seconds to draw one image. As it turned out, I had to add delays to the code to get it the way I wanted. On a badge. Crazy!
Here's the full source code. Download Here and place in the src directory. It replaces "user_program.c" in the project - all the other functionality of the badge remains the same, and you access the demo through menu item 7 "User Program".
/************************************
* This is the framework for those
* who wish to write their own C
* code for the basic badge
*
* Take a look at user_program_temp.c (not included in project, but
* available in src directory) to see how to use IIC routines
************************************/
#include "badge_user.h"
void user_program_init(void)
{
/* This will run when User Program is first selected form the menu */
clr_buffer();
enable_display_scanning(0); //Shut off auto-scanning of character buffer
tft_fill_area(0,0,320,240,0);
}
void surface(void);
uint8_t first_frame = 1;
int16_t n_lines;
void user_program_loop(void){
n_lines = 1;
surface();
}
#include "cos_table.h"
int16_t cos_lookup(int32_t x){
//return 4096.*cos(6.2831853*x/4096.);
if (x < 0) {
x = -x;
}
x = x & (((uint16_t)4 << cos_k) - 1);
if (x > ((uint16_t)2 << cos_k)){
x = ((uint16_t)4 << cos_k) - x ;
}
if (x > ((uint16_t)1 << cos_k)){
x = ((uint16_t)2 << cos_k) - x;
return -cos_table[x];
} else {
if (x == ((uint16_t)1 << cos_k)){
return 0;
}
return cos_table[x];
}
}
#define S 12
#define FP(x) ((int16_t)((x) * (1<<S)))
#define ang 10.
int16_t cs_ang = FP(0.98481); //FP(cos(ang*3.141592653/180.));
int16_t sn_ang = FP(0.17365); //FP(sin(ang*3.141592653/180.));
#define MAX_SURFACE_FRAMES 114
#define MAX_LINES 40
void surface(void){
// buffer for storing pixel locations of previous frame (to erase)
int8_t old_lines[MAX_LINES * 320];
// time
int16_t t = FP(0);
// current shadow heights for each line
int16_t shadow_z[MAX_LINES];
uint16_t i;
for (i=0; i<n_lines; i++){
shadow_z[i] = -32768;
}
// controls shadow angle
int16_t dz = FP(-0.001);
// previous column's z-value used to estimate Nx for lighting calcuations
int16_t old_z[MAX_LINES];
// x step per columns
int16_t dx = FP(1./320.);
// time step
int16_t dt = FP(0.05);
uint32_t frames = 0;
while (frames++ < MAX_SURFACE_FRAMES){
t += dt;
// increase number of lines drawn at beginning for effect
if (n_lines < MAX_LINES) n_lines++;
// kill time for early frames which draw too fast
wait_ms(4*(MAX_LINES - n_lines));
// the single unavoidable division - only one per frame :-)
int16_t dy = (((int32_t)1<<(S+10))/n_lines)>>10;
int16_t x = FP(-0.5) - dx;
uint16_t col;
for (col=0; col<320; col++){
x += dx;
// erase pixels in this column from old frame
if (!first_frame){
uint16_t i;
for (i=0; i<n_lines; i++){
tft_fill_area(col, old_lines[320*i+col], 1, 1, 0);
}
}
int16_t y = FP(-0.5) - dy;
// initialize hidden-line bounds
int16_t min_row = 240;
int16_t max_row = -1;
uint16_t i;
for (i=0; i<n_lines; i++){
y += dy;
// function to plot
int16_t r, z;
if (frames < 40){
// first function: radial ripples
r = ((int32_t)x*x + (int32_t)y*y)>>S;
z = ((int32_t)cos_lookup(7*r-t) * ((FP(3)-12*r)))>>(S+4);
} else if (frames < 104){
// interpolate between first and second function
r = ((int32_t)x*x + (int32_t)y*y)>>S;
z = (((int32_t)cos_lookup(3*x+y+t) * cos_lookup(2*y+x+2*t))>>S);
z = ((int32_t)z * cos_lookup(r))>>(S+3);
int16_t z2;
z2 = ((int32_t)cos_lookup(7*r-t) * ((FP(3)-12*r)))>>(S+4);
int16_t l = (frames - 40)<<(S-6);
z = (((int32_t)z*l)>>S) + (((int32_t)z2*(FP(1.) - l))>>S);
} else {
// second function : waves
r = ((int32_t)x*x + (int32_t)y*y)>>S;
z = (((int32_t)cos_lookup(3*x+y+t) * cos_lookup(2*y+x+2*t))>>S);
z = ((int32_t)z * cos_lookup(r))>>(S+3);
}
if (col == 0){
old_z[i] = z;
}
// orthographic projection
uint16_t row = 120 - (((((int32_t)y * sn_ang)>>S)+
(((int32_t)z * cs_ang)>>S))>>3);
if (row >= 0 || row <= 239){
// shadow test
uint8_t in_shadow = 0;
if (z < shadow_z[i]){
in_shadow = 1;
}
// illumination model: ambient + diffuse
int16_t amb = 75;
int16_t dz = 20*(z - old_z[i]);
if (dz < 0){
dz = 0;
}
dz += amb;
if (dz > 255) dz = 255;
if (in_shadow) dz = 3*amb>>2;
uint32_t shadow_rgb = 0x00550055;
// fade to black at end
if (frames >= (114-32)){
int16_t l = FP(1.) - (((int32_t)frames-(114-32))<<(S-5));
dz = ((int32_t)dz * l)>>S;
int32_t s = 0x55;
s = (s * l)>>S;
shadow_rgb = ((s&0xff)<<16) | (s&0xff);
}
// top of surface: illuminated green with shadows
if (row < min_row){
uint32_t rgb = (uint32_t)dz<<8;
tft_fill_area(col, row, 1, 1, rgb);
old_lines[320*i + col] = row;
}
// bottom of surface: ambient magenta only
if (row > max_row){
uint32_t rgb = shadow_rgb;
tft_fill_area(col, row, 1, 1, rgb);
old_lines[320*i + col] = row;
}
}
// update the hidden-line bounds and the shadow height
if (row < min_row) min_row = row;
if (row > max_row) max_row = row;
if (z > shadow_z[i]) shadow_z[i] = z;
shadow_z[i] += dz;
// store for Nx estimation on next column
old_z[i] = z;
}
}
first_frame = 0;
}
}
I won't paste the whole cosine table into this log ("cos_table.h"). Download Here and place in the src directory.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.