Blob Blame Raw

#include <math.h>
#include <string.h>
#include <helianthus.h>


#define ROAD_SEGMENTS  128
#define SEGMENTS_WIDTH 25


typedef struct {
  double x, y;
} Point;

typedef struct {
  double m00, m01, m10, m11;
} Matrix;

typedef struct {
  Point p0;
  union {
    Matrix mat;
    struct { Point dir, norm; };
  };
  Matrix matInv;
} Segment;

typedef struct {
  double radius;
  Point pos;
  double angle;

  Point velocity;
  double angVel;
  double force;

  int collision;
  Point norm;
} Wheel;

typedef struct {
  Wheel w0, w1;
  double len;
  Point center;
  double angle;
} Car;


Car car;
int roadSegments;
double roadk0, roadk1;
Segment road[ROAD_SEGMENTS];



void matrixInvert(const Matrix *src, Matrix *dst) {
  double kd = src->m00*src->m11 - src->m01*src->m10;
  kd = fabs(kd) > 1e-10 ? 1/kd : 0;
  dst->m00 =  kd*src->m11;
  dst->m01 = -kd*src->m01;
  dst->m10 = -kd*src->m10;
  dst->m11 =  kd*src->m00;
}

void matrixMultPoint(const Matrix *m, const Point *src, Point *dst) {
  dst->x = src->x*m->m00 + src->y*m->m10;
  dst->y = src->x*m->m01 + src->y*m->m11;
}


void segmentInit(Segment *s, const Point *p0, const Point *p1) {
  s->p0.x = p0->x;
  s->p0.y = p0->y;
  s->dir.x = p1->x - p0->x;
  s->dir.y = p1->y - p0->y;
  double kl = sqrt(s->dir.x*s->dir.x + s->dir.y*s->dir.y);
  kl = kl > 1e-10 ? 1/kl : 0;
  s->norm.x =  kl*s->dir.y;
  s->norm.y = -kl*s->dir.x;
  matrixInvert(&s->mat, &s->matInv);
}


void wheelCollideSegment(Wheel *wheel, Segment *s) {
  Point pc, p = { wheel->pos.x - s->p0.x, wheel->pos.y - s->p0.y };
  matrixMultPoint(&s->matInv, &p, &pc);
  if (pc.x >= 0 && pc.x <= 1 && pc.y < wheel->radius && pc.y > -wheel->radius) {
    pc.y = wheel->radius;
    matrixMultPoint(&s->mat, &pc, &p);
    ++wheel->collision;
    wheel->pos.x = p.x + s->p0.x;
    wheel->pos.y = p.y + s->p0.y;
    wheel->norm.x = s->norm.x;
    wheel->norm.y = s->norm.y;
    return;
  }

  double kd = p.x*p.x + p.y*p.y;
  if (kd > 1e-10 && kd < wheel->radius*wheel->radius) {
    kd = 1/sqrt(kd);
    ++wheel->collision;
    wheel->pos.x = p.x*kd*wheel->radius + s->p0.x;
    wheel->pos.y = p.y*kd*wheel->radius + s->p0.y;
    wheel->norm.x = p.x*kd;
    wheel->norm.y = p.y*kd;
    return;
  }
}

void wheelUpdate(Wheel *wheel, double dt) {
  wheel->pos.x += wheel->velocity.x * dt;
  wheel->pos.y += wheel->velocity.y * dt;
  wheel->angle += wheel->angVel * dt;

  wheel->collision = 0;
  for(int i = 0; i < roadSegments; ++i)
    wheelCollideSegment(wheel, &road[i]);

  wheel->velocity.y += 200*dt;
  if (wheel->collision) {
    double dx = -wheel->norm.y;
    double dy =  wheel->norm.x;
    double v = wheel->velocity.x*dx  + wheel->velocity.y*dy;
    v += wheel->force*dt;
    wheel->velocity.x = v*dx;
    wheel->velocity.y = v*dy;
    wheel->angVel = v/wheel->radius;
  } else {
    wheel->angVel += wheel->force/wheel->radius*dt;
    double maxAngVel = 100;
    if (wheel->angVel >  maxAngVel) wheel->angVel =  maxAngVel; else
    if (wheel->angVel < -maxAngVel) wheel->angVel = -maxAngVel;
  }
}

void wheelDraw(Wheel *wheel) {
  saveState();
  strokeWidth(0.1*wheel->radius);
  stroke(COLOR_BLACK);
  fill(COLOR_GREEN);
  circle(wheel->pos.x, wheel->pos.y, wheel->radius);
  circle(wheel->pos.x + 0.5*wheel->radius*cos(wheel->angle),
         wheel->pos.y + 0.5*wheel->radius*sin(wheel->angle), wheel->radius*0.25);
  restoreState();
}

