1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  5  *
  6  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7  * of this software and associated documentation files (the "Software"), to deal
  8  * in the Software without restriction, including without limitation the rights
  9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  * copies of the Software, and to permit persons to whom the Software is
 11  * furnished to do so, subject to the following conditions:
 12  *
 13  * The above copyright notice and this permission notice shall be included in
 14  * all copies or substantial portions of the Software.
 15  *
 16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="point.js"/>
 27 /// <reference path="bounding_box.js"/>
 28 
 29 /**
 30  * A class that represents an affine transformation compatible with the one used
 31  * by the CanvasRenderingContext2D interface, e.g. a 3x3 matrix listed below:
 32  *   | a c tx |
 33  *   | b d ty |
 34  *   | 0 0 1  |
 35  * @constructor
 36  */
 37 createjs.Transform = function() {
 38 };
 39 
 40 /**
 41  * The horizontal scale.
 42  * @type {number}
 43  */
 44 createjs.Transform.prototype.a = 1;
 45 
 46 /**
 47  * The horizontal skew.
 48  * @type {number}
 49  */
 50 createjs.Transform.prototype.b = 0;
 51 
 52 /**
 53  * The vertical skew.
 54  * @type {number}
 55  */
 56 createjs.Transform.prototype.c = 0;
 57 
 58 /**
 59  * The vertical scale.
 60  * @type {number}
 61  */
 62 createjs.Transform.prototype.d = 1;
 63 
 64 /**
 65  * The horizontal translation.
 66  * @type {number}
 67  */
 68 createjs.Transform.prototype.tx = 0;
 69 
 70 /**
 71  * The vertical translation.
 72  * @type {number}
 73  */
 74 createjs.Transform.prototype.ty = 0;
 75 
 76 /**
 77  * Whether this transform is invertible.
 78  * @type {number}
 79  */
 80 createjs.Transform.prototype.invertible = 1;
 81 
 82 /**
 83  * Prepends the specified transform with this transform. This method multiplies
 84  * this transformation matrix with the specified one as listed in the following
 85  * formula.
 86  *   a0 = transform.a, b0 = transform.b, c0 = transform.c, d0 = transform.d,
 87  *   tx0 = transform.tx, ty0 = transfor.ty,
 88  *   a = this.a, b = this.b, c = this.c, d = this.d, tx = this.tx, ty = this.ty.
 89  *   | a0 c0 tx0 |   | a c tz |
 90  *   | b0 d0 ty0 | * | b d ty |
 91  *   | 0  0  1   |   | 0 0 1  |
 92  *       | a0*a+c0*b a0*c+c0*d a0*tx+c0*ty+tx0 |
 93  *     = | b0*a+d0*b b0*c+d0*d b0*tx+d0*ty+ty0 |
 94  *       | 0         0         1               |
 95  * @param {createjs.Transform} transform
 96  * @private
 97  */
 98 createjs.Transform.prototype.prepend_ = function(transform) {
 99   /// <param type="createjs.Transform" name="transform"/>
100   var a = this.a;
101   var b = this.b;
102   var c = this.c;
103   var d = this.d;
104   var tx = this.tx;
105   var ty = this.ty;
106   this.a  = transform.a * a + transform.c * b;
107   this.b  = transform.b * a + transform.d * b;
108   this.c  = transform.a * c + transform.c * d;
109   this.d  = transform.b * c + transform.d * d;
110   this.tx = transform.a * tx + transform.c * ty + transform.tx;
111   this.ty = transform.b * tx + transform.d * ty + transform.ty;
112 };
113 
114 /**
115  * Generates matrix properties from transform properties used by display
116  * objects, and appends them with this matrix.
117  * @param {createjs.Point} position
118  * @param {createjs.Point} scale
119  * @param {number} rotation
120  * @param {createjs.Point} skew
121  * @param {createjs.Point} registration
122  * @const
123  */
124 createjs.Transform.prototype.set =
125     function(position, scale, rotation, skew, registration) {
126   /// <param type="createjs.Point" name="position"/>
127   /// <param type="createjs.Point" name="scale"/>
128   /// <param type="number" name="rotation"/>
129   /// <param type="createjs.Point" name="skew"/>
130   /// <param type="createjs.Point" name="registration"/>
131 
132   // Create a scale matrix.
133   //   | a c 0 |   | scale.x 0       0 |
134   //   | b d 0 | = | 0       scale.y 0 |
135   //   | 0 0 1 |   | 0       0       1 |
136   var a = scale.x;
137   var b = 0;
138   var c = 0;
139   var d = scale.y;
140   if (rotation) {
141     // Multiply a rotation matrix with a scale one as listed in the following
142     // formula.
143     //   | a c 0 |   | cos(r) -sin(r) 0 |   | a 0 0 |
144     //   | b d 0 | = | sin(r) cos(r)  0 | * | 0 d 0 |
145     //   | 0 0 1 |   | 0      0       1 |   | 0 0 1 |
146     //               | cos(r)*a -sin(r)*d 0 |
147     //             = | sin(r)*a cos(r)*d  0 |
148     //               | 0        0         1 |
149     var cos = createjs.cos(rotation);
150     var sin = createjs.sin(rotation);
151     b = sin * a;
152     c = -sin * d;
153     a = cos * a;
154     d = cos * d;
155   }
156   if (skew.x || skew.y) {
157     // Multiply a skew matrix with a rotation one as listed in the following
158     // formula.
159     //   | a c 0 |   | cos(sy) -sin(sx) 0 |   | a c 0 |
160     //   | b d 0 | = | sin(sy) cos(sx)  0 | * | b d 0 |
161     //   | 0 0 1 |   | 0       0        1 |   | 0 0 1 |
162     //               | cos(sy)*a-sin(sx)*b cos(sy)*c-sin(sx)*d 0 |
163     //             = | sin(sy)*a+cos(sx)*b sin(sy)*c+cos(sx)*d 0 |
164     //               | 0                   0                   1 |
165     var a0 = createjs.cos(skew.y);
166     var b0 = createjs.sin(skew.y);
167     var c0 = -createjs.sin(skew.x);
168     var d0 = createjs.cos(skew.x);
169     var a1 = a;
170     var b1 = b;
171     var c1 = c;
172     var d1 = d;
173     a = a0 * a1 + c0 * b1;
174     b = b0 * a1 + d0 * b1;
175     c = a0 * c1 + c0 * d1;
176     d = b0 * c1 + d0 * d1;
177   }
178   this.a = a;
179   this.b = b;
180   this.c = c;
181   this.d = d;
182   this.tx = position.x;
183   this.ty = position.y;
184   if (registration.x || registration.y) {
185     this.tx -= registration.x * this.a + registration.y * this.c;
186     this.ty -= registration.x * this.b + registration.y * this.d;
187   }
188 };
189 
190 /**
191  * Copies the specified transform.
192  * @param {createjs.Transform} transform
193  * @const
194  */
195 createjs.Transform.prototype.copyTransform = function(transform) {
196   /// <param type="createjs.Transform" name="transform"/>
197   this.a = transform.a;
198   this.b = transform.b;
199   this.c = transform.c;
200   this.d = transform.d;
201   this.tx = transform.tx;
202   this.ty = transform.ty;
203 };
204 
205 /**
206  * Generates matrix properties from transform properties used by display
207  * objects, and appends them with this matrix.
208  * @param {createjs.Transform} transform
209  * @param {createjs.Point} position
210  * @param {createjs.Point} scale
211  * @param {number} rotation
212  * @param {createjs.Point} skew
213  * @param {createjs.Point} registration
214  * @const
215  */
216 createjs.Transform.prototype.appendTransform =
217     function(transform, position, scale, rotation, skew, registration) {
218   /// <param type="createjs.Transform" name="transform"/>
219   /// <param type="createjs.Point" name="position"/>
220   /// <param type="createjs.Point" name="scale"/>
221   /// <param type="number" name="rotation"/>
222   /// <param type="createjs.Point" name="skew"/>
223   /// <param type="createjs.Point" name="registration"/>
224   this.set(position, scale, rotation, skew, registration);
225   this.prepend_(transform);
226   this.invertible = (this.a * this.d - this.b * this.c) ? 1 : 0;
227 };
228 
229 /**
230  * Returns an inverse transformation of this transformation.
231  * @return {createjs.Transform}
232  * @const
233  */
234 createjs.Transform.prototype.getInverse = function() {
235   /// <returns type="createjs.Transform"/>
236   if (!this.invertible) {
237     return null;
238   }
239   var idet = 1 / (this.a * this.d - this.b * this.c);
240   var transform = new createjs.Transform();
241   transform.a = this.d * idet;
242   transform.b = -this.b * idet;
243   transform.c = -this.c * idet;
244   transform.d = this.a * idet;
245   transform.tx = (this.c * this.ty - this.d * this.tx) * idet;
246   transform.ty = -(this.a * this.ty - this.b * this.tx) * idet;
247   return transform;
248 };
249 
250 /**
251  * Applies this transformation to the specified point and returns the
252  * transformed point as listed in the following formula.
253  *   | x' |   | a c tx |   | x |
254  *   | y' | = | b d ty | * | y |
255  *   | 1  |   | 0 0 1  |   | 1 |
256  *            | a*x+c*y+tx |
257  *          = | b*x+d*y+ty |
258  *            | 1          |
259  * This method is used for converting a point in a local coordinate to a point
260  * in the global coordinate, and vice versa.
261  * @param {createjs.Point} point
262  * @return {createjs.Point}
263  * @const
264  */
265 createjs.Transform.prototype.transformPoint = function(point) {
266   /// <param type="createjs.Point" name="point"></param>
267   /// <returns type="createjs.Point"/>
268   var x = point.x;
269   var y = point.y;
270   point.x = this.a * x + this.c * y + this.tx;
271   point.y = this.b * x + this.d * y + this.ty;
272   return point;
273 };
274 
275 /**
276  * Transforms a bounding box in the coordinate system of the owner object to a
277  * box in the global coordinate system, which is used by the createjs.Renderer
278  * interface. This method applies an affine transformation to the corner points
279  * of the given box to get their global positions to create its transformed
280  * bounding box.
281  * @param {createjs.BoundingBox} box
282  * @param {createjs.BoundingBox} output
283  * @protected
284  * @const
285  */
286 createjs.Transform.prototype.transformBox = function(box, output) {
287   /// <param type="createjs.BoundingBox" name="box"></param>
288   /// <param type="createjs.BoundingBox" name="output"></param>
289   var minX = box.minX;
290   var minY = box.minY;
291   var maxX = box.maxX;
292   var maxY = box.maxY;
293   if (!this.b && !this.c) {
294     // This transform does not have skew factors and we can transform just the
295     // top-left corner and the bottom-right one to get its transformed bounding
296     // box.
297     //  | x0 |   | this.a this.c tx |   | box.minX |
298     //  | y0 | = | this.b this.d ty | * | box.minY |
299     //  | 1  |   | 0      0      1  |   | 1        |
300     //           | this.a * box.minX + this.c * box.minY + tx |
301     //         = | this.b * box.minX + this.d * box.minY + ty |
302     //           | 1                                          |
303     //  | x1 |   | this.a this.c tx |   | box.maxX |
304     //  | y1 | = | this.b this.d ty | * | box.maxY |
305     //  | 1  |   | 0      0      1  |   | 1        |
306     //           | this.a * box.maxX + this.c * box.maxY + tx |
307     //         = | this.b * box.maxX + this.d * box.maxY + ty |
308     //           | 1                                          |
309     var x0 = this.a * minX + this.c * minY;
310     var x1 = this.a * maxX + this.c * maxY;
311     if (x0 < x1) {
312       output.minX = x0;
313       output.maxX = x1;
314     } else {
315       output.minX = x1;
316       output.maxX = x0;
317     }
318     var y0 = this.b * minX + this.d * minY;
319     var y1 = this.b * maxX + this.d * maxY;
320     if (y0 < y1) {
321       output.minY = y0;
322       output.maxY = y1;
323     } else {
324       output.minY = y1;
325       output.maxY = y0;
326     }
327   } else {
328     var x0 = this.a * minX + this.c * minY;
329     var x1 = this.a * maxX + this.c * minY;
330     var x2 = this.a * minX + this.c * maxY;
331     var x3 = this.a * maxX + this.c * maxY;
332     output.minX = createjs.min(createjs.min(createjs.min(x0, x1), x2), x3);
333     output.maxX = createjs.max(createjs.max(createjs.max(x0, x1), x2), x3);
334     var y0 = this.b * minX + this.d * minY;
335     var y1 = this.b * maxX + this.d * minY;
336     var y2 = this.b * minX + this.d * maxY;
337     var y3 = this.b * maxX + this.d * maxY;
338     output.minY = createjs.min(createjs.min(createjs.min(y0, y1), y2), y3);
339     output.maxY = createjs.max(createjs.max(createjs.max(y0, y1), y2), y3);
340   }
341   output.minX += this.tx;
342   output.maxX += this.tx;
343   output.minY += this.ty;
344   output.maxY += this.ty;
345 };
346