src/PhysicsObject.cc
changeset 197 d9ac888de778
child 200 2dbf40661580
equal deleted inserted replaced
196:e2d32c4601ce 197:d9ac888de778
       
     1 #include "Player.hh"
       
     2 #include "PhysicsObject.hh"
       
     3 #include "Engine.hh"
       
     4 
       
     5 #include <cmath>
       
     6 
       
     7 PhysicsObject::PhysicsObject (PhysicsWorld &world, float mass, 
       
     8                               Vector position, Vector velocity)
       
     9     : world(world), position(position), velocity(velocity),
       
    10       mass(mass), inAir(true), aim(0), facingRight(true), reloadTimer(0) {
       
    11     // TODO: Is thir the right way to do this?
       
    12     //world.addPlayerObject(this);
       
    13 }
       
    14 
       
    15 /**
       
    16  * Player walks on floor.
       
    17  */
       
    18 Vector PhysicsObject::walk_one_step (float partial, bool right) {
       
    19     // which way we are walking
       
    20     float deltaX = right ? partial : -partial;
       
    21     Vector reached = this->position;
       
    22     if(reached.roundToInt() == (reached+Vector(deltaX, 0)).roundToInt()) {
       
    23         return reached+Vector(deltaX, 0);
       
    24     }
       
    25     // Is there upward ramp
       
    26     if(!possibleLocation(position+Vector(deltaX, 0))) {
       
    27         // Yes. Then we check n pixels up
       
    28         for(int i = 1; i < 3; i++) {
       
    29             if(possibleLocation(position+Vector(deltaX, -i))) {
       
    30                 // and when there is finally EMPTY, we can walk
       
    31                 reached = position+Vector(deltaX, -i);
       
    32                 break;
       
    33             }
       
    34         }
       
    35     } else {
       
    36         // Or downward ramp or flat
       
    37         for(int i = 0; 1; i++) {
       
    38 
       
    39             // And when there is finally ground we can step on
       
    40             // it. If there is no gound we still step there,
       
    41             // but will fall one pixel down
       
    42             if(possibleLocation(position+Vector(deltaX, i))) {
       
    43                 reached = position+Vector(deltaX, i);
       
    44             } else {
       
    45                 break;
       
    46             }
       
    47             
       
    48             // If the fall is big enough, set the worm in the air
       
    49             if (i >= 2) {
       
    50 //                Vector back = walk(dt, !right);
       
    51                 this->inAir = true;
       
    52 //                this->velocity.x = right ? velocity : -velocity;
       
    53                 // Avoid stepping two pixels down when it starts to free fall
       
    54                 reached.y -= 2;
       
    55 //                this->velocity = (reached-back)*1000/dt;
       
    56                 break;
       
    57             }
       
    58         }
       
    59     }
       
    60     // And we return where we got
       
    61     return reached;
       
    62 }
       
    63 void PhysicsObject::walk (TimeMS dt, bool right) {
       
    64     float velocity = PLAYER_WALK_SPEED;
       
    65     float walkAmount = (velocity*dt)/1000;
       
    66     Vector reached = this->position;
       
    67     while(walkAmount > 0 && !this->inAir) {
       
    68         this->position = walk_one_step((1 < walkAmount ? 1 : walkAmount), right);
       
    69         walkAmount--;
       
    70     }
       
    71     // TODO: Should the remaining walkAmount be handled somehow?
       
    72 }
       
    73 
       
    74 /**
       
    75  * Makes the player jump in the air.
       
    76  * @param direction -1: jump left, 0: jump up, 1: jump right
       
    77  */
       
    78 void PhysicsObject::jump (int direction) {
       
    79     // Jump only if player is "on the ground"
       
    80     if (!this->inAir) {
       
    81  	    velocity.y = -100;
       
    82         switch (direction) {
       
    83             case 1:
       
    84                 velocity.x += 20;
       
    85                 break;
       
    86             case -1:
       
    87                 velocity.x -= 20;
       
    88                 break;
       
    89             case 0:
       
    90                 break;
       
    91             default:
       
    92                 throw std::logic_error("Invalid jump direction");
       
    93         }
       
    94 	    inAir = true;
       
    95     }
       
    96 }
       
    97 
       
    98 bool PhysicsObject::possibleLocation (Vector loc) {
       
    99     for(unsigned int i = 0; i < this->shape.size(); i++) {
       
   100         if(world.collides(loc+shape[i]))
       
   101             return false;
       
   102     }
       
   103     return true;
       
   104 }
       
   105 
       
   106 void func1() {
       
   107 
       
   108 }
       
   109 
       
   110 /**
       
   111  * Updates object speed and position. This function organises force
       
   112  * integration and collision detection.
       
   113  */   
       
   114 void PhysicsObject::updatePosition () {
       
   115 
       
   116     // Reloads weapon if not reloaded
       
   117     reloadTimer -= PHYSICS_TICK_MS;
       
   118     if(reloadTimer < 0)
       
   119         reloadTimer = 0;
       
   120 
       
   121     // Add gravity to the force queue
       
   122     forceq.push(world.gravity);
       
   123     
       
   124     // Go trough every force in the queue
       
   125     Force total;
       
   126     while (!forceq.empty()) {
       
   127         total += forceq.front();
       
   128         forceq.pop();
       
   129     }
       
   130 
       
   131     // If the player has stopped and there's some ground under some of the 3 some of the 3t
       
   132     // set inAir false
       
   133     if (this->velocity == Vector(0,0)) {
       
   134         this->inAir = !world.collides(this->position+shape[1]+Vector(0, 1))
       
   135                       && !world.collides(this->position+shape[2]+Vector(0, 1))
       
   136                       && !world.collides(this->position+shape[3]+Vector(0, 1));
       
   137         // If, however, there's a force caused by a bomb, e.g., set it in air.
       
   138         // Still, we have to be able to separate forces caused by walking attempts
       
   139         // and bombs etc (+0.1 because float comparison can be dangerous)
       
   140         if (total.y < 0 || abs(total.x) > PLAYER_MOVE_FORCE + 0.1)
       
   141             this->inAir = true;
       
   142     }
       
   143 
       
   144     if(!possibleLocation(position)) {
       
   145         //if we are trapped in ground form dirtball or something
       
   146         //we might want to just return and set velocity to some value
       
   147         //return;
       
   148     }
       
   149 
       
   150     // If the worm is not in the air make it walk,
       
   151     // otherwise integrate the new position and velocity
       
   152     if (!this->inAir) {
       
   153         //std::cout << "Tryin to walk" << std::endl;
       
   154         // It walks only if there's some vertical force
       
   155         if (total.x != 0) {
       
   156             std::cout << "Succeeding to walk" << std::endl;
       
   157             walk(PHYSICS_TICK_MS, total.x > 0);
       
   158             this->velocity = Vector(0,0);
       
   159         }
       
   160     }
       
   161 
       
   162     if(!possibleLocation(position)) {
       
   163         Engine::log(DEBUG, "great failure") << "great failure";
       
   164         func1();
       
   165     }
       
   166     Vector newPosition;
       
   167     Vector velAfterTick;
       
   168     // Calculate new position and velocity to the given references
       
   169     integrate(total, PHYSICS_TICK_MS, newPosition, velAfterTick);
       
   170     this->velocity = velAfterTick;
       
   171 
       
   172    
       
   173     // Collision detection
       
   174     bool collided = false;
       
   175    
       
   176     const Vector diffVec = newPosition-position;
       
   177     const Vector unitVector = diffVec / diffVec.length();
       
   178     Vector reached = position;
       
   179 
       
   180     while ((position-reached).sqrLength() < diffVec.sqrLength()) {
       
   181         reached += unitVector;
       
   182         // Check if any of the shapes points collide
       
   183         for (uint64_t i = 0; i < shape.size(); i++) {
       
   184             if (world.collides(reached+shape[i])) {  // Collision
       
   185                 if (inAir) {
       
   186                     //                    Engine::log(DEBUG, "Here");
       
   187                     this->bounce(world.getNormal(reached+shape[i], 
       
   188                                                  reached-unitVector+shape[i]));
       
   189                     //this->velocity *= COLLISION_ELASTICITY;
       
   190                 }
       
   191                 reached = reached - unitVector; // Return to last point
       
   192                 collided = true;
       
   193                 if (this->velocity.sqrLength() < PLAYER_MIN_SPEED * PLAYER_MIN_SPEED) {
       
   194                     this->velocity = Vector(0,0);
       
   195                 }
       
   196                 break;
       
   197             }
       
   198         }
       
   199         if (collided)
       
   200             break;
       
   201 //        reached += unitVector;
       
   202     }
       
   203    
       
   204     
       
   205     if(!possibleLocation(reached)) {
       
   206         Engine::log(DEBUG, "PhysicsObject.updatePosition") << "logic error reached should not be possible to be impossible.. diffVec: " << diffVec;
       
   207         func1();
       
   208     }
       
   209 
       
   210     // In case of some float error check the final coordinate
       
   211     if(!collided) {
       
   212         if(!possibleLocation(newPosition)) {
       
   213             newPosition = reached;
       
   214         } else {
       
   215             // This means everything was ok, so no need to do anything
       
   216         }
       
   217     } else {
       
   218         newPosition = reached;
       
   219         onCollision();
       
   220         //this->velocity = Vector(0, 0);
       
   221         //TODO: it shouldn't just stop on collision
       
   222     }
       
   223     if(!possibleLocation(newPosition)) {
       
   224         Engine::log(DEBUG, "great failure") << "great failure";
       
   225         func1();
       
   226     }
       
   227     this->position = newPosition;
       
   228     if(!possibleLocation(position)) {
       
   229         Engine::log(DEBUG, "great failure") << "great failure";
       
   230         func1();
       
   231     }
       
   232 //    Engine::log(DEBUG, "PhysicsObject.updatePosition") << "Pos: " << this->position;
       
   233 }
       
   234 
       
   235 /**
       
   236  * Bounces from straight wall in any direction.
       
   237  * Direction given as normal of that wall
       
   238  */
       
   239 void PhysicsObject::bounce (Vector normal) {
       
   240     // normal.sqrLength can't be 0 when got from getNormal()
       
   241     if (normal.sqrLength() != 0) {
       
   242         Vector nvel = velocity;
       
   243         // We project the velocity on normal and remove twice that much from velocity
       
   244         nvel = nvel - ((2)*((nvel*normal)/(normal*normal))*normal);
       
   245         velocity = nvel;
       
   246         // We lose some of our speed on collision
       
   247         this->velocity *= this->collision_elasticity;
       
   248     }
       
   249 }
       
   250 
       
   251 /**
       
   252  * Integrates given force over time and stores new position to
       
   253  * posAfterTick and new velocity to velAfterTick.
       
   254  * @param force Force vector.
       
   255  * @param dt The time the force is applied (<=PHYSICS_TICK_MS)
       
   256  */
       
   257 void PhysicsObject::integrate(Force force, TimeMS dt, Vector &posAfterTick, Vector &velAfterTick) {
       
   258     posAfterTick = position;
       
   259     velAfterTick = velocity;
       
   260     Derivative tmpd;
       
   261     Derivative k1 = evaluate(force, 0, tmpd, posAfterTick, velAfterTick);
       
   262     Derivative k2 = evaluate(force, 0.5f*dt, k1, posAfterTick, velAfterTick);
       
   263     Derivative k3 = evaluate(force, 0.5f*dt, k2, posAfterTick, velAfterTick);
       
   264     Derivative k4 = evaluate(force, dt, k3, posAfterTick, velAfterTick);
       
   265     
       
   266 
       
   267     const Vector dxdt = (k1.dx + (k2.dx + k3.dx) * 2.0f + k4.dx) * 1.0f/6.0f;
       
   268     const Vector dvdt = (k1.dv + (k2.dv + k3.dv) * 2.0f + k4.dv) * 1.0f/6.0f;
       
   269     
       
   270     //    Engine::log(DEBUG, "PhysicsObject.integrate") << "Changes: "<< dxdt << " " << dvdt << " Time: " <<dt;
       
   271     posAfterTick = posAfterTick + (dxdt * dt)/1000;
       
   272     velAfterTick = velAfterTick + (dvdt * dt)/1000;
       
   273     //Engine::log(DEBUG, "PhysicsObject.integrate") << "velAfterTick: " << velAfterTick;
       
   274 }
       
   275 
       
   276 Derivative PhysicsObject::evaluate(Force force, TimeMS dt, Derivative &d, const Vector &posAfterTick, const Vector &velAfterTick) {
       
   277     Vector curPos = posAfterTick + (d.dx*dt)/1000;
       
   278     Vector curVel = velAfterTick + (d.dv*dt)/1000;
       
   279 
       
   280     Derivative out;
       
   281     out.dx = curVel;
       
   282     out.dv = acceleration(force);
       
   283     //Engine::log(DEBUG, "PhysicsObject.evaluate") << "Out.dx: " << out.dx;
       
   284     return out;
       
   285 }
       
   286 
       
   287 Vector PhysicsObject::acceleration(const Force &force) {
       
   288     return (force/mass);
       
   289 }
       
   290 
       
   291 void PhysicsObject::applyForce (Force force) {
       
   292     // Add applied force to the queue
       
   293     forceq.push(force);
       
   294 }
       
   295 
       
   296 void PhysicsObject::changeAim(float da) {
       
   297     this->aim += da;
       
   298 
       
   299     if (this->aim > PLAYER_AIM_MAX) this->aim = PLAYER_AIM_MAX;
       
   300     if (this->aim < PLAYER_AIM_MIN) this->aim = PLAYER_AIM_MIN;
       
   301     //Engine::log(DEBUG, "PhysicsObject.changeAim") << "Player aim: " << this->aim;
       
   302 }
       
   303 
       
   304 void PhysicsObject::setFacing(bool facingRight) {
       
   305     //Engine::log(DEBUG, "PhysicsObject.setFacing") << "Facing: " << right;
       
   306     this->facingRight = facingRight;
       
   307 }
       
   308 
       
   309 void PhysicsObject::updatePhysics (Vector position, Vector velocity, bool inAir) {
       
   310     this->position = position;
       
   311     this->velocity = velocity;
       
   312     this->inAir = inAir;
       
   313 }
       
   314     
       
   315 Vector PhysicsObject::getPosition () {
       
   316     return this->position;
       
   317 }
       
   318 
       
   319 bool PhysicsObject::getFacing() {
       
   320     return this->facingRight;
       
   321 }
       
   322 
       
   323 float PhysicsObject::getAim() {
       
   324     return this->aim;
       
   325 }
       
   326 
       
   327 std::vector<Vector>& PhysicsObject::getShape () {
       
   328     return this->shape;
       
   329 }
       
   330 
       
   331 void PhysicsObject::setShape (std::vector<Vector> shape) {
       
   332     this->shape = shape;
       
   333 }
       
   334 
       
   335 void PhysicsObject::tick () {
       
   336     this->updatePosition();
       
   337 }
       
   338 
       
   339 bool PhysicsObject::canShoot() {
       
   340     return this->reloadTimer <= 0;
       
   341 }
       
   342 
       
   343 void PhysicsObject::draw(CL_GraphicContext *gc) {
       
   344     CL_Quad player(
       
   345                    (position+shape[0]).x, (position+shape[0]).y,
       
   346                    (position+shape[1]).x, (position+shape[1]).y,
       
   347                    (position+shape[2]).x, (position+shape[2]).y,
       
   348                    (position+shape[3]).x, (position+shape[3]).y
       
   349                    );
       
   350     
       
   351     gc->fill_quad(player, CL_Color::green);
       
   352     
       
   353     const uint16_t chlen = 10;
       
   354     uint16_t x = player.center().x;
       
   355     uint16_t y = player.center().y;
       
   356     if (facingRight) {
       
   357         gc->draw_line(x, y,
       
   358                       x + std::cos(aim)*chlen,
       
   359                       y - std::sin(aim)*chlen,
       
   360                       CL_Color::black);
       
   361     } else {
       
   362         gc->draw_line(x, y,
       
   363                       x - std::cos(aim)*chlen,
       
   364                       y - std::sin(aim)*chlen,
       
   365                       CL_Color::black);
       
   366     }
       
   367 }
       
   368