How to calculate the center of a rotated element, after resizing it - javascript

I've already asked this same question months ago, but no one was able to answer me, even after making a fully functional example on Plunker, then, I am going to ask it again, and yes, I still have the same problem.
My problem: find the centre of an element who have some rotation in it, after resizing it, to use it as the new pivot of rotation.
In my practical example, it is possible to see the problem in action; I have created two circles to show the problem better. After rotating and resizing the element, it's possible to see how the red and blue circles are apart from each other.
Blue Circle: the "correct" position of the centre, achieved by setting the cx/cy coordinates as the calculated element centre, plus, applying the transform rotate in it. The transform translates the circle to the correct position.
Red Circle: same as the blue circle, minus the transform rotate, these values are the ones used as the rotation pivot for the transform rotate().
My assumptions until here: By applying the transform rotate() in the blue circle, I'm considering the rotation angle in the calculated centre, so all I have to do is replicate the matrix calculations made by the rotate() function. I'm already doing this with the four handles that the user can click to make a rotation, what could go wrong?
My goal: Resize an element with rotation keeping the pivot of rotation in the centre.
I think this answer gave me some info, the math here helped me with the rotation handles starting position, but still, I can't find the right way to calculate the new centre after the resize.
The example was made using D3js + AngularJS v1. I work actively with both, but I am new to the geometry math world.
Again, this is the project on Plunker.

To get the centre of the transformed and rotated element, the most accurate way would probably be to get the browser to calculate it for you.
First create an SVGPoint object to hold our original centre point.
var centre = svg.createSVGPoint();
Initialize this point with the centre of the original object. You can get that by calling getBBox() on the element, and performing a smiple calculation.
var bbox = obj.getBBox();
centre.x = bbox.x + bbox.width / 2;
centre.y = bbox.y + bbox.height / 2;
Next, get the transform matrix from the transform attribute of the transformed object
var matrix = transformedObj.transform.baseVal.consolidate().matrix
Now we can transform our SVGPoint object with this matrix.
var transformedCentre = centre.matrixTransform(matrix);
After this, the x and y properties of transformedCentre should be your transformed centre point.
This should work, but I haven't tested it.

Related

Understanding rotation and calculating the top left point in KineticJS

I am working on a page where I can view images. I want to create a rotation tool. I've done that, but, it's not working consistently. When I set up the centre point to rotate by, the image jumps slightly, and it gets worse each time. I was experimenting, and, I have code to add a wedge to the top left corner of my top level group ( so, at 0,0 ). If I rotate the image by 45 degrees and drag it so that half of it is off the left edge of my canvas, then I call getAbsolutePosition on the wedge and on the group, I get these values:
layer.getAbsolutePosition()
Object {x: 104.66479545850302, y: 279.2748571151325}
wedge.getAbsolutePosition()
Object {x: 180.2684127179338, y: -73.48773356791764}
I think this means my y position is actually the bottom of the image, which is off screen.
What I want to do, is calculate the absolute position of the middle of my image, when the mouse moves over it, regardless of it's rotation. I have some code that works out points with rotation, which seems like it works at first, almost, but it just gets more and more broken the more I use the tool. I feel like there's something about how Kinetic is tracking these things and what it's reporting, that I am missing. Any hints would be most appreciated. Tutorials I can read are even better ( yes, I've read everything linked from the KineticJS site and searched the web ).
In a nutshell, the question is, if I have an image inside a group, and it's rotated, how do I work out the centre point of the image, taking the rotation in to account, and how do I set the offset so it will rotate from that point, and stay in the same place ?
Thanks
As you've discovered about KinetiJS:
rotation is easy
dragging is easy
dragging+rotation is difficult
After you drag your image you must reset its rotation point (offsetX/offsetY).
KineticJS makes dragging+rotation more difficult than it has to be.
Resetting the offset points of your image will cause KineticJS to automatically move your image (Noooo!!).
That's what's causing your jumping.
The solution to the "jumping" problem:
When you reset the image's rotation point (offsetX/OffsetY) you must also reset the image's X/Y position.
This code resets both XY and Offsets for an image after dragging:
A Demo: http://jsfiddle.net/m1erickson/m9Nw7/
// calc new position and offset
var pos=rect.getPosition();
var size=rect.getSize();
var offset=rect.getOffset();
var newX=pos.x-offset.x+size.width/2;
var newY=pos.y-offset.y+size.height/2;
// reset both position and offset
rect.setPosition([newX,newY]);
rect.setOffset(size.width/2,size.height/2);

