Kirjautuminen

Haku

Tehtävät

Koodit: C++: Törmäysfysiikkaa

Kirjoittaja: os

Kirjoitettu: 08.07.2005 – 08.07.2005

Tagit: grafiikka, koodi näytille, vinkki

Esimerkki kaksiulotteisten kappaleiden (tässä monikulmioiden) kitkattomien törmäysten mallintamisesta. Mallia voi periaatteessa soveltaa mihin tahansa kaksiulotteisiin kappaleisiin - kuten bittikarttoihin, sikäli, kun törmäyspinta voidaan määrittää.

Mukana myös luokat tasovektoreille ja niiden välisille laskutoimituksille, sekä törmäytettäville monikulmioille.

Esimerkkiohjelma osoitteessa "http://olento.dyndns.org/docs/kinematics.zip"

#include "object.h"

bool get_collision_normal(vector_xy &, double &, vector_xy,
                          vector_xy, vector_xy *, int);

/* Funktio selvittää kappaleen a kärkien sijainnin kappaleen b reunoihin
   nähden ja muuttaa tarvittaessa kappaleiden nopeudet. Tarkistus täytyy
   siis tehdä kahteen kertaan (a-b, b-a), mikäli törmäystä ei ensimmäisellä
   kerralla havaita. */
bool collide_objects(object *a, object *b) {
    double impulse_magnitude, // impulssin voimakkuus
           relative_velocity, /* törmäävän kärjen ja reunan välisen nopeuden
                                 törmäyspinnan normaalin suuntainen komponentti
                              */
           collision_depth,   // törmäyksen arvioitu "syvyys"
           backface_distance;

    vector_xy impulse, // impulssi
              distance, // kappaleiden painopisteiden välinen etäisyys
              vertex_position, // a:n mahdollisesti törmäävän kärjen paikka
              vertex_speed, // kärjen ratanopeus suhteessa törmäyspintaan
              torque_length, // kärjen etäisyys a:n painopisteestä
              edge_torque_length, // törmäyspinnan etäisyys b:n painopisteestä
              collision_normal, // törmäyspinnan normaali, impulssin suunta
              backface_normal;

    distance = a->position - b->position;
    // kappaleet ovat liian kaukana toisistaan, ei törmäystä
    if(distance.square()>(a->size+b->size)*(a->size+b->size)) return false;

    // tarkistetaan a:n jokaisen kärjen paikka
    for(int i=0; i<a->vertex_amount; i++) {
      torque_length = a->global_vertices[i] - a->position;
      vertex_position = a->global_vertices[i];
      /* Jos a:n kärki i on juuri törmännyt monikulmion b sivuun, se on
         (ideaalitilanteessa) hyvin lähellä törmäyspintaa. Näin ollen
         mahdollisen törmäyspinnan ratanopeus voidaan laskea suoraan kärjen
         ja b:n painopisteen välistä etäisyyttä hyödyntäen - tietämättä
         edes, mihin b:n sivuun a:n kärki on törmännyt.
      */
      edge_torque_length = vertex_position - b->position;
      // lasketaan kärjen ja mahdollisen törmäyspinnan välinen nopeus
      vertex_speed = a->speed + scalar_cross(torque_length, a->rotation) -
                     b->speed - scalar_cross(edge_torque_length, b->rotation);

      /* Selvitetään, onko kärki i joutunut monikulmion b sisään, ja jos on,
         niin minkä sivujanan se on matkallaan leikannut */
      if(get_collision_normal(collision_normal, collision_depth,
                              vertex_position, -vertex_speed,
                              b->global_vertices, b->vertex_amount)) {
       get_collision_normal(backface_normal, backface_distance,
                            vertex_position, vertex_speed,
                            b->global_vertices, b->vertex_amount);
       /* Jos monikulmion takaseinä on lähempänä kuin havaittu törmäyspinta,
          on kärki (luultavasti) jo matkalla ulos monikulmiosta, ja törmäys
          on näinollen jo tapahtunut.
       */
       if(backface_distance < collision_depth) continue;

       /* lasketaan törmäävien pisteiden välisen nopeuden
          törmäyspinnan normaalin suuntainen komponentti */
       relative_velocity = dot(collision_normal, vertex_speed);

       /* lasketaan impulssin voimakkuus oheisella kaavalla
         (löytyy osoitteesta http://www.cs.unc.edu/~ehmann/RigidTutorial/)

                                 - (1 + e) v
       I =   _______________________________r___________________________
                          _0    _   _0    _        _0    _   _0    _
            1/m  + 1/m  + n * ((r x n ) x r )/J  + n * ((r x n ) x r )/J
               a      b          a         a   a          b         b   b

          missä:
           e  = kimmokerroin (tässä laskettu a:n ja b:n kimmokerrointen tulona)
           vr = ks. "relative_velocity"
           ma = kappaleen a massa
           mb = b:n massa
           n  = törmäyspinnan normaali
           ra = törmäyspisteen etäisyys a:n painopisteeseen
           rb = vastaava vektori kappaleelle b
           Ja = kappaleen a hitausmomentti
           Jb = kappaleen b hitausmomentti

           /  = jakolasku
           *  = vektorien pistetulo
           x  = vektorien ristitulo
           0  = vektorin normalisointi
           _  = vektorimerkki

       */
       impulse_magnitude = -(1.0 + a->e*b->e)*relative_velocity /
         ( 1.0 / a->mass + 1.0 / b->mass
         + dot(
            double_cross(torque_length, collision_normal),
            collision_normal) / a->inertia
         + dot(
            double_cross(edge_torque_length, collision_normal),
            collision_normal) / b->inertia
        );

        impulse = collision_normal * impulse_magnitude;

        // päivitetään kappaleiden nopeudet ja kulmanopeudet
        a->speed += impulse * (1.0 / a->mass);
        b->speed -= impulse * (1.0 / b->mass);
        a->rotation += cross_length(torque_length, impulse) / a->inertia;
        b->rotation -= cross_length(edge_torque_length, impulse) / b->inertia;
        return true;
       }
    }
    return false;
}

