My challenge is to rotate a rectangle inside another rectangle.
Problem description:
The inner rectangle, let’s call it B, is not allowed to cross the outer rectangle, let’s call it A. But if A is rotated further, so that it might come to a legal position again, it should be rotated to it. So the wanted behavior is to stop rotation before it would cross the border and continue the rotation as soon as B would have a legal position again. B is rotated with a mouse and it might be that not every single degree is calculated extra. So the mouse event might point to 20 degrees and in the next iteration to 40 degrees.
My approach is to solve the problem by calculating and working with rotations. I got the alpha rotation pointing to the mouse. Beta is the rotation from alpha to the right top corner of B.
I calculated the bounding sphere (orange) and its collision points with A (Top Left, Top Right, Right Top, Right Bottom (Left and Bot are not mentioned to not complicate the problem).
Made Calculations:
With this approach I managed to calculate when I have to stop, but only for the top side and the right side separately.
For example: Clockwise Rotation -> Top cropping
If (B.leftTopCornerRotation < TopLeft || B.leftTopCornerRotation > TopRight) {
Alpha = TopLeft - Beta;
} else if (B.leftBotCornerRotation < TopLeft || B. leftBotCornerRotation > TopRight) {
Alpha = Topleft + Beta + PI; // + PI rotates the value by 180 degree
} else if (B.rightBotCornerRotation < TopLeft || B. rightBotCornerRotation > TopRight) {
Alpha = TopLeft - Beta + PI;
} else if (B.rightTopCornerRotation < TopLeft || B. rightTopCornerRotation > TopRight) {
Alpha = TopLeft + Beta;
}
My first problem is, that if B is out of bounds on more than one line (e.g. top and right) there come situations in which I correct the rotation, so that there is no overstep for the right side, than correct that there is no overstep on the top side. But with the second correction I cause a overstep on the right side. This will lead to an infinite loop.
My second problem is, that this approach seams really complicated.
My question would be, if there is some better / working approach to calculate a valid rotation for B clockwise and counterclockwise, so that it does not overstep the borders. Best case would be when it would look like it just stops at the corner.
For every B corner find angle intervals when it is lies outside of every A edge (infinite edge for simplicity).
Then make union of these 16 intervals (most of them should be empty if B is not large) and exclude resulting interval set from the full circle range.
Related
I'm trying to learn how to zoom towards mouse using Orthographic projection and so far I've got this:
def dolly(self, wheel, direction, x, y, acceleration_enabled):
v = vec4(*[float(v) for v in glGetIntegerv(GL_VIEWPORT)])
w, h = v[2], v[3]
f = self.update_zoom(direction, acceleration_enabled) # [0.1, 4]
aspect = w/h
x,y = x-w/2, y-h/2
K1 = f*10
K0 = K1*aspect
self.left = K0*(-2*x/w-1)
self.right = K0*(-2*x/w+1)
self.bottom = K1*(2*y/h-1)
self.top = K1*(2*y/h+1)
x/y: mouse screen coordinates
w/h: window width/height
f: factor which goes from 0.1 to 4 when scrolling down/up
left/right/bottom/top: values used to compute the new orthographic projection
The results I'm getting are really strange but I don't know which part of the formulas I've messed up.
Could you please spot which part of my maths are wrong or just post a clear pseudocode I can try? Just for the record, I've read&tested quite a lot of versions out there on the internet but haven't found yet any place where this subject is explained properly.
Ps. You don't need to post any SO link related to this subject as I've read all of them already :)
I'm going to answer this in a general way, based on the following set of assumptions:
You use a matrix P for the (ortho) projection describing the actual mapping of your eye space view volume onto the standard view volume [-1,1]^3 OpenGL will clip against (see also assumption 2) and a matrix V for the view transformtation, that is postion and orientation of the "camera" (if there is such a thing, especially in ortho projections) and basically establishing an eye space where your view volume will be defined relative to.
I will ignore the homogeneous clip space, as you work with completely affine ortho projections only, that means NDC coordinates and clip space will be identical, and no tricks to any w coordinate are applied.
I assume default GL conventions for eye space and projection matrices, notably eye space origin is camera location and camera lookat direction is -z
The viewport is filling the window completely.
Windows Space is default OpenGL convention where the origin is at the bottom left.
Mouse coordinates are in some window-specific coordinate frame where the origin is at top left, mouse is at integer pixel coordinates.
I assume that the view volume defined by P is symmetrical: right = -left and top = -bottom, and it is also supposed to stay symmetrical after the zoom operation, therefore, to compensate for any movement, the view matrix V must be adjusted, too.
What you want to get is a zoom such that the object point under the mouse cursor does not move, so becomes the center of the scale operation. The mouse cursor itself is only 2D and a whole straight line in the 3D space will be mapped to the same pixel location. However, in an ortho projection, that line will be orthogonal to the image plane, so we don't need to bother much with the third dimension.
So what we want is to scale the current situation with P_old (defined by the ortho parameters l_old, r_old, b_old, t_old, n_old and f_old) and V_old (defined by "camera" position c_old and ortientation o_old) by a zoom factor s at mouse position (x,y) (in the space from assumption 6).
We can see a few things directly:
the near and far plane of the projection should be unaffected by the operation, so n_new = n_old and f_new = f_old.
the actual camera orientation (or lookat direction) should also be unaffected: o_new = o_old
If we zoom in by a factor of s, the actual view volume must be scaled by 1/s, since when we zoom in, a smaller part of the complete world is mapper on the screen than before (and appears bigger). So we can simply scale the frustum parameters we had:
l_new = l_old / s, r_new = r_old / s, b_new = b_old / s, t_new = t_old / s
If new only replace P_old by P_new, we get the zoom, but the world point under the mouse cursor will move (except the mouse is exactly in the center of the view). So we have to compensate for that by modifying the camera position.
Let's first put the mouse coords (x,y) into OpenGL window space (assumptions 5 and 6):
x_win = x + 0.5
y_win = height - 0.5 - y
Note that besides mirroring y, I also shift the coordinates by half a pixels. That's because in OpenGL window space, pixel centers are at half-inter coordinates, while I assume that your integer mouse coordinates are to represent the center of the pixel you click onto (will not make a big difference visually, but still)
Now let's further put the coords into Normalized Device Space (relying on assumption 4 here):
x_ndc = 2.0 * x_win / width - 1
y_ndc = 2.0 * y_win / height - 1
By assumption 2, clip and NDC coordiantes will be identical, and we can call the vector v our NDC/space mouse coordinates: v = (x_ndc, y_ndc, 0, 1)^T
We can now state our "point under mouse must not move" condition:
inverse(V_old) * inverse(P_old) * v = inverse(V_new) * inverse(P_new) * v
But let's just go into eye space and let's look at what happened:
Let a = inverse(P_old) * v be the eye space location of the point under the mouse cursor before we scaled.
Let b = inverse(P_new) * v be the eye space location of the pointer under the mouse cursor after we scaled.
Since we assumed a symmetrical view volume, we already know that for the x and y coordinates, b = (1/s) *a holds (assumption 7. if that assumption does not hold, you need to do the actual calculation for b too, which isn't hard either).
So, we can set up an 2D eye space offset vector d which describes how our point of interest was moved by the scale:
d = b - a = (1 / s) *a - a = a (1/s - 1)
To compensate for that movement, we have to move our camera inversely, so by -d.
If you keep the camera position separate as I did in assumption 1, you simply need to update the camera position c accordingly. You just have to take care about the fact that c is the world space postion, while d is an eye space offset:
c_new = c_old - inverse(V_old) * (d_x, d_y, 0, 0)^T
Not that if you do not keep the camera position as a separate variable, but keep the view matrix directly, you can simply pre-multiply the translation: V_new = translate(-d_x, -d_y, 0) * V_old
Update
What I wrote so far is correct, but I took a shortcut which is numerically a very bad idea when working with not-infinite precision data types. The error in camera position accumulates very fast if one zooms out a lot. So after #BPL implemted this, this it what he got:
The main issue seems to be that I directly calculated the offset vector d in eye space, which does not take the current view matrix V_old (and its small errors into account). So a more stable approach is to calculate all of this directly in world space:
a = inverse(P_old * V_old) * v
b = inverse(P_new * V_old) * v
d = b - a
c_new = c_old - d
(doing so makes assumption 7 not needed anymore as a by product, so it directly works in the general case of arbitrary ortho matrices).
Using this approach, the zoom operation worked as expected:
I'm trying to calculate the point marked in red (to create a line between the circle and the corner of the box)
It's a similar problem to this A JavaScript function that returns the x,y points of intersection between two circles?
However this is for 2 circles.
I know the position of both, circle radius etc, how do I calculate the nearest point to that corner on the circle?
const shapeTop = this.shape.getAttribute('position').clone()
//I want to apply the position here
const geo = this.button.children[0].getAttribute('geometry')
if(!geo)
return
const halfWidth = geo.width * 0.5
const halfHeight = geo.height * 0.5
const buttonEdge = {
x: buttonPos.x + (buttonPos.x > 0 ? - halfWidth : halfWidth),
y: buttonPos.y + (buttonPos.y > 0 ? - halfHeight : halfHeight),
z: buttonPos.z,
}
In three.js, you can calculate the desired point like so:
var vector = new THREE.Vector3(); // or Vector2
vector.copy( corner ).sub( center ).setLength( radius ).add( center );
three.js r.93
The core question is, how to find a point on the circle which has the shortest distance to a given rectangle.
After my thought, we can split the whole 2D-plane into two areas, one is where the rectangle can be moved to by translating with the direction of its' borders, the other is where the rectangle can't be moved in that way. The first area paints like a crossing road (the colored area), and the second area is the rest of the 2D-plane (the white area).
If the center of this circle is inside the first area, then the requested point is the intersecting point of ((the circle) and (the perpendicular line from (the center of circle) to (the nearest border of the rectangle))). Else if the center is inside the second area, then the requested point is the nearest corner of the rectangle.
Update: Another thought is to consider just these 6 points: 4 is the intersection of ((the circle) and (the line between circle center and the 4 corner of rectangle)), another 2 is the intersection of ((the circle) and (the perpendicular line from (the center of circle) to (the borders of rectangle))).
As #WestLangley's answer correctly points out, it is easy to find the nearest point of the circle, once the nearest point on the rectangle is known.
However, there are two different types of "nearest point" possibile on the rectangle: a corner or a side. The figure below illustrates both possibilities:
To determine which case you have, project the center of the circle onto each of the four lines (for example, as in this Q&A). If you do a normalized projection, a value <0 or >1 indicates that your nearest point for that segment is a corner. You are then left with the four corners and any projections that resulted in a value between 0 and 1 as candidates.
Once you have found which candidate is nearest the center of the circle, apply the accepted answer.
I have a rectangle that is W x H.
Within that rectangle is another rectangle that is rotated by ϴ degrees which is always between -45 and 45 degrees, and shares the same center as the outer rectangle. I need to find w and h such that the area of the inner rectangle is maximized.
Here's a (ghetto) image to illustrate a bit. Though, the corners of the rectangles should probably be touching, I assume?
Here is the prototype of the function I'm looking to write:
SizeD GetMaxRectangleSize(double outerWidth, double outerHeight, float angle)
SizeD is just a struct that has a width and height in doubles.
Thanks to the comments for steering me in the right direction!
My solution, though perhaps not mathematically optimal, was to assume that if all four corners of the inner rectangle fall on the outer rectangle then area will be maximized.
Therefore:
H = wSin(ϴ) + hCos(ϴ)
W = wCos(ϴ) + hSin(ϴ)
Solving for w and h and substituting gives:
h = (HCos(ϴ) - WSin(ϴ))/(cos(ϴ)^2 - sin(ϴ)^2)
w = (WCos(ϴ) - HSin(ϴ))/(cos(ϴ)^2 - sin(ϴ)^2)
Which happens to work for ϴ = [0,45), and (-45,0] should act the same.
The tricky part of this question isn't how to calculate the area of an interior rectangle, but which of all the possible interior rectangles has maximum area?
To start with, observe that the box in your image is the same area regardless of how it is slid around horizontally, and if it is slid to the rightmost wall, it allows for an easy parameterization of the problem as follows:
I find it a bit easier to think of this problem, with the fixed box rotated by the offset angle so that the interior box lines up in a standard orientation. Here's a figure (I've changed theta to beta just because I can type it easily on a mac, and also left off the left most wall for reasons that will be clear):
So think of this constructed as follows: Pick a point on the right side of the exterior rectangle (shown here by a small circle), note the distance a from this point to the corner, and construct the largest possible interior with a corner at this point (by extending vertical and horizontal lines to the exterior rectangle). Clearly, then, the largest possible rectangle is one of the rectangles derived from the different values for a, and a is a good parameter for this problem.
So given that, then the area of the interior rectangle is:
A = (a * (H-a))/(cosß * sinß)
or, A = c * a * (H-a)
where I've folded the constant trig terms into the constant c. We need to maximize this, and to do that the derivative is useful:
dA/da = c * (H - 2a)
That is, starting at a=0 (ie, the circle in the figure is in the lower corner of the exterior rectangle, resulting in a tall and super skin interior rectangle), then the area of the interior rectangle increases monotonically until a=H/2, and then the area starts to decrease again.
That is, there are two cases:
1) If, as a increase from 0 to H/2, the far interior corner hits the opposite wall of the exterior, then the largest possible rectangle is when this contact occurs (and you know it's the largest due to the monotonic increase -- ie, the positive value of the derivative). This is your guess at the solution.
2) If the far corner never touches a wall, then the largest interior rectangle will be at a=H/2.
I haven't explicitly solved here for the area of the interior rectangle for each case, since that's a much easier problem than the proof, and anyone who could follow the proof, I assume could easily calculate the areas (and it does take a long time to write these things up).
I have a player who can rotate and move around a 2D Cartesian grid, I need to calculate where to draw the enemies on screen.
The player should have a certain viewpoint which is the size of the screen in front of the direction the player is facing. (and a little behind)
I've tried tons of ways to implement this messing with Bi-Polar co-ordinates and Trig but I havn't been able to solve the problem of calculating where on the screen the enemies should be drawn.
The problem is best represent in the form of a graph with green being the viewpoint which is a rectangle that can rotate and move around the grid, and dots representing player and enemy.
So I need to work out the positions of the enemies on screen relative to the players rotation and position.
If you're going for a Doom-like perspective, you should imagine the viewing area as a parallelogram, rather than a rectangle. Imagine that behind your character is a camera man with its own position and angle.
The enemy's screen position is related to the angle between the camera and the enemy.
//indicates where on the screen an enemy should be drawn.
//-1 represents the leftmost part of the screen,
//and 1 is the rightmost.
//Anything larger or smaller is off the edge of the screen and should not be drawn.
float calculateXPosition(camera, enemy){
//the camera man can see anything 30 degrees to the left or right of its line of sight.
//This number is arbitrary; adjust to your own tastes.
frustumWidth = 60;
//the angle between the enemy and the camera, in relation to the x axis.
angle = atan2(enemy.y - camera.y, enemy.x - camera.x);
//the angle of the enemy, in relation to the camera's line of sight. If the enemy is on-camera, this should be less than frustumWidth/2.
objectiveAngle = camera.angle - angle;
//scale down from [-frustumWidth/2, frustumWidth/2] to [-1, 1]
return objectiveAngle / (frustrumWidth / 2);
}
These diagrams visualize what the variables I'm using here represent:
Once you have an "X position" in the range of [-1, 1], it should be easy enough to convert that into pixel coordinates. For example, if your screen is 500 pixels wide, you can do something like ((calculateXPosition(camera, enemy) + 1) / 2) * 500;
Edit:
You can do something similar to find the y-coordinate of a point, based on the point's height and distance from the camera.
(I'm not sure how you should define the height of the enemy and camera - any number should be fine as long as they somewhat match the scale set by the x and y dimensions of the cartesian grid.)
//this gives you a number between -1 and 1, just as calculateXPosition does.
//-1 is the bottom of the screen, 1 is the top.
float getYPosition(pointHeight, cameraHeight, distanceFromCamera){
frustrumWidth = 60;
relativeHeight = pointHeight - cameraHeight;
angle = atan2(relativeHeight, distanceFromCamera);
return angle / (frustrumWidth / 2);
}
You can call the method twice to determine the y position of both the top and the bottom of the enemy:
distanceFromCamera = sqrt((enemy.x - camera.x)^2 + (enemy.y - camera.y)^2);
topBoundary = convertToPixels(getYPosition(enemy.height, camera.height, distanceFromCamera));
bottomBoundary = convertToPixels(getYPosition(0, camera.height, distanceFromCamera));
That should give you enough information to properly scale and position the enemy's sprite.
(aside: the frustrumWidths in the two methods don't need to be the same - in fact, they should be different if the screen you are drawing to is rectangular. The ratios of the x frustrum and y frustrum should be equal to the ratios of the width and height of the screen.)
let me begin by stating that's i'm dreadful at math.
i'm attempting to reposition and rotate a rectangle. however, i need to rotate the rectangle from a point that is not 0,0 but according to how far its coordinates has shifted. i'm sure that doesn't make much sense, so i've made some sketches to help explain what i need.
the image above shows 3 stages of the red rectangle moving from 0% to 100%. the red rectangle's X and Y coordinates (top left of the red rectangle) only moves a percentage of the blue rectangle's height.
the red rectangle can rotate. focusing only on the middle example ("Distance -50%") from above, where the red rectangle is repositioned at -50 of the blue rectangle's height, its new angle in the above image is now -45º. it has been rotated from its 0, 0 point.
now, my problem is that i want its rotational point to reflect its position.
the red and blue rectangles are the same size, but have opposite widths and heights. since the red rectangle's 0,0 coordinates are now -50% of the blue rectangle's height, and since they have opposite widths and heights, i want the rotational point to be 50% of the red rectangle's width (or 50% of the blue rectangle's height, which is the same thing).
rather than specifically telling the red rectangle to rotate at 50% of its width, in order to do what i want, i need to emulate doing so by using a formula that will position the red rectangle's X and Y coordinates so that its rotational point reflects its position.
Here's an illustrated solution to your problem:
I don't exactly understand what you need, but it seems that a procedure to rotate a rectangle around an arbitrary point may help.
Suppose we want to rotate a point (x,y) d radians around the origin (0,0). The formula for the location of the rotated point is:
x' = x*cos(d) - y*sin(d)
y' = x*sin(d) + y*cos(d)
Now we don't want to rotate around the origin, but around a given point (a,b). What we do is first move the origin to (a,b), then apply the rotation formula above, and then move the origin back to (0,0).
x' = (x-a)*cos(d) - (y-b)*sin(d) + a
y' = (x-a)*sin(d) + (y-b)*cos(d) + b
This is your formula for rotating a point (x,y) d radians around the point (a,b).
For your problem (a,b) would be the point halfway on the right side of the blue rectangle, and (x,y) would be every corner of the red rectangle. The formula gives (x',y') for the coordinates of the corners of rotated red rectangle.
It's quite simple really.
1. Let's settle on your point you want to rotate the rectangle about, i.e. the point of rotation (RP) which does not move when you swivel your rectangle around. Let's assume that the point is represented by the diamond in the figure below.
2. Translate the 4 points so that RP is at (0,0). Suppose the coordinates of that point is (RPx,RPy), therefore subtract all 4 corners of the rectangle by those coordinates.
3. Rotate the points with a rotation matrix (which rotates a point anticlockwise around the origin through some angle which is now the point of rotation thanks to the previous translation):
The following figure shows the rectangle rotated by 45° anticlockwise.
4. Translate the rectangle back (by adding RP to all 4 points):
I assume this is what you want :)
It seems like you could avoid a more complex rotation by more crafty positioning initially? For example, in the last example, position the red box at "-25% Blue Height" and "-25% Red Height" -- if I follow your referencing scheme -- then perform the rotation you want.
If you know the origin O and a point P on the side of rotated rectangle, you can calculate the vector between the two:
(source: equationsheet.com)
You can get the angle between the vector and the x-axis by taking the dot product with this vector:
(source: equationsheet.com)
Given this, you can transform any point on the rectangle by multiplying it by a rotation matrix:
(source: equationsheet.com)