Intersection of CGRect and CGPath - ios

Is there an efficient way to detect if a CGPath and a CGRect intersect?
I've thought about looping through every point inside the CGRect sort of like this:
for (CGPoint point in rect) {
if (CGPathContainsPoint(path, nil, point, NO)) {
intersects = YES;
break;
}
But I wanted to know if there was a better way.
In my app there are many CGrects and one CGPath that may change its shape. I just need to check which rects are intersecting with the path, as shown in the image bellow.

Draw the path in a bitmap (white on alpha)
then check the Rectangle part of bitmap. check if there is any white in that area which would mean overlapping
-- to make it more performant draw only the part of the bitmap that is in the rectangle.
I havent tried this and it wont offer real-time performance but it sounds ok to me

The only completely accurate way that I've found of doing this is to loop through every point, something like this (I haven't tested this exact code):
for (NSValue *rectValue in rects) {
CGRect rect = rectValue.CGRectValue;
// Set the initial point values to the top left of the current rect
CGFloat x = CGRectGetMinX(rect);
CGFloat y = CGRectGetMinY(rect);
BOOL intersects = NO;
BOOL morePoints = YES;
// Loop until there are no more points to check in this rect
while (morePoints) {
CGPoint point = CGPointMake(x, y);
if (CGPathContainsPoint(path, nil, point, NO)) {
intersects = YES;
break;
}
// Adjust the x and y values to check all points
if (x < CGRectGetMaxX(rect)) {
x++;
} else if (y < CGRectGetMaxY(rect)) {
x = CGRectGetMinX(rect);
y++;
} else {
morePoints = NO;
}
}
if (intersects) {
// The path intersects the current rect
} else {
// The path does not intersect the current rect
}
}
But this is the most inefficient way.
You could optimize this solution by looping asynchronously:
[rects enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(NSValue * _Nonnull rectValue, NSUInteger idx, BOOL * _Nonnull stop) {
CGRect rect = rectValue.CGRectValue;
...
}];
I only needed to know if the path intersected the edge of a rect so I did something similar to this:
for (NSValue *rectValue in rects) {
CGRect rect = rectValue.CGRectValue;
// Set the initial point values to the top left of the current rect
CGFloat x = CGRectGetMinX(rect);
CGFloat y = CGRectGetMinY(rect);
BOOL intersects = NO;
// top edge
for (; x < CGRectGetMaxX(rect); x++) {
CGPoint point = CGPointMake(x, y);
if (CGPathContainsPoint(path, nil, point, NO)) {
intersects = YES;
break;
}
}
if (intersects) {
// The path intersects the current rect on the top edge
}
// right edge
for (; y < CGRectGetMaxY(rect); y++) {
CGPoint point = CGPointMake(x, y);
if (CGPathContainsPoint(path, nil, point, NO)) {
intersects = YES;
break;
}
}
if (intersects) {
// The path intersects the current rect on the right edge
}
// bottom edge
x = CGRectGetMinX(rect);
for (; x < CGRectGetMaxX(rect); x++) {
CGPoint point = CGPointMake(x, y);
if (CGPathContainsPoint(path, nil, point, NO)) {
intersects = YES;
break;
}
}
if (intersects) {
// The path intersects the current rect on the bottom edge
}
// left edge
x = CGRectGetMinX(rect);
y = CGRectGetMinY(rect);
for (; y < CGRectGetMaxY(rect); y++) {
CGPoint point = CGPointMake(x, y);
if (CGPathContainsPoint(path, nil, point, NO)) {
intersects = YES;
break;
}
}
if (intersects) {
// The path intersects the current rect on the left edge
}
}

Related

Getting UIImage for only particular area bounds drawn - PaintView

