ekku@222: ekku@197: #include "PhysicsObject.hh" ekku@197: #include "Engine.hh" ekku@197: ekku@197: #include ekku@197: ekku@225: PhysicsObject::PhysicsObject (PhysicsWorld &world, float mass, Vector position, Vector velocity, bool enabled) : terom@264: world(world), position(position), velocity(velocity), mass(mass), inAir(true), aim(0), facing(FACING_RIGHT), ekku@229: alive(false), shouldDelete(false), pivot(NULL) ekku@225: { ekku@225: if (enabled) ekku@225: enable(); ekku@222: } ekku@222: ekku@222: PhysicsObject::~PhysicsObject (void) { ekku@225: // Engine::log(DEBUG, "PhysicsObject.destructor") << this /* << ": objects.size=" << ((int) world.objects.size()) */; ekku@197: } ekku@197: ekku@197: /** ekku@197: * Player walks on floor. ekku@197: */ ekku@197: Vector PhysicsObject::walk_one_step (float partial, bool right) { ekku@197: // which way we are walking ekku@197: float deltaX = right ? partial : -partial; ekku@197: Vector reached = this->position; ekku@197: if(reached.roundToInt() == (reached+Vector(deltaX, 0)).roundToInt()) { ekku@197: return reached+Vector(deltaX, 0); ekku@197: } ekku@197: // Is there upward ramp ekku@197: if(!possibleLocation(position+Vector(deltaX, 0))) { ekku@197: // Yes. Then we check n pixels up ekku@197: for(int i = 1; i < 3; i++) { ekku@197: if(possibleLocation(position+Vector(deltaX, -i))) { ekku@197: // and when there is finally EMPTY, we can walk ekku@197: reached = position+Vector(deltaX, -i); ekku@197: break; ekku@197: } ekku@197: } ekku@197: } else { ekku@197: // Or downward ramp or flat ekku@197: for(int i = 0; 1; i++) { ekku@197: ekku@197: // And when there is finally ground we can step on ekku@197: // it. If there is no gound we still step there, ekku@197: // but will fall one pixel down ekku@197: if(possibleLocation(position+Vector(deltaX, i))) { ekku@197: reached = position+Vector(deltaX, i); ekku@197: } else { ekku@197: break; ekku@197: } ekku@197: ekku@197: // If the fall is big enough, set the worm in the air ekku@197: if (i >= 2) { ekku@197: // Vector back = walk(dt, !right); ekku@197: this->inAir = true; ekku@197: // this->velocity.x = right ? velocity : -velocity; ekku@197: // Avoid stepping two pixels down when it starts to free fall ekku@197: reached.y -= 2; ekku@197: // this->velocity = (reached-back)*1000/dt; ekku@197: break; ekku@197: } ekku@197: } ekku@197: } ekku@197: // And we return where we got ekku@197: return reached; ekku@197: } ekku@197: void PhysicsObject::walk (TimeMS dt, bool right) { ekku@197: float velocity = PLAYER_WALK_SPEED; ekku@197: float walkAmount = (velocity*dt)/1000; ekku@197: Vector reached = this->position; terom@221: while (walkAmount > 0 && !this->inAir) { ekku@197: this->position = walk_one_step((1 < walkAmount ? 1 : walkAmount), right); ekku@197: walkAmount--; ekku@197: } ekku@197: // TODO: Should the remaining walkAmount be handled somehow? ekku@197: } ekku@197: ekku@197: /** ekku@197: * Makes the player jump in the air. ekku@197: * @param direction -1: jump left, 0: jump up, 1: jump right ekku@197: */ ekku@197: void PhysicsObject::jump (int direction) { ekku@197: // Jump only if player is "on the ground" ekku@197: if (!this->inAir) { ekku@197: velocity.y = -100; ekku@197: switch (direction) { ekku@197: case 1: ekku@197: velocity.x += 20; ekku@197: break; ekku@197: case -1: ekku@197: velocity.x -= 20; ekku@197: break; ekku@197: case 0: ekku@197: break; ekku@197: default: ekku@197: throw std::logic_error("Invalid jump direction"); ekku@197: } ekku@197: inAir = true; ekku@197: } ekku@197: } ekku@197: ekku@197: bool PhysicsObject::possibleLocation (Vector loc) { ekku@197: for(unsigned int i = 0; i < this->shape.size(); i++) { ekku@197: if(world.collides(loc+shape[i])) ekku@197: return false; ekku@197: } ekku@197: return true; ekku@197: } ekku@197: ekku@197: /** ekku@197: * Updates object speed and position. This function organises force ekku@197: * integration and collision detection. ekku@197: */ terom@205: void PhysicsObject::updatePosition (TimeMS dt) { ekku@197: // Add gravity to the force queue ekku@197: forceq.push(world.gravity); ekku@197: ekku@228: // If the object (practically player) has a pivot point add ekku@228: // a force towards that ekku@228: if (pivot != NULL) { ekku@228: Vector direction(pivot->getPosition()-position); ekku@228: float magnitude = pivot->getPivotForce(this); ekku@228: float length = direction.length(); ekku@228: if (length > 0) { ekku@228: direction = direction / length * magnitude; ekku@228: forceq.push(direction); ekku@228: } ekku@228: } ekku@197: Force total; ekku@197: while (!forceq.empty()) { ekku@197: total += forceq.front(); ekku@197: forceq.pop(); ekku@197: } ekku@197: ekku@197: // If the player has stopped and there's some ground under some of the 3 some of the 3t ekku@197: // set inAir false ekku@197: if (this->velocity == Vector(0,0)) { ekku@197: this->inAir = !world.collides(this->position+shape[1]+Vector(0, 1)) ekku@197: && !world.collides(this->position+shape[2]+Vector(0, 1)) ekku@197: && !world.collides(this->position+shape[3]+Vector(0, 1)); ekku@197: // If, however, there's a force caused by a bomb, e.g., set it in air. ekku@197: // Still, we have to be able to separate forces caused by walking attempts ekku@197: // and bombs etc (+0.1 because float comparison can be dangerous) ekku@247: if (total.y < 0.01 || abs(total.x) > PLAYER_MOVE_FORCE + 0.1) ekku@197: this->inAir = true; ekku@197: } ekku@197: ekku@197: if(!possibleLocation(position)) { ekku@197: //if we are trapped in ground form dirtball or something ekku@197: //we might want to just return and set velocity to some value ekku@197: //return; ekku@197: } ekku@197: ekku@197: // If the worm is not in the air make it walk, ekku@197: // otherwise integrate the new position and velocity ekku@197: if (!this->inAir) { ekku@197: // It walks only if there's some vertical force ekku@197: if (total.x != 0) { terom@205: walk(dt, total.x > 0); ekku@197: this->velocity = Vector(0,0); ekku@247: } ekku@247: // Now the possible walking has been done so we can return from this function. ekku@247: // In walk inAir could have been set true, but that will be handled in the next tick. ekku@247: return; ekku@197: } ekku@197: ekku@222: if (!possibleLocation(position)) ekku@222: Engine::log(DEBUG, "PhysicsObject.updatePosition") << "impossible location: " << position; ekku@222: ekku@197: Vector newPosition; ekku@197: Vector velAfterTick; ekku@197: // Calculate new position and velocity to the given references terom@205: integrate(total, dt, newPosition, velAfterTick); ekku@197: this->velocity = velAfterTick; ekku@197: ekku@197: // Collision detection ekku@197: bool collided = false; ekku@252: Vector collisionPoint; ekku@197: ekku@197: const Vector diffVec = newPosition-position; ekku@197: const Vector unitVector = diffVec / diffVec.length(); ekku@197: Vector reached = position; ekku@247: ekku@197: while ((position-reached).sqrLength() < diffVec.sqrLength()) { ekku@197: reached += unitVector; ekku@197: // Check if any of the shapes points collide ekku@197: for (uint64_t i = 0; i < shape.size(); i++) { ekku@197: if (world.collides(reached+shape[i])) { // Collision ekku@252: ekku@252: collisionPoint = reached+shape[i]; ekku@252: ekku@197: if (inAir) { ekku@197: this->bounce(world.getNormal(reached+shape[i], ekku@197: reached-unitVector+shape[i])); ekku@197: } ekku@197: reached = reached - unitVector; // Return to last point ekku@197: collided = true; ekku@197: if (this->velocity.sqrLength() < PLAYER_MIN_SPEED * PLAYER_MIN_SPEED) { ekku@197: this->velocity = Vector(0,0); ekku@197: } ekku@197: break; ekku@197: } ekku@197: } ekku@197: if (collided) ekku@197: break; ekku@197: // reached += unitVector; ekku@197: } ekku@197: ekku@197: ekku@222: if (!possibleLocation(reached)) ekku@222: Engine::log(DEBUG, "PhysicsObject.updatePosition") << "impossible location: " << position << ", diffVec=" << diffVec; ekku@197: ekku@197: // In case of some float error check the final coordinate ekku@222: if (!collided) { ekku@222: if (!possibleLocation(newPosition)) { ekku@197: newPosition = reached; ekku@197: } else { ekku@197: // This means everything was ok, so no need to do anything ekku@197: } ekku@222: ekku@222: this->position = newPosition; ekku@222: ekku@197: } else { ekku@197: newPosition = reached; nireco@211: this->position = newPosition; ekku@222: ekku@222: // the following may delete this object, so it must be the last thing called ekku@252: onCollision(collisionPoint); ekku@222: ekku@222: return; ekku@197: } ekku@197: } ekku@197: ekku@197: /** ekku@197: * Bounces from straight wall in any direction. ekku@197: * Direction given as normal of that wall ekku@197: */ ekku@197: void PhysicsObject::bounce (Vector normal) { ekku@197: // normal.sqrLength can't be 0 when got from getNormal() ekku@197: if (normal.sqrLength() != 0) { ekku@197: Vector nvel = velocity; ekku@197: // We project the velocity on normal and remove twice that much from velocity ekku@197: nvel = nvel - ((2)*((nvel*normal)/(normal*normal))*normal); ekku@197: velocity = nvel; ekku@197: // We lose some of our speed on collision ekku@197: this->velocity *= this->collision_elasticity; ekku@197: } ekku@197: } ekku@197: ekku@197: /** ekku@197: * Integrates given force over time and stores new position to ekku@197: * posAfterTick and new velocity to velAfterTick. ekku@197: * @param force Force vector. ekku@197: * @param dt The time the force is applied (<=PHYSICS_TICK_MS) ekku@197: */ ekku@197: void PhysicsObject::integrate(Force force, TimeMS dt, Vector &posAfterTick, Vector &velAfterTick) { ekku@197: posAfterTick = position; ekku@197: velAfterTick = velocity; ekku@197: Derivative tmpd; ekku@197: Derivative k1 = evaluate(force, 0, tmpd, posAfterTick, velAfterTick); ekku@197: Derivative k2 = evaluate(force, 0.5f*dt, k1, posAfterTick, velAfterTick); ekku@197: Derivative k3 = evaluate(force, 0.5f*dt, k2, posAfterTick, velAfterTick); ekku@197: Derivative k4 = evaluate(force, dt, k3, posAfterTick, velAfterTick); ekku@197: ekku@197: ekku@197: const Vector dxdt = (k1.dx + (k2.dx + k3.dx) * 2.0f + k4.dx) * 1.0f/6.0f; ekku@197: const Vector dvdt = (k1.dv + (k2.dv + k3.dv) * 2.0f + k4.dv) * 1.0f/6.0f; ekku@197: ekku@197: // Engine::log(DEBUG, "PhysicsObject.integrate") << "Changes: "<< dxdt << " " << dvdt << " Time: " <aim += da; ekku@197: ekku@197: if (this->aim > PLAYER_AIM_MAX) this->aim = PLAYER_AIM_MAX; ekku@197: if (this->aim < PLAYER_AIM_MIN) this->aim = PLAYER_AIM_MIN; ekku@197: //Engine::log(DEBUG, "PhysicsObject.changeAim") << "Player aim: " << this->aim; ekku@197: } ekku@197: terom@264: void PhysicsObject::setFacing (FacingDirection facing) { terom@264: this->facing = facing; ekku@197: } ekku@197: terom@264: void PhysicsObject::updatePhysics (Vector position, Vector velocity, bool inAir, FacingDirection facing, float aim) { ekku@197: this->position = position; ekku@197: this->velocity = velocity; ekku@197: this->inAir = inAir; terom@264: this->facing = facing; terom@200: this->aim = aim; ekku@197: } ekku@225: terom@257: Vector PhysicsObject::getPosition (void) const { terom@257: return position; ekku@197: } terom@257: terom@257: PixelCoordinate PhysicsObject::getCoordinate (void) const { terom@257: return world.getPixelCoordinate(position); ekku@222: } ekku@222: terom@257: Vector PhysicsObject::getVelocity (void) const { terom@257: return velocity; ekku@197: } ekku@197: terom@264: FacingDirection PhysicsObject::getFacing (void) const { terom@264: return facing; ekku@197: } ekku@197: terom@257: float PhysicsObject::getAim (void) const { terom@257: return aim; terom@257: } terom@257: terom@257: Vector PhysicsObject::getDirection (void) const { terom@264: return facing == FACING_RIGHT ? Vector(cos(aim), -sin(aim)) : Vector(-cos(aim), -sin(aim)); terom@235: } terom@235: terom@257: const std::vector& PhysicsObject::getShape () const { terom@257: return shape; ekku@197: } ekku@197: ekku@197: void PhysicsObject::setShape (std::vector shape) { ekku@197: this->shape = shape; ekku@197: } ekku@197: ekku@228: void PhysicsObject::setPivot (PhysicsObject *pivot) { ekku@228: this->pivot = pivot; ekku@228: } ekku@228: terom@205: void PhysicsObject::tick (TimeMS tick_length) { terom@205: this->updatePosition(tick_length); ekku@197: } ekku@222: ekku@225: void PhysicsObject::enable (void) { terom@241: // only enable once until disabled terom@241: if (alive) terom@241: return; terom@241: terom@241: // mark as alive ekku@225: alive = true; terom@241: terom@241: // add the world objects list ekku@225: world.addPhysicsObject(this); ekku@225: } ekku@225: ekku@225: void PhysicsObject::disable (void) { terom@241: // mark as disabled ekku@225: alive = false; ekku@225: } ekku@225: ekku@222: void PhysicsObject::destroy (void) { terom@241: // mark as disabled and for deletion ekku@222: alive = false; ekku@225: shouldDelete = true; ekku@222: } ekku@222: ekku@222: bool PhysicsObject::isDestroyed (void) { ekku@222: return !alive; ekku@222: } ekku@222: ekku@222: bool PhysicsObject::removeIfDestroyed (void) { ekku@222: if (!alive) { ekku@225: if (shouldDelete) ekku@225: delete this; ekku@225: ekku@222: return true; ekku@225: ekku@222: } else { ekku@222: return false; ekku@222: } ekku@222: } ekku@197: terom@264: float PhysicsObject::getPivotForce (PhysicsObject *bob) { terom@264: return 0.0; ekku@197: } ekku@197: