Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : /* implementation of nsISMILType for use by <animateMotion> element */
8 :
9 : #include "SVGMotionSMILType.h"
10 :
11 : #include "gfx2DGlue.h"
12 : #include "mozilla/gfx/Point.h"
13 : #include "nsSMILValue.h"
14 : #include "nsDebug.h"
15 : #include "nsMathUtils.h"
16 : #include "nsISupportsUtils.h"
17 : #include "nsTArray.h"
18 : #include <math.h>
19 :
20 : using namespace mozilla::gfx;
21 :
22 : namespace mozilla {
23 :
24 : /*static*/ SVGMotionSMILType SVGMotionSMILType::sSingleton;
25 :
26 :
27 : // Helper enum, for distinguishing between types of MotionSegment structs
28 : enum SegmentType {
29 : eSegmentType_Translation,
30 : eSegmentType_PathPoint
31 : };
32 :
33 : // Helper Structs: containers for params to define our MotionSegment
34 : // (either simple translation or point-on-a-path)
35 : struct TranslationParams { // Simple translation
36 : float mX;
37 : float mY;
38 : };
39 : struct PathPointParams { // Point along a path
40 : // Refcounted: need to AddRef/Release. This can't be an nsRefPtr because
41 : // this struct is used inside a union so it can't have a default constructor.
42 : Path* MOZ_OWNING_REF mPath;
43 : float mDistToPoint; // Distance from path start to the point on the path that
44 : // we're interested in.
45 : };
46 :
47 : /**
48 : * Helper Struct: MotionSegment
49 : *
50 : * Instances of this class represent the points that we move between during
51 : * <animateMotion>. Each nsSMILValue will get a nsTArray of these (generally
52 : * with at most 1 entry in the array, except for in SandwichAdd). (This
53 : * matches our behavior in nsSVGTransformSMILType.)
54 : *
55 : * NOTE: In general, MotionSegments are represented as points on a path
56 : * (eSegmentType_PathPoint), so that we can easily interpolate and compute
57 : * distance *along their path*. However, Add() outputs MotionSegments as
58 : * simple translations (eSegmentType_Translation), because adding two points
59 : * from a path (e.g. when accumulating a repeated animation) will generally
60 : * take you to an arbitrary point *off* of the path.
61 : */
62 : struct MotionSegment
63 : {
64 : // Default constructor just locks us into being a Translation, and leaves
65 : // other fields uninitialized (since client is presumably about to set them)
66 : MotionSegment()
67 : : mSegmentType(eSegmentType_Translation)
68 : { }
69 :
70 : // Constructor for a translation
71 0 : MotionSegment(float aX, float aY, float aRotateAngle)
72 0 : : mRotateType(eRotateType_Explicit), mRotateAngle(aRotateAngle),
73 0 : mSegmentType(eSegmentType_Translation)
74 : {
75 0 : mU.mTranslationParams.mX = aX;
76 0 : mU.mTranslationParams.mY = aY;
77 0 : }
78 :
79 : // Constructor for a point on a path (NOTE: AddRef's)
80 0 : MotionSegment(Path* aPath, float aDistToPoint,
81 : RotateType aRotateType, float aRotateAngle)
82 0 : : mRotateType(aRotateType), mRotateAngle(aRotateAngle),
83 0 : mSegmentType(eSegmentType_PathPoint)
84 : {
85 0 : mU.mPathPointParams.mPath = aPath;
86 0 : mU.mPathPointParams.mDistToPoint = aDistToPoint;
87 :
88 0 : NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path
89 0 : }
90 :
91 : // Copy constructor (NOTE: AddRef's if we're eSegmentType_PathPoint)
92 0 : MotionSegment(const MotionSegment& aOther)
93 0 : : mRotateType(aOther.mRotateType), mRotateAngle(aOther.mRotateAngle),
94 0 : mSegmentType(aOther.mSegmentType)
95 : {
96 0 : if (mSegmentType == eSegmentType_Translation) {
97 0 : mU.mTranslationParams = aOther.mU.mTranslationParams;
98 : } else { // mSegmentType == eSegmentType_PathPoint
99 0 : mU.mPathPointParams = aOther.mU.mPathPointParams;
100 0 : NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path
101 : }
102 0 : }
103 :
104 : // Destructor (releases any reference we were holding onto)
105 0 : ~MotionSegment()
106 0 : {
107 0 : if (mSegmentType == eSegmentType_PathPoint) {
108 0 : NS_RELEASE(mU.mPathPointParams.mPath);
109 : }
110 0 : }
111 :
112 : // Comparison operators
113 0 : bool operator==(const MotionSegment& aOther) const
114 : {
115 : // Compare basic params
116 0 : if (mSegmentType != aOther.mSegmentType ||
117 0 : mRotateType != aOther.mRotateType ||
118 0 : (mRotateType == eRotateType_Explicit && // Technically, angle mismatch
119 0 : mRotateAngle != aOther.mRotateAngle)) { // only matters for Explicit.
120 0 : return false;
121 : }
122 :
123 : // Compare translation params, if we're a translation.
124 0 : if (mSegmentType == eSegmentType_Translation) {
125 0 : return mU.mTranslationParams.mX == aOther.mU.mTranslationParams.mX &&
126 0 : mU.mTranslationParams.mY == aOther.mU.mTranslationParams.mY;
127 : }
128 :
129 : // Else, compare path-point params, if we're a path point.
130 0 : return (mU.mPathPointParams.mPath == aOther.mU.mPathPointParams.mPath) &&
131 0 : (mU.mPathPointParams.mDistToPoint ==
132 0 : aOther.mU.mPathPointParams.mDistToPoint);
133 : }
134 :
135 0 : bool operator!=(const MotionSegment& aOther) const
136 : {
137 0 : return !(*this == aOther);
138 : }
139 :
140 : // Member Data
141 : // -----------
142 : RotateType mRotateType; // Explicit angle vs. auto vs. auto-reverse.
143 : float mRotateAngle; // Only used if mRotateType == eRotateType_Explicit.
144 : const SegmentType mSegmentType; // This determines how we interpret
145 : // mU. (const for safety/sanity)
146 :
147 : union { // Union to let us hold the params for either segment-type.
148 : TranslationParams mTranslationParams;
149 : PathPointParams mPathPointParams;
150 : } mU;
151 : };
152 :
153 : typedef FallibleTArray<MotionSegment> MotionSegmentArray;
154 :
155 : // Helper methods to cast nsSMILValue.mU.mPtr to the right pointer-type
156 : static MotionSegmentArray&
157 0 : ExtractMotionSegmentArray(nsSMILValue& aValue)
158 : {
159 0 : return *static_cast<MotionSegmentArray*>(aValue.mU.mPtr);
160 : }
161 :
162 : static const MotionSegmentArray&
163 0 : ExtractMotionSegmentArray(const nsSMILValue& aValue)
164 : {
165 0 : return *static_cast<const MotionSegmentArray*>(aValue.mU.mPtr);
166 : }
167 :
168 : // nsISMILType Methods
169 : // -------------------
170 :
171 : void
172 0 : SVGMotionSMILType::Init(nsSMILValue& aValue) const
173 : {
174 0 : MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL type");
175 :
176 0 : aValue.mType = this;
177 0 : aValue.mU.mPtr = new MotionSegmentArray(1);
178 0 : }
179 :
180 : void
181 0 : SVGMotionSMILType::Destroy(nsSMILValue& aValue) const
182 : {
183 0 : MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL type");
184 :
185 0 : MotionSegmentArray* arr = static_cast<MotionSegmentArray*>(aValue.mU.mPtr);
186 0 : delete arr;
187 :
188 0 : aValue.mU.mPtr = nullptr;
189 0 : aValue.mType = nsSMILNullType::Singleton();
190 0 : }
191 :
192 : nsresult
193 0 : SVGMotionSMILType::Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const
194 : {
195 0 : MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types");
196 0 : MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type");
197 :
198 0 : const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aSrc);
199 0 : MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
200 0 : if (!dstArr.Assign(srcArr, fallible)) {
201 0 : return NS_ERROR_OUT_OF_MEMORY;
202 : }
203 :
204 0 : return NS_OK;
205 : }
206 :
207 : bool
208 0 : SVGMotionSMILType::IsEqual(const nsSMILValue& aLeft,
209 : const nsSMILValue& aRight) const
210 : {
211 0 : MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types");
212 0 : MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL type");
213 :
214 0 : const MotionSegmentArray& leftArr = ExtractMotionSegmentArray(aLeft);
215 0 : const MotionSegmentArray& rightArr = ExtractMotionSegmentArray(aRight);
216 :
217 : // If array-lengths don't match, we're trivially non-equal.
218 0 : if (leftArr.Length() != rightArr.Length()) {
219 0 : return false;
220 : }
221 :
222 : // Array-lengths match -- check each array-entry for equality.
223 0 : uint32_t length = leftArr.Length(); // == rightArr->Length(), if we get here
224 0 : for (uint32_t i = 0; i < length; ++i) {
225 0 : if (leftArr[i] != rightArr[i]) {
226 0 : return false;
227 : }
228 : }
229 :
230 0 : return true; // If we get here, we found no differences.
231 : }
232 :
233 : // Helper method for Add & CreateMatrix
234 : inline static void
235 0 : GetAngleAndPointAtDistance(Path* aPath, float aDistance,
236 : RotateType aRotateType,
237 : float& aRotateAngle, // in & out-param.
238 : Point& aPoint) // out-param.
239 : {
240 0 : if (aRotateType == eRotateType_Explicit) {
241 : // Leave aRotateAngle as-is.
242 0 : aPoint = aPath->ComputePointAtLength(aDistance);
243 : } else {
244 0 : Point tangent; // Unit vector tangent to the point we find.
245 0 : aPoint = aPath->ComputePointAtLength(aDistance, &tangent);
246 0 : float tangentAngle = atan2(tangent.y, tangent.x);
247 0 : if (aRotateType == eRotateType_Auto) {
248 0 : aRotateAngle = tangentAngle;
249 : } else {
250 0 : MOZ_ASSERT(aRotateType == eRotateType_AutoReverse);
251 0 : aRotateAngle = M_PI + tangentAngle;
252 : }
253 : }
254 0 : }
255 :
256 : nsresult
257 0 : SVGMotionSMILType::Add(nsSMILValue& aDest, const nsSMILValue& aValueToAdd,
258 : uint32_t aCount) const
259 : {
260 0 : MOZ_ASSERT(aDest.mType == aValueToAdd.mType,
261 : "Incompatible SMIL types");
262 0 : MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type");
263 :
264 0 : MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
265 0 : const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd);
266 :
267 : // We're doing a simple add here (as opposed to a sandwich add below). We
268 : // only do this when we're accumulating a repeat result.
269 : // NOTE: In other nsISMILTypes, we use this method with a barely-initialized
270 : // |aDest| value to assist with "by" animation. (In this case,
271 : // "barely-initialized" would mean dstArr.Length() would be empty.) However,
272 : // we don't do this for <animateMotion>, because we instead use our "by"
273 : // value to construct an equivalent "path" attribute, and we use *that* for
274 : // our actual animation.
275 0 : MOZ_ASSERT(srcArr.Length() == 1, "Invalid source segment arr to add");
276 0 : MOZ_ASSERT(dstArr.Length() == 1, "Invalid dest segment arr to add to");
277 0 : const MotionSegment& srcSeg = srcArr[0];
278 0 : const MotionSegment& dstSeg = dstArr[0];
279 0 : MOZ_ASSERT(srcSeg.mSegmentType == eSegmentType_PathPoint,
280 : "expecting to be adding points from a motion path");
281 0 : MOZ_ASSERT(dstSeg.mSegmentType == eSegmentType_PathPoint,
282 : "expecting to be adding points from a motion path");
283 :
284 0 : const PathPointParams& srcParams = srcSeg.mU.mPathPointParams;
285 0 : const PathPointParams& dstParams = dstSeg.mU.mPathPointParams;
286 :
287 0 : MOZ_ASSERT(srcSeg.mRotateType == dstSeg.mRotateType &&
288 : srcSeg.mRotateAngle == dstSeg.mRotateAngle,
289 : "unexpected angle mismatch");
290 0 : MOZ_ASSERT(srcParams.mPath == dstParams.mPath,
291 : "unexpected path mismatch");
292 0 : Path* path = srcParams.mPath;
293 :
294 : // Use destination to get our rotate angle.
295 0 : float rotateAngle = dstSeg.mRotateAngle;
296 0 : Point dstPt;
297 0 : GetAngleAndPointAtDistance(path, dstParams.mDistToPoint, dstSeg.mRotateType,
298 0 : rotateAngle, dstPt);
299 :
300 0 : Point srcPt = path->ComputePointAtLength(srcParams.mDistToPoint);
301 :
302 0 : float newX = dstPt.x + srcPt.x * aCount;
303 0 : float newY = dstPt.y + srcPt.y * aCount;
304 :
305 : // Replace destination's current value -- a point-on-a-path -- with the
306 : // translation that results from our addition.
307 0 : dstArr.ReplaceElementAt(0, MotionSegment(newX, newY, rotateAngle));
308 0 : return NS_OK;
309 : }
310 :
311 : nsresult
312 0 : SVGMotionSMILType::SandwichAdd(nsSMILValue& aDest,
313 : const nsSMILValue& aValueToAdd) const
314 : {
315 0 : MOZ_ASSERT(aDest.mType == aValueToAdd.mType,
316 : "Incompatible SMIL types");
317 0 : MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type");
318 0 : MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest);
319 0 : const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd);
320 :
321 : // We're only expecting to be adding 1 segment on to the list
322 0 : MOZ_ASSERT(srcArr.Length() == 1,
323 : "Trying to do sandwich add of more than one value");
324 :
325 0 : if (!dstArr.AppendElement(srcArr[0], fallible)) {
326 0 : return NS_ERROR_OUT_OF_MEMORY;
327 : }
328 :
329 0 : return NS_OK;
330 : }
331 :
332 : nsresult
333 0 : SVGMotionSMILType::ComputeDistance(const nsSMILValue& aFrom,
334 : const nsSMILValue& aTo,
335 : double& aDistance) const
336 : {
337 0 : MOZ_ASSERT(aFrom.mType == aTo.mType, "Incompatible SMIL types");
338 0 : MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type");
339 0 : const MotionSegmentArray& fromArr = ExtractMotionSegmentArray(aFrom);
340 0 : const MotionSegmentArray& toArr = ExtractMotionSegmentArray(aTo);
341 :
342 : // ComputeDistance is only used for calculating distances between single
343 : // values in a values array. So we should only have one entry in each array.
344 0 : MOZ_ASSERT(fromArr.Length() == 1,
345 : "Wrong number of elements in from value");
346 0 : MOZ_ASSERT(toArr.Length() == 1,
347 : "Wrong number of elements in to value");
348 :
349 0 : const MotionSegment& from = fromArr[0];
350 0 : const MotionSegment& to = toArr[0];
351 :
352 0 : MOZ_ASSERT(from.mSegmentType == to.mSegmentType,
353 : "Mismatched MotionSegment types");
354 0 : if (from.mSegmentType == eSegmentType_PathPoint) {
355 0 : const PathPointParams& fromParams = from.mU.mPathPointParams;
356 0 : const PathPointParams& toParams = to.mU.mPathPointParams;
357 0 : MOZ_ASSERT(fromParams.mPath == toParams.mPath,
358 : "Interpolation endpoints should be from same path");
359 0 : MOZ_ASSERT(fromParams.mDistToPoint <= toParams.mDistToPoint,
360 : "To value shouldn't be before from value on path");
361 0 : aDistance = fabs(toParams.mDistToPoint - fromParams.mDistToPoint);
362 : } else {
363 0 : const TranslationParams& fromParams = from.mU.mTranslationParams;
364 0 : const TranslationParams& toParams = to.mU.mTranslationParams;
365 0 : float dX = toParams.mX - fromParams.mX;
366 0 : float dY = toParams.mY - fromParams.mY;
367 0 : aDistance = NS_hypot(dX, dY);
368 : }
369 :
370 0 : return NS_OK;
371 : }
372 :
373 : // Helper method for Interpolate()
374 : static inline float
375 0 : InterpolateFloat(const float& aStartFlt, const float& aEndFlt,
376 : const double& aUnitDistance)
377 : {
378 0 : return aStartFlt + aUnitDistance * (aEndFlt - aStartFlt);
379 : }
380 :
381 : nsresult
382 0 : SVGMotionSMILType::Interpolate(const nsSMILValue& aStartVal,
383 : const nsSMILValue& aEndVal,
384 : double aUnitDistance,
385 : nsSMILValue& aResult) const
386 : {
387 0 : MOZ_ASSERT(aStartVal.mType == aEndVal.mType,
388 : "Trying to interpolate different types");
389 0 : MOZ_ASSERT(aStartVal.mType == this,
390 : "Unexpected types for interpolation");
391 0 : MOZ_ASSERT(aResult.mType == this, "Unexpected result type");
392 0 : MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0,
393 : "unit distance value out of bounds");
394 :
395 0 : const MotionSegmentArray& startArr = ExtractMotionSegmentArray(aStartVal);
396 0 : const MotionSegmentArray& endArr = ExtractMotionSegmentArray(aEndVal);
397 0 : MotionSegmentArray& resultArr = ExtractMotionSegmentArray(aResult);
398 :
399 0 : MOZ_ASSERT(startArr.Length() <= 1,
400 : "Invalid start-point for animateMotion interpolation");
401 0 : MOZ_ASSERT(endArr.Length() == 1,
402 : "Invalid end-point for animateMotion interpolation");
403 0 : MOZ_ASSERT(resultArr.IsEmpty(),
404 : "Expecting result to be just-initialized w/ empty array");
405 :
406 0 : const MotionSegment& endSeg = endArr[0];
407 0 : MOZ_ASSERT(endSeg.mSegmentType == eSegmentType_PathPoint,
408 : "Expecting to be interpolating along a path");
409 :
410 0 : const PathPointParams& endParams = endSeg.mU.mPathPointParams;
411 : // NOTE: path & angle should match between start & end (since presumably
412 : // start & end came from the same <animateMotion> element), unless start is
413 : // empty. (as it would be for pure 'to' animation)
414 0 : Path* path = endParams.mPath;
415 0 : RotateType rotateType = endSeg.mRotateType;
416 0 : float rotateAngle = endSeg.mRotateAngle;
417 :
418 : float startDist;
419 0 : if (startArr.IsEmpty()) {
420 0 : startDist = 0.0f;
421 : } else {
422 0 : const MotionSegment& startSeg = startArr[0];
423 0 : MOZ_ASSERT(startSeg.mSegmentType == eSegmentType_PathPoint,
424 : "Expecting to be interpolating along a path");
425 0 : const PathPointParams& startParams = startSeg.mU.mPathPointParams;
426 0 : MOZ_ASSERT(startSeg.mRotateType == endSeg.mRotateType &&
427 : startSeg.mRotateAngle == endSeg.mRotateAngle,
428 : "unexpected angle mismatch");
429 0 : MOZ_ASSERT(startParams.mPath == endParams.mPath,
430 : "unexpected path mismatch");
431 0 : startDist = startParams.mDistToPoint;
432 : }
433 :
434 : // Get the interpolated distance along our path.
435 0 : float resultDist = InterpolateFloat(startDist, endParams.mDistToPoint,
436 0 : aUnitDistance);
437 :
438 : // Construct the intermediate result segment, and put it in our outparam.
439 : // AppendElement has guaranteed success here, since Init() allocates 1 slot.
440 0 : MOZ_ALWAYS_TRUE(resultArr.AppendElement(MotionSegment(path, resultDist,
441 : rotateType,
442 : rotateAngle),
443 : fallible));
444 0 : return NS_OK;
445 : }
446 :
447 : /* static */ gfx::Matrix
448 0 : SVGMotionSMILType::CreateMatrix(const nsSMILValue& aSMILVal)
449 : {
450 0 : const MotionSegmentArray& arr = ExtractMotionSegmentArray(aSMILVal);
451 :
452 0 : gfx::Matrix matrix;
453 0 : uint32_t length = arr.Length();
454 0 : for (uint32_t i = 0; i < length; i++) {
455 0 : Point point; // initialized below
456 0 : float rotateAngle = arr[i].mRotateAngle; // might get updated below
457 0 : if (arr[i].mSegmentType == eSegmentType_Translation) {
458 0 : point.x = arr[i].mU.mTranslationParams.mX;
459 0 : point.y = arr[i].mU.mTranslationParams.mY;
460 0 : MOZ_ASSERT(arr[i].mRotateType == eRotateType_Explicit,
461 : "'auto'/'auto-reverse' should have been converted to "
462 : "explicit angles when we generated this translation");
463 : } else {
464 0 : GetAngleAndPointAtDistance(arr[i].mU.mPathPointParams.mPath,
465 0 : arr[i].mU.mPathPointParams.mDistToPoint,
466 0 : arr[i].mRotateType,
467 0 : rotateAngle, point);
468 : }
469 0 : matrix.PreTranslate(point.x, point.y);
470 0 : matrix.PreRotate(rotateAngle);
471 : }
472 0 : return matrix;
473 : }
474 :
475 : /* static */ nsSMILValue
476 0 : SVGMotionSMILType::ConstructSMILValue(Path* aPath,
477 : float aDist,
478 : RotateType aRotateType,
479 : float aRotateAngle)
480 : {
481 0 : nsSMILValue smilVal(&SVGMotionSMILType::sSingleton);
482 0 : MotionSegmentArray& arr = ExtractMotionSegmentArray(smilVal);
483 :
484 : // AppendElement has guaranteed success here, since Init() allocates 1 slot.
485 0 : MOZ_ALWAYS_TRUE(arr.AppendElement(MotionSegment(aPath, aDist,
486 : aRotateType, aRotateAngle),
487 : fallible));
488 0 : return smilVal;
489 : }
490 :
491 : } // namespace mozilla
|