UIBezierPath пересекаются
Я искал ответ в течение нескольких часов, но мне было трудно найти что-нибудь по этой теме.
У меня есть вопрос, связанный с Objective-c. Я делаю приложение, в котором UIView проверяет касания от пользователя, и если пользователь касается и перемещает его / ее палец, путь, используя UIBezierPath, рисуется. Если пользователь рисует так, что путь пересекает себя, он должен исчезнуть с экрана. Когда пользователь завершит рисование шаблона, линия из последней точки в пути должна автоматически соединиться с первой точкой в пути (для этого я использую метод "closePath"), если эта линия пересекается с другой "линией" "в пути путь также должен исчезнуть с экрана.
Я сохраняю каждую точку прикосновения в CGPoint, которую храню в другом классе с именем Line, как точку A и точку B. Затем я сохраняю "линию" в NSMutableArray, называемом "линии". Каждый раз, когда точка добавляется к пути, я проверяю, пересекается ли линия между этой точкой и точкой, нарисованной до того, как она пересекается с какой-либо из "линий" в линиях, используя метод (-(BOOL)checkLineIntersection:(CGPoint)p1:(CGPoint)p2:(CGPoint)p3:(CGPoint)p4) Я получил из этого урока "http://www.iossourcecode.com/2012/08/02/how-to-make-a-game-like- вырезать на веревку-PART-2/".
Эта проблема
Проблема в том, что когда я запускаю приложение, оно иногда работает, но иногда, когда я рисую так, что линии пересекаются, путь не исчезает. Я не могу понять, почему... Кажется, это происходит чаще, когда я рисую медленно.
Код:
MyView.h:
#import <UIKit/UIKit.h>
#import "Line.h"
@interface MyView : UIView {
NSMutableArray *pathArray;
UIBezierPath *myPath;
NSMutableArray *lines;
Line *line;
}
@end
MyView.m:
#import "MyView.h"
@implementation MyView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
pathArray=[[NSMutableArray alloc]init];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor] setStroke];
[[UIColor blueColor] setFill];
for (UIBezierPath *_path in pathArray) {
//[_path fill];
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
}
#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
myPath = [[UIBezierPath alloc]init];
lines = [[NSMutableArray alloc]init];
myPath.lineWidth=1;
UITouch *mytouch = [[event allTouches] anyObject];
[myPath moveToPoint:[mytouch locationInView:mytouch.view]];
[pathArray addObject:myPath];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(myPath.isEmpty) {
} else {
UITouch *mytouch = [[event allTouches] anyObject];
[myPath addLineToPoint:[mytouch locationInView:mytouch.view]];
CGPoint pointA = [mytouch previousLocationInView:mytouch.view];
CGPoint pointB = [mytouch locationInView:mytouch.view];
line = [[Line alloc]init];
[line setPointA:pointA];
[line setPointB:pointB];
[lines addObject:line];
for(Line *l in lines) {
CGPoint pa = l.pointA;
CGPoint pb = l.pointB;
//NSLog(@"Point A: %@", NSStringFromCGPoint(pa));
//NSLog(@"Point B: %@", NSStringFromCGPoint(pb));
if ([self checkLineIntersection:pointA :pointB :pa :pb])
{
[pathArray removeLastObject];
[myPath removeAllPoints];
[self setNeedsDisplay];
NSLog(@"Removed path!");
return;
}
}
}
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(myPath.isEmpty) {
} else if ([lines count] != 0){
line = [[Line alloc]init];
line = [lines lastObject];
CGPoint pointA = line.pointA;
line = [[Line alloc]init];
line = [lines objectAtIndex:0];
CGPoint pointB = line.pointA;
[myPath closePath];
for(Line *l in lines) {
CGPoint pa = l.pointA;
CGPoint pb = l.pointB;
if ([self checkLineIntersection:pointA :pointB :pa :pb])
{
[pathArray removeLastObject];
[myPath removeAllPoints];
[self setNeedsDisplay];
NSLog(@"Removed path!");
return;
}
}
}
[self setNeedsDisplay];
}
-(BOOL)checkLineIntersection:(CGPoint)p1 :(CGPoint)p2 :(CGPoint)p3 :(CGPoint)p4
{
CGFloat denominator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
/*
// In this case the lines are parallel so you assume they don't intersect
if (denominator == 0.0f)
return NO;
*/
CGFloat ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denominator;
CGFloat ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denominator;
if (ua > 0.0 && ua < 1.0 && ub > 0.0 && ub < 1.0)
{
return YES;
}
return NO;
}
@end
Line.h:
#import <UIKit/UIKit.h>
@interface Line : UIView
@property (nonatomic, assign) CGPoint pointA;
@property (nonatomic, assign) CGPoint pointB;
@end
Line.m:
#import "Line.h"
@implementation Line
@synthesize pointA;
@synthesize pointB;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
@end
Я надеюсь, что кто-то сможет ответить на это. Извините, если это что-то очевидно. Заранее спасибо!
2 ответа
Проблема в checkLineIntersection
метод. С
if (ua > 0.0 && ua < 1.0 && ub > 0.0 && ub < 1.0) { return YES; }
Вы проверяете только, пересекается ли внутренняя часть отрезков. Но если начальная или конечная точка первого линейного сегмента равна начальной или конечной точке второго линейного сегмента, ua
а также ub
будет 0.0
или же 1.0
,
Решение состоит в том, чтобы включить один конец интервала в условие:
if (ua > 0.0 && ua <= 1.0 && ub > 0.0 && ub <= 1.0) { return YES; }
Казалось, что это работает, как ожидалось в моей тестовой программе.
Некоторые дальнейшие замечания:
Я думаю, что вы должны активировать ярлык
if (denominator == 0.0f) return NO;
снова, чтобы избежать деления на ноль.
В
touchesMoved
Вы можете добавить новую строку в массив после проверки на пересечения. Теперь новая строка вставляется первой, что означает, что она проверяется на наличие пересечений.Вы объявили
Line
как подклассUIView
, но это не совсем класс просмотра. Вы могли бы просто объявитьLine
как подклассNSObject
,
ДОБАВЛЕНО: Следующий метод мог бы работать даже лучше, потому что он избегает деления и, следовательно, возможных проблем переполнения с маленькими знаменателями:
-(BOOL)checkLineIntersection:(CGPoint)p1 :(CGPoint)p2 :(CGPoint)p3 :(CGPoint)p4
{
CGFloat denominator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
CGFloat ua = (p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x);
CGFloat ub = (p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x);
if (denominator < 0) {
ua = -ua; ub = -ub; denominator = -denominator;
}
return (ua > 0.0 && ua <= denominator && ub > 0.0 && ub <= denominator);
}
Я нашел другой обходной путь, чтобы проверить, пересекает ли линия себя сам. С фреймворком SceneKit можно создавать фигуры из UIBezierPath. Но если путь пересекается, то ограничивающая рамка узла будет обнуляться.
let path = UIBezierPath()
//...
let testGeometry = SCNShape(path:path, extrusionDepth: 0.5)
let testNode = SCNNode(geometry: testGeometry)
if (testNode.boundingBox.max - testNode.boundingBox.min).length() > 0 {
// No intersection (or empty)
}