Open rvillemeur opened 4 weeks ago
Hi Enzo
Thanks for this great question, I had some fun figuring out what was going on. Short answers, your element is already centered on your element.
Let me show you what's going on by playing a little bit with your example. I have changed the geometry to rectangle, to better figure out what was going on.
I also expanded the BlTransformAnimation with transformDo: to better control the parameter. If you comment out the origin: you'll find back your original behaviour. If you specify the origin:, you'll notice that the element is rotating around this point. By default, this point is the center of your element.
Then come the animation phase where you can see your element rotating. This is managed by the easing: method. By default, it's BlEasing linear
. For the fun, change this to BlEasing bounceOut
or other BlEasing subclass, and you'll notice a slight difference in the way your rotation is displayed.
You're tricked by how easing compute your element bounds position during the animation. It's centered, but the position of its bounds as returned by the easing method make it looks like its not rotating around it. I don't have any straightforward solution for this.
elt := BlElement new background: (Color red alpha: 0.5); position: 100 asPoint; geometry:BlRectangleGeometry new; size: 50@100.
frame := BlElement new background: Color yellow; position: 100 asPoint; geometry:BlRectangleGeometry new; size: 50@100.
container := BlElement new background: Color lightGreen; size: 500 asPoint; addChildren: {frame. elt}.
anim := BlTransformAnimation new
transformDo: [ :aBuilder |
aBuilder origin:( BlAffineTransformationPositionOrigin position: 10@10);
rotateBy: 90];
easing:(BlEasing linear);
duration: 1 seconds.
elt addEventHandlerOn: BlClickEvent do: [ elt addAnimation: anim copy ].
container openInSpace
Last, rotateBy: around: would specify the axis around which your element rotate. By default, it's the Z-Azis, perpendicular to your screen. X and Y axis doesn't seems to be implemented, as I got error when I tried them. If you need rotation around those axis, you probably need another way, or implement the missing methods.
Hi Enzo
As a (simplified) test, I have created a custom animation for element rotation.
BlAnimation << #BlRotateAnimation slots: { #angle }; tag: 'Animation'; package: 'BookletGraphics'
BlRotateAnimation >> angle: anAngle angle := anAngle
BlRotateAnimation >> applyValue: anAngle self target transformDo: [ :t | t rotateBy: anAngle ]
BlRotateAnimation >> valueForStep: aNumber ^ (angle * aNumber)
You then update your example with:
|elt frame container anim|
elt := BlElement new background: (Color red alpha: 0.5); position: 100 asPoint; size: 100 asPoint.
frame := BlElement new background: Color yellow; position: 100 asPoint; size: 100 asPoint.
container := BlElement new background: Color lightGreen; size: 500 asPoint; addChildren: {frame. elt}.
anim := BlRotateAnimation new angle: 90; duration: 1 second.
elt addEventHandlerOn: BlClickEvent do: [ elt addAnimation: anim copy ].
container openInSpace
I think the result is what you were expected. Code is in Booklet-Graphics-Code repository.
Hi, We can see that the animation does not produce the same effect that if we do the transformation in steps programmatically (outside bloc animations) by hand. Basically what we have seen is the following:
1) Rotations of an element in the origin requires two transformations: (1) transposing the origin to 0@0 (local transformation in Bloc terms) and (2) the rotation itself. Both operations are encoded in a single Affine matrix 2) To perform the animation an initial and final matrix are calculated, and then the matrix are interpolatted from the initial to the final.
3) This means that the interpolation not only affect the rotation angle but the transpose, making the result not as expected.
4) As a solution, we need to interpolate the transformation objects and generate a new matrix in each step of the animation.
I think BlTransformAnimation
is overcomplicating things by applying multiple transformation at once during animation phase, with limited benefit (or I don't understand it completely). It only allows rotation, scaling or translation of element, but only one at a time. However the position is computed in valueForStep:
using BlElementAbsoluteTransformation
which try to interpolate value for all 3 transformations, even if you can only specify one when creating your animation.
However, BlTransformationBuilder
allows much more, by combining scaling, rotating and translating element, as well as specifying the origin of the transformation. One simply need to add transformation to the element, specify its origin, and then we could do transformDo: [ :t | t origin: origin; rotateBy: anAngle ]
to achieve similar result, without the graphical effect produced by BlElementAbsoluteTransformation
When I use rotateBy:
, it's using an affine transformation:
rotateBy: aDegreesAngle
affineCompositeTransformation addTransformation:
(BlRotationTransformation new
angle: aDegreesAngle;
axis: BlVector zAxis;
origin: origin;
yourself)
We could redefine BlTransformAnimation
to allow all transformation and only compute those which are necessary
I took a deeper look at how transformation are done in Bloc and understand why we have such difference.
Let's take the initial step-up to show how it works:
| elt frame container anim |
elt := BlElement new background: (Color red alpha: 0.5); position: 100 asPoint; size: 100 asPoint.
frame := BlElement new background: Color yellow; position: 100 asPoint; size: 100 asPoint.
container := BlElement new background: Color lightGreen; size: 500 asPoint; addChildren: { frame. elt }.
Transformation are affine transformation. For more detail, you can search on the internet, there are countless references to it. To simplify it, I'll say we apply a transformation matrix (BlMatrix2D) to all point of our figure path, each point represented as BlVector.
You have 3 type of tranformation available in Bloc:
When you're doing a transformation using transformDo:, you'll, by default, use BlElementLocalTransformation. The origin will be set to BlAffineTransformationCenterOrigin.
For an element rotated to 45 degree, it'll look like:
elt transformation: (BlElementLocalTransformation newWith: (
(BlRotationTransformation new angle: 45)
origin: (BlAffineTransformationCenterOrigin defaultInstance ) )).
However, BlTransformAnimation will use BlElementAbsoluteTransformation to manage the transformation matrix. This transformation works by updating its trasformation matrix, from IdentityMatrix, to target matrix, through interpolation computed at each animation step (fromMatrix interpolate: aNumber to: toMatrix)
This transformation doesn't use an origin point. Instead, It'll apply a translation, so that your shape still look like it's in the element bound. This translation is added to your transformation definition (boundingRectangle:)
So, for the sample 90 degree rotation used as an example, the tranformation will look like below in mid-way (45 degrees rotation, and translation of 50px on 100px)
elt transformation: (BlElementAbsoluteTransformation
matrix: (BlMatrix2D new
x: 50 ;y: 0;
shx: 45 degreesToRadians sin * -1 ; shy: 45 degreesToRadians sin ;
sx: 45 degreesToRadians cos; sy: 45 degreesToRadians cos ) ).
You can notice that for a similar rotation, the position of orange square is not the same.
Now, why do we need to use a BlElementAbsoluteTransformation for BlTransformAnimation. My assumption is that this animation must managed all 3 basic transformation: rotation, translation and scale. Each tranformation will happen gradually during animation, with new position computed at each step.
You can specify a default generic value of 0 for rotation and translation, so if you don't specifiy it, it won't affect your element (At each step, 0*StepValue = 0). However, for scaling, if you don't specify it, you need to keep it's value at 1. Matrix interpolation is one possibility to manage this.
If you want to have an exact rotation animation, it's better to define your own animation to manage only rotation, like I did in a previous comment.
Hi all,
Do you know how can I make my rotate animation centered on my element ? When rotating a square, it seems the corner translates while the full element rotates around it creating some kind of offset we can see on this snippet .
Resulting at some point at this state :
I saw I can use
rotateBy: around:
on the transformation builder of my BlTransformAnimation but I can't make it work as it seems to need a 3D VectorAny idea ?
Thanks, Enzo