Draw non-overlapping arc between two rectangles - algorithm

I'm trying to draw a (svg) arc connecting two rectangles. The catch is, that the arc should start at the border of the rectangles, not at the center.
To illustrate:
I have the center points, width & height of the rectangles C1 w1 h1 C2 w2 h2 and the center and x and y radius of the arc D rx ry. So basically, for drawing the purple arc, i'm missing P1 and P2.
All values are dynamic and can change, so the algorithm needs be agnostic of rx and ry, width and height of the rectangles, how the rectangles are positioned relatively to each other, etc.
Taking the rounded corners into account would be the cherry on top. But that's not really necessary..

Let's center of ellipse be coordinate origin (if not, just shift rectangle coordinates by -D.X and -D.Y).
In this system ellipse equation is
x^2/rx^2 + y^2/ry^2 = 1
Substitute rectangle edge coordinates in this equation and check if result actually belongs to rectangle.
For example, right edge of top rectangle is X = C1'.X + w1. Find Y from ellipse equation and check it is in range C1'Y - h1 .. C1'Y + h1. If yes, P1 = (C1'.X + w1, CalculatedY)

Alright, just for people who might stumble upon this in the future..
Here is what i came up with in javascript (ES6). But it should be easy to port this to other languages..
/**
* Calculate the intersection of the border of the given rectangle
* and a circular arc.
* #param {Object} rectangle Rectangle object with dimensions and position properties.
* #param {Object} arc Arc object with center and radius properties.
* #return {Object} Resulting intersection point, with x- and y-coordinates.
*/
calculateBorderPoint(rectangle, arc) {
// Clone the rectangle, because we don't want to mutate the original.
const a = Object.assign({}, rectangle.position);
// Treat center of circle as coordinate origin.
a.x -= arc.center.x;
a.y -= arc.center.y;
let points = [];
// Check east & west with possible x values
const possibleX = [
a.x - rectangle.dimensions.width / 2,
a.x + rectangle.dimensions.width / 2,
];
possibleX.forEach((x) => {
const ySquared = [
Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(x, 2)),
-Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(x, 2)),
];
// Check if the derived y value is in range of rectangle
ySquared.forEach((y) => {
if (y >= a.y - rectangle.dimensions.height / 2 &&
y <= a.y + rectangle.dimensions.height / 2) {
points.push({x, y});
}
});
});
// Check north & south with possible y values
const possibleY = [
a.y - rectangle.dimensions.height / 2,
a.y + rectangle.dimensions.height / 2,
];
possibleY.forEach((y) => {
const xSquared = [
Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(y, 2)),
-Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(y, 2)),
];
// Check if the derived x value is in range of rectangle
xSquared.forEach((x) => {
if (x >= a.x - rectangle.dimensions.width / 2 &&
x <= a.x + rectangle.dimensions.width / 2) {
points.push({x, y});
}
});
});
// At this point you will propably have multiple possible solutions,
// because the circle might intersect the rectangle at multiple points.
// You need to select the point, that makes most sense in your case.
// One solution would be to select the one closest to the other rectangle.
// Translate it back.
points[0].x += arc.center.x;
points[0].y += arc.center.y;
return points[0];
}
It's not pretty, but it works. I'm happy to hear any suggestions..

Related

Processing - creating circles from current pixels

I'm using processing, and I'm trying to create a circle from the pixels i have on my display.
I managed to pull the pixels on screen and create a growing circle from them.
However i'm looking for something much more sophisticated, I want to make it seem as if the pixels on the display are moving from their current location and forming a turning circle or something like this.
This is what i have for now:
int c = 0;
int radius = 30;
allPixels = removeBlackP();
void draw {
loadPixels();
for (int alpha = 0; alpha < 360; alpha++)
{
float xf = 350 + radius*cos(alpha);
float yf = 350 + radius*sin(alpha);
int x = (int) xf;
int y = (int) yf;
if (radius > 200) {radius =30;break;}
if (c> allPixels.length) {c= 0;}
pixels[y*700 +x] = allPixels[c];
updatePixels();
}
radius++;
c++;
}
the function removeBlackP return an array with all the pixels except for the black ones.
This code works for me. There is an issue that the circle only has the numbers as int so it seems like some pixels inside the circle won't fill, i can live with that. I'm looking for something a bit more complex like I explained.
Thanks!
Fill all pixels of scanlines belonging to the circle. Using this approach, you will paint all places inside the circle. For every line calculate start coordinate (end one is symmetric). Pseudocode:
for y = center_y - radius; y <= center_y + radius; y++
dx = Sqrt(radius * radius - y * y)
for x = center_x - dx; x <= center_x + dx; x++
fill a[y, x]
When you find places for all pixels, you can make correlation between initial pixels places and calculated ones and move them step-by-step.
For example, if initial coordinates relative to center point for k-th pixel are (x0, y0) and final coordinates are (x1,y1), and you want to make M steps, moving pixel by spiral, calculate intermediate coordinates:
calc values once:
r0 = Sqrt(x0*x0 + y0*y0) //Math.Hypot if available
r1 = Sqrt(x1*x1 + y1*y1)
fi0 = Math.Atan2(y0, x0)
fi1 = Math.Atan2(y1, x1)
if fi1 < fi0 then
fi1 = fi1 + 2 * Pi;
for i = 1; i <=M ; i++
x = (r0 + i / M * (r1 - r0)) * Cos(fi0 + i / M * (fi1 - fi0))
y = (r0 + i / M * (r1 - r0)) * Sin(fi0 + i / M * (fi1 - fi0))
shift by center coordinates
The way you go about drawing circles in Processing looks a little convoluted.
The simplest way is to use the ellipse() function, no pixels involved though:
If you do need to draw an ellipse and use pixels, you can make use of PGraphics which is similar to using a separate buffer/"layer" to draw into using Processing drawing commands but it also has pixels[] you can access.
Let's say you want to draw a low-res pixel circle circle, you can create a small PGraphics, disable smoothing, draw the circle, then render the circle at a higher resolution. The only catch is these drawing commands must be placed within beginDraw()/endDraw() calls:
PGraphics buffer;
void setup(){
//disable sketch's aliasing
noSmooth();
buffer = createGraphics(25,25);
buffer.beginDraw();
//disable buffer's aliasing
buffer.noSmooth();
buffer.noFill();
buffer.stroke(255);
buffer.endDraw();
}
void draw(){
background(255);
//draw small circle
float circleSize = map(sin(frameCount * .01),-1.0,1.0,0.0,20.0);
buffer.beginDraw();
buffer.background(0);
buffer.ellipse(buffer.width / 2,buffer.height / 2, circleSize,circleSize);
buffer.endDraw();
//render small circle at higher resolution (blocky - no aliasing)
image(buffer,0,0,width,height);
}
If you want to manually draw a circle using pixels[] you are on the right using the polar to cartesian conversion formula (x = cos(angle) * radius, y = sin(angle) * radius).Even though it's focusing on drawing a radial gradient, you can find an example of drawing a circle(a lot actually) using pixels in this answer

How to make clock ticks stuck to the clock border?

I try to make a clock, in swift, but now i want to make something strange. I want make border radius settable. This is the easy part (is easy because I already did that). I drew 60 ticks around the clock. The problem is that 60 ticks are a perfect circle. If I change the border radius I obtain this clock:
All ticks are made with NSBezierPath, and code for calculate position for every tick is :
tickPath.moveToPoint(CGPoint(
x: center.x + cos(angle) * point1 ,
y: center.y + sin(angle) * point1
))
tickPath.lineToPoint(CGPoint(
x: center.x + cos(angle) * point2,
y: center.y + sin(angle) * point2
))
point1 and point2 are points for 12 clock tick.
My clock background is made with bezier path:
let bezierPath = NSBezierPath(roundedRect:self.bounds, xRadius:currentRadius, yRadius:currentRadius)
currentRadius - is a settable var , so my background cam be, from a perfect circle (when corner radius = height / 2) to a square (when corner radius = 0 ).
Is any formula to calculate position for every tick so, for any border radius , in the end all ticks to be at same distance to border ?
The maths is rather complicated to explain without recourse to graphics diagrams, but basically if you consider a polar coordinates approach with the origin at the clock centre then there are two cases:
where the spoke from the origin hits the straight side of the square - easy by trigonometry
where it hits the circle arc at the corner - we use the cosine rule to solve the triangle formed by the centre of the clock, the centre of the corner circle and the point where the spoke crosses the corner. The origin-wards angle of that triangle is 45º - angleOfSpoke, and two of the sides are of known length. Solve the cosine equation as a quadratic and you have it.
This function does it:
func radiusAtAngle(angleOfSpoke: Double, radius: Double, cornerRadius: Double) -> Double {
// radius is the half-width of the square, = the full radius of the circle
// cornerRadius is, of course, the corner radius.
// angleOfSpoke is the (maths convention) angle of the spoke
// the function returns the radius of the spoke.
let theta = atan((radius - cornerRadius) / radius) // This determines which case
let modAngle = angleOfSpoke % M_PI_2 // By symmetry we need only consider the first quadrant
if modAngle <= theta { // it's on the vertical flat
return radius / cos(modAngle)
} else if modAngle > M_PI_2 - theta { // it's on the horizontal flat
return radius / cos(M_PI_2 - modAngle)
} else { // it's on the corner arc
// We are using the cosine rule to solve the triangle formed by
// the clock centre, the curved corner's centre,
// and the point of intersection of the spoke.
// Then use quadratic solution to solve for the radius.
let diagonal = hypot(radius - cornerRadius, radius - cornerRadius)
let rcosa = diagonal * cos(M_PI_4 - modAngle)
let sqrTerm = rcosa * rcosa - diagonal * diagonal + cornerRadius * cornerRadius
if sqrTerm < 0.0 {
println("Aaargh - Negative term") // Doesn't happen - use assert in production
return 0.0
} else {
return rcosa + sqrt(sqrTerm) // larger of the two solutions
}
}
}
In the diagram OP = diagonal, OA = radius, PS = PB = cornerRadius, OS = function return, BÔX = theta, SÔX = angleOfSpoke

How to find endpoint coordinates with given start, length, and euler angles

I'm working on some 3d fractals... If I take any arbitrary point (x,y,z), start from there, and then draw a line of given length "d", in a direction defined by Euler angles... (by rotation A about the x-axis, B about the y-axis, and C about the z-axis) -- and then calculate the resulting endpoint of the line.
This would be simple in 2 dimensions, as I could find the endpoint like:
endX = beginX + d * cos(angle)
endY = beginY + d * sin(angle)
Basically, I need to fill in the blanks here:
endX = beginX + d * (??)
endY = beginY + d * (??)
endZ = beginZ + d * (??)
Where I only know angles defined by 3 rotations, 1 about each axis

Quaternion translation and rotation in iOS OpenGL ES

