Oolite 1.91.0.7645-241119-222d325
Loading...
Searching...
No Matches
CollisionRegion.m
Go to the documentation of this file.
1/*
2
3CollisionRegion.m
4
5Oolite
6Copyright (C) 2004-2013 Giles C Williams and contributors
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21MA 02110-1301, USA.
22
23*/
24
25#import "CollisionRegion.h"
26#import "OOMaths.h"
27#import "Universe.h"
28#import "Entity.h"
29#import "ShipEntity.h"
30#import "OOSunEntity.h"
31#import "OOPlanetEntity.h"
32#import "StationEntity.h"
33#import "PlayerEntity.h"
34#import "OODebugFlags.h"
35
36
37static BOOL positionIsWithinRegion(HPVector position, CollisionRegion *region);
38static BOOL sphereIsWithinRegion(HPVector position, GLfloat rad, CollisionRegion *region);
39static BOOL positionIsWithinBorders(HPVector position, CollisionRegion *region);
40
41
42@implementation CollisionRegion
43
44// basic alloc/ dealloc routines
45//
46static int crid_counter = 1;
47
48
49- (id) init // Designated initializer.
50{
51 if ((self = [super init]))
52 {
54 entity_array = (Entity **)malloc(max_entities * sizeof(Entity *));
55 if (entity_array == NULL)
56 {
57 [self release];
58 return nil;
59 }
60
62 }
63 return self;
64}
65
66
67- (id) initAsUniverse
68{
69 if ((self = [self init]))
70 {
71 isUniverse = YES;
72 }
73 return self;
74}
75
76
77- (id) initAtLocation:(HPVector)locn withRadius:(GLfloat)rad withinRegion:(CollisionRegion *)otherRegion
78{
79 if ((self = [self init]))
80 {
81 location = locn;
82 radius = rad;
84 parentRegion = otherRegion;
85 }
86 return self;
87}
88
89
90- (void) dealloc
91{
92 free(entity_array);
94
95 [super dealloc];
96}
97
98
99- (NSString *) description
100{
101 return [NSString stringWithFormat:@"<%@ %p>{ID: %d, %lu subregions, %u ents}", [self class], self, crid, [subregions count], n_entities];
102}
103
104
105- (void) clearSubregions
106{
107 [subregions makeObjectsPerformSelector:@selector(clearSubregions)];
108 [subregions removeAllObjects];
109}
110
111
112- (void) addSubregionAtPosition:(HPVector)pos withRadius:(GLfloat)rad
113{
114 // check if this can be fitted within any of the subregions
115 //
116 CollisionRegion *sub = nil;
117 foreach (sub, subregions)
118 {
119 if (sphereIsWithinRegion(pos, rad, sub))
120 {
121 // if it fits, put it in!
123 return;
124 }
125 if (positionIsWithinRegion(pos, sub))
126 {
127 // crosses the border of this region already - leave it out
128 return;
129 }
130 }
131 // no subregion fit - move on...
132 //
133 sub = [[CollisionRegion alloc] initAtLocation:pos withRadius:rad withinRegion:self];
134 if (subregions == nil) subregions = [[NSMutableArray alloc] initWithCapacity:32];
135 [subregions addObject:sub];
136 [sub release];
137}
138
139
140// update routines to check if a position is within the radius or within its borders
141//
142static BOOL positionIsWithinRegion(HPVector position, CollisionRegion *region)
143{
144 if (region == nil) return NO;
145 if (region->isUniverse) return YES;
146
147 HPVector loc = region->location;
148 GLfloat r1 = region->radius;
149
150 if ((position.x < loc.x - r1)||(position.x > loc.x + r1)||
151 (position.y < loc.y - r1)||(position.y > loc.y + r1)||
152 (position.z < loc.z - r1)||(position.z > loc.z + r1))
153 {
154 return NO;
155 }
156
157 return YES;
158}
159
160
161static BOOL sphereIsWithinRegion(HPVector position, GLfloat rad, CollisionRegion *region)
162{
163 if (region == nil) return NO;
164 if (region->isUniverse) return YES;
165
166 HPVector loc = region->location;
167 GLfloat r1 = region->radius;
168
169 if ((position.x - rad < loc.x - r1)||(position.x + rad > loc.x + r1)||
170 (position.y - rad < loc.y - r1)||(position.y + rad > loc.y + r1)||
171 (position.z - rad < loc.z - r1)||(position.z + rad > loc.z + r1))
172 {
173 return NO;
174 }
175
176 return YES;
177}
178
179
180static BOOL positionIsWithinBorders(HPVector position, CollisionRegion *region)
181{
182 if (region == nil) return NO;
183 if (region->isUniverse) return YES;
184
185 HPVector loc = region->location;
186 GLfloat r1 = region->radius + region->border_radius;
187
188 if ((position.x < loc.x - r1)||(position.x > loc.x + r1)||
189 (position.y < loc.y - r1)||(position.y > loc.y + r1)||
190 (position.z < loc.z - r1)||(position.z > loc.z + r1))
191 {
192 return NO;
193 }
194
195 return YES;
196}
197
198
199// collision checking
200//
201- (void) clearEntityList
202{
203 [subregions makeObjectsPerformSelector:@selector(clearEntityList)];
204 n_entities = 0;
205 isPlayerInRegion = NO;
206}
207
208
209- (void) addEntity:(Entity *)ent
210{
211 // expand if necessary
212 //
214 {
215 max_entities = 1 + max_entities * 2;
216 Entity **new_store = (Entity **)realloc(entity_array, max_entities * sizeof(Entity *));
217 if (new_store == NULL)
218 {
219 [NSException raise:NSMallocException format:@"Not enough memory to grow collision region member list."];
220 }
221
222 entity_array = new_store;
223 }
224
225 if ([ent isPlayer]) isPlayerInRegion = YES;
226 entity_array[n_entities++] = ent;
227}
228
229
230- (BOOL) checkEntity:(Entity *)ent
231{
232 HPVector position = ent->position;
233
234 // check subregions
235 CollisionRegion *sub = nil;
236 foreach (sub, subregions)
237 {
238 if (positionIsWithinBorders(position, sub) && [sub checkEntity:ent])
239 {
240 return YES;
241 }
242 }
243
244 if (!positionIsWithinBorders(position, self))
245 {
246 return NO;
247 }
248
249 [self addEntity:ent];
250 [ent setCollisionRegion:self];
251 return YES;
252}
253
254
255- (void) findCollisions
256{
257 // test for collisions in each subregion
258 [subregions makeObjectsPerformSelector:@selector(findCollisions)];
259
260 // reject trivial cases
261 if (n_entities < 2) return;
262
263 //
264 // According to Shark, when this was in Universe this was where Oolite spent most time!
265 //
266 Entity *e1, *e2;
267 HPVector p1;
268 double dist2, r1, r2, r0, min_dist2;
269 unsigned i;
270 Entity *entities_to_test[n_entities];
271
272 // only check unfiltered entities
273 unsigned n_entities_to_test = 0;
274 for (i = 0; i < n_entities; i++)
275 {
276 e1 = entity_array[i];
277 if (e1->collisionTestFilter != 3)
278 {
279 entities_to_test[n_entities_to_test++] = e1;
280 }
281 }
282
283#ifndef NDEBUG
285 {
286 OOLog(@"collisionRegion.debug", @"DEBUG in collision region %@ testing %d out of %d entities", self, n_entities_to_test, n_entities);
287 }
288#endif
289
290 if (n_entities_to_test < 2) return;
291
292 // clear collision variables
293 //
294 for (i = 0; i < n_entities_to_test; i++)
295 {
296 e1 = entities_to_test[i];
297 if (e1->hasCollided)
298 {
299 [[e1 collisionArray] removeAllObjects];
300 e1->hasCollided = NO;
301 }
302 if (e1->isShip)
303 {
304 [(ShipEntity*)e1 setProximityAlert:nil];
305 }
306 e1->collider = nil;
307 }
308
311
312 // test each entity in this region against the entities in its collision chain
313 //
314 for (i = 0; i < n_entities_to_test; i++)
315 {
316 e1 = entities_to_test[i];
317 p1 = e1->position;
318 r1 = e1->collision_radius;
319
320
321
322 // check against the first in the collision chain
323 e2 = e1->collision_chain;
324 while (e2 != nil)
325 {
327 if (e1->isShip && e2->isShip &&
328 [(ShipEntity *)e1 collisionExceptedFor:(ShipEntity *)e2])
329 {
330 // nothing happens
331 }
332 else
333 {
334
335 r2 = e2->collision_radius;
336 r0 = r1 + r2;
337 dist2 = HPdistance2(e2->position, p1);
338 min_dist2 = r0 * r0;
339 if (dist2 < PROXIMITY_WARN_DISTANCE2 * min_dist2)
340 {
341#ifndef NDEBUG
343 {
344 OOLog(@"collisionRegion.debug", @"DEBUG Testing collision between %@ (%@) and %@ (%@)",
345 e1, (e1->collisionTestFilter==3)?@"YES":@"NO", e2, (e2->collisionTestFilter==3)?@"YES":@"NO");
346 }
347#endif
349
350 if (e1->isShip && e2->isShip)
351 {
352 if ((dist2 < PROXIMITY_WARN_DISTANCE2 * r2 * r2) || (dist2 < PROXIMITY_WARN_DISTANCE2 * r1 * r1))
353 {
354 [(ShipEntity*)e1 setProximityAlert:(ShipEntity*)e2];
355 [(ShipEntity*)e2 setProximityAlert:(ShipEntity*)e1];
356 }
357
358 if (dist2 >= min_dist2)
359 {
360 if (e1->isStation)
361 {
362 StationEntity* se1 = (StationEntity *)e1;
363 [se1 shipIsInDockingCorridor:(ShipEntity *)e2];
364 }
365 else if (e2->isStation)
366 {
367 StationEntity* se2 = (StationEntity *)e2;
368 [se2 shipIsInDockingCorridor:(ShipEntity *)e1];
369 }
370 }
371
372 }
373 if (dist2 < min_dist2)
374 {
375 BOOL collision = NO;
376
377 if (e1->isStation)
378 {
379 StationEntity* se1 = (StationEntity *)e1;
380 if ([se1 shipIsInDockingCorridor:(ShipEntity *)e2])
381 {
382 collision = NO;
383 }
384 else
385 {
386 collision = [e1 checkCloseCollisionWith:e2];
387 }
388 }
389 else if (e2->isStation)
390 {
391 StationEntity* se2 = (StationEntity *)e2;
392 if ([se2 shipIsInDockingCorridor:(ShipEntity *)e1])
393 {
394 collision = NO;
395 }
396 else
397 {
398 collision = [e2 checkCloseCollisionWith:e1];
399 }
400 }
401 else
402 {
403 collision = [e1 checkCloseCollisionWith:e2];
404 }
405
406 if (collision)
407 {
408 // now we have no need to check the e2-e1 collision
409 if (e1->collider)
410 {
411 [[e1 collisionArray] addObject:e1->collider];
412 }
413 else
414 {
415 [[e1 collisionArray] addObject:e2];
416 }
417 e1->hasCollided = YES;
418
419 if (e2->collider)
420 {
421 [[e2 collisionArray] addObject:e2->collider];
422 }
423 else
424 {
425 [[e2 collisionArray] addObject:e1];
426 }
427 e2->hasCollided = YES;
428 }
429 }
430 }
431 }
432 // check the next in the collision chain
433 e2 = e2->collision_chain;
434 }
435 }
436
437#ifndef NDEBUG
439 {
440 OOLog(@"collisionRegion.debug",@"Collision test checks %d, within range %d, for %d entities",checks_this_tick,checks_within_range,n_entities_to_test);
441 }
442#endif
443}
444
445
446// an outValue of 1 means it's just being occluded.
447static BOOL entityByEntityOcclusionToValue(Entity *e1, Entity *e2, OOSunEntity *the_sun, float *outValue)
448{
449 if (EXPECT_NOT(e1 == e2))
450 {
451 // you can't shade self
452 return NO;
453 }
454 return shadowAtPointOcclusionToValue(e1->position,e1->collision_radius,e2,the_sun,outValue);
455}
456
457// an outValue of 1 means it's just being occluded.
458BOOL shadowAtPointOcclusionToValue(HPVector e1pos, GLfloat e1rad, Entity *e2, OOSunEntity *the_sun, float *outValue)
459{
460 *outValue = 1.5f; // initial 'fully lit' value
461
462 GLfloat cr_e2;
463 if ([e2 isShip])
464 {
465 cr_e2 = e2->collision_radius * 0.90f;
466 // 10% smaller shadow for ships
467 }
468 else
469 {
470 cr_e2 = e2->collision_radius;
471 }
472 if (cr_e2 < e1rad)
473 {
474 // smaller can't shade bigger
475 return NO;
476 }
477
478 // tested in construction of e2 list
479// if (e2->isSunlit == NO)
480// return NO; // things already /in/ shade can't shade things more.
481 //
482 // check projected sizes of discs
483 GLfloat d2_sun = HPdistance2(e1pos, the_sun->position);
484 GLfloat d2_e2sun = HPdistance2(e2->position, the_sun->position);
485 GLfloat d2_e2 = HPdistance2( e1pos, e2->position);
486
487 if (d2_e2sun > d2_sun)
488 {
489 // you are nearer the sun than the potential occluder, so it
490 // probably can't shade you
491 if (d2_e2 < cr_e2 * cr_e2 && [e2 isShip])
492 {
493 // exception: if within the collision radius of the other
494 // object, might still be shadowed by it.
495 GLfloat bbx = 0.0f, bby = 0.0f, bbz = 0.0f;
496 BoundingBox bb = [(ShipEntity*)e2 totalBoundingBox];
497 bounding_box_get_dimensions(bb,&bbx,&bby,&bbz);
498 float minbb = bbx;
499 if (bby < minbb) { minbb = bby; }
500 if (bbz < minbb) { minbb = bbz; }
501 minbb -= e1rad; // subtract object's size
502 /* closer to the object than the shortest axis. This check
503 * branch is basically for docking at a rock hermit facing
504 * away from the sun, but it checks the shortest bounding
505 * box size rather than the collision radius to avoid
506 * getting weird shadowing effects around large planar
507 * entities like the OXP Torus Station.
508 *
509 * Well... more weird shadowing effects than there already
510 * are, anyway.
511 *
512 * There are more accurate ways to check "sphere inside
513 * bounding box" but this seems accurate enough and is
514 * simpler.
515 *
516 * - CIM
517 */
518 if (d2_e2 < minbb * minbb)
519 {
520 *outValue = 0.1;
521 return YES;
522 }
523 }
524 return NO;
525 }
526
527 GLfloat cr_sun = the_sun->collision_radius;
528
529 GLfloat cr2_sun_scaled = cr_sun * cr_sun * d2_e2 / d2_sun;
530 if (cr_e2 * cr_e2 < cr2_sun_scaled)
531 {
532 // if solar disc projected to the distance of e2 > collision radius it can't be shaded by e2
533 return NO;
534 }
535
536 // check angles subtended by sun and occluder
537 // double theta_sun = asin( cr_sun / sqrt(d2_sun)); // 1/2 angle subtended by sun
538 // double theta_e2 = asin( cr_e2 / sqrt(d2_e2)); // 1/2 angle subtended by e2
539 // find the difference between the angles subtended by occluder and sun
540 float d2_e = sqrt(d2_e2);
541 float theta_diff;
542 if (d2_e < cr_e2)
543 {
544 // then we're "inside" the object. Calculate as if we were on
545 // the edge of it to avoid taking asin(x>1)
546 theta_diff = asin(1) - asin(cr_sun / sqrt(d2_sun));
547 }
548 else
549 {
550 theta_diff = asin(cr_e2 / d2_e) - asin(cr_sun / sqrt(d2_sun));
551 }
552
553 HPVector p_sun = the_sun->position;
554 HPVector p_e2 = e2->position;
555 HPVector p_e1 = e1pos;
556 Vector v_sun = HPVectorToVector(HPvector_subtract(p_sun, p_e1));
557 v_sun = vector_normal_or_zbasis(v_sun);
558
559 Vector v_e2 = HPVectorToVector(HPvector_subtract(p_e2, p_e1));
560 v_e2 = vector_normal_or_xbasis(v_e2);
561
562 float phi = acos(dot_product(v_sun, v_e2)); // angle between sun and e2 from e1's viewpoint
563 *outValue = (phi / theta_diff); // 1 means just occluded, < 1 means in shadow
564
565 if (phi > theta_diff)
566 {
567 // sun is not occluded
568 return NO;
569 }
570
571 // all tests done e1 is in shade!
572 return YES;
573}
574
575
576static inline BOOL testEntityOccludedByEntity(Entity *e1, Entity *e2, OOSunEntity *the_sun)
577{
578 float tmp; // we're not interested in the amount of occlusion just now.
579 return entityByEntityOcclusionToValue(e1, e2, the_sun, &tmp);
580}
581
582
584{
585 // reject trivial cases
586 if (n_entities < 2) return;
587
588 //
589 // Copy/pasting the collision code to detect occlusion!
590 //
591 unsigned i, j;
592
593 if ([UNIVERSE reducedDetail]) return; // don't do this in reduced detail mode
594
595 OOSunEntity* the_sun = [UNIVERSE sun];
596
597 if (the_sun == nil)
598 {
599 return; // sun is required
600 }
601
602 unsigned ent_count = UNIVERSE->n_entities;
603 Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list
604 Entity *planets[ent_count];
605 unsigned n_planets = 0;
606 Entity *ships[ent_count];
607 unsigned n_ships = 0;
608
609 for (i = 0; i < ent_count; i++)
610 {
611 if (uni_entities[i]->isSunlit)
612 {
613 // get a list of planet entities because they can shade across regions
614 if ([uni_entities[i] isPlanet])
615 {
616 // don't bother retaining - nothing will happen to them!
617 planets[n_planets++] = uni_entities[i];
618 }
619
620 // and a list of shipentities large enough that they might cast a noticeable shadow
621 // if we can't see it, it can't be shadowing anything important
622 else if ([uni_entities[i] isShip] &&
623 [uni_entities[i] isVisible] &&
624 uni_entities[i]->collision_radius >= MINIMUM_SHADOWING_ENTITY_RADIUS)
625 {
626 ships[n_ships++] = uni_entities[i]; // don't bother retaining - nothing will happen to them!
627 }
628 }
629 }
630
631 // test for shadows in each subregion
632 [subregions makeObjectsPerformSelector:@selector(findShadowedEntities)];
633
634 // test each entity in this region against the others
635 for (i = 0; i < n_entities; i++)
636 {
637 Entity *e1 = entity_array[i];
638 if (![e1 isVisible])
639 {
640 continue; // don't check shading of objects we can't see
641 }
642 BOOL occluder_moved = NO;
643 if ([e1 status] == STATUS_COCKPIT_DISPLAY)
644 {
645 e1->isSunlit = YES;
647 continue; // don't check shading in demo mode
648 }
649 Entity *occluder = nil;
650 if (e1->isSunlit == NO)
651 {
652 occluder = [UNIVERSE entityForUniversalID:e1->shadingEntityID];
653 if (occluder != nil)
654 {
655 occluder_moved = occluder->hasMoved;
656 }
657 }
658 if (([e1 isShip] ||[e1 isPlanet]) && (e1->hasMoved || occluder_moved))
659 {
660 e1->isSunlit = YES; // sunlit by default
661 e1->shadingEntityID = NO_TARGET;
662 //
663 // check demo mode here..
664 if ([e1 isPlayer] && ([(PlayerEntity*)e1 showDemoShips]))
665 {
666 continue; // don't check shading in demo mode
667 }
668
669 // test last occluder (most likely case)
670 if (occluder)
671 {
672 if (testEntityOccludedByEntity(e1, occluder, the_sun))
673 {
674 e1->isSunlit = NO;
675 e1->shadingEntityID = [occluder universalID];
676 }
677 }
678 if (!e1->isSunlit)
679 {
680 // no point in continuing tests
681 continue;
682 }
683
684 // test planets
685 for (j = 0; j < n_planets; j++)
686 {
687 float occlusionNumber;
688 if (entityByEntityOcclusionToValue(e1, planets[j], the_sun, &occlusionNumber))
689 {
690 e1->isSunlit = NO;
691 e1->shadingEntityID = [planets[j] universalID];
692 break;
693 }
694 if ([e1 isPlayer])
695 {
696 [(PlayerEntity *)e1 setOcclusionLevel:occlusionNumber];
697 }
698 }
699 if (!e1->isSunlit)
700 {
701 // no point in continuing tests
702 continue;
703 }
704
705 // test local entities
706 for (j = 0; j < n_ships; j++)
707 {
708 if (testEntityOccludedByEntity(e1, ships[j], the_sun))
709 {
710 e1->isSunlit = NO;
711 e1->shadingEntityID = [ships[j] universalID];
712 break;
713 }
714 }
715 }
716 }
717}
718
719
720- (NSString *) collisionDescription
721{
722 return [NSString stringWithFormat:@"p%u - c%u", checks_this_tick, checks_within_range];
723}
724
725
726- (NSString *) debugOut
727{
728 NSMutableString *result = [[NSMutableString alloc] initWithFormat:@"%d:", n_entities];
729 CollisionRegion *sub = nil;
730 foreach (sub, subregions)
731 {
732 [result appendString:[sub debugOut]];
733 }
734 return [result autorelease];
735}
736
737@end
NSUInteger gDebugFlags
Definition main.m:7
#define MINIMUM_SHADOWING_ENTITY_RADIUS
#define COLLISION_MAX_ENTITIES
#define COLLISION_REGION_BORDER_RADIUS
BOOL shadowAtPointOcclusionToValue(HPVector e1pos, GLfloat e1rad, Entity *e2, OOSunEntity *the_sun, float *outValue)
static int crid_counter
static BOOL positionIsWithinBorders(HPVector position, CollisionRegion *region)
static BOOL positionIsWithinRegion(HPVector position, CollisionRegion *region)
static BOOL sphereIsWithinRegion(HPVector position, GLfloat rad, CollisionRegion *region)
#define DESTROY(x)
Definition OOCocoa.h:77
@ DEBUG_COLLISIONS
Definition OODebugFlags.h:7
#define EXPECT_NOT(x)
#define OOLog(class, format,...)
Definition OOLogging.h:88
return nil
@ NO_TARGET
Definition OOTypes.h:194
#define UNIVERSE
Definition Universe.h:833
#define PROXIMITY_WARN_DISTANCE2
Definition Universe.h:108
NSString * description()
NSString * collisionDescription()
NSString * debugOut()
static BOOL positionIsWithinBorders(HPVector position, CollisionRegion *region)
static BOOL positionIsWithinRegion(HPVector position, CollisionRegion *region)
void addSubregionAtPosition:withRadius:(HPVector pos,[withRadius] GLfloat rad)
static BOOL entityByEntityOcclusionToValue(Entity *e1, Entity *e2, OOSunEntity *the_sun, float *outValue)
NSMutableArray * subregions
CollisionRegion * parentRegion
Entity ** entity_array
unsigned checks_this_tick
static BOOL sphereIsWithinRegion(HPVector position, GLfloat rad, CollisionRegion *region)
unsigned checks_within_range
static BOOL testEntityOccludedByEntity(Entity *e1, Entity *e2, OOSunEntity *the_sun)
BOOL shadowAtPointOcclusionToValue(HPVector e1pos, GLfloat e1rad, Entity *e2, OOSunEntity *the_sun, float *outValue)
GLfloat collision_radius
Definition Entity.h:111
OOUniversalID universalID
Definition Entity.h:89
NSMutableArray * collisionArray()
Definition Entity.m:923
unsigned hasMoved
Definition Entity.h:96
unsigned isShip
Definition Entity.h:91
unsigned collisionTestFilter
Definition Entity.h:100
unsigned hasCollided
Definition Entity.h:98
unsigned isStation
Definition Entity.h:92
HPVector position
Definition Entity.h:112
OOUniversalID shadingEntityID
Definition Entity.h:126
BOOL checkCloseCollisionWith:(Entity *other)
Definition Entity.m:971
Entity * collider
Definition Entity.h:128
Entity * collision_chain
Definition Entity.h:124
void setCollisionRegion:(CollisionRegion *region)
Definition Entity.m:539
unsigned isSunlit
Definition Entity.h:99
BOOL checkCloseCollisionWith:(Entity *other)
void setProximityAlert:(ShipEntity *targetEntity)
BOOL shipIsInDockingCorridor:(ShipEntity *ship)