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 +7/−6
- Chipmunk2D-7.0.2/include/chipmunk/cpPolyline.h +6/−1
- Chipmunk2D-7.0.2/src/cpPolyline.c +91/−88
- chiphunk.cabal +4/−3
- src/Chiphunk/Low.hs +3/−0
- src/Chiphunk/Low/Helper.chs +14/−0
- src/Chiphunk/Low/Shape.chs +16/−0
- src/Chiphunk/Low/Types.chs +39/−0
- src/Chiphunk/wrapper.c +4/−0
- src/Chiphunk/wrapper.h +2/−0
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);