[ create a new paste ] login | about

Link: http://codepad.org/xYx0eKFw    [ raw code | fork ]

Evetro - C++, pasted on Apr 4:
/*
 * Ball Collision, simulates collision between several
 * freely moving rigid balls.
 * Uses FreeGLUT for graphics.
 * Press SPACE or ENTER to generate new circles, ESC or Q to quit.
 */
#include <cstdlib>    // for rand/srand
#include "GL/freeglut.h"  // normally should be <>
#include <ctime>      // for clock
#include <iostream>   // logging to console
#include <cmath>      // for hypot


//
// simple definitions

/// 2D vector
struct Vec2
{
  double x, y;
  
  Vec2(double nx = 0.0, double ny = 0.0)
    : x(nx), y(ny)
  {
  }

  Vec2& operator+=(const Vec2 &other)
  {
    x += other.x;
    y += other.y;
    return *this;
  }

  Vec2& operator*=(const double coef)
  {
    x *= coef;
    y *= coef;
    return *this;
  }

  double length() const
  {
    return hypot(x, y);
  }
};

inline Vec2 operator+(const Vec2 &a, const Vec2 &b)
{
  return Vec2(a.x + b.x, a.y + b.y);
}

inline Vec2 operator-(const Vec2 &a, const Vec2 &b)
{
  return Vec2(a.x - b.x, a.y - b.y);
}

inline Vec2 operator*(double coef, const Vec2 &v)
{
  return Vec2(coef * v.x, coef * v.y);
}

inline double operator*(const Vec2 &a, const Vec2 &b)
{
  return a.x * b.x + a.y * b.y;
}

std::ostream& operator<<(std::ostream &os, const Vec2 &v)
{
  return os << '(' << v.x << ", " << v.y << ')';
}


/// RGBA color
struct Color
{
  float red, green, blue, alpha;

  Color(float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f)
    : red(r), green(g), blue(b), alpha(a)
  {
  }

  void select() const
  {
    glColor4f(red, green, blue, alpha);
  }
};


/// rigid ball
struct Ball
{
  Vec2 p; ///< position
  Vec2 v; ///< velocity
  Vec2 a; ///< acceleration
  double m; ///< mass
  double r; ///< radius
  Color color;

  Ball
    (
      const Vec2 &pos = Vec2(),
      const Vec2 &vel = Vec2(),
      double mass = 1.0, 
      double rad = 10.0,
      const Color &col = Color(1.0f, 1.0f) // yellow by default
    )
      : p(pos), v(vel), m(mass), r(rad), color(col), a()
  {
  }

  /// OpenGL code to bring Ball to the frame buffer is here
  void paint() const
  {
    glLoadIdentity();
    glTranslated(p.x, p.y, 0.0);
    glScaled(r, r, r);
    color.select();
    glCallList(1);
  }

  /// calculate dynamics
  void move(double time_step)
  {
    const Vec2 ta = time_step * a;
    p += time_step * (v + 0.5 * ta);
    v += ta;
    a = Vec2();
  }
};

std::ostream& operator<<(std::ostream &os, const Ball &c)
{
  return os << "position: " << c.p
          << "\nvelocity: " << c.v
          << "\nmass: " << c.m
          << "\nradius: " << c.r << std::endl;
}


//
// constants and current (global) state
// 
/// amount of bodies
const size_t N = 20;

/// relaxation coefficient (should fall into [0, 1])
const double k = 0.9;

/// central force constant
const double G = 50000.0;

/// actual bodies
Ball C[N];

/// last frame time moment
std::clock_t t;

/// window width and height
float width = 800.0f, height = 600.0f;


//
// aux functions
//

/// returns a random number from [minv, maxv)
inline double random(double minv, double maxv)
{
  static const double anti_rand_max = 1.0 / double(RAND_MAX);
  return minv + (maxv - minv) * std::rand() * anti_rand_max;
}


/// generates randomly moving circles going to collide somewhere
void newBalls()
{
  std::cout << "\nGenerating new circles:\n";

  // target collision point
  const Vec2 cp(random(0.1 * width, 0.9 * width), random(0.1 * height, 0.9 * height));
  std::cout << "Collision point: " << cp << "\n";

  for (size_t i = 0; i < N; ++i)
  {
    Ball &b = C[i]; // setup C[i] fields
    b.p = Vec2(random(0.0, width), random(0.0, height)); // position
    b.v = 0.25 * (cp - b.p); // 4 seconds before it may reach the collision point
    b.m = random(0.1, 10.0); // mass
    b.r = random(4.0, 20.0); // radius
    b.color.red   = static_cast<float>(random(0.0, 1.0));
    b.color.green = static_cast<float>(random(0.0, 1.0));
    b.color.blue  = static_cast<float>(random(0.0, 1.0));

    std::cout << i << ")\n" << b;
  }
}


