packages feed

chiphunk 0.1.1.0 → 0.1.2.0

raw patch · 10 files changed

+186/−98 lines, 10 filesnew-uploaderPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Chiphunk.Low: convexDecomposition :: [Vect] -> Double -> [[Vect]]
+ Chiphunk.Low: shapeDensity :: Shape -> StateVar Double
+ Chiphunk.Low: shapeMass :: Shape -> StateVar Double

Files

Chipmunk2D-7.0.2/include/chipmunk/chipmunk.h view
@@ -1,15 +1,15 @@ /* 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@@ -58,7 +58,7 @@ #define cpAssertHard(__condition__, ...) if(!(__condition__)){cpMessage(#__condition__, __FILE__, __LINE__, 1, 1, __VA_ARGS__); abort();}  #include "chipmunk_types.h"-	+ /// @defgroup misc Misc /// @{ @@ -115,11 +115,12 @@ #include "cpTransform.h" #include "cpSpatialIndex.h" -#include "cpArbiter.h"	+#include "cpArbiter.h"  #include "cpBody.h" #include "cpShape.h" #include "cpPolyShape.h"+#include "cpPolyline.h"  #include "cpConstraint.h" @@ -191,7 +192,7 @@ #if __has_extension(blocks) // Define alternate block based alternatives for a few of the callback heavy functions. // Collision handlers are post-step callbacks are not included to avoid memory management issues.-// If you want to use blocks for those and are aware of how to correctly manage the memory, the implementation is trivial. +// If you want to use blocks for those and are aware of how to correctly manage the memory, the implementation is trivial.  void cpSpaceEachBody_b(cpSpace *space, void (^block)(cpBody *body)); void cpSpaceEachShape_b(cpSpace *space, void (^block)(cpShape *shape));
Chipmunk2D-7.0.2/include/chipmunk/cpPolyline.h view
@@ -1,3 +1,6 @@+#ifndef __POLYLINE_H__+#define __POLYLINE_H__+ // Copyright 2013 Howling Moon Software. All rights reserved. // See http://chipmunk2d.net/legal.php for more information. @@ -6,7 +9,7 @@ // cpPolyline structs are intended to be passed by value and destroyed when you are done with them. typedef struct cpPolyline {   int count, capacity;-  cpVect verts[];+  cpVect verts[0]; } cpPolyline;  /// Destroy and free a polyline instance.@@ -68,3 +71,5 @@ CP_EXPORT cpPolylineSet *cpPolylineConvexDecomposition(cpPolyline *line, cpFloat tol);  #define cpPolylineConvexDecomposition_BETA cpPolylineConvexDecomposition++#endif // __POLYLINE_H__
Chipmunk2D-7.0.2/src/cpPolyline.c view
@@ -26,11 +26,11 @@ cpPolylineMake(int capacity) { 	capacity = (capacity > DEFAULT_POLYLINE_CAPACITY ? capacity : DEFAULT_POLYLINE_CAPACITY);-	+ 	cpPolyline *line = (cpPolyline *)cpcalloc(1, cpPolylineSizeForCapacity(capacity)); 	line->count = 0; 	line->capacity = capacity;-	+ 	return line; } @@ -41,7 +41,7 @@ 	line->count = 2; 	line->verts[0] = a; 	line->verts[1] = b;-	+ 	return line; } @@ -63,15 +63,15 @@ cpPolylineGrow(cpPolyline *line, int count) {   line->count += count;-  +   int capacity = line->capacity;   while(line->count > capacity) capacity *= 2;-  +   if(line->capacity < capacity){     line->capacity = capacity; 		line = (cpPolyline*) cprealloc(line, cpPolylineSizeForCapacity(capacity));   }-	+ 	return line; } @@ -82,7 +82,7 @@   int count = line->count;   line = cpPolylineGrow(line, 1);   line->verts[count] = v;-	+ 	return line; } @@ -96,7 +96,7 @@   line = cpPolylineGrow(line, 1);   memmove(line->verts + 1, line->verts, count*sizeof(cpVect));   line->verts[0] = v;-	+ 	return line; } @@ -117,7 +117,7 @@ 		length += cpvdist(points[i], points[Next(i, count)]); 		if(length > min) return cpFalse; 	}-  +   return cpTrue; } @@ -136,20 +136,20 @@ cpPolylineSimplifyVertexes(cpPolyline *line, cpFloat tol) { 	cpPolyline *reduced = cpPolylineMake2(0, line->verts[0], line->verts[1]);-	+ 	cpFloat minSharp = -cpfcos(tol);-	+ 	for(int i=2; i<line->count; i++){ 		cpVect vert = line->verts[i]; 		cpFloat sharp = Sharpness(reduced->verts[reduced->count - 2], reduced->verts[reduced->count - 1], vert);-		+ 		if(sharp <= minSharp){ 			reduced->verts[reduced->count - 1] = vert; 		} else { 			reduced = cpPolylinePush(reduced, vert); 		} 	}-	+ 	if( 		cpPolylineIsClosed(line) && 		Sharpness(reduced->verts[reduced->count - 2], reduced->verts[0], reduced->verts[1]) < minSharp@@ -157,7 +157,7 @@ 		reduced->verts[0] = reduced->verts[reduced->count - 2]; 		reduced->count--; 	}-	+ 	// TODO shrink 	return reduced; }@@ -171,35 +171,35 @@ ){ 	// Early exit if the points are adjacent   if((end - start + length)%length < 2) return reduced;-  + 	cpVect a = verts[start]; 	cpVect b = verts[end];-	+ 	// Check if the length is below the threshold 	if(cpvnear(a, b, min) && cpPolylineIsShort(verts, length, start, end, min)) return reduced;-	+ 	// Find the maximal vertex to split and recurse on 	cpFloat max = 0.0; 	int maxi = start;-	+ 	cpVect n = cpvnormalize(cpvperp(cpvsub(b, a))); 	cpFloat d = cpvdot(n, a);-	+ 	for(int i=Next(start, length); i!=end; i=Next(i, length)){ 		cpFloat dist = fabs(cpvdot(n, verts[i]) - d);-		+ 		if(dist > max){ 			max = dist; 			maxi = i; 		} 	}-	+ 	if(max > tol){     reduced = DouglasPeucker(verts, reduced, length, start, maxi, min, tol); 		reduced = cpPolylinePush(reduced, verts[maxi]);     reduced = DouglasPeucker(verts, reduced, length, maxi, end, min, tol); 	}-	+ 	return reduced; } @@ -210,13 +210,13 @@ cpPolylineSimplifyCurves(cpPolyline *line, cpFloat tol) { 	cpPolyline *reduced = cpPolylineMake(line->count);-	+ 	cpFloat min = tol/2.0f;-  +   if(cpPolylineIsClosed(line)){ 		int start, end;     cpLoopIndexes(line->verts, line->count - 1, &start, &end);-    + 		reduced = cpPolylinePush(reduced, line->verts[start]); 		reduced = DouglasPeucker(line->verts, reduced, line->count - 1, start, end, min, tol); 		reduced = cpPolylinePush(reduced, line->verts[end]);@@ -227,7 +227,7 @@ 		reduced = DouglasPeucker(line->verts, reduced, line->count, 0, line->count - 1, min, tol); 		reduced = cpPolylinePush(reduced, line->verts[line->count - 1]);   }-	+ 	return cpPolylineShrink(reduced); } @@ -245,7 +245,7 @@ 	set->count = 0; 	set->capacity = 8; 	set->lines = (cpPolyline**) cpcalloc(set->capacity, sizeof(cpPolyline));-	+   return set; } @@ -264,7 +264,7 @@ 			cpPolylineFree(set->lines[i]); 		} 	}-	+ 	cpfree(set->lines); } @@ -283,12 +283,12 @@ cpPolylineSetFindEnds(cpPolylineSet *set, cpVect v){ 	int count = set->count; 	cpPolyline **lines = set->lines;-	+   for(int i=0; i<count; i++){ 		cpPolyline *line = lines[i];     if(cpveql(line->verts[line->count - 1], v)) return i;   }-  +   return -1; } @@ -297,11 +297,11 @@ cpPolylineSetFindStarts(cpPolylineSet *set, cpVect v){ 	int count = set->count; 	cpPolyline **lines = set->lines;-	+   for(int i=0; i<count; i++){     if(cpveql(lines[i]->verts[0], v)) return i;   }-  +   return -1; } @@ -315,7 +315,7 @@     set->capacity *= 2;     set->lines = (cpPolyline**) cprealloc(set->lines, set->capacity*sizeof(cpPolyline));   }-  + 	set->lines[set->count - 1] = line; } @@ -332,13 +332,13 @@ {   cpPolyline *lbefore = set->lines[before];   cpPolyline *lafter = set->lines[after];-  +   // append   int count = lbefore->count;   lbefore = cpPolylineGrow(lbefore, lafter->count);   memmove(lbefore->verts + count, lafter->verts, lafter->count*sizeof(cpVect)); 	set->lines[before] = lbefore;-  +   // delete lafter   set->count--; 	cpPolylineFree(set->lines[after]);@@ -352,7 +352,7 @@ {   int before = cpPolylineSetFindEnds(lines, v0);   int after = cpPolylineSetFindStarts(lines, v1);-  +   if(before >= 0 && after >= 0){     if(before == after){       // loop by pushing v1 onto before@@ -381,7 +381,7 @@ 	cpPolyline *hull = cpPolylineMake(line->count + 1); 	hull->count = cpConvexHull(line->count, line->verts, hull->verts, NULL, tol); 	hull = cpPolylinePush(hull, hull->verts[0]);-	+ 	return cpPolylineShrink(hull); } @@ -399,26 +399,26 @@ { 	cpFloat min = INFINITY; 	cpFloat feature = -1.0;-	+ 	for(int i=1; i<count-1; i++){ 		int index = (notch.i + i)%count;-		+ 		cpVect seg_a = verts[index]; 		cpVect seg_b = verts[Next(index, count)];-		+ 		cpFloat thing_a = cpvcross(notch.n, cpvsub(seg_a, notch.v)); 		cpFloat thing_b = cpvcross(notch.n, cpvsub(seg_b, notch.v)); 		if(thing_a*thing_b <= 0.0){ 			cpFloat t = thing_a/(thing_a - thing_b); 			cpFloat dist = cpvdot(notch.n, cpvsub(cpvlerp(seg_a, seg_b, t), notch.v));-			+ 			if(dist >= 0.0 && dist <= min){ 				min = dist; 				feature = index + t; 			} 		} 	}-	+ 	return feature; } @@ -428,29 +428,29 @@ //	cpVect a = verts[(notch.i + count - 1)%count]; //	cpVect b = verts[(notch.i + 1)%count]; //	cpVect n = cpvnormalize(cpvadd(cpvnormalize(cpvsub(notch.v, a)), cpvnormalize(cpvsub(notch.v, b))));-//	+// //	cpFloat min = INFINITY; //	cpFloat feature = -1.0;-//	+// //	for(int i=1; i<count-1; i++){ //		int index = (notch.i + i)%count;-//		+// //		cpVect seg_a = verts[index]; //		cpVect seg_b = verts[Next(index, count)];-//		+// //		cpFloat thing_a = cpvcross(n, cpvsub(seg_a, notch.v)); //		cpFloat thing_b = cpvcross(n, cpvsub(seg_b, notch.v)); //		if(thing_a*thing_b <= 0.0){ //			cpFloat t = thing_a/(thing_a - thing_b); //			cpFloat dist = cpvdot(n, cpvsub(cpvlerp(seg_a, seg_b, t), notch.v));-//			+// //			if(dist >= 0.0 && dist <= min){ //				min = dist; //				feature = index + t; //			} //		} //	}-//	+// //	cpAssertSoft(feature >= 0.0, "No closest features detected. This is likely due to a self intersecting polygon."); //	return feature; //}@@ -476,35 +476,35 @@ //{ //	cpFloat min = INFINITY; //	cpFloat feature = -1.0;-//	+// //	cpVect support_a = verts[(notch.i - 1 + count)%count]; //	cpVect support_b = verts[(notch.i + 1)%count];-//	+// //	cpVect clip_a = cpvlerp(support_a, support_b, 0.1); //	cpVect clip_b = cpvlerp(support_b, support_b, 0.9);-//	+// //	for(int i=1; i<count - 1; i++){ //		int index = (notch.i + i)%count; //		cpVect seg_a = verts[index]; //		cpVect seg_b = verts[Next(index, count)];-//		+// //		cpVect delta_a = cpvsub(seg_a, notch.v); //		cpVect delta_b = cpvsub(seg_b, notch.v);-//		+// //		// Ignore if the segment faces away from the point. //		if(cpvcross(delta_b, delta_a) > 0.0){ //			struct Range range1 = clip_range(delta_a, delta_b, cpvsub(notch.v, clip_a)); //			struct Range range2 = clip_range(delta_a, delta_b, cpvsub(clip_b, notch.v));-//			+// //			cpFloat min_t = cpfmax(0.0, cpfmax(range1.min, range2.min)); //			cpFloat max_t = cpfmin(1.0, cpfmin(range1.max, range2.max));-//			+// //			// Ignore if the segment has been completely clipped away. //			if(min_t < max_t){ //				cpVect seg_delta = cpvsub(seg_b, seg_a); //				cpFloat closest_t = cpfclamp(cpvdot(seg_delta, cpvsub(notch.v, seg_a))/cpvlengthsq(seg_delta), min_t, max_t); //				cpVect closest = cpvlerp(seg_a, seg_b, closest_t);-//				+// //				cpFloat dist = cpvdistsq(notch.v, closest); //				if(dist < min){ //					min = dist;@@ -513,7 +513,7 @@ //			} //		} //	}-//	+// //	cpAssertWarn(feature >= 0.0, "Internal Error: No closest features detected."); //	return feature; //}@@ -523,18 +523,18 @@ //{ //	cpVect v = verts[notch_i]; //	cpVect n = cpvnormalize(cpvsub(verts[index], v));-//	+// //	for(int i=0; i<count; i++){ //		if(i == index || i == Next(i, count) || i == notch_i || i == Next(notch_i, count)) continue;-//		+// //		cpVect seg_a = verts[i]; //		cpVect seg_b = verts[Next(i, count)];-//		+// //		cpFloat thing_a = cpvcross(n, cpvsub(seg_a, v)); //		cpFloat thing_b = cpvcross(n, cpvsub(seg_b, v)); //		if(thing_a*thing_b <= 0.0) return cpTrue; //	}-//	+// //	return cpFalse; //} //@@ -543,17 +543,17 @@ //{ //	cpFloat min = INFINITY; //	cpFloat feature = -1.0;-//	+// //	for(int i=Next(notch.b, count); i!=notch.a; i=Next(i, count)){ //		cpVect v = verts[i]; //		cpFloat weight = (1.0 + 0.1*convexity[i])/(1.0*cpvdist(notch.v, v));-//		+// //		if(weight <= min && VertexUnobscured(count, verts, i, notch.i)){ //			min = weight; //			feature = i; //		} //	}-//	+// //	cpAssertSoft(feature >= 0.0, "No closest features detected. This is likely due to a self intersecting polygon."); //	return feature; //}@@ -563,79 +563,82 @@ { 	struct Notch notch = {}; 	int j = Next(first, count);-	+ 	for(int i=0; i<hullCount; i++){ 		cpVect a = hullVerts[i]; 		cpVect b = hullVerts[Next(i, hullCount)];-		+ 		// TODO use a cross check instead? 		cpVect n = cpvnormalize(cpvrperp(cpvsub(a, b))); 		cpFloat d = cpvdot(n, a);-		+ 		cpVect v = verts[j]; 		while(!cpveql(v, b)){ 			cpFloat depth = cpvdot(n, v) - d;-			+ 			if(depth > notch.d){ 				notch.d = depth; 				notch.i = j; 				notch.v = v; 				notch.n = n; 			}-			+ 			j = Next(j, count); 			v = verts[j]; 		}-		+ 		j = Next(j, count); 	}-	+ 	return notch; }  static inline int IMAX(int a, int b){return (a > b ? a : b);} +#define MAX_RECURSION_DEPTH 100+ static void-ApproximateConcaveDecomposition(cpVect *verts, int count, cpFloat tol, cpPolylineSet *set)+ApproximateConcaveDecomposition(int depth, cpVect *verts, int count, cpFloat tol, cpPolylineSet *set) { 	int first; 	cpVect *hullVerts = (cpVect*) alloca(count*sizeof(cpVect)); 	int hullCount = cpConvexHull(count, verts, hullVerts, &first, 0.0);-	++ 	if(hullCount != count){ 		struct Notch notch = DeepestNotch(count, verts, hullCount, hullVerts, first, tol);-		-		if(notch.d > tol){++		if(notch.d > tol && depth < MAX_RECURSION_DEPTH){ 			cpFloat steiner_it = FindSteiner(count, verts, notch);-			+ 			if(steiner_it >= 0.0){ 				int steiner_i = (int)steiner_it; 				cpVect steiner = cpvlerp(verts[steiner_i], verts[Next(steiner_i, count)], steiner_it - steiner_i);-				+ 				// Vertex counts NOT including the steiner point. 				int sub1_count = (steiner_i - notch.i + count)%count + 1; 				int sub2_count = count - (steiner_i - notch.i + count)%count; 				cpVect *scratch = (cpVect*) alloca((IMAX(sub1_count, sub2_count) + 1)*sizeof(cpVect));-				+ 				for(int i=0; i<sub1_count; i++) scratch[i] = verts[(notch.i + i)%count]; 				scratch[sub1_count] = steiner;-				ApproximateConcaveDecomposition(scratch, sub1_count + 1, tol, set);-				+				ApproximateConcaveDecomposition(depth+1,scratch, sub1_count + 1, tol, set);+ 				for(int i=0; i<sub2_count; i++) scratch[i] = verts[(steiner_i + 1 + i)%count]; 				scratch[sub2_count] = steiner;-				ApproximateConcaveDecomposition(scratch, sub2_count + 1, tol, set);-				+				ApproximateConcaveDecomposition(depth+1,scratch, sub2_count + 1, tol, set);+ 				return; 			} 		} 	}-	+ 	cpPolyline *hull = cpPolylineMake(hullCount + 1);-	+ 	memcpy(hull->verts, hullVerts, hullCount*sizeof(cpVect)); 	hull->verts[hullCount] = hullVerts[0]; 	hull->count = hullCount + 1;-	+ 	cpPolylineSetPush(set, hull); } @@ -644,9 +647,9 @@ { 	cpAssertSoft(cpPolylineIsClosed(line), "Cannot decompose an open polygon."); 	cpAssertSoft(cpAreaForPoly(line->count, line->verts, 0.0) >= 0.0, "Winding is backwards. (Are you passing a hole?)");-	+ 	cpPolylineSet *set = cpPolylineSetNew();-	ApproximateConcaveDecomposition(line->verts, line->count - 1, tol, set);-	+	ApproximateConcaveDecomposition(0, line->verts, line->count - 1, tol, set);+ 	return set; }
chiphunk.cabal view
@@ -4,16 +4,16 @@ -- -- see: https://github.com/sol/hpack ----- hash: e6e34b0f3a7347782c482b79a689d989bed10c837045db9756529b0b9ba1bc80+-- hash: 2ad0ea8a28a41b5969685566dd3f472c558141b895393055c85b73f670c63421  name:           chiphunk-version:        0.1.1.0+version:        0.1.2.0 synopsis:       Haskell bindings for Chipmunk2D physics engine description:    Please see the README on GitHub at <https://github.com/CthulhuDen/chiphunk#readme> category:       Physics homepage:       https://github.com/CthulhuDen/chiphunk#readme author:         Cthulhu-maintainer:     cthulhu.den@gmail.com+maintainer:     cthulhu.den@gmail.com, lemmih@gmail.com copyright:      Cthulhu (c) 2018 license:        BSD3 license-file:   LICENSE@@ -72,6 +72,7 @@   hs-source-dirs:       src   ghc-options: -Wall+  cc-options: -DNDEBUG   include-dirs:       Chipmunk2D-7.0.2/include       src/Chiphunk
src/Chiphunk/Low.hs view
@@ -419,6 +419,8 @@   , shapeFriction   , shapeSurfaceVelocity   , shapeCollisionType+  , shapeMass+  , shapeDensity   , ShapeFilter (..)   , shapeFilter   , shapeSpace@@ -500,6 +502,7 @@      -- *** Convex Hull Helper Functions   , convexHull+  , convexDecomposition      -- ** Modifying 'Shape's 
src/Chiphunk/Low/Helper.chs view
@@ -10,6 +10,7 @@   , areaForPoly   , centroidForPoly   , convexHull+  , convexDecomposition   ) where  import Data.VectorSpace@@ -97,3 +98,16 @@   alloca $ \pFst -> do     n <- {# call cpConvexHull as c_convexHull #} (fromIntegral $ length vs) pVs pRes pFst (realToFrac tol)     (,) <$> peekArray (fromIntegral n) pRes <*> (fromIntegral <$> peek pFst)++convexDecomposition :: [Vect] -> Double -> [[Vect]]+convexDecomposition [] _ = []+convexDecomposition concavePolygon tol = unsafePerformIO $+  withPolylinePtr (Polyline counterClockwise) $ \lineP -> do+    setP <- {# call cpPolylineConvexDecomposition #} lineP (realToFrac tol)+    set <- peekPolylineSet setP+    {# call cpPolylineSetFree #} setP 1+    return $ map unPolyline $ unPolylineSet set+  where+    counterClockwise+      | areaForPoly concavePolygon 0 < 0 = reverse concavePolygon+      | otherwise                        = concavePolygon
src/Chiphunk/Low/Shape.chs view
@@ -9,6 +9,8 @@   , shapeFriction   , shapeSurfaceVelocity   , shapeCollisionType+  , shapeMass+  , shapeDensity   , ShapeFilter (..)   , ShapeFilterPtr   , shapeFilter@@ -107,6 +109,20 @@ -- See the callbacks section for more information. shapeCollisionType :: Shape -> StateVar CollisionType shapeCollisionType = mkStateVar cpShapeGetCollisionType cpShapeSetCollisionType++{# fun unsafe cpShapeGetMass {`Shape'} -> `Double' #}++{# fun unsafe cpShapeSetMass {`Shape', `Double'} -> `()' #}++shapeMass :: Shape -> StateVar Double+shapeMass = mkStateVar cpShapeGetMass cpShapeSetMass++{# fun unsafe cpShapeGetDensity {`Shape'} -> `Double' #}++{# fun unsafe cpShapeSetDensity {`Shape', `Double'} -> `()' #}++shapeDensity :: Shape -> StateVar Double+shapeDensity = mkStateVar cpShapeGetDensity cpShapeSetDensity  -- | Fast collision filtering type that is used to determine if two objects collide -- before calling collision or query callbacks.
src/Chiphunk/Low/Types.chs view
@@ -21,6 +21,12 @@   , CollisionType   , CPBool   , mkStateVar+  , Polyline(..)+  , PolylinePtr+  , PolylineSet(..)+  , PolylineSetPtr+  , withPolylinePtr+  , peekPolylineSet   ) where  import Data.Cross@@ -244,3 +250,36 @@ -- | 'makeStateVar' lifted to reader monad mkStateVar :: (a -> IO b) -> (a -> b -> IO ()) -> a -> StateVar b mkStateVar g s i = makeStateVar (g i) (s i)++{# pointer *cpPolyline as PolylinePtr -> Polyline #}+newtype Polyline = Polyline { unPolyline :: [Vect] }++foreign import ccall w_cpPolylineVerts :: Ptr Polyline -> Ptr Vect++withPolylinePtr :: Polyline -> (Ptr Polyline -> IO a) -> IO a+withPolylinePtr (Polyline verts) fn = do+    allocaBytes (sizeOf (undefined :: Vect) * (count+10)) $ \p -> do++      {# set cpPolyline->count #} p $ fromIntegral count+      {# set cpPolyline->capacity #} p $ fromIntegral count+      let vp = plusPtr p {# offsetof cpPolyline->verts #}+      pokeArray vp verts+      fn p+  where+    count = length verts++peekPolyline :: Ptr Polyline -> IO Polyline+peekPolyline p = do+  count <- fromIntegral <$> {# get cpPolyline->count #} p+  let vp = w_cpPolylineVerts p+  Polyline <$> peekArray count vp++{# pointer *cpPolylineSet as PolylineSetPtr -> PolylineSet #}++data PolylineSet = PolylineSet { unPolylineSet :: [Polyline] }++peekPolylineSet :: Ptr PolylineSet -> IO PolylineSet+peekPolylineSet p = do+  count <- fromIntegral <$> {# get cpPolyline->count #} p+  lp <- {# get cpPolylineSet->lines #} p+  PolylineSet <$> (mapM peekPolyline =<< peekArray count lp)
src/Chiphunk/wrapper.c view
@@ -174,3 +174,7 @@ {   *out = cpArbiterGetPointB(arbiter, i); }++cpVect* w_cpPolylineVerts(cpPolyline* line) {+  return line->verts;+}
src/Chiphunk/wrapper.h view
@@ -69,3 +69,5 @@ void w_cpArbiterGetPointA(const cpArbiter *arbiter, int i, cpVect *out);  void w_cpArbiterGetPointB(const cpArbiter *arbiter, int i, cpVect *out);++cpVect* w_cpPolylineVerts(cpPolyline *line);