Table of Contents

Additions to SPPoint

The following two functions are used in the below classes, that could go to SPPoint implementation.

- (float) perpPoint: (SPPoint*) point {
    return self.x * point.y - self.y * point.x;
}
 
- (float) dot: (SPPoint*) p {
    return self.x * p.x + self.y * p.y;
}

Segment implementation

Segment is specified by two SPPoints. The below intersection algorithm is very useful when programming AI, movement, collision detection for point or line-like entities, or if you just need to decide if two paths will cross each other or not in the future. Intersection result may give back a few results, 0 if no intersection, 1 if a single point, and 2, if the segments are overlapping, then it returns the overlapping common segment defined by the result points.

// Segment.h
 
@interface SegmentIntersectionResult : NSObject {
 
    int resultValue;
    SPPoint* i1;
    SPPoint* i2;
}
 
@property (nonatomic, retain) SPPoint* i1;
@property (nonatomic, retain) SPPoint* i2;
@property int resultValue;
 
@end
 
@interface Segment : NSObject {
 
    SPPoint* p1;
    SPPoint* p2;
}
 
@property (nonatomic, retain) SPPoint* p1;
@property (nonatomic, retain) SPPoint* p2;
 
+ (id) segmentWithP1: (SPPoint*) p1 p2: (SPPoint*) p2;
 
+ (SegmentIntersectionResult*) intersectSegmentsWithSegment: (Segment*) s1 s2: (Segment*) s2;
+ (BOOL) inSegmentWithPoint: (SPPoint*) p s: (Segment*) s;
+ (SegmentIntersectionResult*) segmentCircleIntersection: (Segment*) s circlePosition: (SPPoint*) circlePosition radius: (float) radius;
+ (BOOL) checkSegmentCircleIntersection: (Segment*) s circlePosition: (SPPoint*) circlePosition radius: (float) radius;
 
@end
 
// Segment.m
 
#import "Segment.h"
 
#define SMALL_NUM  0.00000001
 
@implementation SegmentIntersectionResult
 
@synthesize i1, i2, resultValue;
 
@end
 
@implementation Segment
 
@synthesize p1, p2;
 
+ (id) segmentWithP1: (SPPoint*) p1 p2: (SPPoint*) p2 {
 
    Segment* seg = [[[Segment alloc] init] autorelease];
    seg.p1 = p1;
    seg.p2 = p2;
    return seg;
}
 
+ (SegmentIntersectionResult*) intersectSegmentsWithSegment: (Segment*) s1 s2: (Segment*) s2 {
    SegmentIntersectionResult* result = [[[SegmentIntersectionResult alloc] init] autorelease];
    SPPoint*  u = [s1.p2 subtractPoint: s1.p1];
    SPPoint*  v = [s2.p2 subtractPoint: s2.p1];
    SPPoint*  w = [s1.p1 subtractPoint: s2.p1];
    float     D = [u perpPoint:v];
    float     puw = [u perpPoint:w];
    float     pvw = [v perpPoint:w];
 
    if (fabs(D) < SMALL_NUM) {          // s1 and s2 are parallel
        if (puw != 0 || pvw != 0) {
            result.resultValue = 0;  // they are NOT collinear
            return result;
        }
        // they are collinear or degenerate
        // check if they are degenerate points
        float du = [u dot:u];
        float dv = [v dot:v];
        if (du==0 && dv==0) {           // both segments are points
            if (s1.p1 != s2.p1){         // they are distinct points
                result.resultValue =  0;
                return result;
            }
            result.i1 = s1.p1;                // they are the same point
            result.resultValue =  1;
            return result;
        }
        if (du==0) {                    // S1 is a single point
            if (![Segment inSegmentWithPoint: s1.p1 s: s2]){  // but is not in S2
                result.resultValue = 0;
                return result;
            }
            result.i1 = s1.p1;
            result.resultValue = 1;
            return result;
        }
        if (dv==0) {                    // S2 a single point
            if (![Segment inSegmentWithPoint:s2.p1 s: s1]) {  // but is not in S1
                result.resultValue = 0;
                return result;
            }
            result.i1 = s2.p1;
            result.resultValue = 1;
            return result;
        }
        // they are collinear segments - get overlap (or not)
        float t0, t1;                   // endpoints of S1 in eqn for S2
        SPPoint* w2 = [s1.p2 subtractPoint: s2.p1];
        if (v.x != 0) {
            t0 = w.x / v.x;
            t1 = w2.x / v.x;
        }
        else {
            t0 = w.y / v.y;
            t1 = w2.y / v.y;
        }
        if (t0 > t1) {                  // must have t0 smaller than t1
            float t=t0; t0=t1; t1=t;    // swap if not
        }
        if (t0 > 1 || t1 < 0) {
            result.resultValue = 0;
            return result;     // NO overlap
        }
        t0 = t0<0? 0 : t0;              // clip to min 0
        t1 = t1>1? 1 : t1;              // clip to max 1
        if (t0 == t1) {                 // intersect is a point
            result.resultValue = 1;
            result.i1 = [s2.p1 addPoint:[v scaleBy:t0]];
            return result;
        }
        // they overlap in a valid subsegment
        result.resultValue = 2;
        result.i1 = [s2.p1 addPoint:[v scaleBy:t0]];
        result.i2 = [s2.p1 addPoint:[v scaleBy:t1]];
        return result;
    }
 
    // the segments are skew and may intersect in a point
    // get the intersect parameter for S1
    float sI =  [v perpPoint:w] / D;
    if (sI < 0 || sI > 1) {               // no intersect with S1
        result.resultValue = 0;
        return result;
    }
 
    // get the intersect parameter for S2
    float tI = [u perpPoint:w] / D;
    if (tI < 0 || tI > 1){               // no intersect with S2
        result.resultValue = 0;
        return result;
    }
 
    result.resultValue = 1;
    result.i1 = [s1.p1 addPoint:[u scaleBy:sI]];
    return result;
}
 
