/* Motoko particle system v0.24.20020126 - created by Inphernic

WEB :    http://edu.kauhajoki.fi/~juipe/index.html
E-MALE : juipe@edu.kauhajoki.fi
*/
//----------------------------------------------------------------------------
// INCLUDES

#include "math.h"                                          // Must-have

//----------------------------------------------------------------------------
// DEFINES

#define _motoko_max_g           8                          // Maximum number of groups
#define _motoko_max_p           700                        // Maximum number of particles
#define _motoko_version         "0.24.20020126 WIP"        // Motoko's current version
#define PI                      3.141592654

//----------------------------------------------------------------------------
// TYPEDEFS

typedef unsigned short us;      // Easier to write us than unsigned short
typedef unsigned int   ui;      // Easier to write us than unsigned short
typedef short          sh;      // Easier to write sh than short
typedef float          fl;      // Easier to write fl than fl
typedef struct { us a; us s_u; fl x; fl y; fl x_g; fl y_g; fl z_g; fl m_xza; fl m_xzv; us m_xzs; fl m_yza; us m_yzs; fl m_yzv; fl p_x; fl p_y; fl p_z; sh l; us r; fl xz_d; fl d3_d; us b; us b_f; us p_s; sh x_ri; sh y_ri; sh z_ri; int c; } _motoko_preset;
// Preset

//----------------------------------------------------------------------------
// PROTOTYPES & REFERENCE

void _motoko_particle_init(us a, us b);
     // Initialize a single particle (group, particle)
void _motoko_group_init(us a, us p, fl x, fl y, fl x_g, fl y_g, fl z_g, fl m_xza, fl m_xzs, fl m_xzv, fl m_yza, fl m_yzs, fl m_yzv, fl p_x, fl p_y, fl p_z, us r, sh l, fl xz_d, fl d3_d, us b, us b_f, us p_s, us r_x, us r_y, us r_z, int c);
     // Initialize a group of particles (many variables!)

void _motoko_particle_logic(us a, us b);
     // Advance logic for a single particle (group, particle)
void _motoko_group_logic(us a);
     // Advance logic for a group of particles (group)
void _motoko_all_logic(void);
     // Advance logic for every group of particles

void _motoko_particle_draw(BITMAP *t, us a, us b);
     // Draw a single particle (target bitmap, group, particle)
void _motoko_group_draw(BITMAP *t, us a);
     // Draw a group of particles (target bitmap, group)
void _motoko_alt_group_draw(BITMAP *t, us a, sh x, sh y);
     // Draw a group of particles (target bitmap, group)
void _motoko_all_draw(BITMAP *t);
     // Draw every group of particles (target bitmap)

void _motoko_particle_kill(us a, us b);
     // Kill a single particle (group, particle)
void _motoko_group_kill(us a);
     // KIll a group of particles (group)
void _motoko_all_kill(void);
     // Kill every group of particles

void _motoko_preset_init(_motoko_preset *a, us p, fl x, fl y, fl x_g, fl y_g, fl z_g, fl m_xza, fl m_xzs, fl m_yza, fl m_yzs, fl m_yzv, fl m_xzv, fl p_x, fl p_y, fl p_z, us r, sh l, fl xz_d, fl d3_d, us b, us b_f, us p_s, us r_x, us r_y, us r_z, int c);
     // Save the following setting to preset
void _motoko_preset_group(us a, _motoko_preset *p);
     // Initialize a group of particles using the preset
void _motoko_preset_kill(_motoko_preset *a);
     // Kill the preset

fl _motoko_particle_xvel(us a, us b);
     // Calculate X-movement (group, particle)
fl _motoko_particle_yvel(us a, us b);
     // Calculate Y-movement (group, particle)
fl _motoko_particle_zvel(us a, us b);
     // Calculate Z-movement (group, particle)

void _motoko_system_stats(void);
     // Update statistics for debug/etc purposes
sh _motoko_system_short(sh val, sh min, sh max);
     // Enforce VAL to be between MIN-MAX boundary, returns corrected value

//----------------------------------------------------------------------------
// CORE STRUCT