I have already implemented paint / draw using:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
-(void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
Now issue is that for any line drawn, I want to get that particular line / paint image. I don't want image of entire screen, only area / bounds of line / paint drawn.
Reason is that I want to perform pan gesture / delete functionality on that line / paint drawn.
User can draw multiple lines, so want UIImage for all this lines separately.
Any logic or code snippet will be really helpful
Thanks in advance
Depending on your application, particularly how many times you plan on doing this in a row, you may be able to create a different image/layer for each paint line. Your final image would essentially be all the individual lines drawn on top of each other.
It may be more efficient to create a custom view to capture touch events. You could store the list of touch coordinates for each paint line and render them all at once in a custom drawRect. This way you are storing lists of coordinates for each paint line, and can still access each one, instead of a list of images. You could calculate the area/bounds from the coordinates used to render the line.
Additional context and code may be helpful, I'm not sure I completely understand what you're trying to accomplish!
I take a look at the MVPaint project. It seems you have an object:
MVPaintDrawing _drawing;
which contains an array of MVPaintTransaction. You can iterate on those MVPaintTransaction to draw an UIImage.
So first you can add a method to get an image from a MVPaintTransaction:
- (UIImage *) imageToDrawWithSize:(CGSize) size xScale:(CGFloat)xScale yScale:(CGFloat)yScale {
UIGraphicsBeginImageContext(size);
CGContextScaleCTM(UIGraphicsGetCurrentContext(), xScale, yScale);
// call the existing draw method
[self draw];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Then add a method to get an array of image from the array of MVPaintTransaction in the MVPaintDrawing class:
- (NSArray *) getImagesFromDrawingOnSurface: (UIImageView *) surface xScale: (CGFloat) xScale yScale: (CGFloat) yScale{
NSMutableArray *imageArray = [NSMutableArray new];
for (MVPaintTransaction * transaction in _drawing) {
UIImage *image = [transaction imageToDrawWithSize:surface.frame.size xScale:xScale yScale:yScale];
[imageArray addObject:image];
}
return imageArray;
}
In this way you will have an array of UIImage corresponding to each line you have drawn. If you want those images to have the "minimum" possible size (i mean without extra alpha part), you can apply this method (I added it in the MVPaintTransaction class):
- (UIImage *)trimmedImage:(UIImage *)img {
CGImageRef inImage = img.CGImage;
CFDataRef m_DataRef;
m_DataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage));
UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);
size_t width = CGImageGetWidth(inImage);
size_t height = CGImageGetHeight(inImage);
CGPoint top,left,right,bottom;
BOOL breakOut = NO;
for (int x = 0;breakOut==NO && x < width; x++) {
for (int y = 0; y < height; y++) {
int loc = x + (y * width);
loc *= 4;
if (m_PixelBuf[loc + 3] != 0) {
left = CGPointMake(x, y);
breakOut = YES;
break;
}
}
}
breakOut = NO;
for (int y = 0;breakOut==NO && y < height; y++) {
for (int x = 0; x < width; x++) {
int loc = x + (y * width);
loc *= 4;
if (m_PixelBuf[loc + 3] != 0) {
top = CGPointMake(x, y);
breakOut = YES;
break;
}
}
}
breakOut = NO;
for (int y = height-1;breakOut==NO && y >= 0; y--) {
for (int x = width-1; x >= 0; x--) {
int loc = x + (y * width);
loc *= 4;
if (m_PixelBuf[loc + 3] != 0) {
bottom = CGPointMake(x, y);
breakOut = YES;
break;
}
}
}
breakOut = NO;
for (int x = width-1;breakOut==NO && x >= 0; x--) {
for (int y = height-1; y >= 0; y--) {
int loc = x + (y * width);
loc *= 4;
if (m_PixelBuf[loc + 3] != 0) {
right = CGPointMake(x, y);
breakOut = YES;
break;
}
}
}
CGFloat scale = img.scale;
CGRect cropRect = CGRectMake(left.x / scale, top.y/scale, (right.x - left.x)/scale, (bottom.y - top.y) / scale);
UIGraphicsBeginImageContextWithOptions( cropRect.size,
NO,
scale);
[img drawAtPoint:CGPointMake(-cropRect.origin.x, -cropRect.origin.y)
blendMode:kCGBlendModeCopy
alpha:1.];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CFRelease(m_DataRef);
return croppedImage;
}
Then simply replace in the first method:
return result;
by
return [self trimmedImage:result];

onTouch Events not being called, when sprite moves off initial part of map?

