Close

Source

A project log for Supercon Badge Surface Plotter

Firmware hack for real-time animated 3D surfaces

ted-yapoTed Yapo 11/09/2018 at 03:070 Comments

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