struct {
  struct {
    struct {
      fl x;             // X-position
      fl y;             // Y-position
      fl z;             // Z-position
      fl xz_v;          // Velocity (XZ relative)
      fl xz_a;          // Angle (XZ relative)
      fl yz_v;          // Velocity (YZ relative)
      fl yz_a;          // Angle (YZ relative)
      fl y_g;           // Gravity effect
      fl z_g;           // Gravity effect
      fl x_g;           // Gravity effect
      fl force;         // Downforce
      us a;             // Active
      sh l;             // Life left
      us m;             // Able to move
      fl r_x;           // Random X-movement factor
      fl r_y;           // Random Y-movement factor
      fl r_z;           // Random Z-movement factor
    } p[_motoko_max_p];
    fl xz_d;            // Distance kill (XZ relative)
    fl d3_d;            // Distance kill (real space)
    fl x;               // Fountain X-position on bitmap
    fl y;               // Fountain Y-position on bitmap
    fl p_x;             // Fountain X-position in particle space
    fl p_y;             // Fountain Y-position in particle space
    fl p_z;             // Fountain Z-position in particle space
    fl m_xza;           // Master XZ angle
    fl m_yza;           // Master YZ angle
    us m_xzs;           // Master XZ spread
    us m_yzs;           // Master YZ spread
    fl m_xzv;           // Master XZ velocity
    fl m_yzv;           // Master YZ velocity
    fl x_g;             // Gravity effect on X-axis
    fl y_g;             // Gravity effect on Y-axis
    fl z_g;             // Gravity effect on Z-axis
    us s_u;             // Used particles
    us s_d;             // Drawn particles
    us p_s;             // Particle size
    us a;               // Active
    us b;               // Action when Y-coordinate reaches zero (ground)
    int c;              // Group color (applies to all particles)
    sh l;               // Lifetime
    us r;               // Respawn after death
    us b_f;             // Downforce divisor factor (smaller = more bouncy (superball), larger = less bouncy (100t of concrete))
    sh x_ri;            // X random movement iterations
    sh y_ri;            // Y random movement iterations
    sh z_ri;            // Z random movement iterations
  } g[_motoko_max_g];
  ui s_u;               // Total of used particles
  ui s_d;               // Total of drawn particles
  ui s_a;               // Total of active groups
  ui s_m;               // Memory taken by Motoko
  sh di;                // DIRTY ISOMETRICS (gotta have a better solution!)
} _motoko;

// *********** MOTOKO_SYSTEM FUNCTIONS ***************************************

// Enforce short values to a set boundary
sh _motoko_system_short(sh val, sh min, sh max) {
  // If VAL is inside the bounds, just return the VAL itself
  if (val>min && val<max) return val;
  // If VAL is smaller than the minimum bound, return the MIN bound
  if (val<min) return min;
  // If VAL is larger than the maximum bound, return the MAX bound
  if (val>max) return max;
}

// Calculate the statistics (drawn particles, used memory, etc..) - NOT NECESSARY!
void _motoko_system_stats(void) { us i;
  // Reset previous statistics
  _motoko.s_a=0; _motoko.s_u=0; _motoko.s_d=0;
  // Retrieve the memory used by Motoko
  _motoko.s_m=sizeof(_motoko);
  for (i=0; i<_motoko_max_g; i++) if (_motoko.g[i].a) {
    // Calculate the total of active groups, drawn particles and used particles
    _motoko.s_a++; _motoko.s_d=_motoko.s_d+_motoko.g[i].s_d; _motoko.s_u=_motoko.s_u+_motoko.g[i].s_u;
  }
}

// *********** MOTOKO_DRAW FUNCTIONS *****************************************
// NOTE: These functions were created specifically for Owet Software's isometric engine!

