Détection de hit lors du dessin de lignes dans iOS

Je voudrais permettre à l'user de dessiner des courbes de telle sorte qu'aucune ligne ne puisse traverser une autre ligne ou même elle-même. Dessiner les courbes n'est pas un problème, et j'ai même trouvé que je pouvais créer un path fermé et encore assez linéaire en traçant les noeuds de la ligne en avant et en arrière, puis en fermant le path.

Malheureusement, iOS fournit uniquement un test pour savoir si un point est contenu dans un path fermé (containsPoint: et CGPathContainsPoint). Malheureusement, un user peut assez facilement déplacer son doigt assez rapidement pour que les points de contact atterrissent des deux côtés d'un path existant sans être réellement confinés par ce path, donc tester les points de contact est plutôt inutile.

Je ne trouve aucune méthode "intersection" de paths.

D'autres idées sur la façon d'accomplir cette tâche?

Eh bien, j'ai trouvé un moyen de le faire. C'est imparfait, mais j'ai pensé que d'autres pourraient vouloir voir la technique puisque cette question a été mise à jour à quelques resockets. La technique que j'ai utilisée dessine tous les éléments à tester dans un context bitmap puis dessine le nouveau segment de la ligne en progression dans un autre context bitmap. Les données dans ces contexts sont comparées à l'aide d'opérateurs au niveau du bit et si un chevauchement est trouvé, un hit est déclaré.

L'idée derrière cette technique est de tester chaque segment d'une ligne nouvellement dessinée par rapport à toutes les lignes tracées précédemment et même par rapport à des morceaux antérieurs de la même ligne. En d'autres termes, cette technique détectera quand une ligne traverse une autre ligne et aussi quand elle se croise sur elle-même.

Un exemple d'application démontrant la technique est disponible: LineSample.zip .

Le cœur du test de hit est fait dans mon object LineView. Voici deux methods keys:

- (CGContextRef)newBitmapContext { // creating b&w bitmaps to do hit testing // based on: http://robnapier.net/blog/clipping-cgrect-cgpath-531 // see "Supported Pixel Formats" in Quartz 2D Programming Guide CGContextRef bitmapContext = CGBitmapContextCreate(NULL, // data automatically allocated self.bounds.size.width, self.bounds.size.height, 8, self.bounds.size.width, NULL, kCGImageAlphaOnly); CGContextSetShouldAntialias(bitmapContext, NO); // use CGBitmapContextGetData to get at this data return bitmapContext; } - (BOOL)line:(Line *)line canExtendToPoint:(CGPoint) newPoint { // Lines are made up of segments that go from node to node. If we want to test for self-crossing, then we can't just test the whole in progress line against the completed line, we actually have to test each segment since one segment of the in progress line may cross another segment of the same line (think of a loop in the line). We also have to avoid checking the first point of the new segment against the last point of the previous segment (which is the same point). Luckily, a line cannot curve back on itself in just one segment (think about it, it takes at least two segments to reach yourself again). This means that we can both test progressive segments and avoid false hits by NOT drawing the last segment of the line into the test! So we will put everything up to the last segment into the hitProgressLayer, we will put the new segment into the segmentLayer, and then we will test for overlap among those two and the hitTestLayer. Any point that is in all three layers will indicate a hit, otherwise we are OK. if (line.failed) { // shortcut in case a failed line is retested return NO; } BOOL ok = YES; // thinking positively // set up a context to hold the new segment and stroke it in CGContextRef segmentContext = [self newBitmapContext]; CGContextSetLineWidth(segmentContext, 2); // bit thicker to facilitate hits CGPoint lastPoint = [[[line nodes] lastObject] point]; CGContextMoveToPoint(segmentContext, lastPoint.x, lastPoint.y); CGContextAddLineToPoint(segmentContext, newPoint.x, newPoint.y); CGContextStrokePath(segmentContext); // now we actually test // based on code from benzado: http://stackoverflow.com/questions/6515885/how-to-do-comparisons-of-bitmaps-in-ios/6515999#6515999 unsigned char *completedData = CGBitmapContextGetData(hitCompletedContext); unsigned char *progressData = CGBitmapContextGetData(hitProgressContext); unsigned char *segmentData = CGBitmapContextGetData(segmentContext); size_t bytesPerRow = CGBitmapContextGetBytesPerRow(segmentContext); size_t height = CGBitmapContextGetHeight(segmentContext); size_t len = bytesPerRow * height; for (int i = 0; i < len; i++) { if ((completedData[i] | progressData[i]) & segmentData[i]) { ok = NO; break; } } CGContextRelease(segmentContext); if (ok) { // now that we know we are good to go, // we will add the last segment onto the hitProgressLayer int numberOfSegments = [[line nodes] count] - 1; if (numberOfSegments > 0) { // but only if there is a segment there! CGPoint secondToLastPoint = [[[line nodes] objectAtIndex:numberOfSegments-1] point]; CGContextSetLineWidth(hitProgressContext, 1); // but thinner CGContextMoveToPoint(hitProgressContext, secondToLastPoint.x, secondToLastPoint.y); CGContextAddLineToPoint(hitProgressContext, lastPoint.x, lastPoint.y); CGContextStrokePath(hitProgressContext); } } else { line.failed = YES; [linesFailed addObject:line]; } return ok; } 

J'aimerais entendre des suggestions ou voir des améliorations. D'une part, il serait beaucoup plus rapide de ne vérifier que le rectangle de délimitation du nouveau segment au lieu de l'set de la vue.