Allow the player to go to the left and right when in a cannon.

This commit is contained in:
hiker 2017-03-07 22:11:15 +11:00
parent a7d1a312ad
commit ae375c50b6
6 changed files with 271 additions and 91 deletions

View File

@ -107,3 +107,36 @@ void AnimationBase::update(float dt, Vec3 *xyz, Vec3 *hpr, Vec3 *scale)
}
} // update
// ----------------------------------------------------------------------------
/** Return the time, position and rotation at the specified time. It does not
* update the internal timer as update() does.
* \param dt Time since last call.
* \param xyz Position to be updated.
* \param hpr Rotation to be updated.
*/
void AnimationBase::getAt(float time, Vec3 *xyz, Vec3 *hpr, Vec3 *scale)
{
assert(!std::isnan(time));
// Don't do anything if the animation is disabled
if (!m_playing) return;
for_var_in(Ipo*, curr, m_all_ipos)
{
curr->update(time, xyz, hpr, scale);
}
} // getAt
// ----------------------------------------------------------------------------
/** Returns the derivative at the specified point.
* \param time The time for which to determine the derivative.
* \param xyz Float pointer to store the result.
*/
void AnimationBase::getDerivativeAt(float time, Vec3 *xyz)
{
for_var_in(Ipo*, curr, m_all_ipos)
{
curr->getDerivative(time, xyz);
}
xyz->normalize();
}

View File