// Draw a single particle
void _motoko_particle_draw(BITMAP *t, us a, us b) { fl x, y, z; sh i;
  // Check the given group & particle are valid
  if (a<_motoko_max_g && _motoko.g[a].a && b<_motoko_max_p && _motoko.g[a].p[b].a) {
    // Calculate/retrieve the coordinates where this particle should be drawn
    x = _motoko.g[a].x+_motoko.g[a].p[b].x; y = _motoko.g[a].p[b].y; z = _motoko.g[a].y+(_motoko.g[a].p[b].z/_motoko.di);
    // If the particle is inside the bitmap..
    if (x>=0 && x<=t->w && z-y>=0 && z-y<t->h) {
      // Set transparent drawing mode
      drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
      // Loop depending on the particles size
      for (i=_motoko.g[a].p_s; i>=0; i--) {
        // Change the transparency (linked to 3d space distance from fountain center)
        set_trans_blender(100,100,100, 150-_motoko_system_short(hypot(_motoko.g[a].p[b].x, _motoko.g[a].p[b].z), 0, 150));
        // Draw it!
        circlefill(t, x, z-y, i, _motoko.g[a].c);
      }
      // Raise statistical value (particles drawn in this fountain)
      _motoko.g[a].s_d++;
    }
  }
}

// Draw a whole group of particles
void _motoko_group_draw(BITMAP *t, us a){ us i;
  // Check the given group is valid
  if (a<_motoko_max_g && _motoko.g[a].a) {
    // Reset statistic value (particles drawn on this fountain
    _motoko.g[a].s_d=0; for (i=0; i<_motoko.g[a].s_u; i++) _motoko_particle_draw(t, a, i);
  }
solid_mode();
}

// Alternate method for drawing a group of particles
void _motoko_alt_group_draw(BITMAP *t, us a, sh x, sh y) {
  // Check the given group is valid
  if (a<_motoko_max_g && _motoko.g[a].a) {
     // Just set the X- and Y-coordinates and run the normal _motoko_group_draw
     _motoko.g[a].x=x; _motoko.g[a].y=y; _motoko_group_draw(t, a);
  }
solid_mode();
}

// Draw every particle group
void _motoko_all_draw(BITMAP *t) { us i;
  // Just loop the _motoko_group_draw with every possible group
  for (i=0; i<_motoko_max_g; i++) if (_motoko.g[i].a) _motoko_group_draw(t, i);
solid_mode();
}

// *********** MOTOKO_INIT FUNCTIONS *****************************************

// Initialize a single particle (_motoko_group_init would be nice before this, since this takes a lot of stuff from the group)
void _motoko_particle_init(us a, us p) {
  // Check for valid group & number of particles
  if (a<_motoko_max_g && p<_motoko_max_p) {
    // Just set the stuff
    _motoko.g[a].p[p].a=1; _motoko.g[a].p[p].m=1; _motoko.g[a].p[p].l=_motoko.g[a].l; _motoko.g[a].p[p].x=_motoko.g[a].p_x; _motoko.g[a].p[p].y=_motoko.g[a].p_y; _motoko.g[a].p[p].z=_motoko.g[a].p_z; _motoko.g[a].p[p].x_g=_motoko.g[a].x_g; _motoko.g[a].p[p].y_g=_motoko.g[a].y_g; _motoko.g[a].p[p].z_g=_motoko.g[a].z_g;
    // If spread is 0, avoid crash by just using the raw angle
    if (_motoko.g[a].m_xzs==0) _motoko.g[a].p[p].xz_a=_motoko.g[a].m_xza;
    // Calculate an angle for this particle from master angle and master spread
    else  _motoko.g[a].p[p].xz_a=_motoko.g[a].m_xza-(_motoko.g[a].m_xzs/2)+rand()%_motoko.g[a].m_xzs;
    // If spread is 0, avoid crash by just using the raw angle
    if (_motoko.g[a].m_yzs==0) _motoko.g[a].p[p].yz_a=_motoko.g[a].m_yza;
    // Calculate an angle for this particle from master angle and master spread
    else _motoko.g[a].p[p].yz_a=_motoko.g[a].m_yza-(_motoko.g[a].m_yzs/2)+rand()%_motoko.g[a].m_yzs;
    _motoko.g[a].p[p].xz_v=_motoko.g[a].m_xzv; _motoko.g[a].p[p].yz_v=_motoko.g[a].m_yzv;
    _motoko.g[a].p[p].r_x = (rand()%(_motoko.g[a].x_ri+1))/100; _motoko.g[a].p[p].r_y = (rand()%(_motoko.g[a].y_ri+1))/100; _motoko.g[a].p[p].r_z = (rand()%(_motoko.g[a].z_ri+1))/100;
    ;
  }
}

