I'm working on an orthographic camera for our THREE.js app. Essentially, this camera will present the scene to the user in 2D (users have the option of switching between the 2D and 3D camera). This camera will allow for panning and zooming to mouse point. I have the panning working, and I have zooming working, but not zooming to mouse point. Here's my code:
import React from 'react';
import T from 'three';
let panDamper = 0.15;
let OrthoCamera = React.createClass({
getInitialState: function () {
return {
distance: 150,
position: { x: 8 * 12, y: 2 * 12, z: 20 * 12 },
};
},
getThreeCameraObject: function () {
return this.camera;
},
applyPan: function (x, y) { // Apply pan by changing the position of the camera
let newPosition = {
x: this.state.position.x + x * -1 * panDamper,
y: this.state.position.y + y * panDamper,
z: this.state.position.z
};
this.setState({position: newPosition});
},
applyDirectedZoom: function(x, y, z) {
let zoomChange = 10;
if(z < 0) zoomChange *= -1;
let newDistance = this.state.distance + zoomChange;
let mouse3D = {
x: ( x / window.innerWidth ) * 2 - 1,
y: -( y / window.innerHeight ) * 2 + 1
};
let newPositionVector = new T.Vector3(mouse3D.x, mouse3D.y, 0.5);
newPositionVector.unproject(this.camera);
newPositionVector.sub(this.camera.position);
let newPosition = {
x: newPositionVector.x,
y: newPositionVector.y,
z: this.state.position.z
};
this.setState({
distance: newDistance,
position: newPosition
});
},
render: function () {
let position = new T.Vector3(this.state.position.x, this.state.position.y, this.state.position.z);
let left = (this.state.distance / -2) * this.props.aspect + this.state.position.x;
let right = (this.state.distance / 2) * this.props.aspect + this.state.position.x;
let top = (this.state.distance / 2) + this.state.position.y;
let bottom = (this.state.distance / -2) + this.state.position.y;
// Using react-three-renderer
// https://github.com/toxicFork/react-three-renderer
return <orthographicCamera
{...(_.pick(this.props, ['near', 'far', 'name']))}
position={position}
left={left}
right={right}
top={top}
bottom={bottom}
ref={(camera) => this.camera = camera}/>
}
});
module.exports = OrthoCamera;
Some zooming towards the mouse point happens but it seems erratic. I want to keep a 2D view, so as I zoom, I also move the camera (rather than having a non-perpendicular target, which kills the 2D effect).
I took cues from this question. As far as I can tell, I am successfully converting to THREE.js coordinates in mouse3D (see the answer to this question).
So, given this setup, how can I smoothly zoom to the mouse point (mouse3D) using the orthographic camera and maintaining a two dimensional view? Thanks in advance.
Assuming you have a camera that is described by a position and a look-at (or pivot) point in world coordinates, zooming at (or away from) a specific point is quite simple at its core.
Your representation seems to be even simpler: just a position/distance pair. I didn't see a rotation component, so I'll assume your camera is meant to be a top-down orthographic one.
In that case, your look-at point (which you won't need) is simply (position.x, position.y - distance, position.z).
In the general case, all you need to do is move both the camera position and the look-at point towards the zoom-at point while preserving the camera normal (i.e. direction). Note that this will work regardless of projection type or camera rotation. EDIT (2020/05/01): When using an orthographic projection, this is not all you need to do (see update at the bottom).
If you think about it, this is exactly what happens when you're zooming at a point in 3D. You keep looking at the same direction, but you move ever closer (without ever reaching) your target.
If you want to zoom by a factor of 1.1 for example, you can imagine scaling the vector connecting your camera position to your zoom-at point by 1/1.1.
You can do that by simply interpolating:
var newPosition = new THREE.Vector3();
newPosition.x = (orgPosition.x - zoomAt.x) / zoomFactor + zoomAt.x;
newPosition.y = (orgPosition.y - zoomAt.y) / zoomFactor + zoomAt.y;
newPosition.z = (orgPosition.z - zoomAt.z) / zoomFactor + zoomAt.z;
As I said above, in your case you won't really need to update a look-at point and then calculate the new distance. Your new distance will simply be:
var newDistance = newPosition.y
That should do it.
It only gets a little bit more sophisticated (mainly in the general case) if you want to set minimum and maximum distance limits both between the position/look-at and position/zoom-at point pairs.
UPDATE (2020/05/01):
I just realized that the above, although correct (except for missing one minor but very important step) is not a complete answer to OP's question. Changing the camera's position in orthographic mode won't of course change the scale of graphics being rendered. For that, the camera's projection matrix will have to be updated (i.e. the left, right, top and bottom parameters of the orthographic projection will have to be changed).
For this reason, many graphics libraries include a scaling factor in their orthographic camera class, which does exactly that. I don't have experience with ThreeJS, but I think that property is called 'zoom'.
So, summing everything up:
var newPosition = new THREE.Vector3();
newPosition.x = (orgPosition.x - zoomAt.x) / zoomFactor + zoomAt.x;
newPosition.y = (orgPosition.y - zoomAt.y) / zoomFactor + zoomAt.y;
newPosition.z = (orgPosition.z - zoomAt.z) / zoomFactor + zoomAt.z;
myCamera.zoom = myCamera.zoom * zoomFactor
myCamera.updateProjectionMatrix()
If you want to use your orthographic camera class code above instead, you will probably have to change the section that computes left, right, top and bottom and add a scaling factor in the calculation. Here's an example:
var aspect = this.viewportWidth / this.viewportHeight
var dX = (this.right - this.left)
var dY = (this.top - this.bottom) / aspect
var left = -dX / (2 * this.scale)
var right = dX / (2 * this.scale)
var bottom = -dY / (2 * this.scale)
var top = dY / (2 * this.scale)
mat4.ortho(this.mProjection, left, right, bottom, top, this.near, this.far)
Related
My Three.js project uses and OrthographicCamera and OrthographicTrackBallControls for zoom/pan. I'm trying to add functionality to zoom to the cursor position with no luck. First things first, here's how I'm getting mouse position:
var mX = ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
vector.unproject(camera);
vector.sub(camera.position);
Through looking on StackOverflow, there seems to be a lot of information on how to do this with PerspectiveCamera, but these methods don't work with OrthographicCamera. I was able to find this example:
https://htmlpreview.github.io/?https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html
Which does precisely what I am trying to accomplish, but the code that achieves this is hidden, though I am able to discern that the camera position is being changed.
Another SO question which is similar suggests changing camera.left, camera.right, camera.top and camera.bottom, but I have had no luck with this approach. This approach seems like a possibility, but I dont understand the calculations necessary to get the correct left, right, top and bottom values.
So the way I see it I have two possibilities:
Change camera's left/right/top/bottom to get the correct view rectangle.
Change camera position.
But I don't know how to get the values I need to accomplish either, let alone which is the better approach.
UPDATE 11/16/2018:
I've updated my function to this ( based on https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html):
zoomDirection = new THREE.Vector3();
function mousewheel(event) {
event.preventDefault();
var amount = event.deltaY / 100;
var zoom = camera.zoom - amount;
var offset = el.offset();
;
var mX = amount > 0 ? 0 : ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = amount > 0 ? 0 : -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
zoomDirection.set(mX, mY, 0.001)
.unproject(camera)
.sub(camera.position)
.multiplyScalar(amount / zoom);
camera.position.subVectors(camera.position, zoomDirection);
orthographictrackBallControls.target.subVectors(orthographictrackBallControls.target, webGl.zoomDirection);
camera.zoom = zoom;
camera.updateProjectionMatrix();
}
This seems to work at first: the camera zooms into the mouse point, but then the camera starts to "jump" around after a bit of zooming, with the mesh no longer visible on screen.
Something that might help: I have an axis helper in the screen as well that "flips" when it stops working as expected. When the scene is loaded, the X-axis helper point due left, but when I get to the point where the camera jumps and I no longer see the mesh, the X-axis helper flips to point due right.
Also, if I zoom OUT first, I can zoom in further before the mesh disappears. I'm not sure what this all adds up to but I would appreciate any help.
First week back after New Year and it's taken too long to fix this. Six sides of A4 covered with linear algebra results in
if ( factor !== 1.0 && factor > 0.0 ) {
const mX = (event.offsetX / event.target.width ) * 2 - 1;
const mY = -(event.offsetY / event.target.height) * 2 + 1;
const vector1 = new THREE.Vector3(mX, mY, 0);
const vector2 = new THREE.Vector3(0, 0, 0);
vector1.unproject(this.camera);
vector2.unproject(this.camera);
vector1.subVectors(vector1, vector2);
this.camera.zoom /= factor;
vector1.multiplyScalar(factor - 1.0);
this.camera.position.subVectors(this.camera.position, vector1);
this.controls.target.subVectors(this.controls.target, vector1);
this.camera.updateProjectionMatrix();
this.camera.updateMatrix();
}
Note the different calculation of mX, mY so that it is valid for a viewport.
Implementing the D3-library with its zoom function may seem like a good idea for this case. But giving up the three-controls is in a lot of cases not a deal.
If you want a zoom-behavior like in Google Maps, the following code could be helpful:
const cameraPosition = camera.position.clone();
// my camera.zoom starts with 0.2
if (zoomOld !== 0.2) {
const xNew = this.curserVector.x + (((cameraPosition.x - this.curserVector.x) * camera.zoom) /zoomOld);
const yNew = this.curserVector.y + (((cameraPosition.y - this.curserVector.y) * camera.zoom) /zoomOld);
const diffX = cameraPosition.x - xNew;
const diffY = cameraPosition.y - yNew;
camera.position.x += diffX;
camera.position.y += diffY;
controls.target.x += diffX;
controls.target.y += diffY;
}
zoomOld = camera.zoom;
Your other problem could be caused by the frustum. But I don't know, I'm still a newbie with Three xD
I am using svg.js to create an animation of a bicyle rider. Semi-complete version here: https://pedalfuriously.neocities.org/. I'm running in to a bit of a problem with moving and rotating svg elements during animation created with requestAnimationFrame (rather than the svg.js built in animation).
If you take a look at the link, and use the cadence slider to make the rider pedal very fast, and then flip the slider quickly all the way back to zero, you can see that his lower leg "jiggles" in a disconnected way. What's really doing my head in is that the postion of the legs are determined in each frame based on an absolute relation to the rotation of the cranks (rather than taking some delta time value to determine movement over that frame).
I think I've been able to confirm what aspect of my code is causing the problem. Here is a minimal example that doesn't exhibit the exact behaviour, but I think illustrates the kind of thing I think is responsible:
var draw = SVG("drawing").viewbox(0, 0, 400, 400)
var origin = {
x: 70,
y: 70
}
var length = 60
var blueLine = draw.group()
blueLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#00f",
width: 4
})
blueLine.angle = 0
var greenLine = draw.group()
greenLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#0f0",
width: 4
})
greenLine.angle = 0
var previous = 0
var dt = 0
var step = function(timestamp) {
dt = timestamp - previous
previous = timestamp
blueLine.angle += 0.18 * dt
blueLine.rotate(blueLine.angle, origin.x, origin.y)
var endX = Math.cos(toRad(blueLine.angle)) * length
var endY = Math.sin(toRad(blueLine.angle)) * length
// Comment out this line, and rotation works fine
greenLine.move(endX, endY)
greenLine.angle = blueLine.angle - 10
// Comment out this line, and movement works fine
greenLine.rotate(greenLine.angle, origin.x, origin.y)
// But they don't work together. If I both move and rotate
// the green line, it goes in this crazy huge arc, rather
// than rotating neatly around the end of the blue line
// as expected.
window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step)
function toRad(deg) {
return deg * (Math.PI / 180)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.4/svg.js"></script>
<div id="drawing"></div>
Something else I noticed with my actual code is that if I move the position of the legs, it changes the severity of the problem, or even stops it altogether. If the hips are positioned all the way near the front of the bicycle, the problem is not nearly as bad. Also, if I disable rotation on the lower legs, there is no jiggling. In some positions, the lower leg will just rotate out of the screen instantly on load, even before any motion has been started.
I'm hoping for some guidance on wether I'm misunderstanding the way manipulating elements works, either in svg.js in particular, or SVG in general.
Thank you kind vector graphics experts!
Here is the actual code for the legs. The step() function would probably be the most relevant. Not sure if it will be helpful:
Rider.Leg = function(foot, front, xOffset, yOffset) {
var upper = front ? SVGE.upperLeg : SVGE.upperLegBack
var lower = front ? SVGE.lowerLeg : SVGE.lowerLegBack
this.foot = foot
this.draw = foot.draw
this.geo = {
upper: {
x: this.foot.pedal.gear.x + 150,
y: this.foot.pedal.gear.y - 750,
length: 396
},
lower: {
length: 390
}
}
this.upper = this.draw.group().svg(upper).move(this.geo.upper.x, this.geo.upper.y)
.transform({ scale: 0.95, cx: 0, cy: 0 })
this.lower = this.draw.group().svg(lower).move(this.geo.upper.x, this.geo.upper.y)
}
// Step function does not take in a time argument. Positioning of legs is based only on
// the absolute position of other elements, none of which jiggle.
Rider.Leg.prototype.step = function () {
var angle = this.pedalAngle() - Math.PI
var ha = this.scaleneAngle(this.geo.lower.length, this.geo.upper.length, this.pedalDistance())
var ka = this.scaleneAngle(this.pedalDistance(), this.geo.lower.length, this.geo.upper.length)
var x = this.geo.upper.length * Math.cos(ha + angle)
var y = this.geo.upper.length * Math.sin(ha + angle)
this.upper.rotate(Drive.toDeg(angle + ha), 0, 0)
this.lower.move(this.geo.upper.x + x, + this.geo.upper.y + y)
this.lower.rotate(Drive.toDeg(angle + ha + ka - Math.PI), 0, 0)
}
// Gets the distance between the hip joint and the pedal
Rider.Leg.prototype.pedalDistance = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.hypot(xDist, yDist)
}
// Gets the angle between the hip joint and the pedal
Rider.Leg.prototype.pedalAngle = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.atan2(yDist, xDist)
}
Rider.Leg.prototype.scaleneAngle = function (a, b, c) {
return Math.acos(((b * b) + (c * c) - (a * a)) / (2 * b * c))
}
When you call move() on a group it is internally represented as a translation. svg.js figures out crazy ways to translate the object to the new place without changing any other transformations. That often does not work out. Especially not, when you rotate.
Thats why you should avoid these absolute transformations and go with relative ones. Just call untransform before every move and go from zero. Then you can do:
greenLine.transform({x:endX, y:endY, relative: true})
To move the line by a certain amount. That should work way better.
I'm building an app in which I present some planes with textures. However, I would like to calculate the radius of the helix (which I use in my calculations to create a helix), dynamically based on the frustum width and the camera position.
The helix is positioned at the center of the screen x=0, y=0, z=0.
I would like this to take under consideration the screen orientation (landscape/ portrait).So far this is the code I have but it seems that I'm missing something because the planes at the left and the right are not inside the viewport.
App.prototype.calculateHelixRadius = function(){
// plane width = height = 512;
var friend = this.getFriend();
var vFOV = friend.camera.fov * Math.PI / 180;
var dist = utils.getAbsPointsDistance3D(friend.camera.position, friend.scene.position);
var aspect = friend.settings.container.clientWidth / friend.settings.container.clientHeight;
var frustumHeight = 2.0 * dist * Math.tan(0.5 * vFOV);
var frustumWidth = frustumHeight * aspect;
return utils.isLandscape() ? frustumHeight / 2 : frustumWidth / 2 ;
};
What am I doing wrong and why are the planes at the edges of the screen not inside?
Also for reference here is the code of getAbsPointsDistance3D
var utils = {
// other helpers...
getAbsPointsDistance3D: function(p1, p2) {
var xd = p2.x - p1.x;
var yd = p2.y - p1.y;
var zd = p2.z - p1.z;
return Math.sqrt(xd * xd + yd * yd + zd * zd);
}
};
update
I tried decreasing the dist parameter but the results are not consistent...
I wonder if the following explains your clipping.
You calculate your frustum characteristics, then calculate the helix radius using, say, the frustum width (width or height depending on the screen aspect...I may be getting some of the particulars wrong here because your question does not completely explain the details, but the general concepts still hold). The image below is a top view of the scenario which shows a circle representing the cylinder that encloses the helix. I believe you have calculated radius1. If so, note that there will be clipping of the cylinder (the shaded area), and thus the helix, in "front" of the cylinder centre.
Instead you need to calculate the cylinder/helix radius as shown in the second image, i.e. you need radius2. If the large angle at the image left is fov (again, vFOV? or hFOV?, etc., depending on whether your helix is going up-down or side-to-side, etc.), then its half angle is fov/2. This is the same angle shown in the centre of the cylinder. Thus, you need to decrease your helix radius as follows: radius2 = radius1 * cos(fov/2).
Im creating a simple particle experiment on canvas. Now i want them to "run away" from mouse coursor over canvas. detecting the distance from the mouse is not a problem, but how to code their behaviour?
each particle is created as following:
var particle = {
x: Math.floor(Math.random() * width),
y: Math.floor(Math.random() * height),
xVel: Math.random() * 10 - 5,
yVel: Math.random() * 10 - 5,
}
so i assume i should also save the direction somehow, and if the distance from pointer is < x, reverse the direction? maybe also save old speed, and decrease it slowly while moving away?
how to detect the direction?
Velocity (xVel, yVel, together) is a 2D vector. And so is the distance between the mouse and the particles. A vector contains both direction and magnitude. So you want a vector that is the difference between the mouse position and the particle position.
var posRelativeToMouse = {
x: particle.x - mousPosX,
y: particle.y - mousPosY
};
So small numbers of x and y mean the the particle is close to the mouse, and big mean it's far away.
Next we need to figure out how these numbers should affect the velocity of the particle. So we need 2 things.
What direction do we push them in?
We already have this, mostly. posRelativeToMouse is a vector that has the direction we want. We just normalize it, which means to set the length of the vector to 1. To do that, we divide each component by the current length of the vector. The length of this vector is always the distance to from the particle to the mouse.
var distance = Math.sqrt(
posRelativeToMouse.x * posRelativeToMouse.x +
posRelativeToMouse.y * posRelativeToMouse.y
);
var forceDirection = {
x: posRelativeToMouse.x / distance,
y: posRelativeToMouse.y / distance,
};
How hard do we push the particles?
This is an inverse of the distance. Close means a big push, far means a little push. So lets reuse our distance we calculated above.
// distance past which the force is zero
var maxDistance = 1000;
// convert (0...maxDistance) range into a (1...0).
// Close is near 1, far is near 0
// for example:
// 250 => 0.75
// 100 => 0.9
// 10 => 0.99
var force = (maxDistance - distance) / maxDistance;
// if we went below zero, set it to zero.
if (force < 0) force = 0;
Ok we have a direction, and we have the force. All that's left is to apply this to the particle velocity.
particle.xVel += forceDirection.x * force * timeElapsedSinceLastFrame;
particle.yVel += forceDirection.y * force * timeElapsedSinceLastFrame;
And assuming you are animating your position each frame by that xVel/yVel, you should now have particles being pushed away by the mouse.
you can obtain a vector v by subtracting the position of particle from position of mouse,
then you can find the magnitude of this vector my taking sqrt(x^2 + y^2)
by dividing v by magnitude, you obtain a unit vector in the direction you want your particles to go.
for instance.
suppose I have 10 particles in a list U, each has an x and y field.
I can obtain it's vector from each particle v by setting v = (xpart - mousepart, ypart - mousepart)
then you need to find the magnitude vmag by taking sqrt(vx^2 + vy^2)
then you obtain vunit = (vx / vmag, vy / vmag)
This is the vector "away from the mouse".
the rest can be left to detemining speed you want to move at, and ensuring you bounce of walls and such.
I have a similar project at github open source:
https://github.com/dmitrymakhnin/JavaParticleSystem/blob/master/Main.java
Basically I'm asking this question for JavaScript: Calculate Bounding box coordinates from a rotated rectangle
In this case:
iX = Width of rotated (blue) HTML element
iY = Height of rotated (blue) HTML element
bx = Width of Bounding Box (red)
by = Height of Bounding Box (red)
x = X coord of Bounding Box (red)
y = Y coord of Bounding Box (red)
iAngle/t = Angle of rotation of HTML element (blue; not shown but
used in code below), FYI: It's 37 degrees in this example (not that it matters for the example)
How does one calculate the X, Y, Height and Width of a bounding box (all the red numbers) surrounding a rotated HTML element (given its width, height, and Angle of rotation) via JavaScript? A sticky bit to this will be getting the rotated HTML element (blue box)'s original X/Y coords to use as an offset somehow (this is not represented in the code below). This may well have to look at CSS3's transform-origin to determine the center point.
I've got a partial solution, but the calculation of the X/Y coords is not functioning properly...
var boundingBox = function (iX, iY, iAngle) {
var x, y, bx, by, t;
//# Allow for negetive iAngle's that rotate counter clockwise while always ensuring iAngle's < 360
t = ((iAngle < 0 ? 360 - iAngle : iAngle) % 360);
//# Calculate the width (bx) and height (by) of the .boundingBox
//# NOTE: See https://stackoverflow.com/questions/3231176/how-to-get-size-of-a-rotated-rectangle
bx = (iX * Math.sin(iAngle) + iY * Math.cos(iAngle));
by = (iX * Math.cos(iAngle) + iY * Math.sin(iAngle));
//# This part is wrong, as it's re-calculating the iX/iY of the rotated element (blue)
//# we want the x/y of the bounding box (red)
//# NOTE: See https://stackoverflow.com/questions/9971230/calculate-rotated-rectangle-size-from-known-bounding-box-coordinates
x = (1 / (Math.pow(Math.cos(t), 2) - Math.pow(Math.sin(t), 2))) * (bx * Math.cos(t) - by * Math.sin(t));
y = (1 / (Math.pow(Math.cos(t), 2) - Math.pow(Math.sin(t), 2))) * (-bx * Math.sin(t) + by * Math.cos(t));
//# Return an object to the caller representing the x/y and width/height of the calculated .boundingBox
return {
x: parseInt(x), width: parseInt(bx),
y: parseInt(y), height: parseInt(by)
}
};
I feel like I am so close, and yet so far...
Many thanks for any help you can provide!
TO HELP THE NON-JAVASCRIPTERS...
Once the HTML element is rotated, the browser returns a "matrix transform" or "rotation matrix" which seems to be this: rotate(Xdeg) = matrix(cos(X), sin(X), -sin(X), cos(X), 0, 0); See this page for more info.
I have a feeling this will enlighten us on how to get the X,Y of the bounding box (red) based solely on the Width, Height and Angle of the rotated element (blue).
New Info
Humm... interesting...
Each browser seems to handle the rotation differently from an X/Y perspective! FF ignores it completely, IE & Opera draw the bounding box (but its properties are not exposed, ie: bx & by) and Chrome & Safari rotate the rectangle! All are properly reporting the X/Y except FF. So... the X/Y issue seems to exist for FF only! How very odd!
Also of note, it seems that $(document).ready(function () {...}); fires too early for the rotated X/Y to be recognized (which was part of my original problem!). I am rotating the elements directly before the X/Y interrogation calls in $(document).ready(function () {...}); but they don't seem to update until some time after(!?).
When I get a little more time, I will toss up a jFiddle with the example, but I'm using a modified form of "jquery-css-transform.js" so I have a tiny bit of tinkering before the jFiddle...
So... what's up, FireFox? That ain't cool, man!
The Plot Thickens...
Well, FF12 seems to fix the issue with FF11, and now acts like IE and Opera. But now I am back to square one with the X/Y, but at least I think I know why now...
It seems that even though the X/Y is being reported correctly by the browsers for the rotated object, a "ghost" X/Y still exists on the un-rotated version. It seems as though this is the order of operations:
Starting with an un-rotated element at an X,Y of 20,20
Rotate said element, resulting in the reporting of X,Y as 15,35
Move said element via JavaScript/CSS to X,Y 10,10
Browser logically un-rotates element back to 20,20, moves to 10,10 then re-rotates, resulting in an X,Y of 5,25
So... I want the element to end up at 10,10 post rotation, but thanks to the fact that the element is (seemingly) re-rotated post move, the resulting X,Y differs from the set X,Y.
This is my problem! So what I really need is a function to take the desired destination coords (10,10), and work backwards from there to get the starting X,Y coords that will result in the element being rotated into 10,10. At least I know what my problem is now, as thanks to the inner workings of the browsers, it seems with a rotated element 10=5!
I know this is a bit late, but I've written a fiddle for exactly this problem, on an HTML5 canvas:
http://jsfiddle.net/oscarpalacious/ZdQKg/
I hope somebody finds it useful!
I'm actually not calculating your x,y for the upper left corner of the container. It's calculated as a result of the offset (code from the fiddle example):
this.w = Math.sin(this.angulo) * rotador.h + Math.cos(this.angulo) * rotador.w;
this.h = Math.sin(this.angulo) * rotador.w + Math.cos(this.angulo) * rotador.h;
// The offset on a canvas for the upper left corner (x, y) is
// given by the first two parameters for the rect() method:
contexto.rect(-(this.w/2), -(this.h/2), this.w, this.h);
Cheers
Have you tried using getBoundingClientRect() ?
This method returns an object with current values of "bottom, height, left, right, top, width" considering rotations
Turn the four corners into vectors from the center, rotate them, and get the new min/max width/height from them.
EDIT:
I see where you're having problems now. You're doing the calculations using the entire side when you need to be doing them with the offsets from the center of rotation. Yes, this results in four rotated points (which, strangely enough, is exactly as many points as you started with). Between them there will be one minimum X, one maximum X, one minimum Y, and one maximum Y. Those are your bounds.
My gist can help you
Bounding box of a polygon (rectangle, triangle, etc.):
Live demo https://jsfiddle.net/Kolosovsky/tdqv6pk2/
let points = [
{ x: 125, y: 50 },
{ x: 250, y: 65 },
{ x: 300, y: 125 },
{ x: 175, y: 175 },
{ x: 100, y: 125 },
];
let minX = Math.min(...points.map(point => point.x));
let minY = Math.min(...points.map(point => point.y));
let maxX = Math.max(...points.map(point => point.x));
let maxY = Math.max(...points.map(point => point.y));
let pivot = {
x: maxX - ((maxX - minX) / 2),
y: maxY - ((maxY - minY) / 2)
};
let degrees = 90;
let radians = degrees * (Math.PI / 180);
let cos = Math.cos(radians);
let sin = Math.sin(radians);
function rotatePoint(pivot, point, cos, sin) {
return {
x: (cos * (point.x - pivot.x)) - (sin * (point.y - pivot.y)) + pivot.x,
y: (sin * (point.x - pivot.x)) + (cos * (point.y - pivot.y)) + pivot.y
};
}
let boundingBox = {
x1: Number.POSITIVE_INFINITY,
y1: Number.POSITIVE_INFINITY,
x2: Number.NEGATIVE_INFINITY,
y2: Number.NEGATIVE_INFINITY,
};
points.forEach((point) => {
let rotatedPoint = rotatePoint(pivot, point, cos, sin);
boundingBox.x1 = Math.min(boundingBox.x1, rotatedPoint.x);
boundingBox.y1 = Math.min(boundingBox.y1, rotatedPoint.y);
boundingBox.x2 = Math.max(boundingBox.x2, rotatedPoint.x);
boundingBox.y2 = Math.max(boundingBox.y2, rotatedPoint.y);
});
Bounding box of an ellipse:
Live demo https://jsfiddle.net/Kolosovsky/sLc7ynd1/
let centerX = 350;
let centerY = 100;
let radiusX = 100;
let radiusY = 50;
let degrees = 200;
let radians = degrees * (Math.PI / 180);
let radians90 = radians + Math.PI / 2;
let ux = radiusX * Math.cos(radians);
let uy = radiusX * Math.sin(radians);
let vx = radiusY * Math.cos(radians90);
let vy = radiusY * Math.sin(radians90);
let width = Math.sqrt(ux * ux + vx * vx) * 2;
let height = Math.sqrt(uy * uy + vy * vy) * 2;
let x = centerX - (width / 2);
let y = centerY - (height / 2);