Creating Beautiful Drawings Using Ext JS - Part 3

Sprite Transformations

In the previous article, we learned how to animate sprites by using animation modifiers. In this section, we’ll talk about sprite transformations. There are three standard types of transformations that can be applied to sprites:

  • Scaling.

  • Rotation.

  • Translation.

Note: Standard transformations are always applied in that order.

Let’s take a look at these three transformations.

Scaling

First, let’s examine the scaling transformation. There are four sprite attributes to control it:

  • scalingX - the factor of the horizontal scaling of the sprite

  • scalingY - the factor of the vertical scaling of the sprite

  • scalingCenterX - the x-coordinate of the center of transformation

  • scalingCenterY - the y-coordinate of the center of transformation

Each of the three transformation configs have an alias for the transformation that can be used in configuring the initial transformation. For scaling the config shorthand pseudo config, or alias, is called scaling.

Note: When sprite attributes are set, they normally go through a process of normalization, where known aliases for a particular sprite type expand into actual attributes. Be mindful of that when looking for sprite attributes inside the sprite.attr object. You won’t find aliases there. This also means that pseudo attributes cannot be used with the setAttributes method of the sprite, if its bypassNormalization parameter is true. Finally, aliases are nice for clarity - for example, when declaring sprites in views - but in performance critical code it is best to use original attributes.

  1. scaling: {
  2. x: {Number},
  3. y: {Number},
  4. centerX: {Number},
  5. centerY: {Number}
  6. }

And as with the rotation transformation, the center of scaling is the center of the sprite’s bounding box by default.

Let’s say we want to scale our 'rect' sprite so that it looks like a rectangle rather than a square. To do this, we can simply use different scale factors for horizontal and vertical scaling like so:

  1. scaling: {
  2. x: 2,
  3. y: 1.25
  4. }

image alt text

Open in a fiddle

If we wanted uniform scaling we could just say:

  1. scaling: 2

image alt text

Open in a fiddle

Let’s say we want to change the center of the scaling to the top-left corner of the sprite while maintaining the previous scaling factors.

  1. {
  2. type: 'rect',
  3. x: 100,
  4. y: 100,
  5. //...
  6. scaling: {
  7. x: 2,
  8. y: 1.25,
  9. centerX: 100,
  10. centerY: 100
  11. }
  12. }

This is the result of the above code:

image alt text

Open in a fiddle

The sprite just grew to the bottom/right instead of uniformly in both directions.

Rotation

The second transformation we’ll look at is rotation. There are three sprite attributes that allow us to control it:

Let’s rotate our sprite by 45 degrees clockwise, which is \(\frac{\pi}{4}\) radians. You can always use the Ext.draw.Draw.rad method to convert degrees to radians, e.g.: Ext.draw.Draw.rad(45).

  1. {
  2. type: 'rect',
  3. //...
  4. rotationRads: Math.PI / 4
  5. }

By default, the center of rotation is the center of the sprite’s bounding box. On the image below it is indicated with a black dot.

image alt text

Open in a fiddle

Let’s say we want to rotate our sprite around the top left corner. For this, we can simply move the center of rotation to the top-left corner of the sprite using rotationCenterX and rotationCenterY attributes:

  1. {
  2. type: 'rect',
  3. x: 100,
  4. y: 100,
  5. //...
  6. rotationRads: Math.PI / 4,
  7. rotationCenterX: 100,
  8. rotationCenterY: 100
  9. }

Open in a fiddle

There is also a special pseudo attribute called rotation. It can be either a number or an object. If it’s a number, it specifies the angle of rotation of the sprite in degrees, instead of radians. If it’s an object, it should be in the following form (where all properties are optional):

  1. rotation: {
  2. rads | degrees: {Number},
  3. centerX: {Number},
  4. centerY: {Number}
  5. }

We can rewrite the previous example like this:

  1. {
  2. type: 'rect',
  3. x: 100,
  4. y: 100,
  5. //...
  6. rotation: {
  7. degrees: 45,
  8. centerX: 100,
  9. centerY: 100
  10. }
  11. }

Open in a fiddle

Translation

There are two attributes every sprite has to control translation:

We’ll continue to use the rect sprite from the Part 1 of this series and apply all transformations to it.

  1. {
  2. type: 'rect',
  3. x: 100,
  4. y: 100,
  5. width: 100,
  6. height: 100,
  7. //...
  8. translationX: -50,
  9. translationY: -50
  10. }

The translated sprite will look like the following image, where the dashed line indicates the original untranslated position.

image alt text

Open in a fiddle

Alternatively, you could specify the same transformation using the following syntax:

  1. translation: {
  2. x: 100,
  3. y: -50
  4. }

Open in a fiddle

As with the previous transformations, translation is a pseudo attribute or an alias [^alias].

Combining Transformations

Now that we have looked at each transformation separately, let’s combine them by scaling and rotating the sprite simultaneously.

  1. type: 'rect',
  2. x: 100,
  3. y: 100,
  4. width: 100,
  5. height: 100,
  6. //...
  7. rotation: {
  8. degrees: 45,
  9. centerX: 100,
  10. centerY: 100
  11. },
  12. scaling: {
  13. x: 2,
  14. y: 1.25,
  15. centerX: 100,
  16. centerY: 100
  17. }

image alt text

Open in a fiddle