/// gravity-like force, pulling to the screen center
inline void applyCentralForce(Ball &ball)
{
  Vec2 r(0.5 * width - ball.p.x, 0.5 * height - ball.p.y);
  const double dist = (std::max)(r.length(), 1.0);
  r *= G / (dist * dist * dist);
  ball.a += r;
}

//
// GLUT callbacks
//

/// redraw our scene
void repaint()
{
  glClear(GL_COLOR_BUFFER_BIT);
  
  // paint all balls
  for (size_t i = 0; i < N; ++i)
    C[i].paint();

  // finish and present our frame to the user
  glFinish();
  glutSwapBuffers();
}


/// calculate the next frame
void frameMove()
{
  static const double anti_clocks_per_sec = 1.0 / double(CLOCKS_PER_SEC);

  // check time
  std::clock_t t1 = std::clock();
  if(t1 != t)
  {
    const double h = anti_clocks_per_sec * (t1 - t);
    t = t1;
    
    // move
    for (size_t i = 0; i < N; ++i)
    {
      applyCentralForce(C[i]);
      C[i].move(h);
    }
    
    // check for intersection
    for (size_t i = 0; i < N; ++i)
    {
      for (size_t j = i + 1; j < N; ++j)
      {
        const Vec2 dp = C[i].p - C[j].p;
        const double dplen = dp.length();
        const double delta = dplen - (C[i].r + C[j].r);

        if (delta < 0.0) // are overlapping?
        {
          const Vec2 r = 1.0 / dplen * dp;
          if ( (C[i].v - C[j].v) * r < 0.0) // are approaching?
          {
            // collision detected -- calculate new velocities
            const Vec2 u(-r.y, r.x);
            // old velocities in (r, u) basis
            const double
              v1r = C[i].v * r,
              v2r = C[j].v * r,
              v1u = C[i].v * u,
              v2u = C[j].v * u,
            // new velocities
              v1n = k / (C[i].m + C[j].m) * ((C[i].m - C[j].m) * v1r + 2.0 * C[j].m * v2r),
              v2n = k / (C[i].m + C[j].m) * (2.0 * C[i].m * v1r + (C[j].m - C[i].m) * v2r);

            // calculate resulting velocities in original coordinates
            C[i].v = v1n * r + v1u * u;
            C[j].v = v2n * r + v2u * u;
          }
        }
      }
    }
  

    // check if all disks flew out of viewport
    bool new_round = true;
    for (size_t i = 0; new_round && i < N; ++i)
    {
      new_round = new_round &&
        (C[i].p.x < -C[i].r || C[i].p.y < -C[i].r ||
          C[i].p.x > width + C[i].r || C[i].p.y > height + C[i].r);
    }

    if (new_round)
      newBalls();

    // say GLUT that our frame has changed
    glutPostRedisplay();
  }
}


/// work out a keypress
void keyPressed(unsigned char key, int x, int y)
{
  switch (key)
  {
  case 27: // ESC ASCII code
  case 'q':
  case 'Q': // if ASCII fails ;)
    std::cout << "Exiting\n";
    exit(0); // quit

  case ' ':
  case '\r':
  case '\n':
    newBalls();
  
  default:
    ; // ignore it, suppress compiler's possible warning
  }
}


void reshape(int w, int h)
{
  width = static_cast<float>(w);
  height = static_cast<float>(h);

  // setup projection
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0, w, 0.0, h, -100.0, 100.0);
  glMatrixMode(GL_MODELVIEW);

  // setup viewport
  glViewport(0, 0, w, h);
}


//
// main
//

int main(int argc, char **argv)
{
  // initialization
  glutInit(&argc, argv);
  std::srand(static_cast<unsigned>(std::time(nullptr)));

  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_MULTISAMPLE);
  glutInitWindowPosition(100, 100);
  glutInitWindowSize(width, height);
  glutCreateWindow("Circle collision simulation");

  // setup some OpenGL state
  glClearColor(0.0, 0.0, 0.0, 1.0); // default one (black), just to show glClearColor
  glEnable(GL_COLOR_MATERIAL);
  glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
  
  // create a disk image
  glNewList(1, GL_COMPILE);
  glutSolidSphere(1.0, 16, 4);
  glEndList();
  
  // register callbacks
  glutDisplayFunc(repaint);
  glutKeyboardFunc(keyPressed);
  glutReshapeFunc(reshape);
  glutIdleFunc(frameMove);

  // run
  std::cout << "Starting...\n" << "k = " << k << "\n";
  newBalls();
  t = std::clock();
  glutMainLoop();
}


Create a new paste based on this one


Comments: