Create arc between two CGPoints and get CGPoints along it - ios

I want to display images on an arc between two points. I have a starting CGPoint and an ending CGPoint. I've seen answers for something like this using SceneKit and other answers that produce entire arches and graphs. I'm just working within a regular view.
My end goal is to end up with something like this (where letters represent images):
And I need to know where to set the center for B & C.
let firstLabel = UILabel(frame: CGRectMake(12, 200, 50, 50))
let lastLabel = UILabel(frame: CGRectMake(250, 360, 50, 50))

Here you probably need parametric equation of ellipse rather than circle. It looks like this:
x = a*cos(t) y = b*sin(t), where 0<=t<=2π
a and b are ellipse's radiuses. If we take that A is (x1, y1) and D is (x2, y2), n - number of points you wish between A and D (2 in your example - B and C) then point calculation should look something like this:
let a = x2 - x1
let b = y2 - y1
let angleStep = M_PI_2 / Double(n + 1)
var angle = angleStep
var points: [CGPoint] = []
while angle < M_PI_2 {
let x = a * CGFloat(cos(angle))
let y = y2 - b * CGFloat(sin(angle))
points.append(CGPointMake(x, y))
angle += angleStep
}

Related

How to "rotate" an ellipse?

Using this:
local W, H = 100, 50
function love.draw()
love.graphics.translate(love.graphics.getWidth()/2,love.graphics.getHeight()/2)
for i = 1, 360 do
local I = math.rad(i)
local x,y = math.cos(I)*W, math.sin(I)*H
love.graphics.line(0, 0, x, y)
end
end
I can connect a line with the center of an ellipse (with length W and height H) and the edge. How do you 'rotate' the ellipse around it's center, with a parameter R? I know you can sort of do it with love.graphics.ellipse and love.graphics.rotate but is there any way I can get the coordinates of the points on a rotated ellipse?
This is a Trigonometry problem, here is how the basic 2D rotation work. Imagine a point located at (x,y). If you want to rotate that point around the origin(in your case 0,0) by the angle θ, the coordinates of the new point would be located at (x1,y1) by using the following transformation
x1 = xcosθ − ysinθ
y1 = ycosθ + xsinθ
In your example, I added a new ellipse after rotations
function love.draw()
love.graphics.translate(love.graphics.getWidth()/2,love.graphics.getHeight()/2)
for i = 1, 360, 5 do
local I = math.rad(i)
local x,y = math.cos(I)*W, math.sin(I)*H
love.graphics.setColor(0xff, 0, 0) -- red
love.graphics.line(0, 0, x, y)
end
-- rotate by angle r = 90 degree
local r = math.rad(90)
for i = 1, 360, 5 do
local I = math.rad(i)
-- original coordinates
local x = math.cos(I) * W
local y = math.sin(I) * H
-- transform coordinates
local x1 = x * math.cos(r) - y * math.sin(r)
local y1 = y * math.cos(r) + x * math.sin(r)
love.graphics.setColor(0, 0, 0xff) -- blue
love.graphics.line(0, 0, x1, y1)
end
end

How do I create a circle packing algorithm for circles with unequal sizes in iOS?

I am trying to pack a bunch of round UIViews in a hexagonal pattern.
They all have different sizes.
First I randomly generate the UIViews and put them on the screen as shown:
Then I have an algorithm that arranges the views in a circular pattern around the center. This is the algorithm:
func arrangeViews()
{
let viewCenter = self.view.center
let radius: Double = 50?? <- What do I put here? All the views has different radius?
var currentDistFromCenter: Double = (radius * 2)
var numMoved = 0
let amountOfViews = views.count
numMoved += 1
while numMoved < amountOfViews {
var numberToFit = Double(M_PI / asin(radius / currentDistFromCenter))
if numberToFit > Double(amountOfViews - numMoved) {
numberToFit = Double(amountOfViews - numMoved)
}
for i in 0 ..< Int(numberToFit) {
let currentView = views[numMoved]
let angle = Double(M_PI * 2.0 * Double(i) / numberToFit)
let x = Double(viewCenter.x) + cos(angle) * currentDistFromCenter
let y = Double(viewCenter.y) + sin(angle) * currentDistFromCenter
var newPoint = CGPoint(x: CGFloat(x), y: CGFloat(y))
views.first?.center = self.view.center
if newPoint.x != currentView.frame.origin.x || newPoint.y != currentView.frame.origin.y {
UIView.animate(withDuration: 0.3, animations: {
currentView.center = newPoint
})
numMoved += 1
}
}
currentDistFromCenter += radius * 2
}
}
Here is my result after I run this function:
Now this algorithm is for circles (views) with the same size. You see that they don't lie next to each other as they would if they all had the same size. As shown here:
Is there anyone out there that has any form of clue as to what I could change in the algorithm so I can pack views with different sizes?
Here are some links that I've encountered during my research, but they haven't gotten me very far because Maths isn't my strongest side:
http://www.optimization-online.org/DB_FILE/2008/06/1999.pdf
Packing different sized circles into rectangle - d3.js
http://jsfiddle.net/TDzVE/
Thank you in advance and happy programming!
As per the result needed, I would prefer firstly to draw all 7 circles at same center.
Suppose the red colored center circle is of size (x, y, W, H)
then
1st (Top left) - (x-W/2, y-(H+H/2), W, H)
2nd (Top right) - (x+W/2, y-(H+H/2), W, H)
3rd (Right) - (x+W, y, W, H)
4th (Bottom right) - (x+W/2, y+(H+H/2), W, H)
5th (Bottom left) - (x-W/2, y+(H+H/2), W, H)
6th (Left) - (x-W, y, W, H)
This will be applied in all cases of hexagonal part, just W/H will change for each circle like
1st (Top left) - (x-W/2, y-(H+H/2), W1, H1)
2nd (Top right) - (x+W/2, y-(H+H/2), W2, H2)
3rd (Right) - (x+W, y, W3, H3)
4th (Bottom right) - (x+W/2, y+(H+H/2), W4, H4)
5th (Bottom left) - (x-W/2, y+(H+H/2), W5, H5)
6th (Left) - (x-W, y, W6, H6)

