From a6414b08b41ffff8805338405d5c3034654e4e15 Mon Sep 17 00:00:00 2001 From: hikerstk Date: Wed, 17 Aug 2011 23:06:48 +0000 Subject: [PATCH] Rubber balls should now target the first kart again; improved handling of the situation that the target kart is lost (e.g. it might have finished the race), so that the ball smoothly aims at the new target now. git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@9547 178a84e3-b1eb-0310-8ba1-8eac791a3b58 --- src/items/rubber_ball.cpp | 245 +++++++++++++++++--------------------- src/items/rubber_ball.hpp | 16 +-- 2 files changed, 117 insertions(+), 144 deletions(-) diff --git a/src/items/rubber_ball.cpp b/src/items/rubber_ball.cpp index f30d46240..7ee22498e 100644 --- a/src/items/rubber_ball.cpp +++ b/src/items/rubber_ball.cpp @@ -43,40 +43,51 @@ RubberBall::RubberBall(Kart *kart) setAdjustUpVelocity(false); m_max_lifespan = 9999; m_target = NULL; + // Just init the previoux coordinates with some value that's not getXYZ() + m_previous_xyz = m_owner->getXYZ(); computeTarget(); - // Get 4 points for the interpolation - // Determine distance along track + // Initialises the current graph node TrackSector::update(getXYZ()); - m_control_points[0] = m_owner->getXYZ(); + initializeControlPoints(m_owner->getXYZ()); + + // At the start the ball aims at quads till it gets close enough to the + // target: + m_aiming_at_target = false; + m_timer = 0.0f; + m_interval = m_st_interval; + m_current_max_height = m_max_height; +} // RubberBall + +// ----------------------------------------------------------------------------- +/** Sets up the control points for the interpolation. The parameter contains + * the coordinates of the first control points (i.e. a control point that + * influences the direction the ball is flying only, not the actual + * coordinates - see details about Catmull-Rom splines). This function will + * then set the 2nd control point to be the current coordinates of the ball, + * and find two more appropriate control points for a smooth movement. + * \param xyz Coordinates of the first control points. + */ +void RubberBall::initializeControlPoints(const Vec3 &xyz) +{ + m_control_points[0] = xyz; m_control_points[1] = getXYZ(); m_last_aimed_graph_node = getSuccessorToHitTarget(getCurrentGraphNode()); // This call defined m_control_points[3], but also sets a new // m_last_aimed_graph_node, which is further away from the current point, // which avoids the problem that the ball might go too quickly to the // left or right when firing the ball off track. - static int xx=0; - xx++; if(xx>1) xx=0; - if(xx) getNextControlPoint(); + getNextControlPoint(); m_control_points[2] = QuadGraph::get()->getQuadOfNode(m_last_aimed_graph_node).getCenter(); // This updates m_last_aimed_graph_node, and sets m_control_points[3] getNextControlPoint(); m_length_cp_1_2 = (m_control_points[2]-m_control_points[1]).length(); - - m_t = 0; - m_t_increase = m_speed/m_length_cp_1_2; - - // At the start the ball aims at quads till it gets close enough to the - // target: - m_aiming_at_target = false; - m_wrapped_around = false; - m_timer = 0.0f; - m_interval = m_st_interval; - m_current_max_height = m_max_height; -} // RubberBall + m_t = 0; + m_t_increase = m_speed/m_length_cp_1_2; +} // initialiseControlPoints // ----------------------------------------------------------------------------- /** Determines the first kart. If a target has already been identified in an @@ -193,47 +204,32 @@ void RubberBall::update(float dt) hit(NULL); return; } + + checkDistanceToTarget(); + // FIXME: do we want to test if we have overtaken the target kart? + m_previous_xyz = getXYZ(); - // If we have reached or overshot the next control point, move to the - // the next section of the spline - m_t += m_t_increase * dt; - if(m_t > 1.0f) + Vec3 next_xyz; + if(m_aiming_at_target) { - // Move the control points and estimated distance forward. - for(unsigned int i=1; i<4; i++) - m_control_points[i-1] = m_control_points[i]; - m_length_cp_1_2 = m_length_cp_2_3; - int old = m_last_aimed_graph_node; - // - // This automatically sets m_control_points[3] - getNextControlPoint(); - //printf("1_2 %f length %f\n", m_length_cp_1_2, - // (m_control_points[2]-m_control_points[1]).length()); - m_t_increase = m_speed/m_length_cp_1_2; - m_t -= 1.0f; + // If the rubber ball is already close to a target, i.e. aiming + // at it directly, stop interpolating, instead fly straight + // towards it. + Vec3 diff = m_target->getXYZ()-getXYZ(); + next_xyz = getXYZ() + (dt*m_speed/diff.length())*diff; } - - Vec3 next_xyz = 0.5f * ((-m_control_points[0] + 3*m_control_points[1] -3*m_control_points[2] + m_control_points[3])*m_t*m_t*m_t - + (2*m_control_points[0] -5*m_control_points[1] + 4*m_control_points[2] - m_control_points[3])*m_t*m_t - + (-m_control_points[0]+m_control_points[2])*m_t - + 2*m_control_points[1]); - - float old_distance = getDistanceFromStart(); + else + { + interpolate(&next_xyz, dt); + } + m_timer += dt; + float height = updateHeight(); + next_xyz.setY(getHoT()+height); // Determine new distance along track TrackSector::update(next_xyz); - float track_length = World::getWorld()->getTrack()->getTrackLength(); - - // Detect if the ball crossed the start line - m_wrapped_around = old_distance > 0.9f * track_length && - getDistanceFromStart()< 10.0f; - - m_timer += dt; - float height = updateHeight(); - next_xyz.setY(getHoT()+height); - // Ball squashing: // =============== // If we start squashing the ball as soon as the height is smaller than @@ -250,6 +246,38 @@ void RubberBall::update(float dt) setXYZ(next_xyz); } // update +// ---------------------------------------------------------------------------- +/** Uses Hermite splines (Catmull-Rom) to interpolate the position of the + * ball between the control points. If the next point would be outside of + * the spline between control_points[1] and [2], a new control point is + * added. + * \param next_xyz Returns the new position. + * \param The time step size. + */ +void RubberBall::interpolate(Vec3 *next_xyz, float dt) +{ + // If we have reached or overshot the next control point, move to the + // the next section of the spline + m_t += m_t_increase * dt; + if(m_t > 1.0f) + { + // Move the control points and estimated distance forward. + for(unsigned int i=1; i<4; i++) + m_control_points[i-1] = m_control_points[i]; + m_length_cp_1_2 = m_length_cp_2_3; + + // This automatically sets m_control_points[3] + getNextControlPoint(); + m_t_increase = m_speed/m_length_cp_1_2; + m_t -= 1.0f; + } + + *next_xyz = 0.5f * ((-m_control_points[0] + 3*m_control_points[1] -3*m_control_points[2] + m_control_points[3])*m_t*m_t*m_t + + (2*m_control_points[0] -5*m_control_points[1] + 4*m_control_points[2] - m_control_points[3])*m_t*m_t + + (-m_control_points[0]+m_control_points[2])*m_t + + 2*m_control_points[1]); +} // interpolate + // ---------------------------------------------------------------------------- /** Updates the height of the rubber ball, and if necessary also adjusts the * maximum height of the ball depending on distance from the target. The @@ -306,106 +334,49 @@ float RubberBall::updateHeight() } // updateHeight // ---------------------------------------------------------------------------- -/** Determines which coordinates the ball should aim at next. If the ball is - * still 'far' away from the target (>20), then it will aim at the next - * graph node. If it's closer, the ball will aim directly at the kart and - * keep on aiming at the kart, it will not follow the drivelines anymore. - * \param aim_xyz On return contains the xyz coordinates to aim at. - */ -void RubberBall::determineTargetCoordinates(float dt, Vec3 *aim_xyz) +void RubberBall::checkDistanceToTarget() { // If aiming at target phase, keep on aiming at target. // ---------------------------------------------------- - if(m_aiming_at_target) - { - *aim_xyz = m_target->getXYZ(); - return; - } - - // Aiming at a graph node - // ---------------------- - GraphNode *gn = &(QuadGraph::get()->getNode(m_last_aimed_graph_node)); - - // At this stage getDistanceFromStart() is already the new distance (set - // in the previous time step when aiming). It has to be detected if the - // ball is now ahead of the graph node, and if so, the graph node has to - // be updated till it is again ahead of the ball. Three distinct cases - // have to be considered: - // 1) The ball just crossed the start line (-> distance close to 0), - // but the graph node is still before the start line, in which case - // a new graph node has to be determined. - // 2) The ball hasn't crossed the start line, but the graph node has - // (i.e. graph node is 0), in which case the graph node is correct. - // This happens after the first iteration, i.e. graph node initially - // is the last one (distance close to track length), but if the ball - // is ahead (distance of a graph node is measured to the beginning of - // the quad, so if the ball is in the middle of the last quad it will - // be ahead of the graph node!) the graph node will be set to the - // first graph node (distance close to 0). In this case the graph node - // should not be changed anymore. - // 3) All other cases that do not involve the start line at all - // (including e.g. ball and graph node crossed start line, neither - // ball nor graph node crossed start line), which means that a new - // graph node need to be determined only if the distance along track - // of the ball is greater than the distance for - float gn_distance = gn->getDistanceFromStart(); - float track_length = World::getWorld()->getTrack()->getTrackLength(); - - // Test 1: ball wrapped around, and graph node is close to end of track - bool ball_ahead = m_wrapped_around && gn_distance >0.9f*track_length; - - // Test 3: distance of ball greater than distance of graph node - if(!ball_ahead && gn_distance < getDistanceFromStart()) - // The distance test only applies if the graph node hasn't wrapped - // around - ball_ahead = true; - - while(ball_ahead) - { - // FIXME: aim better if necessary! - m_last_aimed_graph_node = getSuccessorToHitTarget(m_last_aimed_graph_node); - gn = &(QuadGraph::get()->getNode(m_last_aimed_graph_node)); - - // Detect a wrap around of the graph node. We could just test if - // the index of the new graph node is 0, but since it's possible - // that we might have tracks with a more complicated structure, e.g - // with two different start lines, we use this more general test: - // If the previous distance was close to the end of the track, and - // the new distance is close to 0, the graph node wrapped around. - // This test prevents an infinite loop if the ball is on the last - // quad, in which case no graph node would fulfill the distance test. - if(gn_distance > 0.9f*track_length && - gn->getDistanceFromStart()<10.0f) - break; - gn_distance = gn->getDistanceFromStart(); - ball_ahead = m_wrapped_around && gn_distance >0.9f*track_length; - ball_ahead = !ball_ahead && - gn->getDistanceFromStart() < getDistanceFromStart(); - } + if(m_aiming_at_target) return; const LinearWorld *world = dynamic_cast(World::getWorld()); if(!world) return; // FIXME battle mode float target_distance = world->getDistanceDownTrackForKart(m_target->getWorldKartId()); + float ball_distance = getDistanceFromStart(); - // Handle wrap around of distance if target crosses finishing line - if(getDistanceFromStart() > target_distance) - target_distance += world->getTrack()->getTrackLength(); - - // If the ball is close enough, start aiming directly at the target kart - // --------------------------------------------------------------------- - if(target_distance-getDistanceFromStart()< 20) + float diff = target_distance - ball_distance; + if(diff < 0) { - m_aiming_at_target = true; - *aim_xyz = m_target->getXYZ(); - return; + diff += world->getTrack()->getTrackLength(); } - // ------------------------------ - *aim_xyz = gn->getQuad().getCenter(); + if(diff < 50) + { + m_aiming_at_target = true; + return; + } + else if(m_aiming_at_target) + { + // It appears that we have lost the target. It was within + // the target distance, and now it isn't. That means either + // the original target escaped, or perhaps that there is a + // new target. In this case we have to reset the control + // points, since it's likely that the ball is (after some time + // going directly towards the target) far outside of the + // old control points. -} // determineTargetCoordinates + // We use the previous XYZ point to define the first control + // point, which results in a smooth transition from aiming + // directly at the target back to interpolating again. + initializeControlPoints(m_previous_xyz); + m_aiming_at_target = false; + } + + return; +} // checkDistanceToTarget // ---------------------------------------------------------------------------- /** Callback from the physics in case that a kart or object is hit. The rubber diff --git a/src/items/rubber_ball.hpp b/src/items/rubber_ball.hpp index f5d682837..822861158 100644 --- a/src/items/rubber_ball.hpp +++ b/src/items/rubber_ball.hpp @@ -49,9 +49,14 @@ private: int m_last_aimed_graph_node; /** Keep the last two, current, and next aiming points - * for interpolation. */ + * for interpolation. */ Vec3 m_control_points[4]; + /** Saves the previous location of the ball. This is needed if a ball + * should lose it target, and has to reinitialise the control points + * for the interpolation. */ + Vec3 m_previous_xyz; + /** Estimated length of the spline between the control points * 1 and 2. */ float m_length_cp_1_2; @@ -86,11 +91,6 @@ private: * reduced if the ball gets closer to the target. */ float m_current_max_height; - /** True if the ball just crossed the start line, i.e. its - * distance changed from close to length of track in the - * previous time step to a bit over zero now. */ - bool m_wrapped_around; - /** Once the ball is close enough, it will aim for the kart. If the * kart should be able to then increase the distance to the ball, * the ball will be removed and the kart escapes. This boolean is @@ -98,11 +98,13 @@ private: bool m_aiming_at_target; void computeTarget(); - void determineTargetCoordinates(float dt, Vec3 *aim_xyz); + void checkDistanceToTarget(); unsigned int getSuccessorToHitTarget(unsigned int node_index, float *f=NULL); void getNextControlPoint(); float updateHeight(); + void interpolate(Vec3 *next_xyz, float dt); + void initializeControlPoints(const Vec3 &xyz); public: RubberBall (Kart* kart); static void init(const XMLNode &node, scene::IMesh *bowling);