/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #import #import "ObjectiveChipmunk/ObjectiveChipmunk.h" #import "ObjectiveChipmunk/ChipmunkAutogeometry.h" @interface ConvexTest : XCTestCase {} @end @implementation ConvexTest //MARK: QuickHull tests static inline cpFloat frand(void){return (cpFloat)rand()/(cpFloat)RAND_MAX;} static int FirstExpected(int expectedCount, cpVect *expectedVerts, cpVect *verts) { for(int i=0; i= 0.0f, @""); XCTAssertTrue(cpAreaForPoly(resultCount, resultVerts, 0.0f) >= 0.0f, @""); XCTAssertTrue(cpveql(resultVerts[0], rotated[first]), @""); AssertExpected(self, resultVerts, expectedCount, expectedVerts); AssertHullsEqual(self, resultCount, resultVerts); } } static void AssertRandomHull(id self, int count) { cpVect verts[count]; for(int i=0; i 0.0f, @""); } -(void)testConvexHull { { // Trivial cases, reversed windings up to a square. cpVect verts[] = {{0,0}, {1,0}}; cpVect expected[] = {{0,0}, {1,0}}; AssertHull(self, 1, verts, 1, expected); AssertHull(self, 2, verts, 2, expected); } { cpVect verts[] = {{0,0}, {1,1}, {1,0}}; cpVect expected[] = {{0,0}, {1,0}, {1,1}}; AssertHull(self, 3, verts, 3, expected); } { cpVect verts[] = {{0,0}, {0,1}, {1,1}, {1,0}}; cpVect expected[] = {{0,0}, {1,0}, {1,1}, {0,1}}; AssertHull(self, 4, verts, 4, expected); } { // Degenerate hourglass shaped square. cpVect verts[] = {{0,0}, {1,0}, {0,1}, {1,1}}; cpVect expected[] = {{0,0}, {1,0}, {1,1}, {0,1}}; AssertHull(self, 4, verts, 4, expected); } { // A unit square and a bunch of random vertexes inside it. cpVect verts[] = { {0.9, 0.5}, {0.2, 0.3}, {1.0, 0.0}, {0.8, 0.6}, {0.8, 0.4}, {0.1, 0.4}, {0.4, 0.1}, {0.0, 0.0}, {0.1, 0.9}, {0.9, 0.6}, {0.6, 0.2}, {0.7, 0.3}, {0.1, 0.3}, {0.1, 0.5}, {0.8, 0.3}, {0.3, 0.8}, {0.0, 1.0}, {0.6, 0.7}, {1.0, 1.0}, {0.6, 0.8}, {0.1, 0.7}, {0.7, 0.9}, {0.9, 0.2}, {0.1, 0.1} }; cpVect expected[] = {{0,0}, {1,0}, {1,1}, {0,1}}; AssertHull(self, 24, verts, 4, expected); } { // A bunch of colinear points on X cpVect verts[] = {{0,0}, {0,1}, {0,2}, {0,3}, {0,4}, {0,5}}; cpVect expected[] = {{0,0}, {0,5}}; AssertHull(self, 6, verts, 2, expected); } { // A bunch of colinear points on Y cpVect verts[] = {{0,0}, {1,0}, {2,0}, {3,0}, {4,0}, {5,0}}; cpVect expected[] = {{0,0}, {5,0}}; AssertHull(self, 6, verts, 2, expected); } { // A bunch of colinear points on XY cpVect verts[] = {{0,0}, {1,1}, {2,2}, {3,3}, {4,4}, {5,5}}; cpVect expected[] = {{0,0}, {5,5}}; AssertHull(self, 6, verts, 2, expected); } { // A bunch of equivalent points cpVect verts[] = {{0,0}, {0,0}, {0,0}, {0,0}, {0,0}}; cpVect expected[] = {{0,0}}; AssertHull(self, 1, verts, 1, expected); AssertHull(self, 2, verts, 1, expected); AssertHull(self, 3, verts, 1, expected); AssertHull(self, 4, verts, 1, expected); AssertHull(self, 5, verts, 1, expected); } { // Unit square with reversed winding and colinear face points. cpVect verts[] = { {1.0f, 0.0f}, {1.0f, 0.5f}, {1.0f, 1.0f}, {0.5f, 1.0f}, {0.0f, 1.0f}, {0.0f, 0.5f}, {0.0f, 0.0f}, {0.5f, 0.0f}, }; cpVect expected[] = {{0,0}, {1,0}, {1,1}, {0,1}}; AssertHull(self, 8, verts, 4, expected); } { // Unit square with duplicate vertexes. cpVect verts1[] = {{0,0}, {0,1}, {1,1}, {1,0}, {0,0}, {0,1}, {1,1}, {1,0}}; cpVect verts2[] = {{0,0}, {0,0}, {0,1}, {0,1}, {1,1}, {1,1}, {1,0}, {1,0}}; cpVect expected[] = {{0,0}, {1,0}, {1,1}, {0,1}}; AssertHull(self, 8, verts1, 4, expected); AssertHull(self, 8, verts2, 4, expected); } // Check that a convex hull returns itself using (seeded) random hulls. srand(5594); AssertRandomHull(self, 100); AssertRandomHull(self, 1000); AssertRandomHull(self, 10000); // TODO Need tolerance tests. } -(void)testValidate { cpVect a = cpv(3.354, 2.546); cpVect b = cpv(55.213, 77.1232); int numVerts = 113; // cpVect verts[] = {a, cpvadd(cpvlerp(a, b, 0.5), cpv(0, -1e-7)), b}; cpVect verts[numVerts]; for(int i=0; i= 0.0f, @""); } static cpPolyline * MakeLoopedPolyline(id self, int count, cpVect *verts) { cpPolylineSet *set = cpPolylineSetNew(); cpPolylineSetCollectSegment(verts[count - 1], verts[0], set); for(int i=1; icount == 1, @""); XCTAssert(cpPolylineIsClosed(set->lines[0]), @""); cpPolyline *line = set->lines[0]; cpPolylineSetFree(set, cpFalse); return line; } static void AssertPolyline(id self, cpPolyline *line, int expectedCount, cpVect *expectedVerts) { XCTAssertEqual(line->count, expectedCount + 1, @""); XCTAssert(cpPolylineIsClosed(line), @""); AssertExpected(self, line->verts, expectedCount, expectedVerts); } -(void)testConvexDecomposition { { cpVect verts[] = {{0,0}}; cpPolyline *line = MakeLoopedPolyline(self, 1, verts); cpPolylineSet *set = cpPolylineConvexDecomposition_BETA(line, 0.0); XCTAssertEqual(set->count, 1, @""); AssertPolyline(self, set->lines[0], 1, verts); } { cpVect verts[] = {{0,0}, {1,0}}; cpPolyline *line = MakeLoopedPolyline(self, 2, verts); cpPolylineSet *set = cpPolylineConvexDecomposition_BETA(line, 0.0); XCTAssertEqual(set->count, 1, @""); AssertPolyline(self, set->lines[0], 2, verts); } { cpVect verts[] = {{0,0}, {1,0}, {0,1}}; cpPolyline *line = MakeLoopedPolyline(self, 3, verts); cpPolylineSet *set = cpPolylineConvexDecomposition_BETA(line, 0.0); XCTAssertEqual(set->count, 1, @""); AssertPolyline(self, set->lines[0], 3, verts); } { cpVect verts[] = {{0,0}, {1,0}, {1,1}, {0,1}}; cpPolyline *line = MakeLoopedPolyline(self, 4, verts); cpPolylineSet *set = cpPolylineConvexDecomposition_BETA(line, 0.0); XCTAssertEqual(set->count, 1, @""); AssertPolyline(self, set->lines[0], 4, verts); } { cpVect verts[] = {{0,0}, {1,1}, {2,0}, {1,2}}; cpVect expected1[] = {{1,1}, {2,0}, {1,2}}; cpVect expected2[] = {{0,0}, {1,1}, {1,2}}; cpPolyline *line = MakeLoopedPolyline(self, 4, verts); cpPolylineSet *set = cpPolylineConvexDecomposition_BETA(line, 0.0); XCTAssertEqual(set->count, 2, @""); AssertPolyline(self, set->lines[0], 3, expected1); AssertPolyline(self, set->lines[1], 3, expected2); } } @end