CraftAR Image recognition - Translating matchBoundingBox to points in screen

I am using on device image recognition from Catchoom CraftAR and working with the example available on Github https://github.com/Catchoom/craftar-example-ios-on-device-image-recognition.
The image recognition works, I would like to use the matchBoundingBox to draw some squares on all the 4 corners. Somehow the calculations I am doing are not working, I have based them on this article:
http://support.catchoom.com/customer/portal/articles/1886553-obtain-the-bounding-boxes-of-the-results-of-image-recognition
The square views are added to the scanning overlay and this is how I am calculating the points where to add the 4 views:
CraftARSearchResult *bestResult = [results objectAtIndex:0];
BoundingBox *box = bestResult.matchBoundingBox;
float w = self._preview.frame.size.width;
float h = self._preview.frame.size.height;
CGPoint tr = CGPointMake(w * box.topRightX , h * box.topRightY);
CGPoint tl = CGPointMake(w * box.topLeftX, h * box.topLeftY);
CGPoint br = CGPointMake(w * box.bottomRightX, h * box.bottomRightY);
CGPoint bl = CGPointMake(w * box.bottomLeftX, h * box.bottomLeftY);
The x position looks like it is pretty close, but the y position is completely off and looks like mirrored.
I am testing on iOS 10 iPhone 6s
Am I missing something?
The issue was that I was using the preview frame to make the translation to the points in screen. But the points that come through with bounding box are not relative to the preview view, they are relative to the VideoFrame (as the support people of catchoom.com pointed out). The VideoFrame size is set by the capturePreset which only accepts two values AVCaptureSessionPreset1280x720 and AVCaptureSessionPreset640x480. The default one is AVCaptureSessionPreset1280x720
So in my case I had to make the calculations with size 1280x720 and then make the conversion from those coordinates to the coordinates in my preview view size.
So it ended up looking like this:
let box = bestResult.matchBoundingBox
let wVideoFrame:CGFloat = 1080.0;
let hVideoFrame:CGFloat = 720.0;
let wRelativePreview = wVideoFrame/CGFloat(preview.frame.size.height)
let hRelativePreview = wVideoFrame/CGFloat(preview.frame.size.width)
var tl = CGPoint(x: wVideoFrame * CGFloat(box.topLeftX),y: hVideoFrame * CGFloat(box.topLeftY));
var tr = CGPoint(x: wVideoFrame * CGFloat(box.topRightX) ,y: hVideoFrame * CGFloat(box.topRightY));
var br = CGPoint(x: wVideoFrame * CGFloat(box.bottomRightX),y: hVideoFrame * CGFloat(box.bottomRightY));
var bl = CGPoint(x: wVideoFrame * CGFloat(box.bottomLeftX),y: hVideoFrame * CGFloat(box.bottomLeftY));
tl = CGPoint(x: tl.x/wRelativePreview, y: tl.y/hRelativePreview)
tr = CGPoint(x: tr.x/wRelativePreview, y: tr.y/hRelativePreview)
br = CGPoint(x: br.x/wRelativePreview, y: br.y/hRelativePreview)
bl = CGPoint(x: bl.x/wRelativePreview, y: bl.y/hRelativePreview)
// 4 square visualize top-left, top.right, bottom-left and bottom-right points
var fr = vTL.frame;
fr.origin = tl;
vTL.frame = fr;
fr.origin = tr;
vTR.frame = fr;
fr.origin = br;
vBR.frame = fr;
fr.origin = bl;
vBL.frame = fr;
Now the points looked quite ok on screen, but they looked some how rotated. So I rotated the view 90 degrees:
// overlay is the container of the 3 squares to visualize the points in screen
overlay.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI/2.0))
Note this is not the official response from support from catchoom, this might not be 100% correct, but it worked for me quite well.