/* Selvitetään, onko piste p joutunut monikulmion pg sisään, ja jos on,
   niin minkä sivujanan se on matkallaan leikannut */
bool get_collision_normal(vector_xy &normal, // törmäyspinnan normaali
                          double &distance, // törmäyksen "syvyys"
                          vector_xy p, // mahdollisesti törmäävä kärki
                          vector_xy r, // törmäyksen suunta
                          vector_xy *pg, // mahdollisesti törmäävä monikulmio
                          int n // monikulmion kulmien määrä
                          ) {

/*
    Sovelletaan "even-odd" -sääntöä:
      "Piste on suljetun monikulmion sisällä, joss pisteestä lähtevä
       mielivaltainen puolisuora leikkaa parittoman määrän monikulmion
       sivujanoja."

    Valitaan puolisuoran suunnaksi pisteen p "tulosuunta", r,
    ja etsitään lähin sivujana, jonka puolisuora leikkaa. Tämä
    on todennäköisesti sivujana, johon kärki p on törmännyt.
*/

      int intersections = 0;
      double least_distance, d, edge_length;
      vector_xy vertex, edge;

      for(int i=0; i<n; i++) {
        vertex=pg[i];
        if(i!=n-1) edge = pg[i+1] - pg[i];
        else edge = pg[0] - pg[i];

        if(edge.x==0.0) {
          if(r.x==0.0) continue;
          distance = (vertex.x - p.x) / r.x;
        } else {
          d = r.y - edge.y/edge.x*r.x;
          if(d==0.0) continue;
          distance = (vertex.y - p.y + edge.y/edge.x*(p.x - vertex.x)) / d;
        }

        edge_length = dot(r*distance+p-vertex, edge);
        if(distance<0.0 || edge_length >= edge.square() || edge_length < 0.0)
          continue;

        if(!intersections++ || distance<least_distance) {
          least_distance = distance;
          normal = edge;
        }
      }

      // jos leikkausten määrä on parillinen, piste ei ole monikulmion sisällä
      if(!(intersections & 0x1)) return false;

      // tallennetaan törmäyspinnan normaali ja törmäyksen arvioitu syvyys
      if(dot(r,edge)<0.0) normal = scalar_cross(normal, -1);
      else normal = scalar_cross(normal, 1);
      normal.normalize();
      distance = least_distance;
      return true;
}

