# "There are better ways to implement lineAtPoint:"

#1

For fun, here’s one “better” way to implement lineAtPoint: (as mentioned in the section “Detecting Taps with UITapGestureRecognizer”).

The math comes from paulbourke.net/geometry/pointlineplane. (Note the stackoverflow answer contains a small typo in the first function, which it erroneously calls sqrtf.)

In TouchDrawView.m:

``````static CGFloat BNRLineAtPointMaxDistance = 20.0;

- (Line *)lineAtPoint:(CGPoint)p
{
Line *closestLine = nil;
CGFloat closestDistance;
for (Line *line in _completeLines) {
CGFloat distance = BNR_distanceToSegment(p, [line begin], [line end]);
if (!closestLine || distance < closestDistance) {
closestLine = line;
closestDistance = distance;
}
}
if (closestLine && closestDistance <= BNRLineAtPointMaxDistance) {
return closestLine;
} else {
return nil;
}
}

CGFloat BNR_sqr(CGFloat x) {
return x * x;
}

CGFloat BNR_dist2(CGPoint v, CGPoint w)
{
return BNR_sqr(v.x - w.x) + BNR_sqr(v.y - w.y);
}

CGFloat BNR_distanceToSegment(CGPoint p, CGPoint v, CGPoint w)
{
CGFloat l2 = BNR_dist2(v, w);
if (l2 == 0.0) {
return sqrtf(BNR_dist2(p, v));
}

CGFloat t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
if (t < 0.0) {
return sqrtf(BNR_dist2(p, v));
}
if (t > 1.0) {
return sqrtf(BNR_dist2(p, w));
}
return sqrtf(BNR_dist2(p, CGPointMake(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y))));
}``````

#2

I didn’t really test this.

``````- (CGPoint)projectPoint:(CGPoint)p
{
float dx = [self begin].x - [self end].x;
float dy = [self begin].y - [self end].y;
float len = dx * dx + dy * dy;
float dotProd = dx * (p.x - [self end].x) + dy * (p.y - [self end].y);

return CGPointMake([self end].x + dx * (dotProd / len), [self end].y + dy * (dotProd / len));
}

- (float)distanceFromPoint:(CGPoint)p
{
CGPoint proj = [self projectPoint:p];
return sqrtf(pow(proj.x - p.x, 2) + pow(proj.y - p.y, 2));
}``````

#3

[quote=“JoeConway”]I didn’t really test this.
[/quote]

Sweet! Needs bounds checking, though, since we’re dealing with line segments rather than lines. Otherwise you end up with a situation like this, where the projection of the point is close, but the line segment itself is not close:

(The menu shows where I tapped.)

Hence the ugly factorization of the code I stole from stackoverflow.

I totally stole your use of pow(…, 2), though. Forgot about that, and it makes the code slightly less ugly.

#4

Makes sense, you can clamp dotProd/len to 0…1.

``````#define CLAMP(min, val, max) (val < min ? min : (val > max ? max : val))

- (CGPoint)projectPoint:(CGPoint)p
{
float dx = [self begin].x - [self end].x;
float dy = [self begin].y - [self end].y;
float len = dx * dx + dy * dy;
float dotProd = dx * (p.x - [self end].x) + dy * (p.y - [self end].y);

float t = CLAMP(0, dotProd / len, 1);
return CGPointMake([self end].x + t *dx, [self end].y + t * dy);
}``````

#5

It is all a matter of taste, but I think it might be more elegant to move the distance calculation to the “Line”-class. The “TouchDrawView”-class is already complicated enough.

[code]- (CGFloat)getDistanceFromPoint:(CGPoint)def
{
// length components of line
CGFloat lengthX = end.x - begin.x;
CGFloat lengthY = end.y - begin.y;

``````// calculate lambda
CGFloat lambda = lengthX * (def.x - begin.x) + lengthY * (def.y - begin.y);
lambda /= lengthX * lengthX + lengthY * lengthY;

// if lambda < 0 or if lambda > 1 we are beyond the ends of the line
CGFloat clampedLambda = lambda;
if(clampedLambda < 0.0) clampedLambda = 0.0;
if(clampedLambda > 1.0) clampedLambda = 1.0;

// calculate closest point on the line
closest.x = begin.x + clampedLambda * lengthX;
closest.y = begin.y + clampedLambda * lengthY;

// finally calculate closest distance between point and line
return hypot(closest.x - def.x, closest.y - def.y);
``````

}
[/code]