I was doing one of the examples on the p5.js website - https://p5js.org/examples/form-regular-polygon.html. I got really confused by the way rotate function worked in that code . IN the below function if I just pass rotate(frameCount) , in the browser it shows me rotating two triangles intersected within forming a star , but as soon as I divide the frameCount it disappears. Also the equation used in the code - can some one give the mathematical intuition on how we reached to this point.
let sx = x + cos(a) * radius;
let sy = y + sin(a) * radius;
push();
translate(width * 0.2, height * 0.5);
rotate(frameCount / 50);
polygon(0,0,82,3);
pop();
Regarding "two triangles intersected within forming a star":
By default, the rotate function takes radians. When you do rotate(frameCount), you are increasing the angle by 1 radian at each frame. One radian equals about 57 degrees, so your triangle would rotate about 57 degrees at each frame. At frame 3, the triangle would have rotated about 120 degrees, and it would roughly overlap with the triangle at frame 1. Similarly, the triangle at frame 4 would roughly overlap with the triangle at frame 2.
The "two triangles" you are seeing is just two groups of triangles, one group being triangles at frame 1, 3, 5... and another group being triangles at frame 2, 4, 6...
That is why you should divide frameCount by some number if you would like to obtain a rather continuous rotation. Alternatively, you could also set angleMode to DEGREES. In that case, you don't have to divide frameCount anymore because at each frame the triangle would only rotate 1 degree instead of 1 radian.
Regarding the math formula:
In fact, the function used in that example should be called regularPolygon instead of polygon because it only draws regular polygons.
Now, how do you draw a regular polygon? You know the distance from each vertex to the center is a constant number. In this example, that number is the radius variable. And you know if you use polar coordinates with the center of the polygon as the origin point, the angle difference between every two adjacent vertices is also a constant number. In this example, that number is the angle variable.
More precisely, the polar coordinates of the vertices should take the form of:
v1 = (radius, 0)
v2 = (radius, angle)
v3 = (radius, angle*2)
...
Convert them to cartesian coordinates, you would obtain something like:
v1 = (cos(0) * radius, sin(0) * radius)
v2 = (cos(angle) * radius, sin(angle) * radius)
v3 = (cos(angle*2) * radius, sin(angle*2) * radius)
...
But what if the center of the polygon is not the origin point, but point (x, y), as in the example? Now the cartesian coordinates of the vertices become:
v1 = (x + cos(0) * radius, y + sin(0) * radius)
v2 = (x + cos(angle) * radius, y + sin(angle) * radius)
v3 = (x + cos(angle*2) * radius, y + sin(angle*2) * radius)
So when you do:
for (let a = 0; a < TWO_PI; a += angle) {
let sx = x + cos(a) * radius;
let sy = y + sin(a) * radius;
vertex(sx, sy);
}
You are really drawing the vertices v1, v2, v3....
Related
I am trying to implement an algorithm that will align a rectangle on an ellipse when a point is dragged.
The data I have is:
I know what corner is being dragged
the starting position of it
the ending position of it
My old algorithm was to align the adjacent corners, but that only worked if the ellipse or rectangle weren't at an angle.
I am trying to implement something like Figma does:
My current idea is to take the sides that were changed on drag and match the other sides that weren't changed to the size of the changed sides. Though I'm not sure if that's correct.
Let rectangle is described by center point (CX, CY) and two unit direction vectors (WX, WY) and (HX, HY), also W is half-width, H is half-height.
As far as I understand, rectangle slope is preserved, so direction vectors remain the same.
When corner number k was shifted, it's new position is (NX, NY). Opposite vertex has number (k+2)%4 and it's position is (PX, PY) (doesn't change)
New center is
CX' = (PX + NX) / 2
CY' = (PY + NY) / 2
New half-width and half-height
W' = 0.5 * Abs(WX * (NX - PX) + WY * (NY - PY))
H' = 0.5 * Abs(HX * (NX - PX) + HY * (NY - PY))
I am writing a simple graphics editor where a user can draw a triangle (in either clockwise or counterclockwise orientation) and can then select the triangle and drag it around.
My issue comes down to finding whether or not the mouse cursor coordinates intersect with the triangle. Because I am using GLFW, my window space is defined on a Cartesian coordinate system where x/y have range [-1, 1].
This causes an issue when I try to determine whether I have an intersection using barycentric coordinates (or any other method found here)
My current approach is as follows:
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
// Get the size of the window
int width, height;
glfwGetWindowSize(window, &width, &height);
// Convert screen position to world coordinates
xworld = ((xpos / double(width)) * 2) - 1;
yworld = (((height - 1 - ypos) / double(height)) * 2) - 1; // NOTE: y axis is flipped in glfw
double area = 0.5 * (-p1y * p2x + p0y * (-p1x + p2x) + p0x * (p1y - p2y) + p1x * p2y);
double s = 1 / (2 * area) * (p0y * p2x - p0x * p2y + (p2y - p0y) * xworld + (p0x - p2x) * yworld),
t = 1 / (2 * area) * (p0x * p1y - p0y * p1x + (p0y - p1y) * xworld + (p1x - p0x) * yworld);
if (s > 0 && t > 0 && 1 - s - t > 0)
return true;
return false;
Which kinda works if triangle in the first quadrant and oriented counter-clockwise, however, it also recognizes as intersection if to the left of the triangle.
Thanks for any help.
EDIT: Issue was a typo I had in my code (wrong value for a vertice in my triangle) Ended up using the calculation of area approach to detect intersection.
If you don't know the winding order of your triangle, you can check if mouse cursor position is to the left of every edge and if it is to the right of every edge. If one of this is true, so the mouse cursor is inside the triangle indeed.
Luckily, in the case of a triangle, any 2-combination of its vertices yield its edge. So the problem amounts to calculate six cross products.
But also, after the user has finished drawing of a triangle you can save triangle's vertices with a certain winding order, then you can do only three cross products.
I want to do it that a convex(consist of some line or arc) rotate around it's geometrical center(Cx,Cy). Meanwhile the convex flanked by two circle (given by radius: R and left center:(Lx,Cy), right center:(Rx,Cy) ). It means the circle center with same X axis as geometrical(Cy).
The model picture:
The algorithm aims to calculate the Lx and Rx when rotate theta(0 to 360 degree) and the convex with two circle only have one contact point separately. How can I achieve it?
Assume we can get the convex discretization points by given delta distance or delta theta around geometrical center.
To provide contact between rotated convex and circle, it is worth to define convex pieces analytically.
For example, if straight segment has parametric equation
X = X0 + t * (X1-X0)
Y = Y0 + t * (Y1-Y0)
then solve equation system
(X - Lx)^2 + (Y - Cy)^2 = R^2 //distance
(X - Lx) * (X1 - X0) + (Y - Cy) * (Y1 - Y0) = 0 //tangent perpendicularity to radius
for unknowns t and Lx and check that t lies in range 0..1. If true - circle touches this segment, Lx if valid
If curved segment is analytical curve, then normal to the curve in touch point should be collinear with radius.
For circle arc segment with radius aR and center ax, ay tangency condition is:
(ax - Lx)^2 + (ay - Cy)^2 = (aR + R)^2
again - one need to check whether tangent point is inside arc limits
The problem states that I have a big rectangle, with dimension L and W, and unlimited number of identical small rectangles, with dimension l and w. L * W is guaranteed to be larger than l *w. So I want to find out the maximum number of small rectangles that I can put into the big rectangle, without overlapping. You can put the small rectangles in any orientation you like.
Axis Aligned
Rectangles fit best when they are tiled, with no space between. So, an outline of how to do it:
First, figure out how many fit if you just tile them in:
lFit = L / l // integer division for these, no remainder
wFit = W / w
You know that you can fit lFit * wFit rectangles inside if you stack them in the same orientation as the big rectangle. Check again with the other orientation, and choose the bigger one as your base.
Now you may have some space left over. That space is made up of rectangles. You can easily find the size of these from the previous step. Run it again for these smaller rectangles and add to the base number. Recurse until no more fit.
If no tiles will fit into the "remaining" smaller rectangle, it's time to check for tilted ones.
Diagonal
Once your tile does not fit axis-aligned into the rectangle, you need to tilt it. You can pack the most tiles in by tilting them just enough to fit in the longest box dimension, and placing it firmly against three walls. Then you try stacking more in below it.
Note: for all the math here, I'm using width as the "longest edge". If your rectangle/tile doesn't match, just flip the dimensions.
To figure out what the proper rotation angle is, you can use a trial/error binary search. I'm sure there's a more "mathy" way to do it, but this works well enough. The formula for the bounding width of a rotated rectangle is(angle in radians):
width = w * cos(angle) + h * sin(angle)
To do a trial/error, just loop it until you reach your tolerance:
// boxWidth, tileWidth, tileHeight
public static double getAngle(double bw, double tw, double th){
double err = 10;
double maxAngle = PI * 0.25; // 45 degrees, any more would be taller than wide
double angle = maxAngle * 0.5; // start in the middle
double angleDelta = angle * 0.5; // amount to change;
count = 0;
while(count++ < 100){
double rotatedWidth = tw * Math.cos(angle) + th * Math.sin(angle);
err = rotatedWidth - bw;
if(Math.abs(err) < TOLERANCE){
return angle;
} else if(err < 0){
angle -= angleDelta;
} else {
angle += angleDelta;
}
angleDelta *= 0.5;
}
return -1; // found no good angle in 100 attempts
}
Once you have the angle, you can use basic trig to figure out some other points:
Find the lowest y-point of the top edge of the tile where it will be placed. Call this y1
y1 = sin(angle) * tileWidth
Find the lowest point of the left edge of the tile. Call this y2
y2 = sin((PI * 0.5) - radians) * tileHeight
Each added tile will take up y2 vertical space, so the number that will fit is:
(boxHeight - y1) / y2
I created a small ideone.com example that you can play with, also. The code is rather ugly, but it works. For your example in comments(13x8, 14x1), it shows:
Rotated 26.23397827148437 degrees
y1 = 6.188525444904378
y2 = 0.8969959689614577
numTiles = 2
I'm finding the angle between the centre of my circle and the triangle in degrees like so:
atan2((centre.y-triangle.y), (centre.x-triangle.x) * 180 / PI - 90
I'm setting the rotation of my triangle object which takes degrees as a parameter. The issue is all of my triangles are not rotated outwards correctly, which I presume is a result of the calculation of my position which is done like this:
triangle.x = -(width / 2) + (stage.width / 2) + radius * sin((index / total) * (2 * PI))
Here is an example of what happens, as you can see the last few triangles in the circle appear to be facing outwards correctly.
OK, I need some answer space to put all this info.
First of all you need to calculate the angle of a given triangle. You can do that with the following:
int angle = (360 / numberOfElements) * triangleIndex;
You also need to work out a "slice" (don't no what that is, just read it) to use for calculating the new positon:
var slice = (2 * Math.PI / numberOfElements) * triangleIndex;
Next, you need to work out the position of each triangle:
int tempRadius = radius + (int)(triangleHeight / 2);
int traingleCentreX = (int)(centre.X + tempRadius * Math.Cos(slice));
int traingleCentreY = (int)(centre.Y + tempRadius * Math.Sin(slice));
//assuming centre is the centre of the circle
[Credit for all this maths goes to this answer
]
Now that you have the correct position of each of your triangles, you should be able to apply the rotation (using angle) and it should look amaze-balls!
NOTE: Positions will be calculating starting at the right (i.e. 90 degrees). So when doing the rotation add an extra 90 degrees!
http://jsfiddle.net/TcENr/ (it as the quickest to test!)
The issue with the subtle offset of the rotation was because I wasn't adding the half width and height of the triangle to it's position, this fixed the problem:
rotation = atan2(centreY-(triangleY+triangleHalfHeight),centreX-(triangleX+triangleHalfWidth)) * 180 / Math.PI - 90;