object.h

#include "vector.h"
#define I_RES 1000

bool is_inside_polygon(vector_xy, vector_xy *, int);

/* Luokka yhtenäisille, suljetuille, kaksiulotteisille kulmikkaille
   kappaleille. Kärjet yhdistetään siinä järjestyksessä, kun ne syötetään,
   siten että viimeinen kärki yhdistetään vielä ensimmäiseen.
   Monikulmion ei siis tarvitse olla konveksi. */
class object {
  public:
  // Muuttumattomat ominaisuudet
    int vertex_amount; // kärkien määrä
    vector_xy *local_vertices; // kärkien sijainnit painopisteeseen nähden
    double mass,        // massa
           inertia,     // hitausmomentti
           size,        // suurin mahdollinen säde
           e;           // kimmokerroin

  // Muuttuvat ominaisuudet (päivitetään kerran aikayksikössä)
    double angle,       // vaihekulma (rad)
           rotation;    // kulmanopeus
    vector_xy position; // kappaleen (painopisteen) sijainti
    vector_xy speed;    // nopuesvektori
    vector_xy *global_vertices; // kärkien paikat globaalissa koordinaatistossa

    object(vector_xy *vertices, int number_of_vertices,
            double m, double cr, vector_xy pos, vector_xy sp,
            double rot, double a) {
          mass = m;
          position = pos;
          speed = sp;
          rotation = rot;
          angle = a;
          e = cr;
          vertex_amount = number_of_vertices;
          local_vertices = vertices;
          global_vertices = new vector_xy[vertex_amount];
          calculate_inertia();
    }

    ~object() { delete [] global_vertices; delete [] local_vertices; }

    void move() { // kutsutaan kerran aikayksikössä (framessa)
      position += speed;
      angle += rotation;

      if(angle>2.0*PI) angle-=2.0*PI;
      if(angle<0.0) angle+=2.0*PI;

      vector_xy v(cos(angle), sin(angle));

      // lasketaan kärkien paikat globaalissa koordinaatistossa
      for(int i=0; i<vertex_amount; i++)
        global_vertices[i] =
          vector_transformation(local_vertices[i], v) + position;
    }

    void calculate_inertia() {
    /* Selvitetään painopisteen sijainti ja hitausmomentti numeerisesti
       integroimalla ja kiinnitetään painopiste koordinaatiston origoon
       muuntamalla kärkien koordinaatteja

       Syötettyjen koordinaattien (local_vertices) on oltava positiivisia
    */
      char *buffer = new char[I_RES*I_RES];
      double max_size = local_vertices[0].x;

      for(int i=0; i<vertex_amount; i++) {
        if(local_vertices[i].x>max_size) max_size = local_vertices[i].x;
        if(local_vertices[i].y>max_size) max_size = local_vertices[i].y;
      }

      int area = 0, x_sum = 0, y_sum = 0;
      double d = (double)max_size / (double)I_RES, // "mittakaava"
             com_x, com_y, // painopisteen koordinaatit
             r; // kappaleen koko, kaukaisimman kärjen etäisyys painopisteestä

      inertia = 0.0; // hitausmomentti

      // piirretään monikulmio bittikartaksi puskuriin, ...
      for(int y=0; y<I_RES; y++)
      for(int x=0; x<I_RES; x++)
       if(is_inside_polygon(_vector_xy((double)x*d,(double)y*d),
                            local_vertices, vertex_amount)) {
          buffer[y*I_RES+x] = true;
          area++;
          x_sum += x;
          y_sum += y;
       } else buffer[y*I_RES+x] = 0;

      // ... lasketaan painopisteen koordinaatit ...
      com_x = (double)x_sum * d / (double)area;
      com_y = (double)y_sum * d / (double)area;

      // ... sekä kappaleen hitausmomentti ...
      for(int y=0; y<I_RES; y++)
      for(int x=0; x<I_RES; x++)
        if(buffer[y*I_RES+x]) {
          r = (x*d-com_x)*(x*d-com_x)+(y*d-com_y)*(y*d-com_y);
          if(r>size) size=r; // ... ja mitat
          inertia += r;
        }

      // siirretään kärkiä siten, että painopiste on origossa
      for(int i=0; i<vertex_amount; i++)
        local_vertices[i] -= _vector_xy(com_x, com_y);

      // hitausmomentti, J = sum(x² + y²) / A * m
      inertia *= mass / (double)area;
      size = sqrt(size);
      delete [] buffer;
    }
};