@ -67,8 +67,11 @@ public:
AnimationBase(const XMLNode &node);
AnimationBase(Ipo *ipo);
virtual ~AnimationBase() {}
virtual void update(float dt, Vec3 *xyz=NULL, Vec3 *hpr=NULL,
Vec3 *scale=NULL);
virtual void update(float dt, Vec3 *xyz=NULL, Vec3 *hpr=NULL,
Vec3 *scale=NULL);
virtual void getAt(float time, Vec3 *xyz = NULL, Vec3 *hpr = NULL,
Vec3 *scale = NULL);
virtual void getDerivativeAt(float time, Vec3 *xyz);
/** This needs to be implemented by the inheriting classes. It is called
* once per frame from the track. It has a dummy implementation that
* just asserts so that this class can be instantiated in

View File

@ -346,7 +346,7 @@ float Ipo::IpoData::get(float time, unsigned int index, unsigned int n)
} // switch
// Keep the compiler happy:
return 0;
} // get
} // IpoData::get
// ----------------------------------------------------------------------------
/** Computes a cubic bezier curve for a given t in [0,1] and four control
@ -361,39 +361,65 @@ float Ipo::IpoData::getCubicBezier(float t, float p0, float p1,
float b = 3.0f*(p2-p1)-c;
float a = p3 - p0 - c - b;
return ((a*t+b)*t+c)*t+p0;
} // bezier
} // getCubicBezier
// ----------------------------------------------------------------------------
/** Determines the rotation between the start and end of this curve.
/** Determines the derivative of a IPO at a given point.
* \param time At what time value the derivative is to be computed.
* \param index IpoData is based on 3d data. The index specified which
* value to use (0=x, 1=y, 2=z).
* \param n Curve segment to be used for the computation. It must be correct
* for the specified time value.
*/
btQuaternion Ipo::IpoData::getOverallRotation()
float Ipo::IpoData::getDerivative(float time, unsigned int index,
unsigned int n)
{
// Vectors at start and end of curve
Vec3 start, end;
if (m_interpolation == IP_BEZIER)
switch (m_interpolation)
{
// In case of Bezier use the handles to get initial and final
// orientation
start = m_handle2[0] - m_handle1[0];
end = *m_handle2.back() - *m_handle1.back();
}
else // Const or linear
{
// In this case determine the start vector by selecting using the
// beginning and a second point a bit further on the curve
start.setX(get(m_start_time + 0.1f, 0, 0) - m_points[0].getX());
start.setY(get(m_start_time + 0.1f, 1, 0) - m_points[0].getY());
start.setZ(get(m_start_time + 0.1f, 2, 0) - m_points[0].getZ());
int n = m_points.size() - 2;
end. setX(get(m_end_time - 0.1f, 0, n) - m_points[n].getX());
end. setY(get(m_end_time - 0.1f, 1, n) - m_points[n].getY());
end. setZ(get(m_end_time - 0.1f, 2, n) - m_points[n].getZ());
case IP_CONST: return 0; // Const --> Derivative is 0
case IP_LINEAR: {
return (m_points[n + 1][index] - m_points[n][index]) /
(m_points[n + 1].getW() - m_points[n].getW());
}
case IP_BEZIER: {
if (n == m_points.size() - 1)
{
// Only const, so derivative is 0
return 0;
}
float t = (time - m_points[n].getW())
/ (m_points[n + 1].getW() - m_points[n].getW());
return getCubicBezierDerivative(t,
m_points [n ][index],
m_handle2[n ][index],
m_handle1[n + 1][index],
m_points [n + 1][index] );
} // case IPBEZIER
default:
Log::warn("Ipo::IpoData", "Incorrect interpolation %d",
m_interpolation);
} // switch
return 0;
} // IpoData::getDerivative
btQuaternion q = shortestArcQuatNormalize2(start, end);
return q;
} // IpData::getOverallRoation
// ----------------------------------------------------------------------------
/** Returns the derivative of a cubic bezier curve for a given t in [0,1] and
* four control points. The curve will go through p0 (t=0).
* \param t The parameter for the bezier curve, must be in [0,1].
* \param p0, p1, p2, p3 The four control points.
*/
float Ipo::IpoData::getCubicBezierDerivative(float t, float p0, float p1,
float p2, float p3) const
{
float c = 3.0f*(p1 - p0);
float b = 3.0f*(p2 - p1) - c;
float a = p3 - p0 - c - b;
// f(t) = ((a*t + b)*t + c)*t + p0;
// = a*t^3 +b*t^2 + c*t + p0
// --> f'(t) = 3*a*t^2 + 2*b*t + c
return (3*a * t + 2*b) * t + c;
} // bezier
// ============================================================================
/** The Ipo constructor. Ipos can share the actual data to interpolate, which
@ -503,6 +529,28 @@ void Ipo::update(float time, Vec3 *xyz, Vec3 *hpr,Vec3 *scale)
} // update
// ----------------------------------------------------------------------------
/** Updates the value of m_next_n to point to the right ipo segment based on
* the time.
* \param t Time for which m_next_n needs to be updated.
*/
void Ipo::updateNextN(float time) const
{
time = m_ipo_data->adjustTime(time);
// Time was reset since the last cached value for n,
// reset n to start from the beginning again.
if (time < m_ipo_data->m_points[m_next_n - 1].getW())
m_next_n = 1;
// Search for the first point in the (sorted) array which is greater or equal
// to the current time.
while (m_next_n < m_ipo_data->m_points.size() - 1 &&
time >= m_ipo_data->m_points[m_next_n].getW())
{
m_next_n++;
} // while
} // updateNextN
// ----------------------------------------------------------------------------
/** Returns the interpolated value at the current time (which this objects
* keeps track of).
@ -516,34 +564,50 @@ float Ipo::get(float time, unsigned int index) const
if(m_next_n==0)
return m_ipo_data->m_points[0][index];
time = m_ipo_data->adjustTime(time);
updateNextN(time);
// Time was reset since the last cached value for n,
// reset n to start from the beginning again.
if(time < m_ipo_data->m_points[m_next_n-1].getW())
m_next_n = 1;
// Search for the first point in the (sorted) array which is greater or equal
// to the current time.
while(m_next_n<m_ipo_data->m_points.size()-1 &&
time >=m_ipo_data->m_points[m_next_n].getW())
m_next_n++;
float rval = m_ipo_data->get(time, index, m_next_n-1);
assert(!std::isnan(rval));
return rval;
} // get
// ----------------------------------------------------------------------------
/** Return the quaternion that rotates an object form the start of the IPO
* to the end.
/** Returns the derivative for any location based curves.
* \param time Time for which the derivative is being computed.
* \param xyz Pointer where the results should be stored.
*/
btQuaternion Ipo::getOverallRotation()
void Ipo::getDerivative(float time, Vec3 *xyz)
{
// In case of a single point only:
// Avoid crash in case that only one point is given for this IPO.
if (m_next_n == 0)
{
// Return a unit quaternion
btQuaternion q(0, 0, 0, 1);
return q;
// Derivative has no real meaning in case of a single point.
// So just return a dummy value.
xyz->setValue(1, 0, 0);
return;
}
return m_ipo_data->getOverallRotation();
} // getOverallRoation
updateNextN(time);
switch (m_ipo_data->m_channel)
{
case Ipo::IPO_LOCX: xyz->setX(m_ipo_data->getDerivative(time, m_next_n, 0)); break;
case Ipo::IPO_LOCY: xyz->setY(m_ipo_data->getDerivative(time, m_next_n, 0)); break;
case Ipo::IPO_LOCZ: xyz->setZ(m_ipo_data->getDerivative(time, m_next_n, 0)); break;
case Ipo::IPO_LOCXYZ:
{
if (xyz)
{
for (unsigned int j = 0; j < 3; j++)
(*xyz)[j] = m_ipo_data->getDerivative(time, j, m_next_n-1);
}
break;
}
default: Log::warn("IPO", "Unexpected channel %d for derivate.",
m_ipo_data->m_channel );
xyz->setValue(1, 0, 0);
break;
} // switch
} // getDerivative