// Initialize a group of particles (recommended before particle initialization)
void _motoko_group_init(us a, us p, fl x, fl y, fl x_g, fl y_g, fl z_g,
  fl m_xza, fl m_xzs, fl m_yza, fl m_yzs, fl m_yzv, fl m_xzv, fl p_x, fl p_y,
  fl p_z, us r, sh l, fl xz_d, fl d3_d, us b, us b_f, us p_s, us r_x,
  us r_y, us r_z, int c) { us i=0;

  // Check for valid group & number of particles
  if (a<_motoko_max_g && p<_motoko_max_p) {
    // Just set the stuff
    _motoko.g[a].x_ri = r_x; _motoko.g[a].y_ri = r_y; _motoko.g[a].z_ri = r_z;
    _motoko.g[a].x = x; _motoko.g[a].y = y; _motoko.g[a].s_u = p; _motoko.g[a].a = 1; _motoko.g[a].s_d = 0; _motoko.g[a].x_g=x_g; _motoko.g[a].y_g=y_g; _motoko.g[a].z_g=z_g; _motoko.g[a].p_x=p_x; _motoko.g[a].p_y=p_y; _motoko.g[a].p_z=p_z;_motoko.g[a].p_s=p_s; _motoko.g[a].r=r; _motoko.g[a].l=l; _motoko.g[a].xz_d=xz_d; _motoko.g[a].d3_d=d3_d; _motoko.g[a].b=b; _motoko.g[a].b_f=b_f; _motoko.g[a].m_xza = m_xza; _motoko.g[a].m_xzs = m_xzs; _motoko.g[a].m_yza = m_yza;_motoko.g[a].m_yzs = m_yzs; _motoko.g[a].m_yzv = m_yzv; _motoko.g[a].m_xzv = m_xzv; _motoko.g[a].c = c;
    // Loop _motoko_particle_init with the number of particles which are to be used
    for(i=0; i<p; i++) _motoko_particle_init(a,i);
  }
}

// *********** MOTOKO_PRESET FUNCTIONS ***************************************

// Initialize a group of particles using the preset
void _motoko_preset_group(us a, _motoko_preset *p) {
  // Use the preset's settings to initialize a group
  if (a<_motoko_max_g) _motoko_group_init(a, p->s_u, p->x, p->y, p->x_g, p->y_g, p->z_g, p->m_xza, p->m_xzs, p->m_yza, p->m_yzs, p->m_yzv, p->m_xzv, p->p_x, p->p_y, p->p_z, p->r, p->l, p->xz_d, p->d3_d, p->b, p->b_f, p->p_s, p->x_ri, p->y_ri, p->z_ri, p->c);
}

// Save the following settings to preset
void _motoko_preset_init(_motoko_preset *a, us p, fl x, fl y, fl x_g, fl y_g, fl z_g, fl m_xza, fl m_xzs, fl m_yza, fl m_yzs, fl m_yzv, fl m_xzv, fl p_x, fl p_y, fl p_z, us r, sh l, fl xz_d, fl d3_d, us b, us b_f, us p_s, us r_x, us r_y, us r_z, int c) {
  // Throw the setting into the _motoko_preset
  a->s_u=p; a->x=x; a->y=y; a->x_g=x_g; a->y_g=y_g; a->z_g=z_g; a->m_xza=m_xza; a->m_xzs=m_xzs; a->m_xzv=m_xzv; a->m_yza=m_yza; a->m_yzs=m_yzs; a->m_yzv=m_yzv; a->p_x=p_x; a->p_y=p_y; a->p_z=p_z; a->r=r; a->l=l; a->xz_d=xz_d; a->d3_d=d3_d; a->b=b; a->b_f=b_f; a->p_s=p_s; a->x_ri=r_x; a->y_ri=r_y; a->z_ri=r_z; a->c=c;
}

// Kill the preset
void _motoko_preset_kill(_motoko_preset *a) {
  // Throw the setting into the _motoko_preset
  a->s_u=0; a->x=0; a->y=0; a->x_g=0; a->y_g=0; a->z_g=0; a->m_xza=0; a->m_xzs=0; a->m_xzv=0; a->m_yza=0; a->m_yzs=0; a->m_yzv=0; a->p_x=0; a->p_y=0; a->p_z=0; a->r=0; a->l=0; a->xz_d=0; a->d3_d=0; a->b=0; a->b_f=0; a->p_s=0; a->x_ri=0; a->y_ri=0; a->z_ri=0; a->c=0;
  free(a);
}