Rotate a Two.js object in its position

I have a large circle with smaller ones inside made using two.js.
My problem is that these two do not rotate in their own place but in the top left axis.
I want the group of circles (circlesGroup) rotate only inside the large one in a static position. The circlesGroup and the large circle are grouped together as rotatoGroup.
two.bind('update', function(frameCount, timeDelta) {
circlesGroup.rotation = frameCount / 120;
});
two.bind('update', function(frameCount, timeDelta) {
rotatoGroup.rotation = frameCount / 60;
});
The whole code is in CodePen.
All visible shapes when invoked with two.make... ( circles, rectangles, polygons, and lines ) are oriented in the center like this Adobe Illustrator example:
When this shape's translation, rotation, or scale change those changes will be reflected as transformations about the center of the shape.
Two.Groups however do not behave this way. Think of them as display-less rectangles. They're origin, i.e group.translation vector, always begins at (0, 0). In your case you can deal with this by normalizing the translation your defining on all your circles.
Example 1: Predefined in normalized space
In this codepen example we're defining the position of all the circles around -100, 100, effectively half the radius in both positive-and-negative x-and-y directions. Once we've defined the circles within these constraints we can move the whole group with group.translation.set to place it in the center of the screen. Now when the circles rotate they are perceived as rotating around themselves.
Example 2: Normalizing after the fact
In this codepen example we're working with what we already have. A Two.Group that contains all of our shapes ( the bigger circle as well as the array of the smaller circles ). By using the method group.center(); ( line 31 ) we can normalize the children of the group to be around (0, 0). We can then change the translation of the group in order to be in the desired position.
N.B: This example is a bit complicated because it invokes underscore's defer method which forces the centering of the group after all the changes have been registered. I'm in the process of fixing this.

pixi.js pivot affects object position

I have a question with regards to the pivot property of a DisplayObject. In particular, i want to rotate a DisplayObjectContainer around its center; so i set the pivot to its center point. However, i've noticed that this affects the position of the element.
For example, if i set the position to 0,0 (which is the default one) pixijs will try to position the object according to its center point and not the top left corner. So the children of the the DisplayObjectContainer (which in my case is an instance of the Graphics class) run out of the main viewport.
Is there any way to set the rotation point but still position the object in respect to its top left corner.
You need to draw the graphics container at the point at which you wish your object to rotate around and then draw the object so that its center is the graphic's x/y position. So, to draw a rectangle that is drawn at the precise coordinates you wish while pivoting around its center, you would use this function.
self.createRect = function (x1, y1, x2, y2, color) {
var graphics = new PIXI.Graphics();
graphics.beginFill(color || 0x000000);
//This is the point around which the object will rotate.
graphics.position.x = x1 + (x2/2);
graphics.position.y = y1 + (y2/2);
// draw a rectangle at -width/2 and -height/2. The rectangle's top-left corner will
// be at position x1/y1
graphics.drawRect(-(x2/2), -(y2/2), x2, y2);
self.stage.addChild(graphics);
return graphics;
};
Since it's a square or rectangle, we can calculate where its center should be on the screen by using x1 + (width/2) and y1 + (height/2) then we offset the rect to the left and to the top by half of its width and half of its height using drawRect(-(width/2), -(height/2), x2, y2);
The pivot property is a bit confusing. Imagine the following example:
Your pivot is a screw located somewhere on the object (it can be of course also located somewhere outside, but just for better understanding imagine that your object is a plank of wood with a screw sticking out). Your position (of the graphics/container) is a screw. The object is always rotating around the position, but you can change the pivot (the position of the screw on your plank of wood) on the object, so it will be the new point of the rotation for the object. Finally you can try to screw your plank of wood to the screw.
Basically, the default values of the position and pivot is 0. So if you have your object drawn for example here:
test.drawRoundedRect(100, 100, 200, 200,12);
you can now try to rotate it and you'll see, that it is rotating around the point (0,0).
The graphic is always rotating around the position, you can try to locate it somewhere else:
test.position.x = 200;
test.position.y = 200;
The object is now rotating around the point (200,200). But that's just a shift.
We can now try to change the pivot point (which is the screw) to any different position. So, on your plank of wood you just place the screw on (50,50), then (100,100), etc and you'll see that it affects your object position.
Now, for our example, we can set the pivot point to (200,200) to the same coordinates as the position of the object.
test.pivot.x = 200;
test.pivot.y = 200;
And finally it is rotating around the center point of the drawn object.
The solution provided by #Spencer is an alternative of the pivot property.