I'm struggling with some quaternion code in iOS. I have an open cube, which i've rotated into an isometric view. i am able to rotate the cube with touch and rotate about its axis and also zoom in/out. I also have labels associated with the cube - which also need to rotate with the cube. Again, i've managed to do this.
However, i'm now trying to implement being able to drag the label (ie. translate it) from one position, to another. If we look at the image below, what i've tried to illustrate is that i want to be able to translate the label from "label from" to the position "label to". Then, when i come to rotating the cube, the label should stay in its new position and rotate with the cube. However, i'm making a cock-up of this translation and when i try rotating the cube, the label jumps to a new position since i've not set the label coordinates properly.
I have the quaternion associated with the cube.
With the following code, i have been able to translate the label properly when the quaternion is set to [0, 0, 0, 1] (so that the cube is front-on - looks like a square from this position).
- (void) rotateWithAngle:(float) radians andVector:(GLKVector3) axis andScale:(float) scale
{
if (radians != self.lastRadians
|| (axis.v[0] != self.lastAxis.v[0] || axis.v[1] != self.lastAxis.v[1] || axis.v[2] != self.lastAxis.v[2])
|| scale != self.lastScale)
{
GLKMatrix4 m = GLKMatrix4MakeTranslation(self.normX, self.normY, self.normZ);
if (radians != 0)
m = GLKMatrix4Rotate(m, radians, axis.x, -axis.y, axis.z);
m = GLKMatrix4Scale(m, scale, scale, scale);
float x = (m.m00 * m.m30) + (m.m01 * m.m31) + (m.m02 * m.m32) + (m.m03 * m.m33);
float y = (m.m10 * m.m30) + (m.m11 * m.m31) + (m.m12 * m.m32) + (m.m13 * m.m33);
float z = (m.m20 * m.m30) + (m.m21 * m.m31) + (m.m22 * m.m32) + (m.m23 * m.m33);
x /= m.m33;
y /= m.m33;
z /= m.m33;
float w = (((x+self.winSz) / (self.winSz * 2.0)) * self.parentFrame.size.width) + self.parentFrame.origin.x;
float h = (((y+self.winSz) / (self.winSz * 2.0)) * self.parentFrame.size.height) + self.parentFrame.origin.y;
self.lastRadians = radians;
self.lastAxis = axis;
self.lastScale = scale;
[self setCenter:CGPointMake(w,h)];
}
}
- (void) translateFromTouch:(UIPanGestureRecognizer *) pan
{
CGPoint translation = [pan translationInView:self];
CGPoint imageViewPosition = self.center;
GLKVector3 axis = GLKQuaternionAxis(*_quaternion);
float rot = GLKQuaternionAngle(*_quaternion);
CGFloat h = self.parentFrame.size.height;
CGFloat w = self.parentFrame.size.width;
imageViewPosition.x += translation.x;
imageViewPosition.y += translation.y;
self.center = imageViewPosition;
// recalculate the norm position
float x = ((2.0 * self.winSz * (imageViewPosition.x - self.parentFrame.origin.x)) / w) - self.winSz;
float y = ((2.0 * self.winSz * (imageViewPosition.y - self.parentFrame.origin.y)) / h) - self.winSz;
self.normX = x;
self.normY = y;
[pan setTranslation:CGPointZero inView:self];
}
These methods are hit if a label (based on a UILabel) is either dragged or the cube (or the opengl scene) is rotated.
This works when we are looking front-on, so that the x,y values can easily be converted from pixel coords into normal or world coords.
However, when the axis is not front-on, i'm struggling to figure it out. For instance, we we have the quaternion set at (0, sqrt(2)/2, 0, sqrt(2)/2) then all x translations correspond to z world coords. So how do i make this connection/calculation? I'm sure it's fairly easy but i've hit a wall with this.
(winSz i have set to 1.5. model coords very between -1 and 1)

How do I translate and scale points within a bounding box?

I have a number of points P of the form (x, y) where x,y are real numbers. I want to translate and scale all these points within a bounding box (rectangle) which begins at the point (0,0) (top left) and extends to the point (1000, 1000) (bottom right).
Why is it that the following algorithm does not produce points in that bounding box?
for Point p in P:
max = greatest(p.x, p.y, max)
scale = 1000 / max
for Point p in P:
p.x = (p.x - 500) * scale + 500
p.y = (p.y - 500) * scale + 500
I fear that this won't work when p.x or p.y is a negative number.
I would also like to maintain the "shape" of the points.
Find all of yMin, yMax, xMin, xMax, xDelta = xMax-xMin and yDelta = yMax-yMin for your set of points.
Set max = greatest(xDelta,yDelta).
Foreach Point p set p.X = (p.X - xMin) * scale and p.Y = (p.Y - yMin) * scale

Resources