// *********** MOTOKO_VEL FUNCTIONS ******************************************

// Calculate X-movement for a particle
fl _motoko_particle_xvel(us a, us b) {
  // Check for valid group & particle, return the movement
  if (a<_motoko_max_g && b<_motoko_max_p) return (_motoko.g[a].p[b].xz_v+_motoko.g[a].p[b].r_x)*cos(((PI/180)*_motoko.g[a].p[b].xz_a));
}

// Calculate Y-movement for a particle
fl _motoko_particle_yvel(us a, us b) {
  // Check for valid group & particle, return the movement
  if (a<_motoko_max_g && b<_motoko_max_p) return (_motoko.g[a].p[b].yz_v+_motoko.g[a].p[b].r_y)*cos(((PI/180)*_motoko.g[a].p[b].yz_a));
}

// Calculate Z-movement for a particle
fl _motoko_particle_zvel(us a,us b) {
  // Check for valid group & particle, return the movement
  if (a<_motoko_max_g && b<_motoko_max_p) return (_motoko.g[a].p[b].xz_v+_motoko.g[a].p[b].r_z)*sin(((PI/180)*_motoko.g[a].p[b].xz_a));
}

// *********** MOTOKO_KILL FUNCTIONS *****************************************

// Kill a particle
void _motoko_particle_kill(us a, us b) {
  // Check the given group & particle are valid
  if (a<_motoko_max_g && b<_motoko_max_p) {
    // Set every value of this particle to zero
    _motoko.g[a].p[b].r_x=0; _motoko.g[a].p[b].r_y=0; _motoko.g[a].p[b].r_z=0; _motoko.g[a].p[b].force=0; _motoko.g[a].p[b].x=0; _motoko.g[a].p[b].y=0; _motoko.g[a].p[b].z=0; _motoko.g[a].p[b].a=0; _motoko.g[a].p[b].l=0; _motoko.g[a].p[b].m=0; _motoko.g[a].p[b].xz_a=0; _motoko.g[a].p[b].xz_v=0; _motoko.g[a].p[b].yz_a=0; _motoko.g[a].p[b].yz_v=0; _motoko.g[a].p[b].y_g=0; _motoko.g[a].p[b].z_g=0; _motoko.g[a].p[b].x_g=0;
    // Respawn the particle
    if (_motoko.g[a].r) _motoko_particle_init(a,b);
  }
}

// Kill a whole group of particles
void _motoko_group_kill(us a) { us i;
  // If the group is valid..
  if (a<_motoko_max_g) {
    // Just loop the _motoko_particle_kill with every particle in the fountain
    for(i=0; i<_motoko_max_p; i++) _motoko_particle_kill(a, i);
    // Set group internal values to zero
    _motoko.g[a].x_ri=0; _motoko.g[a].y_ri=0; _motoko.g[a].z_ri=0; _motoko.g[a].x = 0; _motoko.g[a].y = 0; _motoko.g[a].s_u = 0; _motoko.g[a].a = 0; _motoko.g[a].s_d = 0; _motoko.g[a].y_g=0; _motoko.g[a].z_g=0; _motoko.g[a].x_g=0; _motoko.g[a].p_x=0; _motoko.g[a].p_y=0; _motoko.g[a].p_z=0;_motoko.g[a].m_xza=0; _motoko.g[a].m_yza=0; _motoko.g[a].m_xzs=0; _motoko.g[a].m_yzs=0; _motoko.g[a].m_xzv=0; _motoko.g[a].m_yzv=0; _motoko.g[a].p_s=0; _motoko.g[a].r=0; _motoko.g[a].b=0; _motoko.g[a].b_f=0; _motoko.g[a].l=0; _motoko.g[a].c=0;
  }
}

// Kill all particle groups (total apocalypse)
void _motoko_all_kill(void) { us i;
  // Just loop the _motoko_group_kill with every possible group
  for(i=0; i<_motoko_max_g; i++) _motoko_group_kill(i);
}

// *********** MOTOKO_LOGIC FUNCTIONS ****************************************

