/*************************************************************************** BounceBehavior.cpp - description ------------------- begin : Thu Mar 9 2000 copyright : (C) 2000 by Henrik Enqvist email : henqvist@excite.com ***************************************************************************/ #include "Private.h" #include "BounceBehavior.h" #include "Group.h" #include "Pinball.h" #include "Keyboard.h" #include "StateBehavior.h" #include "ArmBehavior.h" #include "PlungerBehavior.h" #include "BumperBehavior.h" #include "Score.h" #include "BallGroup.h" #include "Table.h" #include "Loader.h" #include #if EM_DEBUG #include #endif // these values are pure guesses Vertex3D g_vtxArmTable[] = { {0.07f, 0.0f, -0.07f}, {0.09f, 0.0f, -0.18}, {0.1f, 0.0f, -0.33}, {0.07f, 0.0f, -0.45f}, {0.0f, 0.0f, -0.5f}, {-0.11f, 0.0f, -0.5f}, {-0.15f, 0.0f, -0.3f}, {-0.14f, 0.0f, -0.14f} }; BounceBehavior::BounceBehavior(int ball) : Behavior() { m_iBall = ball; m_bAlive = false; m_vtxDir.x = 0.0; m_vtxDir.y = 0.0; m_vtxDir.z = 0.0; m_vtxOldDir.x = 0.0; m_vtxOldDir.y = 0.0; m_vtxOldDir.z = 0.0; m_iDirFactor = 0; m_iCollisionPrio = 0; m_bFire = false; m_bStopped = false; this->setType(PBL_TYPE_BOUNCEBEH); } BounceBehavior::~BounceBehavior() { } /* The current direction is need by behaviors objects of other balls. * The upcomming direction may change, therefore we need to give the old direction. */ void BounceBehavior::getDirection(Vertex3D & vtx) { vtx.x = m_vtxOldDir.x; vtx.y = m_vtxOldDir.y; vtx.z = m_vtxOldDir.z; } void BounceBehavior::activateBall(float x, float y, float z) { m_bAlive = true; this->getParent()->setTranslation(x, y, z); m_vtxDir.x = 0; m_vtxDir.y = 0; m_vtxDir.z = 0; } void BounceBehavior::StdOnSignal() { EM_COUT("BounceBehavior::onSignal()", 0); OnSignal( PBL_SIG_RESET_ALL) { m_bAlive = false; m_bStopped = false; this->getParent()->setTranslation(-4*m_iBall, 0, 40); } OnSignal( PBL_SIG_BNUDGE ) { m_vtxDir.z -= SPEED_FCT*0.1f; } ElseOnSignal( PBL_SIG_TNUDGE ) { m_vtxDir.z += SPEED_FCT*0.1f; } ElseOnSignal( PBL_SIG_LNUDGE ) { m_vtxDir.x -= SPEED_FCT*0.1f; } ElseOnSignal( PBL_SIG_RNUDGE ) { m_vtxDir.x += SPEED_FCT*0.1f; } ElseOnSignal( Loader::getInstance()->getSignal("stop_balls") ) { m_bStopped = true; } ElseOnSignal( Loader::getInstance()->getSignal("resume_balls") ) { m_bStopped = false; } } void BounceBehavior::checkSpeed() { float len = EMath::vectorLength(m_vtxDir); if (len > MAX_SPEED ) { float len_1 = len / MAX_SPEED; m_vtxDir.x /= len_1; m_vtxDir.y /= len_1; m_vtxDir.z /= len_1; } if (len > FIRE_SPEED && m_bFire) { EmAssert(this->getParent()->getUserProperties() & PBL_BALL, "BounceBehavior::onTick()"); ((BallGroup*)this->getParent())->setFireTimer(20); } if (m_vtxDir.y > MAX_SPEED_Y_DOWN) m_vtxDir.y = MAX_SPEED_Y_DOWN; } void BounceBehavior::onTick() { EM_COUT("BounceBehavior::onTick()", 0); EmAssert(this->getParent() != NULL, "BounceBehavior::onTick()"); if (m_bAlive && !m_bStopped) { // reset collision m_iCollisionPrio = 0; // debug stuff #if EM_DEBUG if (Keyboard::isKeyDown(SDLK_i)) { if (m_vtxDir.z > 0.0f) m_vtxDir.z = 0.0f; m_vtxDir.z += -0.005f; } if (Keyboard::isKeyDown(SDLK_k)) { if (m_vtxDir.z < 0.0f) m_vtxDir.z = 0.0f; m_vtxDir.z += 0.005f; } if (Keyboard::isKeyDown(SDLK_j)) { if (m_vtxDir.x > 0.0f) m_vtxDir.x = 0.0f; m_vtxDir.x += -0.005f; } if (Keyboard::isKeyDown(SDLK_l)) { if (m_vtxDir.x < 0.0f) m_vtxDir.x = 0.0f; m_vtxDir.x += 0.005f; } if (Keyboard::isKeyDown(SDLK_v)) return; #endif // Gravity m_vtxDir.z += Z_GRAVITY; m_vtxDir.y += Y_GRAVITY; float x, y, z; this->getParent()->getTranslation(x, y, z); if (y <= 0) { this->getParent()->addTranslation(0,-y,0); m_vtxDir.y = EM_MAX(m_vtxDir.y, 0); } EM_COUT("BounceBehavior::onTick() " << x <<" "<< y <<" "<< z, 0); this->checkSpeed(); // move the ball m_vtxOldDir = m_vtxDir; this->getParent()->addTranslation(m_vtxDir.x, m_vtxDir.y, m_vtxDir.z); if (z > 39) { m_bAlive = false; Table::getInstance()->unActivateBall(m_iBall); SendSignal( PBL_SIG_BALL_OFF, 0, this->getParent(), NULL ); } } EmAssert(this->getParent()->getUserProperties() & PBL_BALL, "BounceBehavior::onTick()"); ((BallGroup*)this->getParent())->tick(); } // each collision changes the direction of the ball void BounceBehavior::onCollision(const Vertex3D & vtxW, const Vertex3D & vtxOwn, Group * pGroup) { EmAssert(this->getParent() != NULL && pGroup != NULL, "BounceBehavior::onCollision()"); if (!m_bAlive) return; EM_COUT("BounceBehavior::onCollision() wall " << setprecision ( 4 ) << vtxW.x <<" "<< vtxW.y <<" "<< vtxW.z, 0); EM_COUT("BounceBehavior::onCollision() own " << vtxOwn.x <<" "<< vtxOwn.y <<" "<< vtxOwn.z, 0); // Undo last translation //this->getParent()->addTranslation(-m_vtxDir.x, -m_vtxDir.y, -m_vtxDir.z); // spagetti code :( - TODO - make some functions!!! // change direction depending on which type the colliding object is if (pGroup->getUserProperties() & PBL_PLUNGER) { // plunger Behavior* beh = pGroup->getBehavior(); EmAssert(beh != NULL, "No behavior"); EmAssert(beh->getType() == PBL_TYPE_PLUNGERBEH, "StateBehavior expected"); if (m_iCollisionPrio > 6) return; m_iCollisionPrio = 6; if (!((PlungerBehavior*)beh)->getLaunch()) return; float power = ((PlungerBehavior*)beh)->getPower(); EMath::reflectionDamp(m_vtxOldDir, vtxW, m_vtxDir, (float)1.0, (float)SPEED_FCT*power, 1, true); EM_COUT("BounceBehavior.onCollision() plunger " << power, 0); } else if (pGroup->getUserProperties() & (PBL_LOCK | PBL_TRAP)) { // trap in // Behavior* beh = pGroup->getBehavior(0); Behavior* beh = pGroup->getBehavior(); EmAssert(beh != NULL, "No behavior"); if (beh->getType() == PBL_TYPE_STATEBEH) { if (((StateBehavior*)beh)->getOwnerBall() != m_iBall) { EM_COUT(((StateBehavior*)beh)->getOwnerBall() << " != " << m_iBall, 0); return; } } else { throw string("StateBehavior expected"); } if (pGroup->getUserProperties() & PBL_LOCK) { Table::getInstance()->lockBall(m_iBall); } if (m_iCollisionPrio > 5) return; m_iCollisionPrio = 5; float tx, ty, tz; float bx, by, bz; // move the ball slowly towards the center of the cave pGroup->getTranslation(tx, ty, tz); this->getParent()->getTranslation(bx, by, bz); m_vtxDir.x = (tx-bx)*0.1; m_vtxDir.y = 0; m_vtxDir.z = (tz-bz)*0.1; EM_COUT("BounceBehavior::onCollision() cave", 0); } else if (pGroup->getUserProperties() & PBL_BUMPER) { // bumper if (m_iCollisionPrio > 3) return; m_iCollisionPrio = 3; Behavior* beh = pGroup->getBehavior(); EmAssert(beh != NULL, "No behavior"); EmAssert(beh->getType() == PBL_TYPE_BUMPERBEH, "BumperBehavior"); float power = ((BumperBehavior*)beh)->getPower(); // use bumper as base EMath::reflectionDamp(m_vtxOldDir, vtxW, m_vtxDir, (float)1.0, (float)SPEED_FCT*power, 1, true); EM_COUT("BounceBehavior::onCollision() bumper", 0); } else if (pGroup->getUserProperties() & PBL_ACTIVE_ARM) { // active arm, the given speed is caclulated with a look up table if (m_iCollisionPrio > 3) return; m_iCollisionPrio = 3; // get the behavior Behavior* beh = pGroup->getBehavior(); EmAssert(beh != NULL, "No behavior"); EmAssert(beh->getType() == PBL_TYPE_ARMBEH, "ArmBehavior"); EM_COUT("BounceBehavior::onCollision()", 0); Matrix mtxArmRot, mtxArmRotInv; // the rotation of the arm Vertex3D vtxArmRot; // the opposite rotation Vertex3D vtxArmRotInv; // the balls position and project relative to the arm Vertex3D vtxArmTr, vtxBall, vtxProj, vtxBallRot; // the new calculated direction and speed vector Vertex3D vtxDir; const Vertex3D vtxNull = {0.0f, 0.0f, 0.0f}; const Vertex3D vtxOne = {1.0f, 1.0f, 1.0f}; const Vertex3D vtxRight = {1.0f, 0.0f, 0.0f}; const Vertex3D vtxSlightUp = {0.0f, 0.0f, -0.5f}; // get arm rotation and translation pGroup->getTranslation(vtxArmTr.x, vtxArmTr.y, vtxArmTr.z); pGroup->getRotation(vtxArmRot.x, vtxArmRot.y, vtxArmRot.z); vtxArmRotInv.x = -vtxArmRot.x; vtxArmRotInv.y = -vtxArmRot.y; vtxArmRotInv.z = -vtxArmRot.z; // get distance from center of ball to arm this->getParent()->getTranslation(vtxBall.x, vtxBall.y, vtxBall.z); vtxBall.x -= vtxArmTr.x; vtxBall.y -= vtxArmTr.y; vtxBall.z -= vtxArmTr.z; // project the ball on the longside of the arm // first rotate everything so that the arms long side lies along x-axis EMath::getTransformationMatrix(mtxArmRotInv, vtxNull, vtxArmRotInv, vtxOne); EMath::applyMatrixRot(mtxArmRotInv, vtxBall, vtxBallRot); EMath::projection(vtxBallRot, vtxRight, vtxProj); EM_COUT("arm " << vtxArmTr.x <<" "<< vtxArmTr.y <<" "<< vtxArmTr.z, 0); EM_COUT("bal " << vtxBall.x <<" "<< vtxBall.y <<" "<< vtxBall.z, 0); EM_COUT("pro " << vtxProj.x <<" "<< vtxProj.y <<" "<< vtxProj.z, 0); // find the new speed and angle from the lookup table float length = EM_ABS(vtxProj.x); float findex = ((float)(ARM_TABLE_SIZE)*length/ARM_WIDTH); int iindex = (int)(findex); float foffset = findex - iindex; iindex = EM_MAX(0, EM_MIN(iindex, ARM_TABLE_SIZE-1)); if (iindex < ARM_TABLE_SIZE-2) { // interpolate across table vtxDir.x = EMath::quadratic(g_vtxArmTable[iindex].x, g_vtxArmTable[iindex+1].x, g_vtxArmTable[iindex+2].x, foffset); vtxDir.z = EMath::quadratic(g_vtxArmTable[iindex].z, g_vtxArmTable[iindex+1].z, g_vtxArmTable[iindex+2].z, foffset); } else if (iindex == ARM_TABLE_SIZE-2) { vtxDir.x = EMath::quadratic(g_vtxArmTable[iindex].x, g_vtxArmTable[iindex+1].x, 0.0f, foffset); vtxDir.z = EMath::quadratic(g_vtxArmTable[iindex].z, g_vtxArmTable[iindex+1].z, 0.0f, foffset); } else { vtxDir.x = g_vtxArmTable[iindex].x; vtxDir.z = g_vtxArmTable[iindex].z; } // left arm needs to swap table values if (!((ArmBehavior*)beh)->getIsRight()) { vtxDir.x = -vtxDir.x; } vtxDir.y = 0.0f; // rotate the new direction according to the arm EMath::getTransformationMatrix(mtxArmRot, vtxNull, vtxArmRot, vtxOne); EMath::applyMatrixRot(mtxArmRot, vtxDir, m_vtxDir); // move the ball slightly off the arm TODO - fix this to something better EMath::applyMatrixRot(mtxArmRot, vtxSlightUp, vtxDir); EM_COUT("sli " << vtxDir.x <<" "<< vtxDir.y <<" "<< vtxDir.z, 0); this->getParent()->addTranslation(vtxDir); EM_COUT("BounceBehavior.onCollision() active arm\n", 0); } else if (pGroup->getUserProperties() & PBL_TRAP_BOUNCE) { // trap out if (m_iCollisionPrio > 3) return; m_iCollisionPrio = 3; // use the trap polygon as base // Bbounce ball out of cave, 0 means ignore old speed EMath::reflectionDamp(m_vtxOldDir, vtxW, m_vtxDir, (float)0, (float)SPEED_FCT, 1, true); EM_COUT("BounceBehavior.onCollision() cave bounce", 0); EM_COUT(vtxW.x << " " << vtxW.y << " " << vtxW.z, 0); } else if (pGroup->getUserProperties() & PBL_WALLS_ONE) { // oneway wall if (m_iCollisionPrio > 2) return; m_iCollisionPrio = 2; // use wall as base EMath::reflectionDamp(m_vtxOldDir, vtxW, m_vtxDir, (float)0.5, 0, 1); // move the ball slightly off the wall this->getParent()->addTranslation(vtxW.x * BORDER, vtxW.y * BORDER, vtxW.z * BORDER); EM_COUT("BounceBehavior.onCollision() walls one", 0); } else if (pGroup->getUserProperties() & PBL_UNACTIVE_ARM) { // unactive arm if (m_iCollisionPrio > 2) return; m_iCollisionPrio = 2; // use arm as base Vertex3D vtxWall; vtxWall.x = vtxW.x; vtxWall.y = 0; vtxWall.z = vtxW.z; EMath::reflectionDamp(m_vtxOldDir, vtxWall, m_vtxDir, (float)0.2, 0, 1); // move the ball slightly off the wall this->getParent()->addTranslation(vtxWall.x * BORDER, vtxWall.y * BORDER, vtxWall.z * BORDER); EM_COUT("BounceBehavior.onCollision() unactive arm", 0); } else if (pGroup->getUserProperties() & PBL_WALLS) { // walls if (m_iCollisionPrio > 1) return; m_iCollisionPrio = 1; Vertex3D vtxWall; if (vtxW.y > 0.2 || vtxW.y < -0.2) { // This a ramp, use the wall as a base for collision. // But look out !!! , using the walls as a base is "evil", // it easily causes the ball to pass through walls. Design your ramps // smooth and with no tight corners or nearby walls. vtxWall.x = vtxW.x; vtxWall.y = vtxW.y; vtxWall.z = vtxW.z; } else { // else use the ball as the base // vtxWall.x = vtxW.x; // vtxWall.y = vtxW.y; // vtxWall.z = vtxW.z; vtxWall.x = -vtxOwn.x; vtxWall.y = 0; vtxWall.z = -vtxOwn.z; } EMath::reflectionDamp(m_vtxOldDir, vtxWall, m_vtxDir, (float)0.5, 0, 1); // move the ball slightly off the wall this->getParent()->addTranslation(vtxWall.x * BORDER, vtxWall.y * BORDER, vtxWall.z * BORDER); EM_COUT("BounceBehavior.onCollision() walls" , 0); } else if (pGroup->getUserProperties() & PBL_BALL) { EM_COUT("BounceBehavior.onCollision() ball", 0); // ball if (m_iCollisionPrio > 0) return; m_iCollisionPrio = 0; // use ball as base Vertex3D vtxWall; vtxWall.x = -vtxOwn.x; vtxWall.y = 0; vtxWall.z = -vtxOwn.z; Behavior* beh = pGroup->getBehavior(); EmAssert(beh != NULL && beh->getType() == PBL_TYPE_BOUNCEBEH, "The behavior is not a BounceBeavior"); Vertex3D vtxDir2, vtxPrj1, vtxPrj2; ((BounceBehavior*)beh)->getDirection(vtxDir2); // the speed given from the other ball to this ball is the projection of the speed onto the wall EMath::projection(vtxDir2, vtxWall, vtxPrj2); // the speed given from this ball to the other ball is the projection of the speed // onto the inverse wall (which was vtxOwn). EMath::projection(m_vtxOldDir, vtxOwn, vtxPrj1); // From my physics book m1*u1 + m2u2 = m1*v1 + m2*v2. (m1 == m2 in this case) // Gives us v1 = u1 + vtxPrj2 - vtxPrj1. And v2 = u2 + vtxPrj1 - vtxPrj2. m_vtxDir.x += (vtxPrj2.x - vtxPrj1.x); m_vtxDir.y += (vtxPrj2.y - vtxPrj1.y); m_vtxDir.z += (vtxPrj2.z - vtxPrj1.z); EM_COUT("BounceBehavior.onCollision() ball", 0); // move the ball slightly away the other this->getParent()->addTranslation(vtxWall.x * BORDER, vtxWall.y * BORDER, vtxWall.z * BORDER); } else { EM_COUT("BounceBehavior.onCollision() unknown", 0); } // this->getParent()->addTranslation(m_vtxDir.x, m_vtxDir.y, m_vtxDir.z); }