How to compute rotation/scale pivot coordinates for SVG transformations (I'm using Raphael.js)?

I'm having serious trouble understanding how to compute the coordinates of rotation/scale pivots (e.g. rotation point) for SVG transformations, using Raphael.js. In short, if you apply a transformation such as image.transform("S1.5R45"), the transformations are applied in relation to the default rotation & scale pivot, which I'm not sure how to calculate.
To exemplify, I've put together a fiddle (jsfiddle.net/GVEqf/), where the aim is to have exactly the same output in both the viewports, for a couple of transformations on an image object. In the first viewport, I don't specify the rotation point, but in the second one I do. However, I can't get the same results. What I need is to input the rotation coordinates for the second viewport, so that the output is identical with the first case.
Please help.
When not specified the pivot is the center of the element.
Here you have to take care of the position you have applied to the images and the scaling that will be done. Since in this case your scaling is relative to the top left corner of the image, we can just multiply the center coordinate by it.
// Compute rotation pivot coordinates
var scaling = 1.5;
rx = (x + (img_width / 2)) * scaling;
ry = (y + (img_height / 2)) * scaling;
// Apply transformations
image1.transform("S1.5,1.5,0,0R45");
image2.transform("S1.5,1.5,0,0R45,"+rx+","+ry);
http://jsfiddle.net/TYCJ7/

Calculate new width when skewing in canvas

I'm using canvas for a project and I have a number of elements that I'm skewing. I'm only skewing on the y value and just want to know what the new width of the image is after skewing (so I can align it with another canvas element). Check out the code below to see what I mean
ctx.save();
//skew the context
ctx.transform(1,0,1.3,0,0,0);
//draw two images with different heights/widths
ctx.drawImage(image,0,0,42,60);
ctx.drawImage(image,0,0,32,25);
The goal would be to know that the 42 by 60 image was now a X by 60 image so I could do some translating before drawing it at 0,0. It's easy enough to measure each image individually, but I have different skew values and heights/widths throughout the project that need to be align. Currently I use this code (works decently for images between 25 and 42 widths):
var skewModifier = imageWidth*(8/6)+(19/3);
var skewAmount = 1.3; //this is dynamic in my app
var width = (skewModifier*skewAmount)+imageWidth;
As images get wider though this formula quickly falls apart (I think it's a sloping formula not a straight value like this one). Any ideas on what canvas does for skews?
You should be able to derive it mathematically. I believe:
Math.atan(skewAmount) is the angle, in radians, that something is skewed with respect to the origin.
So 1.3 would skew the object by 0.915 radians or 52 degrees.
So here's a red unskewed object next to the same object skewed (painted green). So you have a right triangle:
We know the origin angle (0.915 rads) and we know the adjacent side length, which is 60 and 25 for your two images. (red's height).
The hypotenuse is the long side thats being skewed.
And the opposite side is the triangle bottom - how much its been skewed!
Tangent gets us opposite / adjacent if I recall, so for the first one:
tan(0.915) = opposite / 60, solving for the opposite in JavaScript code we have:
opposite = Math.tan(0.915)*60
So the bottom side of the skewed object starts about 77 pixels away from the origin. Lets check our work in the canvas:
http://jsfiddle.net/LBzUt/
Looks good to me!
The triangle in question of course is the canvas origin, that black dot I painted, and the bottom-left of the red rectangle, which is the original position that we're searching for before skewing.
That was a bit of a haphazard explanation. Any questions?
Taking Simon's fiddle example one step further, so you can simply enter the degrees:
Here's the fiddle
http://jsfiddle.net/LBzUt/33/

Categories