/* * Computes the total distance to be travelled by a paced animation. * * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if * our values don't support distance computation. */ double nsSMILAnimationFunction::ComputePacedTotalDistance( const nsSMILValueArray& aValues) const { NS_ASSERTION(GetCalcMode() == CALC_PACED, "Calling paced-specific function, but not in paced mode"); double totalDistance = 0.0; for (PRUint32 i = 0; i < aValues.Length() - 1; i++) { double tmpDist; nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist); if (NS_FAILED(rv)) { return COMPUTE_DISTANCE_ERROR; } // Clamp distance value to 0, just in case we have an evil ComputeDistance // implementation somewhere NS_ABORT_IF_FALSE(tmpDist >= 0.0f, "distance values must be non-negative"); tmpDist = NS_MAX(tmpDist, 0.0); totalDistance += tmpDist; } return totalDistance; }
nsresult nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult) { if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) { const nsSMILValue& lastValue = aValues[aValues.Length() - 1]; // If the target attribute type doesn't support addition, Add will // fail and we leave aResult untouched. aResult.Add(lastValue, mRepeatIteration); } return NS_OK; }
/* * Given the simple progress for a paced animation, this method: * - determines which two elements of the values array we're in between * (returned as aFrom and aTo) * - determines where we are between them * (returned as aIntervalProgress) * * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance * computation. */ nsresult nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues, double aSimpleProgress, double& aIntervalProgress, const nsSMILValue*& aFrom, const nsSMILValue*& aTo) { NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f, "aSimpleProgress is out of bounds"); NS_ASSERTION(GetCalcMode() == CALC_PACED, "Calling paced-specific function, but not in paced mode"); NS_ABORT_IF_FALSE(aValues.Length() >= 2, "Unexpected number of values"); // Trivial case: If we have just 2 values, then there's only one interval // for us to traverse, and our progress across that interval is the exact // same as our overall progress. if (aValues.Length() == 2) { aIntervalProgress = aSimpleProgress; aFrom = &aValues[0]; aTo = &aValues[1]; return NS_OK; } double totalDistance = ComputePacedTotalDistance(aValues); if (totalDistance == COMPUTE_DISTANCE_ERROR) return NS_ERROR_FAILURE; // total distance we should have moved at this point in time. // (called 'remainingDist' due to how it's used in loop below) double remainingDist = aSimpleProgress * totalDistance; // Must be satisfied, because totalDistance is a sum of (non-negative) // distances, and aSimpleProgress is non-negative NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative"); // Find where remainingDist puts us in the list of values // Note: We could optimize this next loop by caching the // interval-distances in an array, but maybe that's excessive. for (PRUint32 i = 0; i < aValues.Length() - 1; i++) { // Note: The following assertion is valid because remainingDist should // start out non-negative, and this loop never shaves off more than its // current value. NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative"); double curIntervalDist; nsresult rv = aValues[i].ComputeDistance(aValues[i+1], curIntervalDist); NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "If we got through ComputePacedTotalDistance, we should " "be able to recompute each sub-distance without errors"); NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative"); // Clamp distance value at 0, just in case ComputeDistance is evil. curIntervalDist = NS_MAX(curIntervalDist, 0.0); if (remainingDist >= curIntervalDist) { remainingDist -= curIntervalDist; } else { // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why? // Because this clause is only hit when remainingDist < curIntervalDist, // and if curIntervalDist were 0, that would mean remainingDist would // have to be < 0. But that can't happen, because remainingDist (as // a distance) is non-negative by definition. NS_ASSERTION(curIntervalDist != 0, "We should never get here with this set to 0..."); // We found the right spot -- an interpolated position between // values i and i+1. aFrom = &aValues[i]; aTo = &aValues[i+1]; aIntervalProgress = remainingDist / curIntervalDist; return NS_OK; } } NS_NOTREACHED("shouldn't complete loop & get here -- if we do, " "then aSimpleProgress was probably out of bounds"); return NS_ERROR_FAILURE; }
nsresult nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult, nsSMILValue& aBaseValue) { nsresult rv = NS_OK; const nsSMILTime& dur = mSimpleDuration.GetMillis(); // Sanity Checks NS_ABORT_IF_FALSE(mSampleTime >= 0.0f, "Sample time should not be negative"); NS_ABORT_IF_FALSE(dur >= 0.0f, "Simple duration should not be negative"); if (mSampleTime >= dur || mSampleTime < 0.0f) { NS_ERROR("Animation sampled outside interval"); return NS_ERROR_FAILURE; } if ((!IsToAnimation() && aValues.Length() < 2) || (IsToAnimation() && aValues.Length() != 1)) { NS_ERROR("Unexpected number of values"); return NS_ERROR_FAILURE; } // End Sanity Checks double fTime = double(mSampleTime); double fDur = double(dur); // Get the normalised progress through the simple duration double simpleProgress = (fDur > 0.0) ? fTime / fDur : 0.0; // Handle bad keytimes (where first != 0 and/or last != 1) // See http://brian.sol1.net/svg/range-for-keytimes for more info. if (HasAttr(nsGkAtoms::keyTimes) && GetCalcMode() != CALC_PACED) { double first = mKeyTimes[0]; if (first > 0.0 && simpleProgress < first) { if (!IsToAnimation()) aResult = aValues[0]; return rv; } double last = mKeyTimes[mKeyTimes.Length() - 1]; if (last < 1.0 && simpleProgress >= last) { if (IsToAnimation()) aResult = aValues[0]; else aResult = aValues[aValues.Length() - 1]; return rv; } } if (GetCalcMode() != CALC_DISCRETE) { // Get the normalised progress between adjacent values const nsSMILValue* from = nsnull; const nsSMILValue* to = nsnull; double intervalProgress; if (IsToAnimation()) { from = &aBaseValue; to = &aValues[0]; if (GetCalcMode() == CALC_PACED) { // Note: key[Times/Splines/Points] are ignored for calcMode="paced" intervalProgress = simpleProgress; } else { ScaleSimpleProgress(simpleProgress); intervalProgress = simpleProgress; ScaleIntervalProgress(intervalProgress, 0, 1); } } else { if (GetCalcMode() == CALC_PACED) { rv = ComputePacedPosition(aValues, simpleProgress, intervalProgress, from, to); // Note: If the above call fails, we'll skip the "from->Interpolate" // call below, and we'll drop into the CALC_DISCRETE section // instead. (as the spec says we should, because our failure was // presumably due to the values being non-additive) } else { // GetCalcMode() == CALC_LINEAR or GetCalcMode() == CALC_SPLINE ScaleSimpleProgress(simpleProgress); PRUint32 index = (PRUint32)floor(simpleProgress * (aValues.Length() - 1)); from = &aValues[index]; to = &aValues[index + 1]; intervalProgress = simpleProgress * (aValues.Length() - 1) - index; ScaleIntervalProgress(intervalProgress, index, aValues.Length() - 1); } } if (NS_SUCCEEDED(rv)) { NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation"); NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation"); NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f, "Interval progress should be in the range [0, 1)"); rv = from->Interpolate(*to, intervalProgress, aResult); } } // Discrete-CalcMode case // Note: If interpolation failed (isn't supported for this type), the SVG // spec says to force discrete mode. if (GetCalcMode() == CALC_DISCRETE || NS_FAILED(rv)) { if (IsToAnimation()) { // SMIL 3, 12.6.4: Since a to animation has only 1 value, a discrete to // animation will simply set the to value for the simple duration. aResult = aValues[0]; } else { PRUint32 index = (PRUint32) floor(simpleProgress * (aValues.Length())); aResult = aValues[index]; } rv = NS_OK; } return rv; }
nsresult nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult, nsSMILValue& aBaseValue) { // Sanity check animation values if ((!IsToAnimation() && aValues.Length() < 2) || (IsToAnimation() && aValues.Length() != 1)) { NS_ERROR("Unexpected number of values"); return NS_ERROR_FAILURE; } if (IsToAnimation() && aBaseValue.IsNull()) { return NS_ERROR_FAILURE; } // Get the normalised progress through the simple duration. // // If we have an indefinite simple duration, just set the progress to be // 0 which will give us the expected behaviour of the animation being fixed at // its starting point. double simpleProgress = 0.0; if (mSimpleDuration.IsDefinite()) { nsSMILTime dur = mSimpleDuration.GetMillis(); MOZ_ASSERT(dur >= 0, "Simple duration should not be negative"); MOZ_ASSERT(mSampleTime >= 0, "Sample time should not be negative"); if (mSampleTime >= dur || mSampleTime < 0) { NS_ERROR("Animation sampled outside interval"); return NS_ERROR_FAILURE; } if (dur > 0) { simpleProgress = (double)mSampleTime / dur; } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0) } nsresult rv = NS_OK; nsSMILCalcMode calcMode = GetCalcMode(); // Force discrete calcMode for visibility since StyleAnimationValue will // try to interpolate it using the special clamping behavior defined for // CSS. if (nsSMILCSSValueType::PropertyFromValue(aValues[0]) == eCSSProperty_visibility) { calcMode = CALC_DISCRETE; } if (calcMode != CALC_DISCRETE) { // Get the normalised progress between adjacent values const nsSMILValue* from = nullptr; const nsSMILValue* to = nullptr; // Init to -1 to make sure that if we ever forget to set this, the // MOZ_ASSERT that tests that intervalProgress is in range will fail. double intervalProgress = -1.f; if (IsToAnimation()) { from = &aBaseValue; to = &aValues[0]; if (calcMode == CALC_PACED) { // Note: key[Times/Splines/Points] are ignored for calcMode="paced" intervalProgress = simpleProgress; } else { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0); } } else if (calcMode == CALC_PACED) { rv = ComputePacedPosition(aValues, simpleProgress, intervalProgress, from, to); // Note: If the above call fails, we'll skip the "from->Interpolate" // call below, and we'll drop into the CALC_DISCRETE section // instead. (as the spec says we should, because our failure was // presumably due to the values being non-additive) } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); uint32_t index = (uint32_t)floor(scaledSimpleProgress * (aValues.Length() - 1)); from = &aValues[index]; to = &aValues[index + 1]; intervalProgress = scaledSimpleProgress * (aValues.Length() - 1) - index; intervalProgress = ScaleIntervalProgress(intervalProgress, index); } if (NS_SUCCEEDED(rv)) { MOZ_ASSERT(from, "NULL from-value during interpolation"); MOZ_ASSERT(to, "NULL to-value during interpolation"); MOZ_ASSERT(0.0f <= intervalProgress && intervalProgress < 1.0f, "Interval progress should be in the range [0, 1)"); rv = from->Interpolate(*to, intervalProgress, aResult); } } // Discrete-CalcMode case // Note: If interpolation failed (isn't supported for this type), the SVG // spec says to force discrete mode. if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, CALC_DISCRETE); // Floating-point errors can mean that, for example, a sample time of 29s in // a 100s duration animation gives us a simple progress of 0.28999999999 // instead of the 0.29 we'd expect. Normally this isn't a noticeable // problem, but when we have sudden jumps in animation values (such as is // the case here with discrete animation) we can get unexpected results. // // To counteract this, before we perform a floor() on the animation // progress, we add a tiny fudge factor to push us into the correct interval // in cases where floating-point errors might cause us to fall short. static const double kFloatingPointFudgeFactor = 1.0e-16; if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) { scaledSimpleProgress += kFloatingPointFudgeFactor; } if (IsToAnimation()) { // We don't follow SMIL 3, 12.6.4, where discrete to animations // are the same as <set> animations. Instead, we treat it as a // discrete animation with two values (the underlying value and // the to="" value), and honor keyTimes="" as well. uint32_t index = (uint32_t)floor(scaledSimpleProgress * 2); aResult = index == 0 ? aBaseValue : aValues[0]; } else { uint32_t index = (uint32_t)floor(scaledSimpleProgress * aValues.Length()); aResult = aValues[index]; } rv = NS_OK; } return rv; }
nsresult nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues, nsSMILValue& aResult, nsSMILValue& aBaseValue) { // Sanity check animation values if ((!IsToAnimation() && aValues.Length() < 2) || (IsToAnimation() && aValues.Length() != 1)) { NS_ERROR("Unexpected number of values"); return NS_ERROR_FAILURE; } if (IsToAnimation() && aBaseValue.IsNull()) { return NS_ERROR_FAILURE; } // Get the normalised progress through the simple duration. // // If we have an indefinite simple duration, just set the progress to be // 0 which will give us the expected behaviour of the animation being fixed at // its starting point. double simpleProgress = 0.0; if (mSimpleDuration.IsResolved()) { nsSMILTime dur = mSimpleDuration.GetMillis(); NS_ABORT_IF_FALSE(dur >= 0, "Simple duration should not be negative"); NS_ABORT_IF_FALSE(mSampleTime >= 0, "Sample time should not be negative"); if (mSampleTime >= dur || mSampleTime < 0) { NS_ERROR("Animation sampled outside interval"); return NS_ERROR_FAILURE; } if (dur > 0) { simpleProgress = (double)mSampleTime / dur; } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0) } nsresult rv = NS_OK; nsSMILCalcMode calcMode = GetCalcMode(); if (calcMode != CALC_DISCRETE) { // Get the normalised progress between adjacent values const nsSMILValue* from = nsnull; const nsSMILValue* to = nsnull; // Init to -1 to make sure that if we ever forget to set this, the // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail. double intervalProgress = -1.f; if (IsToAnimation()) { from = &aBaseValue; to = &aValues[0]; if (calcMode == CALC_PACED) { // Note: key[Times/Splines/Points] are ignored for calcMode="paced" intervalProgress = simpleProgress; } else { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0); } } else if (calcMode == CALC_PACED) { rv = ComputePacedPosition(aValues, simpleProgress, intervalProgress, from, to); // Note: If the above call fails, we'll skip the "from->Interpolate" // call below, and we'll drop into the CALC_DISCRETE section // instead. (as the spec says we should, because our failure was // presumably due to the values being non-additive) } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, calcMode); PRUint32 index = (PRUint32)floor(scaledSimpleProgress * (aValues.Length() - 1)); from = &aValues[index]; to = &aValues[index + 1]; intervalProgress = scaledSimpleProgress * (aValues.Length() - 1) - index; intervalProgress = ScaleIntervalProgress(intervalProgress, index); } if (NS_SUCCEEDED(rv)) { NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation"); NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation"); NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f, "Interval progress should be in the range [0, 1)"); rv = from->Interpolate(*to, intervalProgress, aResult); } } // Discrete-CalcMode case // Note: If interpolation failed (isn't supported for this type), the SVG // spec says to force discrete mode. if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) { double scaledSimpleProgress = ScaleSimpleProgress(simpleProgress, CALC_DISCRETE); if (IsToAnimation()) { // We don't follow SMIL 3, 12.6.4, where discrete to animations // are the same as <set> animations. Instead, we treat it as a // discrete animation with two values (the underlying value and // the to="" value), and honor keyTimes="" as well. PRUint32 index = (PRUint32)floor(scaledSimpleProgress * 2); aResult = index == 0 ? aBaseValue : aValues[0]; } else { PRUint32 index = (PRUint32)floor(scaledSimpleProgress * aValues.Length()); aResult = aValues[index]; } rv = NS_OK; } return rv; }