// Selvittää, onko piste monikulmion sisällä soveltaen "even-odd" -sääntöä
bool is_inside_polygon(vector_xy p, vector_xy *vertices, int n) {
      unsigned intersections = 0;
      double v1x, v1y, v2x, v2y;
      for(int i=0; i<n; i++) {
        v1x = vertices[i].x; v1y = vertices[i].y;
        if(i!=n-1) { v2x = vertices[i+1].x; v2y = vertices[i+1].y; }
        else { v2x = vertices[0].x; v2y = vertices[0].y; }

        if((v1x < p.x && v2x < p.x) ||
           (v1y > p.y && v2y > p.y) ||
           (v1y < p.y && v2y < p.y)) continue;
        if((v1x>=p.x && v2x>=p.x) || (v1y==v2y)) { intersections++; continue; }
        if(  -(v1y-p.y)*(v2x-v1x)/(v2y-v1y)+v1x >= p.x) intersections++;
      }
      return (intersections & 0x1);
}

vector.h

#include <math.h>
#define PI 3.1415926535897932384626433832795

struct vector_xy
{
    double x;
    double y;

  vector_xy() {}
  vector_xy(double x2, double y2) { x = x2; y = y2; }
  void set(double x2, double y2) { x = x2; y = y2; }

  void operator=(vector_xy v) { x = v.x; y = v.y; }
  void operator+=(vector_xy v) { x += v.x; y += v.y; }
  void operator-=(vector_xy v) { x -= v.x; y -= v.y; }
  void operator*=(double r) { x *= r; y *= r; }

  vector_xy operator+(vector_xy v) { vector_xy p(x+v.x,y+v.y); return p; }
  vector_xy operator-(vector_xy v) { vector_xy p(x-v.x,y-v.y); return p; }
  vector_xy operator*(double a) { vector_xy p(x*a,y*a); return p; }
  vector_xy operator-() { vector_xy p(-x,-y); return p; }

  double length() { return sqrt(x*x+y*y); }
  double square() { return x*x+y*y; }
  void normalize() { double l = length(); if (l) { x /= l; y /= l; }}

};

// pistetulo
double dot(vector_xy a, vector_xy b) { return a.x*b.x + a.y*b.y; }

// | a x b |
double cross_length(vector_xy a, vector_xy b) {
  return a.x*b.y - a.y*b.x;
}

vector_xy _vector_xy(double x, double y) {
    vector_xy v(x,y);
    return v;
}

// v:n ja z-akselin suuntaisen s-pituisen vektorin ristitulo
vector_xy scalar_cross(vector_xy v, double s) {
    vector_xy u(-v.y*s, v.x*s);
    return u;
}

// a x b x a
vector_xy double_cross(vector_xy a, vector_xy b) {
    vector_xy v(a.y*a.y*b.x - a.x*a.y*b.y, a.x*a.x*b.y - a.x*a.y*b.x);
    return v;
}

