// This function handles the collision between the ball and a wall void World::wall_collide_handler(const CollisionEntry& colEntry) { // First we calculate some numbers we need to do a reflection NodePath renderNp = m_windowFrameworkPtr->get_render(); // The normal of the wall LVector3f norm = colEntry.get_surface_normal(renderNp) * -1; // The current speed float curSpeed = m_ballV.length(); // The direction of travel LVector3f inVec = m_ballV / curSpeed; // Angle of incidence float velAngle = norm.dot(inVec); LPoint3f hitDir = colEntry.get_surface_point(renderNp) - m_ballRootNp.get_pos(); hitDir.normalize(); // The angle between the ball and the normal float hitAngle = norm.dot(hitDir); // Ignore the collision if the ball is either moving away from the wall // already (so that we don't accidentally send it back into the wall) // and ignore it if the collision isn't dead-on (to avoid getting caught on // corners) if(velAngle > 0 && hitAngle > .995) { // Standard reflection equation LVector3f reflectVec = (norm * norm.dot(inVec * -1) * 2) + inVec; // This makes the velocity half of what it was if the hit was dead-on // and nearly exactly what it was if this is a glancing blow m_ballV = reflectVec * (curSpeed * (((1-velAngle)*.5)+.5)); // Since we have a collision, the ball is already a little bit buried in // the wall. This calculates a vector needed to move it so that it is // exactly touching the wall LPoint3f disp = (colEntry.get_surface_point(renderNp) - colEntry.get_interior_point(renderNp)); LPoint3f newPos = m_ballRootNp.get_pos() + disp; m_ballRootNp.set_pos(newPos); } }
// If the ball hits a hole trigger, then it should fall in the hole. // This is faked rather than dealing with the actual physics of it. void World::lose_game(const CollisionEntry& entry) { // The triggers are set up so that the center of the ball should move to the // collision point to be in the hole NodePath renderNp = m_windowFrameworkPtr->get_render(); LPoint3f toPos = entry.get_interior_point(renderNp); // Stop the maze task PT(GenericAsyncTask) rollTaskPtr = DCAST(GenericAsyncTask, AsyncTaskManager::get_global_ptr()->find_task("rollTask")); if(rollTaskPtr != NULL) { AsyncTaskManager::get_global_ptr()->remove(rollTaskPtr); } // Move the ball into the hole over a short sequence of time. Then wait a // second and call start to reset the game // Note: Sequence is a python only class. We have to manage using CMetaInterval for the animation // with a callback event when the animation is done to callback on World::start() to restart the game. PT(CLerpNodePathInterval) lerp1Ptr = new CLerpNodePathInterval("lerp1", 0.1, CLerpInterval::BT_no_blend, true, false, m_ballRootNp, NodePath()); PT(CLerpNodePathInterval) lerp2Ptr = new CLerpNodePathInterval("lerp2", 0.1, CLerpInterval::BT_no_blend, true, false, m_ballRootNp, NodePath()); PT(WaitInterval) waitPtr = new WaitInterval(1); PT(CMetaInterval) cMetaIntervalPtr = new CMetaInterval("sequence"); if(lerp1Ptr == NULL || lerp2Ptr == NULL || waitPtr == NULL || cMetaIntervalPtr == NULL) { nout << "ERROR: out of memory" << endl; return; } float endPosZ = m_ballRootNp.get_pos().get_z() - 0.9; LVecBase3f midEndPos(toPos.get_x(), toPos.get_y(), 0.5*(m_ballRootNp.get_pos().get_z()+endPosZ)); lerp1Ptr->set_end_pos(midEndPos); LVecBase3f endPos(toPos.get_x(), toPos.get_y(), endPosZ); lerp2Ptr->set_end_pos(endPos); cMetaIntervalPtr->add_c_interval(lerp1Ptr, 0, CMetaInterval::RS_previous_end); cMetaIntervalPtr->add_c_interval(lerp2Ptr, 0, CMetaInterval::RS_previous_end); cMetaIntervalPtr->add_c_interval(waitPtr , 0, CMetaInterval::RS_previous_end); cMetaIntervalPtr->set_done_event("restartGame"); cMetaIntervalPtr->start(); EventHandler::get_global_event_handler()->add_hook("restartGame", call_start, this); PT(GenericAsyncTask) intervalManagerTaskPtr = DCAST(GenericAsyncTask, AsyncTaskManager::get_global_ptr()->find_task("intervalManagerTask")); if(intervalManagerTaskPtr == NULL) { intervalManagerTaskPtr = new GenericAsyncTask("intervalManagerTask", step_interval_manager, NULL); if(intervalManagerTaskPtr != NULL) { AsyncTaskManager::get_global_ptr()->add(intervalManagerTaskPtr); } } }