Undestanding UIBezierPath curving mechanism, controlPoint and the curve point

I'm trying to draw a simple Parabola shape using UIBezierPath. I have a maxPoint and a boundingRect of which I'm basing the width and stretch of the parabola.
Here's the function I made to draw the parabola (I draw the parabola in a container view, rect will be container.bounds):
func addParabolaWithMax(maxPoint: CGPoint, inRect boundingRect: CGRect) {
let path = UIBezierPath()
let p1 = CGPointMake(1, CGRectGetMaxY(boundingRect)-1)
let p3 = CGPointMake(CGRectGetMaxX(boundingRect)-1, CGRectGetMaxY(boundingRect)-1)
path.moveToPoint(p1)
path.addQuadCurveToPoint(p3, controlPoint: maxPoint)
// Drawing code
...
}
My problem is, that I want the maxPoint that I send in the function to be the actual extreme point in the parabola itself. So for example, if I send in (CGRectGetMidX(container.bounds), 0), The maximum point should be at the top-most center. But in using this function with this particular point, this is what the result looks like:
So what exactly the path does here? Or in other words, how can I get from the controlPoint to the actual max point that I need? I've tried adding and subtracting different values from the y value, based on the height of the boundingRect, but I couldn't quite find the right combination, as in different points with different y values it behaves differently. There seem to be some kind of multiplier being added in, how can I solve it?
For may applications adam.wulf's solution is fine, but it doesn't actually create a parabola. To create a parabola, we need to compute the control point given the midpoint of the quadratic curve. Bézier paths are just math; we can compute this quite easily. We just need to invert the Bézier function and solve it for t=0.5.
The Bézier solution at 0.5 (the midpoint) is derived nicely at Draw a quadratic Bézier curve through three given points.
2*Pc - P0/2 - P2/2
Where Pc is the point we want to go through and P0 and P2 are the end points.
(Computing the Bézier at other points is not very intuitive. The value at t=0.25 is not "a quarter of the way along the path." But luckily for our purposes, t=0.5 matches quite nicely to our intuition of "the midpoint" on a quadratic.)
Given our solution, we can write our code. Forgive the translation to Swift 3; my copy of Xcode 7.3 isn't very happy with iOS playgrounds, but it should be easy to convert to 2.2.
func addParabolaWithMax(maxPoint: CGPoint, inRect boundingRect: CGRect) -> UIBezierPath {
func halfPoint1D(p0: CGFloat, p2: CGFloat, control: CGFloat) -> CGFloat {
return 2 * control - p0 / 2 - p2 / 2
}
let path = UIBezierPath()
let p0 = CGPoint(x: 0, y: boundingRect.maxY)
let p2 = CGPoint(x: boundingRect.maxX, y: boundingRect.maxY)
let p1 = CGPoint(x: halfPoint1D(p0: p0.x, p2: p2.x, control: maxPoint.x),
y: halfPoint1D(p0: p0.y, p2: p2.y, control: maxPoint.y))
path.move(to: p0)
path.addQuadCurve(to: p2, controlPoint: p1)
return path
}
The halfPoint1D function is the the one-dimensional implementation of our solution. For our two-dimentional CGPoint, we just have to call it twice.
If I could recommend just one resource for understanding Bézier curves, it would probably be the "Constructing Bézier curves" section from Wikipedia. Studying the little animations that show how the curves come about I find very enlightening. The "Specific Cases" section is useful as well. For a deep exploration of the topic (and one that I recommend all developers have a passing familiarity with), I like A Primer on Bézier Curves. It's ok to skim it and just read the parts that interest you at the moment. But a basic understanding of this group of functions will go a long way to removing the magic from drawing in Core Graphics and make UIBezierPath a tool rather than a black box.
let path = UIBezierPath()
let p1 = CGPointMake(0,self.view.frame.height/2)
let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2)
path.moveToPoint(p1)
path.addQuadCurveToPoint(p3, controlPoint: CGPoint(x: self.view.frame.width/2, y: -self.view.frame.height/2))
let line = CAShapeLayer()
line.path = path.CGPath;
line.strokeColor = UIColor.blackColor().CGColor
line.fillColor = UIColor.redColor().CGColor
view.layer.addSublayer(line)
this is the reason: https://cdn.tutsplus.com/mobile/authors/legacy/Akiel%20Khan/2012/10/15/bezier.png you should have to consider the tangent concept
The trick is to split the curve into two pieces so that you can control which points the curve passes through. As mentioned in Eduardo's answer, control points handle tangent, and end points are on the curve. This lets you have a curve from the bottom left to top center, then from top center to bottom right:
let p1 = CGPointMake(0,self.view.frame.height/2)
let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2)
let ctrlRight = CGPointMake(self.view.frame.width,0)
let ctrlLeft = CGPointZero
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(p1)
bezierPath.addCurveToPoint(maxPoint, controlPoint1: p1, controlPoint2: ctrlLeft)
bezierPath.addCurveToPoint(p3, controlPoint1: ctrlRight, controlPoint2: p3)
UIColor.blackColor().setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
I needed to do something similar where I wanted to have a UIBezierPath that exactly matched a specific parabola definition. So I made this little class that creates a parabola based on the focus and directrix or the a, b, c of the general equation. I threw in a convenience init which can use your boundingRect and maxPoint concepts. Either adapt those or the init where the upper corners of the box are its 1 and 2 and the middle of the bottom edge is the vertex.
Use the xform to scale and translate as needed. You can create/draw the path based on any two points on the parabola. They don't have to have the same y-value. The resulting shape will still exactly match the specified parabola.
This is not completely general in terms of rotation but it's a start.
class Parabola
{
var focus: CGPoint
var directrix: CGFloat
var a, b, c: CGFloat
init(_ f: CGPoint, _ y: CGFloat)
{
focus = f
directrix = y
let dy = f.y - y
a = 1 / (2*dy)
b = -f.x / dy
c = (f.x*f.x + f.y*f.y - y*y) / (2*dy)
}
init(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat)
{
self.a = a
self.b = b
self.c = c
focus = CGPoint(x: -b / (2*a), y: (4*a*c - b*b + 1) / (4*a))
directrix = (4*a*c - b*b - 1) / (4*a)
}
convenience init(_ v: CGPoint,
_ pt1: CGPoint,
_ pt2: CGPoint)
{
let a = (pt2.y - v.y) / (pt2.x - v.x) / (pt2.x - v.x)
self.init(CGPoint(x: v.x, y: v.y + 1/(4*a)),
v.y - 1/(4*a))
}
func f(of x: CGFloat) -> CGFloat
{
a*x*x + b*x + c
}
func path(_ x1: CGFloat, _ x2: CGFloat,
_ xform: CGAffineTransform? = .identity) -> UIBezierPath
{
let pt1 = CGPoint(x1, f(of: x1))
let pt2 = CGPoint(x2, f(of: x2))
let x = (x1 + x2) / 2
let y = (2*a * x1 + b) * (x - x1) + pt1.y
let path = UIBezierPath()
path.move(to: pt1)
path.addQuadCurve(to: pt2, controlPoint: CGPoint(x: x, y: y))
path.apply(xform!)
return path
}
}

