forked from adriand/intercept-calculator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
InterceptCalculator.m
143 lines (123 loc) · 5.87 KB
/
InterceptCalculator.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//
// InterceptCalculator.m
//
// Copyright (c) 2012 Adrian Duyzer
//
#import "InterceptCalculator.h"
typedef BOOL (^PointFilter)(CGPoint);
@implementation InterceptCalculator
+ (CGPoint)findInterceptFromSource:(CGPoint)source andTouch:(CGPoint)touch withinBounds:(CGRect)bounds
{
NSMutableArray *intercepts = [[NSMutableArray alloc] init];
// In my intercept_math.rb class, bounds has four coordinates. Here, however, bounds has an origin and a size, so I need to translate
// into the coordinate types that I need.
CGFloat boundsLeftX = bounds.origin.x;
CGFloat boundsRightX = bounds.size.width;
CGFloat boundsBottomY = bounds.origin.y;
CGFloat boundsTopY = bounds.size.height;
// using this method to store the CGPoint structs in the intercepts NSMutableArray: http://stackoverflow.com/a/9606903/105650
// check special case #1: touch is on the same spot as the source
if (source.x == touch.x && source.y == touch.y) {
// TODO: fix this special case (I can't return nil, unfortunately)...
return CGPointMake(-99999.0, -99999.0);
// check special case #2: vertical line
} else if (source.x == touch.x) {
[intercepts addObject:[self CGPointValueFromX:source.x andY:boundsBottomY]];
[intercepts addObject:[self CGPointValueFromX:source.x andY:boundsTopY]];
// check special case #3: horizontal line
} else if (source.y == touch.y) {
[intercepts addObject:[self CGPointValueFromX:boundsLeftX andY:source.y]];
[intercepts addObject:[self CGPointValueFromX:boundsRightX andY:source.y]];
// regular cases
} else {
// we want to define a line as y = mx + b
// 1. find the slope of the line: (y2 - y1) / (x2 - x1)
CGFloat slope = (touch.y - source.y) / (touch.x - source.x);
// 2. Substitute slope plus one coordinate (we'll use the source's coordinate) into y = mx + b to find b
// To find b, the equation y = mx + b can be rewritten as b = y - mx
CGFloat b = source.y - (slope * source.x);
// We now have what we need to create the equation y = mx + b. Now we need to find the intercepts.
// left vertical intercept - we have x, we must solve for y
CGFloat y = slope * boundsLeftX + b;
[intercepts addObject:[self CGPointValueFromX:boundsLeftX andY:y]];
// right vertical intercept - we have x, we must solve for y
y = slope * boundsRightX + b;
[intercepts addObject:[self CGPointValueFromX:boundsRightX andY:y]];
// bottom horizontal intercept - we have y, we must solve for x
// x = (y - b) / m
CGFloat x = (boundsBottomY - b) / slope;
[intercepts addObject:[self CGPointValueFromX:x andY:boundsBottomY]];
// top horizontal intercept - we have y, we must solve for x
x = (boundsTopY - b) / slope;
[intercepts addObject:[self CGPointValueFromX:x andY:boundsTopY]];
}
[self filterIntercepts:intercepts byBounds:bounds];
[self filterInterceptsByDirection:intercepts withSource:source andTouch:touch];
CGPoint interceptPoint = [self pointFromValue:[intercepts objectAtIndex:0]];
return interceptPoint;
}
// This method iterates through the intercepts and send each intercept point into the block.
// The block should return either YES or NO. If it returns YES, the point is ultimately
// removed from the intercepts.
+ (void)filterIntercepts:(NSMutableArray *)intercepts usingFilterBlock:(PointFilter)filterBlock
{
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
CGPoint interceptPoint;
for (NSValue *pointValue in intercepts) {
interceptPoint = [self pointFromValue:pointValue];
if (filterBlock(interceptPoint))
[objectsToRemove addObject:pointValue];
}
[intercepts removeObjectsInArray:objectsToRemove];
}
+ (void)filterIntercepts:(NSMutableArray *)intercepts byBounds:(CGRect)bounds
{
// to be a valid intercept, the x value cannot exceed the bounds of the left and right verticals,
// and the y value cannot exceed the bounds of the bottom and top horizontals
[self filterIntercepts:intercepts usingFilterBlock:^BOOL(CGPoint point) {
return (point.x < bounds.origin.x || point.x > (bounds.size.width) || point.y < bounds.origin.y || point.y > bounds.size.height);
}];
}
// we must determine the correct intercept based on the intended direction of the projectile
+ (void)filterInterceptsByDirection:(NSMutableArray *)intercepts withSource:(CGPoint)source andTouch:(CGPoint)touch
{
// we must establish the direction that was intended for the touch
// if the difference between the touch's x and the source's x is positive, then any intercept with an x position that
// is less than the source's y position is invalid
CGFloat xDelta = touch.x - source.x;
if (xDelta >= 0.0) {
[self filterIntercepts:intercepts usingFilterBlock:^BOOL(CGPoint point) {
return (point.x < source.x);
}];
} else {
[self filterIntercepts:intercepts usingFilterBlock:^BOOL(CGPoint point) {
return (point.x >= source.x);
}];
}
CGFloat yDelta = touch.y - source.y;
if (yDelta >= 0.0) {
[self filterIntercepts:intercepts usingFilterBlock:^BOOL(CGPoint point) {
return (point.y < source.y);
}];
} else {
[self filterIntercepts:intercepts usingFilterBlock:^BOOL(CGPoint point) {
return (point.y >= source.y);
}];
}
}
+ (NSValue *)CGPointValueFromX:(CGFloat)x andY:(CGFloat)y
{
CGPoint point = CGPointMake(x, y);
return [self valueFromCGPoint:point];
}
+ (NSValue *)valueFromCGPoint:(CGPoint)point
{
return [NSValue valueWithBytes:&point objCType:@encode(CGPoint)];
}
+ (CGPoint)pointFromValue:(NSValue *)pointValue
{
CGPoint point;
[pointValue getValue:&point];
return point;
}
@end