void carUpdate(Car *car, double dt) {
  Point c = { (car->w0.pos.x + car->w1.pos.x)/2,
              (car->w0.pos.y + car->w1.pos.y)/2 };
  car->center.x = c.x;
  car->center.y = c.y;

  Point d = { car->w1.pos.x - car->w0.pos.x,
              car->w1.pos.y - car->w0.pos.y };
  double kl = d.x*d.x + d.y*d.y;
  kl = kl > 1e-10 ? 1/sqrt(kl) : 0;
  Point n = { d.x*kl, d.y*kl };
  car->angle = atan2(n.y, n.x);

  car->w0.pos.x = c.x - n.x*car->len/2;
  car->w0.pos.y = c.y - n.y*car->len/2;
  car->w1.pos.x = c.x + n.x*car->len/2;
  car->w1.pos.y = c.y + n.y*car->len/2;

  Point np = { -n.y, n.x };
  Point cv = { (car->w0.velocity.x + car->w1.velocity.x)/2,
               (car->w0.velocity.y + car->w1.velocity.y)/2 };
  double v0 = (car->w0.velocity.x - cv.x)*np.x
            + (car->w0.velocity.y - cv.y)*np.y;
  car->w0.velocity.x = cv.x + v0*np.x;
  car->w0.velocity.y = cv.y + v0*np.y;
  double v1 = (car->w1.velocity.x - cv.x)*np.x
            + (car->w1.velocity.y - cv.y)*np.y;
  car->w1.velocity.x = cv.x + v1*np.x;
  car->w1.velocity.y = cv.y + v1*np.y;



  wheelUpdate(&car->w0, dt);
  wheelUpdate(&car->w1, dt);
}

void carDraw(Car *car) {
  saveState();
  translate(car->center.x, car->center.y);
  rotate(car->angle/PI*180);

  strokeWidth(0.02*car->len);
  stroke(COLOR_BLACK);
  fill(COLOR_BLUE);
  double l = car->len;

  moveTo(0, 0);
  lineTo( l*0.75, 0);
  lineTo( l*0.75, -l*0.25);
  lineTo( l*0.50, -l*0.25);
  lineTo( l*0.25, -l*0.50);
  lineTo(-l*0.25, -l*0.50);
  lineTo(-l*0.50, -l*0.25);
  lineTo(-l*0.75, -l*0.25);
  lineTo(-l*0.75, 0);
  closePath();
  restoreState();

  wheelDraw(&car->w0);
  wheelDraw(&car->w1);
}

void roadAddSegemnt(double segmentWidth) {
  if (!roadSegments) {
    Point p0 = { 0, 0 };
    Point p1 = { 0, segmentWidth };
    segmentInit(&road[0], &p0, &p1);
    ++roadSegments;
    return;
  }

  if (roadSegments >= ROAD_SEGMENTS) {
    memmove(road, road + 1, sizeof(*road)*(ROAD_SEGMENTS - 1));
    --roadSegments;
  }

  double ky = 0;
  for(int i = 0; i < 5; ++i) {
    roadk0 += (randomFloat()*2 - 1 - roadk0)*0.05;
    roadk1 += (roadk0 - roadk1)*0.25;
    ky += roadk1;
  }

  for(int i = 0; i < roadSegments; ++i)
    road[i].p0.x -= segmentWidth;
  car.w0.pos.x -= segmentWidth;
  car.w1.pos.x -= segmentWidth;

  Segment *prev = &road[roadSegments-1];
  Point p0 = { prev->p0.x + prev->dir.x,
               prev->p0.y + prev->dir.y };
  Point p1 = { p0.x + segmentWidth,
               p0.y + segmentWidth*0.5*ky };
  segmentInit(&road[roadSegments], &p0, &p1);
  ++roadSegments;
}



void init() {
  for(int i = 0; i < ROAD_SEGMENTS; ++i)
    roadAddSegemnt(SEGMENTS_WIDTH);
  car.w0.pos.x  = car.w1.pos.x  = road[ROAD_SEGMENTS/2].p0.x;
  car.w0.pos.y  = car.w1.pos.y  = road[ROAD_SEGMENTS/2].p0.y - 100;
  car.w0.radius = car.w1.radius = 20;
  car.w1.pos.x += 1;
  car.len = 100;
}


void draw() {
  double dt = windowGetFrameTime();
  double w = windowGetWidth();
  double h = windowGetHeight();

  double fk = 100;
  double force = 0;
  if (keyDown("left"))  force -= 1000;
  if (keyDown("right")) force += 1000;
  car.w0.force = car.w1.force = force;
  if (car.w0.angVel > 1e-10 && car.w0.force > 0) car.w0.force *= 1/(fabs(car.w0.angVel)/fk + 1);
  if (car.w0.angVel < 1e-10 && car.w0.force < 0) car.w0.force *= 1/(fabs(car.w0.angVel)/fk + 1);
  if (car.w1.angVel > 1e-10 && car.w1.force > 0) car.w1.force *= 1/(fabs(car.w1.angVel)/fk + 1);
  if (car.w1.angVel < 1e-10 && car.w1.force < 0) car.w1.force *= 1/(fabs(car.w1.angVel)/fk + 1);

  while(car.w0.pos.x - road[0].p0.x > 512 && road[roadSegments-1].p0.x - car.w0.pos.x < w)
    roadAddSegemnt(SEGMENTS_WIDTH);

  carUpdate(&car, dt);

  saveState();
  translate(w/2, h/2);
  translate(-car.w0.pos.x, -car.w0.pos.y);

  saveState();
  moveTo(road[0].p0.x, road[0].p0.y);
  for(int i = 1; i < roadSegments; ++i)
    lineTo(road[i].p0.x, road[i].p0.y);
  strokePath();
  restoreState();

  carDraw(&car);

  restoreState();
}


int main() {
  windowSetResizable(TRUE);
  windowSetVariableFrameRate();
  windowSetInit(&init);
  windowSetDraw(&draw);
  windowRun();
  return 0;
}