// Advance logic for particle B from group A
void _motoko_particle_logic(us a, us b) {
  // Check the given group & particle are valid
  if (a<_motoko_max_g && _motoko.g[a].a && b<_motoko_max_p && _motoko.g[a].p[b].a) {
    // If particle has life left, it loses one point of life
    if (_motoko.g[a].p[b].l>0) _motoko.g[a].p[b].l--;
    // If a particle has no life left, kill it
    if (_motoko.g[a].p[b].l==0) _motoko_particle_kill(a,b);
    // If a particle exceeds the set distance limit in XZ (2d) space, kill it
    if (_motoko.g[a].xz_d<=hypot(_motoko.g[a].p[b].x,_motoko.g[a].p[b].z))_motoko_particle_kill(a,b);
    // If a particle exceeds the set distance limit in 3d space, kill it
    if (_motoko.g[a].d3_d<=hypot(hypot(_motoko.g[a].p[b].x,_motoko.g[a].p[b].z),_motoko.g[a].p[b].y)) _motoko_particle_kill(a,b);
    // If the particle is allowed to move..
    if (_motoko.g[a].p[b].m) {
      // Add the fountain gravity value to particle gravity (makes the gravity pull gradually stronger)
      _motoko.g[a].p[b].y_g = _motoko.g[a].p[b].y_g + _motoko.g[a].y_g; _motoko.g[a].p[b].x_g = _motoko.g[a].p[b].z_g + _motoko.g[a].x_g; _motoko.g[a].p[b].z_g = _motoko.g[a].p[b].x_g + _motoko.g[a].z_g;
      // Make the actual gravity changes to the particle's coordinates and calculate movement
      _motoko.g[a].p[b].y = _motoko.g[a].p[b].y + _motoko.g[a].p[b].y_g + _motoko_particle_yvel(a,b); _motoko.g[a].p[b].x = _motoko.g[a].p[b].x + _motoko.g[a].p[b].x_g + _motoko_particle_xvel(a,b); _motoko.g[a].p[b].z = _motoko.g[a].p[b].z + _motoko.g[a].p[b].z_g + _motoko_particle_zvel(a,b);
      // If the particle is travelling down on Y-axis, add the travelling velocity to downforce factor
      if (_motoko.g[a].p[b].y_g+_motoko_particle_yvel(a,b)<0) _motoko.g[a].p[b].force = _motoko.g[a].p[b].force + _motoko.g[a].p[b].y_g + _motoko_particle_yvel(a,b);
      // If the particle's Y-coordinate is below/equal to 0 (ground)..
      if (_motoko.g[a].p[b].y<=0) {
        // If this fountain's particles are set to die when they reach zero kill it
        if (_motoko.g[a].b==2) _motoko_particle_kill(a,b);
        if (_motoko.g[a].b==1) {
          // Take the particle back to "ground level" in case it managed to burrow "underground"
          _motoko.g[a].p[b].y=0;
          // Set particle's YZ-angle to 0 (straight up)
          _motoko.g[a].p[b].yz_a=0;
          // Calculate the particle's bounce velocity from the downforce
          _motoko.g[a].p[b].yz_v=(-1*_motoko.g[a].p[b].force)/_motoko.g[a].b_f;
          // Set particle's Y-axis gravity effect to 0
          _motoko.g[a].p[b].y_g=0;
          // Set particle's downforce to 0
          _motoko.g[a].p[b].force=0;
          // If the bounce velocity is next to none..
          if(_motoko.g[a].p[b].yz_v>=0 && _motoko.g[a].p[b].yz_v<=0.2) _motoko_particle_kill(a,b);
        }
      }
    }
  }
}

// Advance logic for a whole group of particles
void _motoko_group_logic(us a) { us i;
  // Check the given group is valid
  if (a<_motoko_max_g && _motoko.g[a].a) {
    // Just loop the _motoko_particle_logic with every particle on the fountain
    for(i=0; i<_motoko.g[a].s_u; i++) _motoko_particle_logic(a,i);
  }
}

// Advance logic for every particle group
void _motoko_all_logic(void) { us i;
  // Just loop the _motoko_group_logic with every group available
  for(i=0; i<_motoko_max_g; i++) if (_motoko.g[i].a) _motoko_group_logic(i);
}