View File

@ -81,6 +81,8 @@ private:
private:
float getCubicBezier(float t, float p0, float p1,
float p2, float p3) const;
float getCubicBezierDerivative(float t, float p0, float p1,
float p2, float p3) const;
void approximateBezier(float t0, float t1,
const Vec3 &p0, const Vec3 &p1,
const Vec3 &h0, const Vec3 &h2,
@ -94,7 +96,7 @@ private:
const Vec3 &h1, const Vec3 &h2);
float adjustTime(float time);
float get(float time, unsigned int index, unsigned int n);
btQuaternion getOverallRotation();
float getDerivative(float time, unsigned int index, unsigned int n);
}; // IpoData
// ------------------------------------------------------------------------
@ -114,6 +116,8 @@ private:
* it is declared mutable). */
mutable unsigned int m_next_n;
void updateNextN(float time) const;
Ipo(const Ipo *ipo);
public:
Ipo(const XMLNode &curve, float fps=25, bool reverse=false);
@ -121,10 +125,10 @@ public:
Ipo *clone();
void update(float time, Vec3 *xyz=NULL, Vec3 *hpr=NULL,
Vec3 *scale=NULL);
void getDerivative(float time, Vec3 *xyz);
float get(float time, unsigned int index) const;
void setInitialTransform(const Vec3 &xyz, const Vec3 &hpr);
void reset();
btQuaternion getOverallRotation();
// ------------------------------------------------------------------------
/** Returns the raw data points for this IPO. */
const std::vector<Vec3>& getPoints() const { return m_ipo_data->m_points; }

View File