// kerrotaan vektori v vektoria t vastaavalla kompleksiluvulla (t.x + t.y*i)
vector_xy vector_transformation(vector_xy v, vector_xy t) {
    vector_xy r(v.x*t.x - v.y*t.y, v.x*t.y + t.x*v.y);
    return r;
}

Kommentit

T.M. [08.07.2005 20:09:44]

#

Siisti, vielä kun saisi massan noille kappaleille, että tulis ripaus realistisuutta peliin :)

Merri [08.07.2005 21:37:28]

#

Muuten hyvä, mutta tuntuu käynnistyvän yllättävän kauan ja ei tunnu ottavan huomioon useampaa yhtäaikaista törmäystä, jolloin objekti voi mennä toisen sisälle hetkeksi kun sattuu kaksi tai useampi törmäys yhteen objektiin samanaikaisesti. Siinä onkin jo pohtimista kerrakseen.

os [09.07.2005 13:00:14]

#

lainaus:

Muuten hyvä, mutta tuntuu käynnistyvän yllättävän kauan

Käynnistysviive johtuu hitausmomenttien ja painopisteiden laskennasta ("calculate_inertia()") ja lyhennee, jos numeerisen integroinnin tarkkuutta ("I_RES") pienennetään.

lainaus:

objekti voi mennä toisen sisälle hetkeksi kun sattuu kaksi tai useampi törmäys yhteen objektiin samanaikaisesti.

Kappaleiden sisäkkäin meneminen on tosiaan ongelma - varsinkin painovoiman ja pienten kimmokerrointen tapauksessa, jolloin kappaleiden tulisi levätä toistensa päällää.

Olli Vanhoja [10.07.2005 03:50:56]

#

Massa olis kiva :/
Yritä joku threadaus kehittää niin ei pääse kappaleet sisäkkäin ;) Aiheesta löytynee osoitteesta: http://www.osdever.net/

os [10.07.2005 16:04:47]

#

lainaus:

Massa olis kiva :/

Ohjelma lukee asetukset mukana olevasta tiedostosta "settings.cfg". Oletusarvoilla (ilman tiedostoa) kappaleiden nopeus on laskentatarkkuuteen verrattuna liian suuri, ja kappaleet ovat kokoonsa nähden liian kevyitä (kaikilla kappaleilla on kyllä massa, tosin joskus hyvin pieni)

thefox [11.07.2005 14:29:46]

#

lainaus:

Yritä joku threadaus kehittää niin ei pääse kappaleet sisäkkäin ;) Aiheesta löytynee osoitteesta: http://www.osdever.net/

Missä määrin säikeiden käyttäminen auttaa kappaleiden sisäkkäisyys -ongelmassa?

Itse vinkki näyttää hyvältä; asia on esitetty selkeästi ja kommentointi on runsasta.

rndprogy [11.07.2005 19:30:31]

#

merri kirjoitti:

ei tunnu ottavan huomioon useampaa yhtäaikaista törmäystä, jolloin objekti voi mennä toisen sisälle hetkeksi kun sattuu kaksi tai useampi törmäys yhteen objektiin samanaikaisesti.

Tuli mieleen että elastomaniassa(yks peli, kyl varmaan tiiätte :P) on tällainen bugi.

tesmu [12.07.2005 12:35:54]

#

Kieltämättä aika hien

Ja tottakai jokainen tietää elastomanian (:

Olli Vanhoja [12.07.2005 22:22:34]

#

No en ehtinyt koodin tutustua joten heitin vaan, että saattaa auttaa ;)

Markus [13.07.2005 13:28:54]

#

Lukekaa myös:
http://www.suomipelit.com/nayta_artikkeli.php?id­=67&tyyppi=0

water flea [06.07.2006 15:20:44]

#

Ei sillä etteikö olisi jo vuoden vanha mutta kun pitää päällä 15 tuntia niin liikkuvat käsittämätöntä vauhtia ja 2 vuorokauden kohdalla kappaleita ei enää ruudulla näy.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta