1 // Copyright 2013 Howling Moon Software. All rights reserved.
2 // See http://chipmunk2d.net/legal.php for more information.
4 #import "ChipmunkTileCache.h"
7 @interface ChipmunkCachedTile : NSObject {
11 ChipmunkCachedTile *_next, *_prev;
16 @property(nonatomic, readonly) cpBB bb;
17 @property(nonatomic, assign) bool dirty;
19 @property(nonatomic, assign) ChipmunkCachedTile *next;
20 @property(nonatomic, assign) ChipmunkCachedTile *prev;
22 @property(nonatomic, retain) NSArray *shapes;
28 @implementation ChipmunkCachedTile
30 @synthesize bb = _bb, dirty = _dirty, shapes = _shapes, next = _next, prev = _prev;
33 ChipmunkCachedTileBB(ChipmunkCachedTile *tile)
39 ChipmunkCachedTileQuery(cpVect *pos, ChipmunkCachedTile *tile, cpCollisionID id, ChipmunkCachedTile **out)
41 if(cpBBContainsVect(tile->_bb, *pos)) (*out) = tile;
45 -(id)initWithBB:(cpBB)bb
47 if((self = [super init])) _bb = bb;
61 @implementation ChipmunkAbstractTileCache
63 @synthesize marchHard = _marchHard, sampler = _sampler, tileOffset = _tileOffset;
65 -(id)initWithSampler:(ChipmunkAbstractSampler *)sampler space:(ChipmunkSpace *)space tileSize:(cpFloat)tileSize samplesPerTile:(NSUInteger)samplesPerTile cacheSize:(NSUInteger)cacheSize
67 if((self = [super init])){
68 _sampler = [sampler retain];
69 _space = [space retain];
72 _samplesPerTile =samplesPerTile;
73 _tileOffset = cpvzero;
75 _cacheSize = cacheSize;
82 -(void)removeShapesForTile:(ChipmunkCachedTile *)tile
84 for(ChipmunkShape *shape in tile.shapes) [_space remove:shape];
89 for(ChipmunkCachedTile *tile = _cacheTail; tile; tile = tile.next){
96 cpSpatialIndexFree(_tileIndex);
103 _ensuredDirty = TRUE;
105 // Reset the spatial index.
106 if(_tileIndex) cpSpatialIndexFree(_tileIndex);
107 _tileIndex = cpSpaceHashNew(_tileSize, (int)_cacheSize, (cpSpatialIndexBBFunc)ChipmunkCachedTileBB, NULL);
109 // Remove all the shapes and release all the tiles.
110 for(ChipmunkCachedTile *tile = _cacheTail; tile; tile = tile.next){
111 [self removeShapesForTile:tile];
115 // Clear out the tile list.
116 _cacheHead = _cacheTail = nil;
120 -(void)marchTile:(ChipmunkCachedTile *)tile
122 // Remove old shapes for this tile.
123 for(ChipmunkShape *shape in tile.shapes) [_space remove:shape];
124 cpPolylineSet *set = cpPolylineSetNew();
126 (_marchHard ? cpMarchHard : cpMarchSoft)(
127 tile.bb, _samplesPerTile, _samplesPerTile, _sampler.marchThreshold,
128 (cpMarchSegmentFunc)cpPolylineSetCollectSegment, set,
129 _sampler.sampleFunc, _sampler
133 ChipmunkBody *staticBody = [ChipmunkBody staticBody];
134 NSMutableArray *shapes = [NSMutableArray array];
136 for(int i=0; i<set->count; i++){
137 cpPolyline *simplified = [self simplify:set->lines[i]];
139 for(int i=0; i<simplified->count - 1; i++){
140 ChipmunkSegmentShape *segment = [self makeSegmentFor:staticBody from:simplified->verts[i] to:simplified->verts[i+1]];
141 [shapes addObject:segment];
142 [_space add:segment];
145 cpPolylineFree(simplified);
148 tile.shapes = shapes;
153 cpPolylineSetFree(set, TRUE);
157 static inline ChipmunkCachedTile *
158 GetTileAt(cpSpatialIndex *index, int i, int j, cpFloat size, cpVect offset)
160 // Cannot directly get spatial hash cells, so we'll point query at the centers.
161 cpVect point = cpv((i + 0.5)*size + offset.x, (j + 0.5)*size + offset.y);
162 ChipmunkCachedTile *tile = nil;
163 cpSpatialIndexQuery(index, &point, cpBBNewForCircle(point, 0.0f), (cpSpatialIndexQueryFunc)ChipmunkCachedTileQuery, &tile);
168 struct TileRect {int l, b, r, t;};
171 BBForTileRect(struct TileRect rect, cpFloat size, cpVect offset)
173 return cpBBNew(rect.l*size + offset.x, rect.b*size + offset.y, rect.r*size + offset.x, rect.t*size + offset.y);
176 static inline struct TileRect
177 TileRectForBB(cpBB bb, cpFloat size, cpVect offset, cpFloat spt_inv)
179 return (struct TileRect){
180 (int)cpffloor((bb.l - offset.x)/size - spt_inv),
181 (int)cpffloor((bb.b - offset.x)/size - spt_inv),
182 (int) cpfceil((bb.r - offset.y)/size + spt_inv),
183 (int) cpfceil((bb.t - offset.y)/size + spt_inv),
187 -(void)markDirtyRect:(cpBB)bounds
189 cpFloat size = _tileSize;
190 cpVect offset = _tileOffset;
191 struct TileRect rect = TileRectForBB(bounds, size, offset, 1.0/(cpFloat)_samplesPerTile);
193 if(!_ensuredDirty && cpBBContainsBB(_ensuredBB, BBForTileRect(rect, size, offset))){
194 _ensuredDirty = TRUE;
197 for(int i=rect.l; i<rect.r; i++){
198 for(int j=rect.b; j<rect.t; j++){
199 ChipmunkCachedTile *tile = GetTileAt(_tileIndex, i, j, size, offset);
200 if(tile) tile.dirty = TRUE;
205 -(void)pushTile:(ChipmunkCachedTile *)tile
209 _cacheHead.next = tile;
210 tile.prev = _cacheHead;
213 _cacheTail = _cacheTail ?: tile;
216 -(void)removeTile:(ChipmunkCachedTile *)tile
218 if(tile.prev == nil && _cacheTail == tile) _cacheTail = tile.next;
219 if(tile.next == nil && _cacheHead == tile) _cacheHead = tile.prev;
221 tile.prev.next = tile.next;
222 tile.next.prev = tile.prev;
223 tile.prev = tile.next = nil;
228 #define LOADING_FACTOR 1.0
230 //extern double GetMilliseconds();
232 -(void)ensureRect:(cpBB)bounds
234 // double time = GetMilliseconds();
236 cpFloat size = _tileSize;
237 cpVect offset = _tileOffset;
238 struct TileRect rect = TileRectForBB(bounds, size, offset, 1.0/(cpFloat)_samplesPerTile);
240 cpBB ensure = BBForTileRect(rect, size, offset);
241 if(!_ensuredDirty && cpBBContainsBB(_ensuredBB, ensure)) return;
244 // printf("Marching tiles in (% 4d, % 4d) - (% 4d, % 4d):\n", l, b, r, t);
245 for(int i=rect.l; i<rect.r; i++){
246 for(int j=rect.b; j<rect.t; j++){
247 // printf("Marching tile (% 4d, % 4d)\n", i, j);
249 ChipmunkCachedTile *tile = GetTileAt(_tileIndex, i, j, size, offset);
252 // Tile does not exist yet, make a new dirty tile.
253 // Let the code below push it into the tile list.
254 tile = [[ChipmunkCachedTile alloc] initWithBB:BBForTileRect((struct TileRect){i, j, i+1, j+1}, size, offset)];
257 cpSpatialIndexInsert(_tileIndex, tile, (cpHashValue)tile);
261 if(tile.dirty) [self marchTile:tile];
263 // Move tile to the front of the cache. (or add it for the first time)
264 [self removeTile:tile];
265 [self pushTile:tile];
270 _ensuredDirty = FALSE;
272 // Remove tiles used the longest ago if over the cache count;
273 NSInteger removeCount = _tileCount - _cacheSize;
274 for(int i=0; i<removeCount; i++){
275 cpSpatialIndexRemove(_tileIndex, _cacheTail, (cpHashValue)_cacheTail);
276 [self removeShapesForTile:_cacheTail];
277 [self removeTile:_cacheTail];
282 // NSLog(@"Updated %3d tiles in %6.3fms", count, GetMilliseconds() - time);
285 -(cpPolyline *)simplify:(cpPolyline *)polyline
288 exceptionWithName:NSInternalInconsistencyException
289 reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
294 -(ChipmunkSegmentShape *)makeSegmentFor:(ChipmunkBody *)staticBody from:(cpVect)a to:(cpVect)b
297 exceptionWithName:NSInternalInconsistencyException
298 reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
307 @implementation ChipmunkBasicTileCache
309 @synthesize simplifyThreshold = _simplifyThreshold;
311 @synthesize segmentRadius = _segmentRadius;
313 @synthesize segmentFriction = _segmentFriction;
314 @synthesize segmentElasticity = _segmentElasticity;
316 @synthesize segmentFilter = _segmentFilter;
318 @synthesize segmentCollisionType = _segmentCollisionType;
320 -(id)initWithSampler:(ChipmunkAbstractSampler *)sampler space:(ChipmunkSpace *)space tileSize:(cpFloat)tileSize samplesPerTile:(NSUInteger)samplesPerTile cacheSize:(NSUInteger)cacheSize
322 if((self = [super initWithSampler:sampler space:space tileSize:tileSize samplesPerTile:samplesPerTile cacheSize:cacheSize])){
323 _simplifyThreshold = 2.0;
325 _segmentRadius = 0.0;
327 _segmentFriction = 1.0;
328 _segmentElasticity = 1.0;
330 _segmentFilter = CP_SHAPE_FILTER_ALL;
332 _segmentCollisionType = (cpCollisionType)0;
338 -(cpPolyline *)simplify:(cpPolyline *)polyline
340 return cpPolylineSimplifyCurves(polyline, _simplifyThreshold);
343 -(ChipmunkSegmentShape *)makeSegmentFor:(ChipmunkBody *)staticBody from:(cpVect)a to:(cpVect)b
345 ChipmunkSegmentShape *segment = [ChipmunkSegmentShape segmentWithBody:staticBody from:a to:b radius:_segmentRadius];
346 segment.friction = _segmentFriction;
347 segment.elasticity = _segmentElasticity;
348 segment.filter = _segmentFilter;
349 segment.collisionType = _segmentCollisionType;