It appears no onTouch events are being called (put NSLog statements in to confirm) when my player is moved above or to the right of my map. Map is big enough, and you can still see the map slightly when I get to the top or right, as the layer shifts as intended, but when clicking any further then what's initially loaded on screen, no touch events are being fired and char is stuck. Any ideas?
- (void)setCenterOfScreen:(CGPoint) position {
CGSize screenSize = [[CCDirector sharedDirector] viewSize];
int x = MAX(position.x, screenSize.width/2);
int y = MAX(position.y, screenSize.height/2);
x = MIN(x, theMap.mapSize.width * theMap.tileSize.width - screenSize.width/2);
y = MIN(y, theMap.mapSize.height * theMap.tileSize.height - screenSize.height/2);
CGPoint goodPoint = ccp(x,y);
CGPoint centerOfScreen = ccp(screenSize.width/2, screenSize.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
self.position = difference;
}
-(void) touchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLoc = [touch locationInNode:self];
// NOT CALLED WHEN PLAYER REACHES TOP OR RIGHT OF MAP (INITIAL SCREEN LOAD PART OF MAP)
// When clicking any furhter or top on the map, no touch event is fired and character (sprite) doesn't move
NSLog(#"Touch fired");
CGPoint playerPos = mainChar.position;
CGPoint diff = ccpSub(touchLoc, playerPos);
// Move horizontal or vertical?
if (abs(diff.x) > abs(diff.y))
{
if (diff.x > 0)
{
playerPos.x += theMap.tileSize.width;
}
else
{
playerPos.x -= theMap.tileSize.width;
}
}
else
{
if (diff.y > 0)
{
playerPos.y += theMap.tileSize.height;
}
else
{
playerPos.y -= theMap.tileSize.height;
}
}
if (playerPos.x <= (theMap.mapSize.width * theMap.tileSize.width) &&
playerPos.y <= (theMap.mapSize.height * theMap.tileSize.height) &&
playerPos.y >= 0 &&
playerPos.x >= 0)
{
mainChar.position = playerPos;
}
[self setCenterOfScreen:mainChar.position];
}
It is most likely your scene's content size. I believe you are moving the scene (CCScene) with the call to setCenterOfScreen as you move the player right? If I remember correctly the content size and bounding box for the scene doesn't change as you add children to it. If that is the case (or still the case) then you'll need to set the content size of the scene to the size of your entire level (map). You'll also want to ensure that the lower-left or your map aligns with the lower-left of the scene at (0,0) so that your scene's new bounding box encloses all of your map.
To check, after you've setup the scene CCLOG the scene's content size. If it is not the size of your map then you know that is the problem if you are indeed moving the scene with the call to setCenterOfScreen.
Another solution is to create a root content node that you add to the scene, then you add your game elements to that content node. That way the touch occurs on the scene but when you do the movements you move the content node, not the scene. For example:
#implementation YourScene
- (void)onEnter
{
[super onEnter];
self.contentNode = [CCNode node];
[self addChild:self.contentNode];
CCSprite* bg = ...;
CCSprite* player = ...;
[self.contentNode addChild:bg];
[self.contentNode addChild:player];
...
}
- (void)setCenterOfScreen:(CGPoint)position
{
CGSize screenSize = [[CCDirector sharedDirector] viewSize];
int x = MAX(position.x, screenSize.width/2);
int y = MAX(position.y, screenSize.height/2);
x = MIN(x, theMap.mapSize.width * theMap.tileSize.width - screenSize.width/2);
y = MIN(y, theMap.mapSize.height * theMap.tileSize.height - screenSize.height/2);
CGPoint goodPoint = ccp(x,y);
CGPoint centerOfScreen = ccp(screenSize.width/2, screenSize.height/2);
CGPoint difference = ccpSub(centerOfScreen, goodPoint);
self.contentNode.position = ccpAdd(self.contentNode.position, difference);
}
Hope this helps.

Vertically scrolling background - Sprite Kit

I'm working on a game that is in portrait mode and can't seem to get this background to scroll smoothly on the y-axis. It doesn't properly add a second background so there is always a gap between the 2 backgrounds.
Heres the code:
static const float BG_VELOCITY = 100.0;
static inline CGPoint CGPointAdd(const CGPoint a, const CGPoint b)
{
return CGPointMake(a.x + b.x, a.y + b.y);
}
static inline CGPoint CGPointMultiplyScalar(const CGPoint a, const CGFloat b)
{
return CGPointMake(a.x * b, a.y * b);
}
Methods:
-(void)initalizingScrollingBackground
{
for (int i = 0; i < 2; i++) {
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg"];
bg.zRotation = M_PI_2;
bg.position = CGPointMake(320, self.frame.origin.y);
bg.anchorPoint = CGPointZero;
bg.name = #"bg";
[self addChild:bg];
}
}
- (void)moveBg
{
[self enumerateChildNodesWithName:#"bg" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(0, -BG_VELOCITY);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
//Checks if bg node is completely scrolled of the screen, if yes then put it at the end of the other node
if (bg.position.y <= -bg.size.height)
{
bg.position = CGPointMake(bg.position.x,
bg.position.y + bg.size.height*2);
}
}];
}
What am I doing wrong that it has a gap in between the two backgrounds. Note: Background size is 568 x 320 if that is necessary info.
Thanks.
There a few problems. The biggest is the fact that your image is made in landscape mode. Simply edit the image in photoshop or preview to rotate it 90 degrees, then take out the zRotation. This will work for your height dilemma. Then change bg.position = CGPointMake(320,... etc to bg.position = CGPointMake (0, i * bg.size.height);
Also change your if statement to:
if (bg.position.y <= -bg.size.height)
{
bg.position = CGPointMake(0, bg.position.y + bg.size.height*2);
}
And your code will work.

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.

How to figure out that given point is inside the rectangle or not

i am working on app in which i want to draw an Rectangle.for draw a rectangle i have two CGRECT point.after draw a rectangle i have to pass a point and check whether the given point is lying inside the rectangle or not.can someone help me.Thanks
-(void)touchBeganAtPoint:(CGPoint)point{
if (isDrawingCompleted) {
match = NSNotFound;
for (NSInteger i = 0; i < [pointArray count]; i++)
{
NSValue *touchPointValue = [pointArray objectAtIndex:i];
CGPoint currentPoint = [touchPointValue CGPointValue];
CGRect controlPointRect = [self controlPointRectForPoint:currentPoint];
if (CGRectContainsPoint(controlPointRect, point))
{
match = i;
break;
}
}
}
}
point is the variable which i have to check lie inside the rectangle which will make with two point store in point array.point array have always two point.
Use CGRectContainsPoint.
bool CGRectContainsPoint (
CGRect rect,
CGPoint point
);
Use CGRectContainsPoint function

Resources