Calculate vertices for n sided regular polygon

I have tried to follow this answer
It works fine for creating the polygons, however I can see that it doesn't reach the edges of the containing rectangle.
The following gif shows what I mean. Especially for the 5 sided polygon it is clear that it doesn't "span" the rectangle which I would like it to do
This is the code I use for creating the vertices
func verticesForEdges(_edges: Int) -> [CGPoint] {
let offset = 1.0
var vertices: [CGPoint] = []
for i in 0..._edges {
let angle = M_PI + 2.0 * M_PI * Double(i) / Double(edges)
var x = (frame.width / 2.0) * CGFloat(sin(angle)) + (bounds.width / 2.0)
var y = (frame.height / 2.0) * CGFloat(cos(angle)) + (bounds.height / 2.0)
vertices.append(CGPoint(x: x, y: y))
}
return vertices
}
And this is the code that uses the the vertices
override func layoutSublayers() {
super.layoutSublayers()
var polygonPath = UIBezierPath()
let vertices = verticesForEdges(edges)
polygonPath.moveToPoint(vertices[0])
for v in vertices {
polygonPath.addLineToPoint(v)
}
polygonPath.closePath()
self.path = polygonPath.CGPath
}
So the question is. How do I make the the polygons fill out the rectangle
Update:
The rectangle is not necessarily a square. It can have a different height from its width. From the comments it seems that I am fitting the polygon in a circle, but what is intentioned is to fit it in a rectangle.
If the first (i=0) vertice is fixed at the middle of top rectangle edge, we can calculate minimal width and height of bounding rectangle:
The rightmost vertice index
ir = (N + 2) / 4 // N/4, rounded to the closest integer, not applicable to triangle
MinWidth = 2 * R * Sin(ir * 2 * Pi / N)
The bottom vertice index
ib = (N + 1) / 2 // N/2, rounded to the closest integer
MinHeight = R * (1 + Abs(Cos(ib * 2 * Pi / N)))
So for given rectangle dimensions we can calculate R parameter to inscribe polygon properly

Resources