In this example the center of transformation is the same for both scaling and rotation. The transformed sprite looks as if the original sprite was first scaled, then rotated.

Finally, let’s add the translation transformation to the mix:

  1. translation: {
  2. x: 50,
  3. y: -50
  4. },
  5. rotation: {
  6. degrees: 45,
  7. centerX: 100,
  8. centerY: 100
  9. },
  10. scaling: {
  11. x: 2,
  12. y: 1.25,
  13. centerX: 100,
  14. centerY: 100
  15. }

image alt text

Open in a fiddle

Notice how translation is applied after scaling and rotation. This order is always the same:

  1. scale -> rotate -> translate

Note: the default order of transformations has changed in Ext JS 6.0.1+ from rotate->scale->translate to scale->rotate->translate. This has been done to be more compatible with other libraries and to prevent shearing when transformations are applied. This change won’t affect charts, but it may affect your custom drawing code. Please keep this in mind when upgrading to Ext JS 6.0.1+. You can also use an override, if you’d rather keep the previous order of transformations.

Transformations on an Existing Sprite

We just covered all of the standard transformations that are always applied in the same order. But what if we wanted to change this order, apply more than three transformations at once, or maybe use another type of transformation entirely, like shearing? That’s where transformation matrices come in handy.

Every sprite has a transformation matrix set by default with no transformations applied. This matrix communicates to the coordinate system of the surface how the sprite should be deformed and / or located within the coordinate plane. The coordinate plane of the draw surface starts with 0,0 as the top-left corner by default. The setTransform method will override the current transformation matrix of a sprite with the given matrix. Alternatively, you can use the transform method of a sprite, which will multiply the given matrix with current transformation matrix. In other words, it will add to the current transformation instead of replacing it.

Since multiplying matrices manually can be rather tedious, the Ext.draw.Matrix class is meant to help us alleviate the task. For example, this is how we can create an identity matrix and then multiply it, first with a scale matrix, and then with a translation matrix:

var m = new Ext.draw.Matrix().scale(sx, sy).translate(tx, ty);

The matrix m represents a composite transformation that we can pass to the transform and setTransform methods of a sprite instead of an array of matrix elements.

Let’s examine a concrete example of how transformation matrices can be used. Let’s say we have a “rect” sprite like this:

  1. {
  2. type: 'rect',
  3. x: 0,
  4. y: 0,
  5. width: 100,
  6. height: 100
  7. }

And we want to rotate it around the bottom-right corner counter-clockwise by 180 degrees:

image alt text

Since we want to rotate the sprite around the bottom-right corner, we can’t just apply the rotation transformation:

  1. rectSprite.transform(new Ext.draw.Matrix().rotate(Math.PI));

This will rotate our sprite around the origin in such a way that it will go off-screen:

image alt text

So first we need to translate the origin to the bottom right corner of the sprite or (100, 100) coordinate:

image alt text

Then rotate the coordinate space by 180°:

image alt text

Then we have to translate the origin back by (-100, -100). But now that we rotated the coordinate grid meaning the back (negative coordinates) is forward:

image alt text

And now our sprite is rendered with (0, 0) as the top-left coordinate and (100, 100) as the bottom-right coordinate (its normal coordinates) in the transformed coordinate space.

image alt text

This is exactly the type of transformation we wanted. The code for it will look like this:

  1. var M = new Ext.draw.Matrix().translate(100,100).rotate(Math.PI).translate(-100, -100);
  2. rectSprite.setTransform(M);

We only performed the above transformation this way to give an example of how you can combine multiple simple transformations to create a composite transformation with predictable results. You can actually rotate a sprite around a certain point, by passing two extra parameters to the rotate method - the coordinates of the center of rotation:

  1. var M = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  2. rectSprite.setTransform(M);

Transformation Matrix Detail

There may be times when the transformation you need to apply cannot be easily accomplished using the sprite’s convenience transform methods. Or, you may want to pass a matrix directly to the sprite in order to set a transformation. The setTransform method allows for this sort of advanced usage as well. The following tables show each transformation matrix used when applying transformations to a sprite.

\(Translate = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \\ \end{bmatrix}\quad\\)
\\(Rotate = \begin{bmatrix} \cos \theta & -\sin \theta & 0 \\ \sin \theta & \cos \theta & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}\quad\\)
\\(Scale = \begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}\\)
\\(Shear_x = \begin{bmatrix} 1 & \lambda_x & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}\quad\\)
\\(Shear_y = \begin{bmatrix} 1 & 0 & 0 \\ \lambda_y & 1 & 0 \\ 0 & 0 & 1 \\ \end{bmatrix}\)

By multiplying matrices for translation, rotation, scaling, and shearing any number of times in the desired order, you will get a single matrix for a composite transformation as a result. This matrix can then be used as a value for the setTransform method of a sprite like so:

  1. mySprite.setTransform([a, b, c, d, e, f]);

In this case, a, b, c, d, e, f are numeric values that correspond to the following transformation matrix components:

\[ M = \begin{bmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \\ \end{bmatrix} \]

Conclusion

The Draw package enables you to create simple transform methods when needed and gets out of your way when you want to take the wheel. The Draw API simplifies the job of transforming the shape of sprites with its convenient transform methods. It will additionally yield control to the power user needing to articulate a sprite using pre-defined transformation matrices.

Have fun creating your own custom drawings for your apps and as always, reach out to us on the forums if you have questions!