How to discover if 2 CGPath roteted rects intersect? - ios

I am building 2 rects when view controller is loaded, using CGPath. The rects can be moved with PanGestureRecognizer. The question is how can I know when 2 rects had met? In order to not let them intersect?
MainViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
for (int i=0; i< 2; i++) {
//create rects of CGPath
CGRectCustomView* randomColorRect =
[[CGRectCustomView alloc]initWithFrame:
CGRectMake(<random place on screen>)];
//random angle
randomColorRect.transform =
CGAffineTransformMakeRotation
(DegreesToRadians([Shared randomIntBetween:0 and:360]));
[self.view addSubview:randomColorRect];
}
}
- (BOOL)areRectsCollide {
???How to find this???
}
CGRectCustomView.m:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 8.0);
CGContextStrokePath(context); // do actual stroking
CGContextSetRGBFillColor(context, <green color>, 1);
CGContextFillRect(context, CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height));
path = CGContextCopyPath(context);
}
In Apple guide here, there is a function that determines if a path contains point
- (BOOL)containsPoint:(CGPoint)point onPath:(UIBezierPath *)path inFillArea:(BOOL)inFil,
but I have a rectangle which is endless number of points. So what should I do? Breaking my head...

Found it!
I used algorithm described here.
Just one thing: I move the rects around on the screen. So in order for them not to stack after the first collision, I save the last uncollisioned point and if collision accusers, I restore last location.
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
BOOL rectsColide = NO;
for (RandomColorRect* buttonRectInStoredRects in arreyOfPaths) {
if (buttonRectInStoredRects.tag != recognizer.view.tag) {
if ([self view:buttonRectInStoredRects intersectsWith:recognizer.view]) {
rectsColide = YES;
}
}
}
CGPoint translation = [recognizer translationInView:self.view];
if (!rectsColide) {
lastPoint = recognizer.view.center;
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
}else{
recognizer.view.center = CGPointMake(lastPoint.x ,lastPoint.y);
}
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
- (void)projectionOfPolygon:(CGPoint *)poly count:(int)count onto:(CGPoint)perp min:(CGFloat *)minp max:(CGFloat *)maxp
{
CGFloat minproj = MAXFLOAT;
CGFloat maxproj = -MAXFLOAT;
for (int j = 0; j < count; j++) {
CGFloat proj = poly[j].x * perp.x + poly[j].y * perp.y;
if (proj > maxproj)
maxproj = proj;
if (proj < minproj)
minproj = proj;
}
*minp = minproj;
*maxp = maxproj;
}
-(BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2
{
for (int i = 0; i < count1; i++) {
// Perpendicular vector for one edge of poly1:
CGPoint p1 = poly1[i];
CGPoint p2 = poly1[(i+1) % count1];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);
// Projection intervals of poly1, poly2 onto perpendicular vector:
CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];
// If projections do not overlap then we have a "separating axis"
// which means that the polygons do not intersect:
if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}
// And now the other way around with edges from poly2:
for (int i = 0; i < count2; i++) {
CGPoint p1 = poly2[i];
CGPoint p2 = poly2[(i+1) % count2];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);
CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];
if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}
// No separating axis found, then the polygons must intersect:
return YES;
}
- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2
{
CGPoint poly1[4];
CGRect bounds1 = view1.bounds;
poly1[0] = [view1 convertPoint:bounds1.origin toView:nil];
poly1[1] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y) toView:nil];
poly1[2] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y + bounds1.size.height) toView:nil];
poly1[3] = [view1 convertPoint:CGPointMake(bounds1.origin.x, bounds1.origin.y + bounds1.size.height) toView:nil];
CGPoint poly2[4];
CGRect bounds2 = view2.bounds;
poly2[0] = [view2 convertPoint:bounds2.origin toView:nil];
poly2[1] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y) toView:nil];
poly2[2] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y + bounds2.size.height) toView:nil];
poly2[3] = [view2 convertPoint:CGPointMake(bounds2.origin.x, bounds2.origin.y + bounds2.size.height) toView:nil];
return [self convexPolygon:poly1 count:4 intersectsWith:poly2 count:4];
}