//    inSegment(): determine if a point is inside a segment
//    Return: YES when P is inside S
//            NO when P is not inside S
+ (BOOL) inSegmentWithPoint: (SPPoint*) p s: (Segment*) s {
    if (s.p1.x != s.p2.x) {    // S is not vertical
        if (s.p1.x <= p.x && p.x <= s.p2.x)
            return YES;
        if (s.p1.x >= p.x && p.x >= s.p2.x)
            return YES;
    }
    else {    // S is vertical, so test y coordinate
        if (s.p1.y <= p.y && p.y <= s.p2.y)
            return YES;
        if (s.p1.y >= p.y && p.y >= s.p2.y)
            return YES;
    }
    return NO;
}
 
+ (BOOL) checkSegmentCircleIntersection: (Segment*) s circlePosition: (SPPoint*) circlePosition radius: (float) radius {
    SPPoint* segV = [s.p2 subtractPoint:s.p1];
    SPPoint* ptV = [circlePosition subtractPoint:s.p1];
    float proj = [ptV dot:[segV normalize]];
 
    SPPoint* projV = [[segV normalize] scaleBy:proj];
    SPPoint* closest; 
    if(proj < 0) {
        closest = s.p1;
    } else if(proj > [segV length]){
        closest = s.p2;
    } else {
        closest = [s.p1 addPoint:projV];
    }
 
    SPPoint* distV = [circlePosition subtractPoint:closest];
    if([distV length] < radius){
        return true;
    }
    return false;
}
 
 
+ (SegmentIntersectionResult*) segmentCircleIntersection: (Segment*) s circlePosition: (SPPoint*) circlePosition radius: (float) radius {
 
    SegmentIntersectionResult* result = [[[SegmentIntersectionResult alloc] init] autorelease];
 
    SPPoint* d = [s.p1 subtractPoint:circlePosition];    
    SPPoint* D = [s.p1 subtractPoint:s.p2];
    float a = [D dot:D];    
    float b = [d dot: D];    
    float c = [d dot: d] - radius * radius;    
    float disc = b * b - a * c;    
    if (disc < 0.0f)
    {        
        result.resultValue = 0;
        return result;    
    }    
    float sqrtDisc = sqrtf(disc);    
    float invA = 1.0f / a;
    float t[2];
    t[0] = (-b - sqrtDisc) * invA;    
    t[1] = (-b + sqrtDisc) * invA;    
 
    result.i1 = [s.p1 addPoint:[D scaleBy:t[0]]];
    result.i2 = [s.p1 addPoint:[D scaleBy:t[1]]];    
    return result;
}
 
@end

Hermite spline

Hermite is a very useful spline implementation, easy to create wavelike surfaces, enemy movements, make motion look more natural.

// Hermite2D.h
 
@interface Hermite2D : NSObject {
 
    SPPoint* a;
    SPPoint* b;
    SPPoint* t1;
    SPPoint* t2;
}
 
@property (nonatomic, retain) SPPoint* a;
@property (nonatomic, retain) SPPoint* b;
@property (nonatomic, retain) SPPoint* t1;
@property (nonatomic, retain) SPPoint* t2;
 
-(id) initWithData: (SPPoint*) start: (SPPoint*) end: (SPPoint*) tangentStart: (SPPoint*) tangentEnd;
-(SPPoint*) interpolate: (float) s;
 
@end
 
// Hermite2D.m
#import "Hermite2D.h"
 
@implementation Hermite2D
 
@synthesize a, b, t1, t2;
 
-(id) initWithData: (SPPoint*) start: (SPPoint*) end: (SPPoint*) tangentStart: (SPPoint*) tangentEnd {
    self = [super init];
    if(self){
        self.a = start;
        self.b = end;
        self.t1 = tangentStart;
        self.t2 = tangentEnd;
    }
    return self;
}
 
// s goes from 0 - 1, returns the point on the curve between the start point and
// endpoint, 0 means you are in startpoint, 1 means you are in end point.
-(SPPoint*) interpolate: (float) s {
    float h1 = (2 * s*s*s) - (3 * s * s) + 1;
    float h2 = (-2 * s*s*s) + (3 * s * s);
    float h3 = (s*s*s) - (2 * s * s) + s;
    float h4 = (s*s*s) - (s * s);
 
    SPPoint* p = [[[[a scaleBy:h1] addPoint:[b scaleBy:h2]] addPoint:[t1 scaleBy:h3]] addPoint:[t2 scaleBy:h4]];
    return p;
}
 
- (void) dealloc
{
    [a release];
    [b release];
    [t1 release];
    [t2 release];
    [super dealloc];
}
 
@end