@ -35,26 +35,90 @@ CannonAnimation::CannonAnimation(AbstractKart *kart, Ipo *ipo,
m_curve = new AnimationBase(ipo);
m_timer = ipo->getEndTime();
float kw2 = m_kart->getKartModel()->getWidth()*0.5f;
// First make sure that left and right points are indeed correct
Vec3 my_start_left = start_left;
Vec3 my_start_right = start_right;
Vec3 p0, p1;
m_curve->getAt(0, &p0);
m_curve->getAt(0.1f, &p1);
Vec3 p2 = 0.5f*(p0 + p1) + m_kart->getNormal();
if (start_left.sideofPlane(p0, p1, p2) < 0)
{
my_start_left = start_right;
my_start_right = start_left;
}
// First adjust start and end points to take on each side half the kart
// width into account:
Vec3 direction = my_start_right - my_start_left;
direction.normalize();
Vec3 adj_start_left = my_start_left + kw2 * direction;
Vec3 adj_start_right = my_start_right - kw2 * direction;
// Same adjustments for end points
float t = m_curve->getAnimationDuration();
Vec3 my_end_left = end_left;
Vec3 my_end_right = end_right;
m_curve->getAt(t-0.1f, &p0);
m_curve->getAt(t, &p1);
p2 = 0.5f*(p0 + p1) + m_kart->getNormal();
if (end_left.sideofPlane(p0, p1, p2) < 0)
{
my_end_left = end_right;
my_end_right = end_left;
}
// Left and right end points are sometimes swapped
direction = my_end_right - my_end_left;
direction.normalize();
Vec3 adj_end_left = my_end_left + kw2 * direction;
Vec3 adj_end_right = my_end_right - kw2 * direction;
// The kart position is divided into three components:
// 1) The point at the curve at t=0.
// 2) A component parallel to the start line. This component is scaled
// depending on time, length of start- and end-line (e.g. if the
// end line is twice as long as the start line, this will make sure
// that a kart starting at the very left of the start line will end
// up at the very left of the end line. This component can also be
// adjusted by steering while in the air. This is done by modifying
// m_fraction_of_line, which is multiplied with the current width
// vector.
// 3) The rest, i.e. the amoount that the kart is ahead and above the
// start line. This is stored in m_delta.
//
// Compute the delta between the kart position and the start of the curve.
// This delta is rotated with the kart and added to the interpolated curve
// position to get the actual kart position during the animation.
m_curve->update(0, &m_previous_orig_xyz);
m_delta = kart->getXYZ() - m_previous_orig_xyz;
Vec3 v1 = start_left - start_right;
Vec3 v2 = end_left - end_right;
m_delta_rotation = shortestArcQuatNormalize2(v1, v2 );
Vec3 curve_xyz;
m_curve->update(0, &curve_xyz);
m_delta = kart->getXYZ() - curve_xyz;
m_start_line = 0.5f*(adj_start_right - adj_start_left);
m_end_line = 0.5f*(adj_end_right - adj_end_left );
Vec3 v = adj_start_left - adj_start_right;
float l = v.length();
v /= l;
// Now the delta vector needs to be rotated back, so that it will point
// in the right direction when it is (in update) rotated to be the same
// as the kart's heading. To estimate the angle at the start, use the
// interpolated value at t=dt:
const float dt = 0.1f;
Vec3 xyz1;
m_curve->update(dt, &xyz1);
// Compute on which fraction of the start line the kart is
float f = v.dot(adj_start_left - kart->getXYZ());
if (f <= 0)
f = 0;
else if (f >= l)
f = l;
else
f = f / l;
// Now f is in [0,1]. Convert it to [-1,1] assuming that the
// ipo for the cannon is in the middle of the start and end line
m_fraction_of_line = 2.0f*f - 1.0f;
Vec3 delta = m_start_line * m_fraction_of_line;
m_delta = m_delta - delta;
// The previous call to m_curve->update will set the internal timer
// of the curve to dt. Reset it to 0 to make sure the timer is in
// synch with the timer of the CanonAnimation
@ -68,8 +132,9 @@ CannonAnimation::~CannonAnimation()
btTransform pos;
pos.setOrigin(m_kart->getXYZ());
pos.setRotation(btQuaternion(btVector3(0.0f, 1.0f, 0.0f),
m_kart->getHeading() ));
//pos.setRotation(btQuaternion(btVector3(0.0f, 1.0f, 0.0f),
// m_kart->getHeading() ));
pos.setRotation(m_kart->getRotation());
m_kart->getBody()->setCenterOfMassTransform(pos);
Vec3 v(0, 0, m_kart->getKartProperties()->getEngineMaxSpeed());
@ -89,30 +154,39 @@ void CannonAnimation::update(float dt)
return;
}
// Adjust the horizontal location based on steering
m_fraction_of_line += m_kart->getSteerPercent()*dt*2.0f;
// The timer count backwards, so the fraction goes from 1 to 0
float f = m_timer / m_curve->getAnimationDuration();
btClamp(m_fraction_of_line, -1.0f, 1.0f);
Vec3 current_width = m_start_line * f + m_end_line * (1.0f - f);
// Get the tangent = derivative at the current point to compute the
// orientation of the kart
Vec3 tangent;
m_curve->getDerivativeAt(m_curve->getAnimationDuration() - m_timer,
&tangent);
Vec3 forward = m_kart->getTrans().getBasis().getColumn(2);
forward.normalize();
// Only adjust the heading. I tried to also adjust pitch,
// but that adds a strong roll to the kart on some cannons
Vec3 v1(tangent), v2(forward);
v1.setY(0); v2.setY(0);
btQuaternion q = m_kart->getRotation()*shortestArcQuatNormalize2(v2, v1);
m_kart->setRotation(q);
Vec3 xyz;
m_curve->update(dt, &xyz);
// It can happen that the same position is returned, e.g. if the end of
// the curve is reached, but due to floating point differences the
// end is not detected in the above test. To avoid that the kart then
// rotates to a heading of 0, do not rotate in this case at all, i.e.
// the previous rotation is kept.
if(xyz!=m_previous_orig_xyz)
{
btQuaternion prev_rot = m_kart->getRotation();
core::vector3df rot = (xyz-m_previous_orig_xyz).toIrrVector()
.getHorizontalAngle();
btQuaternion q(Vec3(0,1,0),rot.Y*DEGREE_TO_RAD);
m_kart->setRotation(prev_rot.slerp(q,0.1f));
}
m_previous_orig_xyz = xyz;
Vec3 rotated_delta = quatRotate(q, m_delta ) + current_width * m_fraction_of_line;
m_kart->setXYZ(xyz+rotated_delta);
// m_kart->setXYZ(xyz);
btQuaternion zero(0, 0, 0, 1);
// The timer count backwards, so the fraction goes from 1 to 0
float f = m_timer / m_curve->getAnimationDuration();
btQuaternion current_rot = m_delta_rotation.slerp(zero, f);
Vec3 rotated_delta = quatRotate(current_rot, m_delta);
m_kart->setXYZ(xyz + rotated_delta);
AbstractKartAnimation::update(dt);
} // update

View File

@ -43,16 +43,18 @@ protected:
* kart position (so the kart moves relative to the curve). */
Vec3 m_delta;
/** The amount of rotation to be applied to m_delta so that it keeps
* being on the 'right' side of the curve. */
btQuaternion m_delta_rotation;
/** Stores the curve interpolation for the cannon. */
AnimationBase *m_curve;
/** This stores the original (unmodified) interpolated curve value. THis
* is used to determine the orientation of the kart. */
Vec3 m_previous_orig_xyz;
/** The original checkline at start. */
Vec3 m_start_line;
/** The original checkline at end. */
Vec3 m_end_line;
/** Stores the position of the kart relative to the line width
* at the current location. */
float m_fraction_of_line;
public:
CannonAnimation(AbstractKart *kart, Ipo *ipo,