If I understand your question, so in order to check if 2 CGRects meet you better use:
/* Return the intersection of `r1' and `r2'. This may return a null rect. */
CG_EXTERN CGRect CGRectIntersection(CGRect r1, CGRect r2)
For example:
CGRect rect1 = CGRectMake(0, 0, 10, 10);
CGRect rect2 = CGRectMake(5, 5, 10, 10);
CGRect rect3 = CGRectMake(20, 20, 10, 10);
CGRect r1 = CGRectIntersection(rect1, rect2); // Returns a CGRect with (0,0,5,5)
CGRect r2 = CGRectIntersection(rect1, rect3); // Returns a CGRect with (Inf,Inf,0,0)
if (CGSizeEqualToSize(r2.size,CGSizeZero)) {
// rect1 rect3 Intersects at CGSizeZero == Do not Intersect
}

Related

How to redraw on erased image iOS? e.g. I have one erased image, would like to get redraw erased image when move on context

To Erase I'm using kCGBlendModeClear.
e.g. I have one erased image, would like to get redraw on erased image when move on CGContext.
Image : https://www.dropbox.com/s/hsltjqa3ue2dpx8/Simulator%20Screen%20Shot%20-%20iPhone%208%20-%202020-05-09%20at%2019.56.15.png?dl=0
My erase code :
-(void)initialize {
self.userInteractionEnabled = YES;
_tilesFilled = 0;
dragVw = [[UIView alloc] init];
dragVw.backgroundColor = [UIColor redColor];
[dragVw removeFromSuperview];
[self addSubview:dragVw];
if (nil == self.image) {
_tilesX = _tilesY = 0;
self.maskedMatrix = nil;
if (NULL != _imageContext) {
CGContextRelease(_imageContext);
_imageContext = NULL;
}
if (NULL != _colorSpace) {
CGColorSpaceRelease(_colorSpace);
_colorSpace = NULL;
}
} else {
self.touchPoints = [NSMutableArray array];
// CGSize size = self.image.size;
// CGSize fromImage = [self aspectScaledImageSizeForImageView:self image:self.image];
CGSize size = /*CGSizeMake(fromImage.width * pinchScale, fromImage.height * pinchScale);*/ CGSizeMake(self.image.size.width * self.image.scale, self.image.size.height * self.image.scale);
// initalize bitmap context
if (NULL == _colorSpace) {
_colorSpace = CGColorSpaceCreateDeviceRGB();
}
if (NULL != _imageContext) {
CGContextRelease(_imageContext);
}
_imageContext = CGBitmapContextCreate(0, size.width, size.height, 8, size.width * 4 , _colorSpace, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(_imageContext, CGRectMake(0, 0, size.width, size.height), self.image.CGImage);
int blendMode = kCGBlendModeClear;
CGContextSetBlendMode(_imageContext, (CGBlendMode) blendMode);
_tilesX = size.width / (2 * __radius);
_tilesY = size.height / (2 * __radius);
offset = 0;
}
-(void)moveTouches:(NSSet*)touches {
// CGSize fromImage = [self aspectScaledImageSizeForImageView:self image:self.image];
CGSize size = /*CGSizeMake(fromImage.width, fromImage.height);*/ CGSizeMake(self.image.size.width * self.image.scale, self.image.size.height * self.image.scale);
CGContextRef ctx = _imageContext;
CGContextSetFillColorWithColor(ctx,[UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(ctx,[UIColor colorWithRed:0 green:0 blue:0 alpha:0].CGColor);
// int tempFilled = _tilesFilled;
// process touches
for (UITouch *touch in touches) {
CGContextBeginPath(ctx);
CGPoint touchPoint = CGPointMake([touch locationInView:self].x , [touch locationInView:self].y);
touchPoint = fromUItoQuartz(touchPoint, self.bounds.size);
touchPoint = scalePoint(touchPoint, self.bounds.size, size);
CGPoint dragPoint = [touch locationInView:self];
CGFloat fineRadius = __radius;
NSLog(#"__radius : %zu",__radius);
dispatch_async(dispatch_get_main_queue(), ^{
self->dragVw.frame = CGRectMake(dragPoint.x - fineRadius/2 , dragPoint.y - fineRadius/2, fineRadius, fineRadius);
self->dragVw.layer.cornerRadius = self->dragVw.frame.size.height / 2;
self->dragVw.alpha = 0;
// NSLog(#"DragVw frame : %#",NSStringFromCGRect(self->dragVw.frame));
});
if(UITouchPhaseBegan == touch.phase){
[self.touchPoints removeAllObjects];
[self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]];
[self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]];
// on begin, we just draw ellipse
CGRect rect = CGRectMake(touchPoint.x - __radius, touchPoint.y - __radius, __radius*2, __radius*2);
CGContextAddEllipseInRect(ctx, rect);
CGContextFillPath(ctx);
static const FillTileWithPointFunc fillTileFunc = (FillTileWithPointFunc) [self methodForSelector:#selector(fillTileWithPoint:)];
(*fillTileFunc)(self,#selector(fillTileWithPoint:),rect.origin);
} else if (UITouchPhaseMoved == touch.phase) {
[self.touchPoints addObject:[NSValue valueWithCGPoint:touchPoint]];
// then touch moved, we draw superior-width line
CGContextSetStrokeColor(ctx, CGColorGetComponents([UIColor clearColor].CGColor));
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetLineWidth(ctx, 2 * __radius);
while(self.touchPoints.count > 3){
CGPoint bezier[4];
bezier[0] = ((NSValue*)self.touchPoints[1]).CGPointValue;
bezier[3] = ((NSValue*)self.touchPoints[2]).CGPointValue;
CGFloat k = 0.3;
CGFloat len = sqrt(pow(bezier[3].x - bezier[0].x, 2) + pow(bezier[3].y - bezier[0].y, 2));
bezier[1] = ((NSValue*)self.touchPoints[0]).CGPointValue;
bezier[1] = [self normalizeVector:CGPointMake(bezier[0].x - bezier[1].x - (bezier[0].x - bezier[3].x), bezier[0].y - bezier[1].y - (bezier[0].y - bezier[3].y) )];
bezier[1].x *= len * k;
bezier[1].y *= len * k;
bezier[1].x += bezier[0].x;
bezier[1].y += bezier[0].y;
bezier[2] = ((NSValue*)self.touchPoints[3]).CGPointValue;
bezier[2] = [self normalizeVector:CGPointMake( (bezier[3].x - bezier[2].x) - (bezier[3].x - bezier[0].x), (bezier[3].y - bezier[2].y) - (bezier[3].y - bezier[0].y) )];
bezier[2].x *= len * k;
bezier[2].y *= len * k;
bezier[2].x += bezier[3].x;
bezier[2].y += bezier[3].y;
CGContextMoveToPoint(ctx, bezier[0].x , bezier[0].y + offset );
CGContextAddCurveToPoint(ctx, bezier[1].x , bezier[1].y + offset, bezier[2].x , bezier[2].y + offset, bezier[3].x , bezier[3].y + offset);
[self.touchPoints removeObjectAtIndex:0];
}
CGContextStrokePath(ctx);
CGPoint prevPoint = [touch previousLocationInView:self];
prevPoint = fromUItoQuartz(prevPoint, self.bounds.size);
prevPoint = scalePoint(prevPoint, self.bounds.size, size);
static const FillTileWithTwoPointsFunc fillTileFunc = (FillTileWithTwoPointsFunc) [self methodForSelector:#selector(fillTileWithTwoPoints:end:)];
(*fillTileFunc)(self,#selector(fillTileWithTwoPoints:end:),touchPoint, prevPoint);
}
}
}

iOS: Resize and Rotate UIView Concurrently

Using a UIPanGestureRecognizer in my view controller, I'm trying to draw a view (ArrowView) at an angle based upon the touch location. I'm trying to use CGAffineTransformRotate to rotate the view based up the angle between the first touch and the current touch, but this isn't working unless the view has already been drawn by at lease 20 or more pixels. Also, when drawing, the view doesn't always line up under my finger. Is this the correct approach for this situation? If not, does anyone recommend a better way of accomplishing this? If so, what am I doing wrong?
ViewController.m
#implementation ViewController {
ArrowView *_selectedArrowView;
UIColor *_selectedColor;
CGFloat _selectedWeight;
CGPoint _startPoint;
}
- (void)viewDidLoad {
[super viewDidLoad];
_selectedColor = [UIColor yellowColor];
_selectedWeight = 3;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panHandler:)];
[self.view addGestureRecognizer:pan];
}
- (void) panHandler: (UIPanGestureRecognizer *) sender {
if (sender.state == UIGestureRecognizerStateBegan) {
//Instantiate the arrow
CGPoint touchPoint = [sender locationInView:sender.view];
_startPoint = touchPoint;
_selectedArrowView = [[ArrowView alloc] initWithFrame:CGRectMake(touchPoint.x, touchPoint.y, 0, 25) withColor:_selectedColor withWeight:_selectedWeight];
_selectedArrowView.delegate = self;
[self.view addSubview:_selectedArrowView];
[self.view bringSubviewToFront:_selectedArrowView];
} else if (sender.state == UIGestureRecognizerStateChanged) {
//"Draw" the arrow based upon finger postion
CGPoint touchPoint = [sender locationInView:sender.view];
[_selectedArrowView drawArrow:_startPoint to:touchPoint];
}
}
#end
ArrowView.m
- (void) drawArrow: (CGPoint) startPoint to: (CGPoint) endPoint {
startPoint = [self convertPoint:startPoint fromView:self.superview];
endPoint = [self convertPoint:endPoint fromView:self.superview];
if (_initialAngle == -1000 /*Initially set to an arbitrary value so I know when the draw began*/) {
_initialAngle = atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
[self setPosition:0];
} else {
CGFloat ang = atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
ang -= _initialAngle;
self.transform = CGAffineTransformRotate(self.transform, ang);
CGFloat diff = (endPoint.x - self.bounds.size.width);
NSLog(#"\n\n diff: %f \n\n", diff);
self.bounds = CGRectMake(0, 0, self.bounds.size.width + diff, self.bounds.size.height);
_endPoint = CGPointMake(self.bounds.size.width, self.bounds.size.height);
[self setNeedsDisplay];
}
}
- (void) setPosition: (CGFloat) anchorPointX {
CGPoint layerLoc;
if (anchorPointX == 0) {
layerLoc = CGPointMake(self.layer.bounds.origin.x, self.layer.bounds.origin.y + (self.layer.bounds.size.height / 2));
} else {
layerLoc = CGPointMake(self.layer.bounds.origin.x + self.layer.bounds.size.width, self.layer.bounds.origin.y + (self.layer.bounds.size.height / 2));
}
CGPoint superLoc = [self convertPoint:layerLoc toView:self.superview];
self.layer.anchorPoint = CGPointMake(anchorPointX, 0.5);
self.layer.position = superLoc;
}
- (CGFloat) pToA: (CGPoint) touchPoint {
CGPoint start;
if (_dotButtonIndex == kDotButtonFirst) {
start = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
} else {
start = CGPointMake(CGRectGetMinX(self.bounds), CGRectGetMinY(self.bounds));
}
return atan2(start.y - touchPoint.y, start.x - touchPoint.x);
}
Link to project on GitHub: Project Link
Figured it out.
I had to make it have an initial width so the angle would work.
_initialAngle = atan2(startPoint.y - endPoint.y, startPoint.x - (endPoint.x + self.frame.size.width));

Wrong touch point location in UIView after rotatation

I'm trying to make a custom button using a UIView and when touched on a specific part of the view perform an action, for example when touching the left part of the view log LEFT and when touching the Right part of the view log RIGHT.
I'm using touchesBegan and touchesEnded to detect touch.
Everything works fine when in portrait mode, however, once rotated to landscape mode it doesn't work, the view is displayed in the right place and the touch on the view is detected but it seems the touch point is not in the correct x,y coordinates so I can't detect the LEFT/RIGHT touches correctly.
Couldn't find any previous answers here that helped me.
Tried looking into CGAffineTransform, but still can't fix it.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_default.png"]];
self.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
topLeft = CGPointMake(self.frame.origin.x, self.frame.origin.y);
topRight = CGPointMake(self.frame.origin.x + 105, self.frame.origin.y);
bottomLeft = CGPointMake(self.frame.origin.x, self.frame.origin.y + 105);
bottomRight = CGPointMake(self.frame.origin.x + 105, self.frame.origin.y + 105);
NSLog(#"topLeft: x = %f, y = %f", topLeft.x, topLeft.y);
NSLog(#"topRight: x = %f, y = %f", topRight.x, topRight.y);
NSLog(#"bottomLeft: x = %f, y = %f", bottomLeft.x, bottomLeft.y);
NSLog(#"bottomRight: x = %f, y = %f", bottomRight.x, bottomRight.y);
NSLog(#"self.center: x = %f, y = %f", self.center.x, self.center.y);
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"topLeft: x = %f, y = %f", topLeft.x, topLeft.y);
NSLog(#"topRight: x = %f, y = %f", topRight.x, topRight.y);
NSLog(#"bottomLeft: x = %f, y = %f", bottomLeft.x, bottomLeft.y);
NSLog(#"bottomRight: x = %f, y = %f", bottomRight.x, bottomRight.y);
NSLog(#"self.center: x = %f, y = %f", self.center.x, self.center.y);
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:nil];
NSLog(#"touchesBegan: touchPoint: x = %f, y = %f", touchPoint.x, touchPoint.y);
if ([self isPoint:touchPoint inTriangle:topLeft :self.center :topRight]) {
NSLog(#"touchesBegan: UP");
direction = 1;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_up.png"]];
} else if ([self isPoint:touchPoint inTriangle:bottomLeft :self.center :bottomRight]) {
NSLog(#"touchesBegan: DOWN");
direction = 2;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_down.png"]];
} else if ([self isPoint:touchPoint inTriangle:topLeft :self.center :bottomLeft]) {
NSLog(#"touchesBegan: LEFT");
direction = 3;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_left.png"]];
} else if ([self isPoint:touchPoint inTriangle:topRight :self.center :bottomRight]) {
NSLog(#"touchesBegan: RIGHT");
direction = 4;
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_pressed_right.png"]];
} else {
NSLog(#"touchesBegan: NOTHING");
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"round_directions_button_default.png"]];
}
- (BOOL) isPoint:(CGPoint)point inTriangle:(CGPoint)v1 :(CGPoint)v2 :(CGPoint)v3 {
BOOL b1, b2, b3;
b1 = ((point.x - v2.x) * (v1.y - v2.y) - (v1.x - v2.x) * (point.y - v2.y)) < 0.0f;
b2 = ((point.x - v3.x) * (v2.y - v3.y) - (v2.x - v3.x) * (point.y - v3.y)) < 0.0f;
b3 = ((point.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (point.y - v1.y)) < 0.0f;
return ((b1 == b2) && (b2 == b3));
}
in view controller viewDidLoad:
CustomButton *customButton = [[CustomButton alloc] initWithFrame:CGRectMake(1, self.view.bounds.size.height - 106, 105, 105)];
[self.view addSubview:customButton];
Any help will be appreciated.
Thanks
If the size of the button doesn't change with rotation you can set points just once inside initWithFrame and use bounds instead of frame.
topLeft = CGPointMake(0, 0);
topRight = CGPointMake(self.bounds.size.width, 0);
bottomLeft = CGPointMake(0, self.bounds.size.height);
bottomRight = CGPointMake(self.bounds.size.width, self.bounds.size.height);
Then in touchesBegan use [touch locationInView:self]; to get the touch location in the button own coordinate system.
If the size changes, a solution can be to recalculate points on every touch so place the above code inside touchesBegan.
EDIT:
If you want to use local button coordinate you have to calculate the center because self.center contains center point of the view in the superview’s coordinate system.

How can i draw Two line with some angle between them, later can change angle by dragging any line

I want to make UBersense like app (http://blog.ubersense.com/2013/01/03/how-to-use-the-drawing-tools-in-ubersense/), there i need to draw two line with some angle, after that i can adjust the angle between two line by dragging any line or intersection point.
can you guys please provide me some idea or code snippet.
screenshots url:
https://picasaweb.google.com/yunusm7/AppScreenshots#slideshow/5952787957718627714
Thanks in advance.
You have a construction with three points, one point is an angle point, and two others are just vertices. First of all you should create a new class like this:
#interface MyAngle : NSObject {
}
#property (nonatomic) CGPoint p1;
#property (nonatomic) CGPoint p2;
#property (nonatomic) CGPoint v; // this is an angle point
#end
You can use the default implementation of this without any tricks with such sample init:
- (id)init {
if (self = [super init]) {
p1 = CGPointMake(1,0);
p2 = CGPointMake(0,1);
v = CGPointZero;
}
return self;
}
But also as I understood you need to know the value of the angle. You can do this using the following way:
- (CGFloat)valueOfAngle {
CGPoint v1 = CGPointMake(p1.x-v.x, p1.y-v.y);
CGPoint v2 = CGPointMake(p2.x-v.x, p2.y-v.y);
CGFloat scalarProduct = v1.x*v2.x + v1.y*v2.y;
CGFloat lengthProduct = sqrt(v1.x*v1.x + v1.y*v1.y)*sqrt(v2.x*v2.x + v2.y*v2.y);
CGFloat fraction = scalarProduct / lengthProduct;
if (fraction < -1) fraction = -1;
if (fraction > 1) fraction = 1;
return acos(fraction);
}
If you want to obtain angles more than 180 degrees you should change the code above a little. But there are too much information about how to do this in the Internet, so I will skip this part.
Then you need to create an instance of MyAngle in your viewController. Let it be called "angle". Knowing coordinates of every three points if enough do draw it (!!!). Implement drawRect method in a view that will contain the MyAngle instance (I strongly recommend do to this on your own subclass of UIView):
- (void)drawRect {
[super drawRect];
// set options of drawing
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
CGContextSetLineWidth(c, 3.0);
CGContextSetStrokeColor(c, red);
// draw an angle directly
CGContextBeginPath(c);
CGContextMoveToPoint(c, angle.p1.x, angle.p1.y);
CGContextAddLineToPoint(c, angle.v.x, angle.v.y);
CGContextAddLineToPoint(c, angle.p2.x, angle.p2.y);
CGContextStrokePath(c);
// draw circles around vertices (like on the screenshot you provided)
CGFloat R = 7.0f;
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.p1.x - R, angle.p1.y - R, 2*R, 2*R));
CGContextStrokePath(c);
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.p2.x - R, angle.p2.y - R, 2*R, 2*R));
CGContextStrokePath(c);
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.v.x - R, angle.v.y - R, 2*R, 2*R));
CGContextStrokePath(c);
}
And that's all you need to know for drawing what you want! You can change the stroke color or radius of three circles if you want.
Then you need to have a possibility to change the locations of your angle's points. For this you can just implement panGestureRecognizer in your viewController's viewDidLoad method like this:
UIPanGestureRecognizer *pan = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveAngle:)] autorelease];
pan.delegate = self;
[self.view addGestureRecognizer:pan];
Implement UIGestureRecognizerDelegate method:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint p = [gestureRecognizer locationInView:self.view];
CGFloat d1 = sqrt((p.x-angle.p1.x)*(p.x-angle.p1.x) + (p.y-angle.p1.y)*(p.y-angle.p1.y);
CGFloat d2 = sqrt((p.x-angle.p2.x)*(p.x-angle.p2.x) + (p.y-angle.p2.y)*(p.y-angle.p2.y);
CGFloat d3 = sqrt((p.x-angle.v.x)*(p.x-angle.v.x) + (p.y-angle.v.y)*(p.y-angle.v.y);
// just check if we touched the screen near some of angle's points
CGFloat tolerance = 15.0f;
return (d1 < tolerance) || (d2 < tolerance) || (d3 < tolerance);
}
return YES;
}
and tagret's selector (also in your viewController):
- (void)moveAngle:(UIPanGestureRecognizer *)gr {
CGPoint p = [gr locationInView:self.view];
if (gr.state == UIGestureRecognizerStateBegan) {
CGFloat d1 = sqrt((p.x-angle.p1.x)*(p.x-angle.p1.x) + (p.y-angle.p1.y)*(p.y-angle.p1.y);
CGFloat d2 = sqrt((p.x-angle.p2.x)*(p.x-angle.p2.x) + (p.y-angle.p2.y)*(p.y-angle.p2.y);
CGFloat d3 = sqrt((p.x-angle.v.x)*(p.x-angle.v.x) + (p.y-angle.v.y)*(p.y-angle.v.y);
// pointToMove is your int variable
CGFloat tolerance = 15.0f;
if (d1 < tolerance) {
pointToMove = 1;
}
else if (d2 < tolerance) {
pointToMove = 2;
}
else {
pointToMove = 3;
}
}
else {
if (pointToMove == 1) {
angle.p1 = loc;
}
else if (pointToMove == 2) {
angle.p2 = loc;
}
else {
angle.v = loc;
}
[yourCustomView setNeedsDisplay];
[yourLabel setText:[NSString stringWithFormat:#"%.3f", [angle valueOfangle]*180/PI]];
}
}
Maybe I skip some evident things, but I think it should be enough for you to begin writing some code.

Objective-C check if subviews of rotated UIViews intersect?

I don't know where to start with this one. Obviously CGRectIntersectsRect will not work in this case, and you'll see why.
I have a subclass of UIView that has a UIImageView inside it that is placed in the exact center of the UIView:
I then rotate the custom UIView to maintain the frame of the inner UIImageView while still being able to perform a CGAffineRotation. The resulting frame looks something like this:
I need to prevent users from making these UIImageViews intersect, but I have no idea how to check intersection between the two UIImageViews, since not only do their frames not apply to the parent UIView, but also, they are rotated without it affecting their frames.
The only results from my attempts have been unsuccessful.
Any ideas?
The following algorithm can be used to check if two (rotated or otherwise transformed) views overlap:
Use [view convertPoint:point toView:nil] to convert the 4 boundary points of both views
to a common coordinate system (the window coordinates).
The converted points form two convex quadrilaterals.
Use the SAT (Separating Axis Theorem) to check if the quadrilaterals intersect.
This: http://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf is another description of the algorithm containing pseudo-code, more can be found by googling for "Separating Axis Theorem".
Update: I have tried to create a Objective-C method for the "Separating Axis Theorem", and this is what I got. Up to now, I did only a few tests, so I hope that there are not too many errors.
- (BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2;
tests if 2 convex polygons intersect. Both polygons are given as a CGPoint array of the vertices.
- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2
tests (as described above) if two arbitrary views intersect.
Implementation:
- (void)projectionOfPolygon:(CGPoint *)poly count:(int)count onto:(CGPoint)perp min:(CGFloat *)minp max:(CGFloat *)maxp
{
CGFloat minproj = MAXFLOAT;
CGFloat maxproj = -MAXFLOAT;
for (int j = 0; j < count; j++) {
CGFloat proj = poly[j].x * perp.x + poly[j].y * perp.y;
if (proj > maxproj)
maxproj = proj;
if (proj < minproj)
minproj = proj;
}
*minp = minproj;
*maxp = maxproj;
}
-(BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2
{
for (int i = 0; i < count1; i++) {
// Perpendicular vector for one edge of poly1:
CGPoint p1 = poly1[i];
CGPoint p2 = poly1[(i+1) % count1];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);
// Projection intervals of poly1, poly2 onto perpendicular vector:
CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];
// If projections do not overlap then we have a "separating axis"
// which means that the polygons do not intersect:
if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}
// And now the other way around with edges from poly2:
for (int i = 0; i < count2; i++) {
CGPoint p1 = poly2[i];
CGPoint p2 = poly2[(i+1) % count2];
CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);
CGFloat minp1, maxp1, minp2, maxp2;
[self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
[self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];
if (maxp1 < minp2 || maxp2 < minp1)
return NO;
}
// No separating axis found, then the polygons must intersect:
return YES;
}
- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2
{
CGPoint poly1[4];
CGRect bounds1 = view1.bounds;
poly1[0] = [view1 convertPoint:bounds1.origin toView:nil];
poly1[1] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y) toView:nil];
poly1[2] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y + bounds1.size.height) toView:nil];
poly1[3] = [view1 convertPoint:CGPointMake(bounds1.origin.x, bounds1.origin.y + bounds1.size.height) toView:nil];
CGPoint poly2[4];
CGRect bounds2 = view2.bounds;
poly2[0] = [view2 convertPoint:bounds2.origin toView:nil];
poly2[1] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y) toView:nil];
poly2[2] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y + bounds2.size.height) toView:nil];
poly2[3] = [view2 convertPoint:CGPointMake(bounds2.origin.x, bounds2.origin.y + bounds2.size.height) toView:nil];
return [self convexPolygon:poly1 count:4 intersectsWith:poly2 count:4];
}
Swift version. (Added this behaviour to UIView via an extension)
extension UIView {
func projection(of polygon: [CGPoint], perpendicularVector: CGPoint) -> (CGFloat, CGFloat) {
var minproj = CGFloat.greatestFiniteMagnitude
var maxproj = -CGFloat.greatestFiniteMagnitude
for j in 0..<polygon.count {
let proj = polygon[j].x * perpendicularVector.x + polygon[j].y * perpendicularVector.y
if proj > maxproj {
maxproj = proj
}
if proj < minproj {
minproj = proj
}
}
return (minproj, maxproj)
}
func convex(polygon: [CGPoint], intersectsWith polygon2: [CGPoint]) -> Bool {
//
let count1 = polygon.count
for i in 0..<count1 {
let p1 = polygon[i]
let p2 = polygon[(i+1) % count1]
let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)
let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
let minp1 = m1.0
let maxp1 = m1.1
let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
let minp2 = m2.0
let maxp2 = m2.1
if maxp1 < minp2 || maxp2 < minp1 {
return false
}
}
//
let count2 = polygon2.count
for i in 0..<count2 {
let p1 = polygon2[i]
let p2 = polygon2[(i+1) % count2]
let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)
let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
let minp1 = m1.0
let maxp1 = m1.1
let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
let minp2 = m2.0
let maxp2 = m1.0
if maxp1 < minp2 || maxp2 < minp1 {
return false
}
}
//
return true
}
func intersects(with someView: UIView) -> Bool {
//
var points1 = [CGPoint]()
let bounds1 = bounds
let p11 = convert(bounds1.origin, to: nil)
let p21 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y), to: nil)
let p31 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y + bounds1.size.height) , to: nil)
let p41 = convert(CGPoint(x: bounds1.origin.x, y: bounds1.origin.y + bounds1.size.height), to: nil)
points1.append(p11)
points1.append(p21)
points1.append(p31)
points1.append(p41)
//
var points2 = [CGPoint]()
let bounds2 = someView.bounds
let p12 = someView.convert(bounds2.origin, to: nil)
let p22 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y), to: nil)
let p32 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y + bounds2.size.height) , to: nil)
let p42 = someView.convert(CGPoint(x: bounds2.origin.x, y: bounds2.origin.y + bounds2.size.height), to: nil)
points2.append(p12)
points2.append(p22)
points2.append(p32)
points2.append(p42)
//
return convex(polygon: points1, intersectsWith: points2)
}

Resources