1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #include "Space.h"
5 
6 #include "Body.h"
7 #include "CityOnPlanet.h"
8 #include "Frame.h"
9 #include "Game.h"
10 #include "GameSaveError.h"
11 #include "HyperspaceCloud.h"
12 #include "Lang.h"
13 #include "MathUtil.h"
14 #include "Pi.h"
15 #include "Planet.h"
16 #include "Player.h"
17 #include "SpaceStation.h"
18 #include "Star.h"
19 #include "SystemView.h"
20 #include "collider/CollisionContact.h"
21 #include "collider/CollisionSpace.h"
22 #include "galaxy/Galaxy.h"
23 #include "graphics/Graphics.h"
24 #include "lua/LuaEvent.h"
25 #include "lua/LuaTimer.h"
26 #include <algorithm>
27 #include <functional>
28 
29 //#define DEBUG_CACHE
30 
RelocateStarportIfNecessary(SystemBody * sbody,Planet * planet,vector3d & pos,matrix3x3d & rot,const std::vector<vector3d> & prevPositions)31 static void RelocateStarportIfNecessary(SystemBody *sbody, Planet *planet, vector3d &pos, matrix3x3d &rot, const std::vector<vector3d> &prevPositions)
32 {
33 	const double radius = planet->GetSystemBody()->GetRadius();
34 
35 	// suggested position
36 	rot = sbody->GetOrbit().GetPlane();
37 	pos = rot * vector3d(0, 1, 0);
38 
39 	// Check if height varies too much around the starport center
40 	// by sampling 6 points around it. try upto 100 new positions randomly until a match is found
41 	// this is not guaranteed to find a match but greatly increases the chancessteroids which are not too steep.
42 
43 	bool variationWithinLimits = true;
44 	double bestVariation = 1e10; // any high value
45 	matrix3x3d rotNotUnderwaterWithLeastVariation = rot;
46 	vector3d posNotUnderwaterWithLeastVariation = pos;
47 	const double heightVariationCheckThreshold = 0.008;					 // max variation to radius radius ratio to check for local slope, ganymede is around 0.01
48 	const double terrainHeightVariation = planet->GetMaxFeatureRadius(); //in radii
49 
50 	//Output("%s: terrain height variation %f\n", sbody->name.c_str(), terrainHeightVariation);
51 
52 	// 6 points are sampled around the starport center by adding/subtracting delta to to coords
53 	// points must stay within max height variation to be accepted
54 	//    1. delta should be chosen such that it a distance from the starport center that encloses landing pads for the largest starport
55 	//    2. maxSlope should be set so maxHeightVariation is less than the height of the landing pads
56 	const double delta = 20.0 / radius;							 // in radii
57 	const double maxSlope = 0.2;								 // 0.0 to 1.0
58 	const double maxHeightVariation = maxSlope * delta * radius; // in m
59 
60 	matrix3x3d rot_ = rot;
61 	vector3d pos_ = pos;
62 
63 	const bool manualRelocationIsEasy = !(planet->GetSystemBody()->GetType() == SystemBody::TYPE_PLANET_ASTEROID || terrainHeightVariation > heightVariationCheckThreshold);
64 
65 	// warn and leave it up to the user to relocate custom starports when it's easy to relocate manually, i.e. not on asteroids and other planets which are likely to have high variation in a lot of places
66 	const bool isRelocatableIfBuried = !(sbody->IsCustomBody() && manualRelocationIsEasy);
67 
68 	bool isInitiallyUnderwater = false;
69 	bool initialVariationTooHigh = false;
70 
71 	Random r(sbody->GetSeed());
72 
73 	for (int tries = 0; tries < 200; tries++) {
74 		variationWithinLimits = true;
75 
76 		const double height = planet->GetTerrainHeight(pos_) - radius; // in m
77 
78 		// check height at 6 points around the starport center stays within variation tolerances
79 		// GetHeight gives a varying height field in 3 dimensions.
80 		// Given it's smoothly varying it's fine to sample it in arbitary directions to get an idea of how sharply it varies
81 		double v[6];
82 		v[0] = fabs(planet->GetTerrainHeight(vector3d(pos_.x + delta, pos_.y, pos_.z)) - radius - height);
83 		v[1] = fabs(planet->GetTerrainHeight(vector3d(pos_.x - delta, pos_.y, pos_.z)) - radius - height);
84 		v[2] = fabs(planet->GetTerrainHeight(vector3d(pos_.x, pos_.y, pos_.z + delta)) - radius - height);
85 		v[3] = fabs(planet->GetTerrainHeight(vector3d(pos_.x, pos_.y, pos_.z - delta)) - radius - height);
86 		v[4] = fabs(planet->GetTerrainHeight(vector3d(pos_.x, pos_.y + delta, pos_.z)) - radius - height);
87 		v[5] = fabs(planet->GetTerrainHeight(vector3d(pos_.x, pos_.y - delta, pos_.z)) - radius - height);
88 
89 		// break if variation for all points is within limits
90 		double variationMax = 0.0;
91 		for (int i = 0; i < 6; i++) {
92 			variationWithinLimits = variationWithinLimits && (v[i] < maxHeightVariation);
93 			variationMax = (v[i] > variationMax) ? v[i] : variationMax;
94 		}
95 
96 		// check if underwater
97 		const bool starportUnderwater = (height <= 0.0);
98 
99 		//Output("%s: try no: %i, Match found: %i, best variation in previous results %f, variationMax this try: %f, maxHeightVariation: %f, Starport is underwater: %i\n",
100 		//	sbody->name.c_str(), tries, (variationWithinLimits && !starportUnderwater), bestVariation, variationMax, maxHeightVariation, starportUnderwater);
101 
102 		bool tooCloseToOther = false;
103 		for (vector3d oldPos : prevPositions) {
104 			// is the distance between points less than the delta distance?
105 			if ((pos_ - oldPos).LengthSqr() < (delta * delta)) {
106 				tooCloseToOther = true; // then we're too close so try again
107 				break;
108 			}
109 		}
110 
111 		if (tries == 0) {
112 			isInitiallyUnderwater = starportUnderwater;
113 			initialVariationTooHigh = !variationWithinLimits;
114 		}
115 
116 		if (!starportUnderwater && variationMax < bestVariation) {
117 			bestVariation = variationMax;
118 			posNotUnderwaterWithLeastVariation = pos_;
119 			rotNotUnderwaterWithLeastVariation = rot_;
120 		}
121 
122 		if (variationWithinLimits && !starportUnderwater && !tooCloseToOther)
123 			break;
124 
125 		// try new random position
126 		const double r3 = r.Double();
127 		const double r2 = r.Double(); // function parameter evaluation order is implementation-dependent
128 		const double r1 = r.Double(); // can't put two rands in the same expression
129 		rot_ = matrix3x3d::RotateZ(2.0 * M_PI * r1) * matrix3x3d::RotateY(2.0 * M_PI * r2) * matrix3x3d::RotateX(2.0 * M_PI * r3);
130 		pos_ = rot_ * vector3d(0, 1, 0);
131 	}
132 
133 	if (isInitiallyUnderwater || (isRelocatableIfBuried && initialVariationTooHigh)) {
134 		pos = posNotUnderwaterWithLeastVariation;
135 		rot = rotNotUnderwaterWithLeastVariation;
136 	}
137 
138 	if (sbody->IsCustomBody()) {
139 		const SystemPath &p = sbody->GetPath();
140 		if (initialVariationTooHigh) {
141 			if (isRelocatableIfBuried) {
142 				Output("Warning: Lua custom Systems definition: Surface starport has been automatically relocated. This is in order to place it on flatter ground to reduce the chance of landing pads being buried. This is not an error as such and you may attempt to move the starport to another location by changing latitude and longitude fields.\n      Surface starport name: %s, Body name: %s, In sector: x = %i, y = %i, z = %i.\n",
143 					sbody->GetName().c_str(), sbody->GetParent()->GetName().c_str(), p.sectorX, p.sectorY, p.sectorZ);
144 			} else {
145 				Output("Warning: Lua custom Systems definition: Surface starport may have landing pads buried. The surface starport has not been automatically relocated as the planet appears smooth enough to manually relocate easily. This is not an error as such and you may attempt to move the starport to another location by changing latitude and longitude fields.\n      Surface starport name: %s, Body name: %s, In sector: x = %i, y = %i, z = %i.\n",
146 					sbody->GetName().c_str(), sbody->GetParent()->GetName().c_str(), p.sectorX, p.sectorY, p.sectorZ);
147 			}
148 		}
149 		if (isInitiallyUnderwater) {
150 			Output("Error: Lua custom Systems definition: Surface starport is underwater (height not greater than 0.0) and has been automatically relocated. Please move the starport to another location by changing latitude and longitude fields.\n      Surface starport name: %s, Body name: %s, In sector: x = %i, y = %i, z = %i.\n",
151 				sbody->GetName().c_str(), sbody->GetParent()->GetName().c_str(), p.sectorX, p.sectorY, p.sectorZ);
152 		}
153 	}
154 }
155 
Prepare()156 void Space::BodyNearFinder::Prepare()
157 {
158 	PROFILE_SCOPED()
159 	m_bodyDist.clear();
160 
161 	for (Body *b : m_space->GetBodies())
162 		m_bodyDist.emplace_back(b, b->GetPositionRelTo(m_space->GetRootFrame()).Length());
163 
164 	std::sort(m_bodyDist.begin(), m_bodyDist.end());
165 }
166 
GetBodiesMaybeNear(const Body * b,double dist)167 Space::BodyNearList Space::BodyNearFinder::GetBodiesMaybeNear(const Body *b, double dist)
168 {
169 	return GetBodiesMaybeNear(b->GetPositionRelTo(m_space->GetRootFrame()), dist);
170 }
171 
GetBodiesMaybeNear(const vector3d & pos,double dist)172 Space::BodyNearList Space::BodyNearFinder::GetBodiesMaybeNear(const vector3d &pos, double dist)
173 {
174 	if (m_bodyDist.empty()) {
175 		m_nearBodies.clear();
176 		return std::move(m_nearBodies);
177 	}
178 
179 	const double len = pos.Length();
180 
181 	std::vector<BodyDist>::const_iterator min = std::lower_bound(m_bodyDist.begin(), m_bodyDist.end(), len - dist);
182 	std::vector<BodyDist>::const_iterator max = std::upper_bound(min, m_bodyDist.cend(), len + dist);
183 
184 	m_nearBodies.clear();
185 	m_nearBodies.reserve(max - min);
186 
187 	std::for_each(min, max, [&](BodyDist const &bd) { m_nearBodies.push_back(bd.body); });
188 
189 	return std::move(m_nearBodies);
190 }
191 
Space(Game * game,RefCountedPtr<Galaxy> galaxy,Space * oldSpace)192 Space::Space(Game *game, RefCountedPtr<Galaxy> galaxy, Space *oldSpace) :
193 	m_starSystemCache(oldSpace ? oldSpace->m_starSystemCache : galaxy->NewStarSystemSlaveCache()),
194 	m_game(game),
195 	m_bodyIndexValid(false),
196 	m_sbodyIndexValid(false),
197 	m_bodyNearFinder(this)
198 #ifndef NDEBUG
199 	,
200 	m_processingFinalizationQueue(false)
201 #endif
202 {
203 	m_background.reset(new Background::Container(Pi::renderer, Pi::rng, this, m_game->GetGalaxy()));
204 
205 	m_rootFrameId = Frame::CreateFrame(FrameId::Invalid, Lang::SYSTEM, Frame::FLAG_DEFAULT, FLT_MAX);
206 
207 	GenSectorCache(galaxy, &game->GetHyperspaceDest());
208 }
209 
Space(Game * game,RefCountedPtr<Galaxy> galaxy,const SystemPath & path,Space * oldSpace)210 Space::Space(Game *game, RefCountedPtr<Galaxy> galaxy, const SystemPath &path, Space *oldSpace) :
211 	m_starSystemCache(oldSpace ? oldSpace->m_starSystemCache : galaxy->NewStarSystemSlaveCache()),
212 	m_starSystem(galaxy->GetStarSystem(path)),
213 	m_game(game),
214 	m_bodyIndexValid(false),
215 	m_sbodyIndexValid(false),
216 	m_bodyNearFinder(this)
217 #ifndef NDEBUG
218 	,
219 	m_processingFinalizationQueue(false)
220 #endif
221 {
222 	PROFILE_SCOPED()
223 	Uint32 _init[5] = { path.systemIndex, Uint32(path.sectorX), Uint32(path.sectorY), Uint32(path.sectorZ), UNIVERSE_SEED };
224 	Random rand(_init, 5);
225 	m_background.reset(new Background::Container(Pi::renderer, rand, this, m_game->GetGalaxy()));
226 
227 	CityOnPlanet::SetCityModelPatterns(m_starSystem->GetPath());
228 
229 	m_rootFrameId = Frame::CreateFrame(FrameId::Invalid, Lang::SYSTEM, Frame::FLAG_DEFAULT, FLT_MAX);
230 
231 	std::vector<vector3d> positionAccumulator;
232 	GenBody(m_game->GetTime(), m_starSystem->GetRootBody().Get(), m_rootFrameId, positionAccumulator);
233 	Frame::UpdateOrbitRails(m_game->GetTime(), m_game->GetTimeStep());
234 
235 	GenSectorCache(galaxy, &path);
236 }
237 
Space(Game * game,RefCountedPtr<Galaxy> galaxy,const Json & jsonObj,double at_time)238 Space::Space(Game *game, RefCountedPtr<Galaxy> galaxy, const Json &jsonObj, double at_time) :
239 	m_starSystemCache(galaxy->NewStarSystemSlaveCache()),
240 	m_game(game),
241 	m_bodyIndexValid(false),
242 	m_sbodyIndexValid(false),
243 	m_bodyNearFinder(this)
244 #ifndef NDEBUG
245 	,
246 	m_processingFinalizationQueue(false)
247 #endif
248 {
249 	PROFILE_SCOPED()
250 	Json spaceObj = jsonObj["space"];
251 
252 	m_starSystem = StarSystem::FromJson(galaxy, spaceObj);
253 
254 	const SystemPath &path = m_starSystem->GetPath();
255 	Uint32 _init[5] = { path.systemIndex, Uint32(path.sectorX), Uint32(path.sectorY), Uint32(path.sectorZ), UNIVERSE_SEED };
256 	Random rand(_init, 5);
257 	m_background.reset(new Background::Container(Pi::renderer, rand, this, m_game->GetGalaxy()));
258 
259 	RebuildSystemBodyIndex();
260 
261 	CityOnPlanet::SetCityModelPatterns(m_starSystem->GetPath());
262 
263 	if (!spaceObj.count("frame")) throw SavedGameCorruptException();
264 	m_rootFrameId = Frame::FromJson(spaceObj["frame"], this, FrameId::Invalid, at_time);
265 
266 	try {
267 		Json bodyArray = spaceObj["bodies"].get<Json::array_t>();
268 		for (Uint32 i = 0; i < bodyArray.size(); i++)
269 			m_bodies.push_back(Body::FromJson(bodyArray[i], this));
270 	} catch (Json::type_error &) {
271 		throw SavedGameCorruptException();
272 	}
273 
274 	RebuildBodyIndex();
275 
276 	Frame::PostUnserializeFixup(m_rootFrameId, this);
277 	for (Body *b : m_bodies)
278 		b->PostLoadFixup(this);
279 
280 	// some spaceports could be moved, now their physical bodies were loaded from
281 	// json, with offsets, now we should move the system bodies also so that
282 	// there is no mismatch
283 	for (auto sbody : m_starSystem->GetBodies()) {
284 		if (sbody->GetSuperType() == SystemBody::SUPERTYPE_ROCKY_PLANET) {
285 			// needs a clean posAccum for each planet
286 			std::vector<vector3d> posAccum;
287 			SystemPath planet_path = m_starSystem->GetPathOf(sbody.Get());
288 			Planet *planet = static_cast<Planet *>(FindBodyForPath(&planet_path));
289 			for (auto kid : sbody->GetChildren()) {
290 				if (kid->GetType() == SystemBody::TYPE_STARPORT_SURFACE) {
291 					// out arguments
292 					matrix3x3d rot;
293 					vector3d pos;
294 					RelocateStarportIfNecessary(kid, planet, pos, rot, posAccum);
295 					// the surface starport's location is stored in its "orbit", as orientation matrix
296 					kid->SetOrbitPlane(rot);
297 					// accumulate for testing against
298 					posAccum.push_back(pos);
299 				}
300 			}
301 		}
302 	}
303 
304 	GenSectorCache(galaxy, &path);
305 
306 	//DebugDumpFrames();
307 }
308 
~Space()309 Space::~Space()
310 {
311 	UpdateBodies(); // make sure anything waiting to be removed gets removed before we go and kill everything else
312 	for (Body *body : m_bodies)
313 		KillBody(body);
314 	UpdateBodies();
315 	Frame::DeleteFrames();
316 }
317 
RefreshBackground()318 void Space::RefreshBackground()
319 {
320 	PROFILE_SCOPED()
321 	const SystemPath &path = m_starSystem->GetPath();
322 	Uint32 _init[5] = { path.systemIndex, Uint32(path.sectorX), Uint32(path.sectorY), Uint32(path.sectorZ), UNIVERSE_SEED };
323 	Random rand(_init, 5);
324 	m_background.reset(new Background::Container(Pi::renderer, rand, this, m_game->GetGalaxy()));
325 }
326 
ToJson(Json & jsonObj)327 void Space::ToJson(Json &jsonObj)
328 {
329 	PROFILE_SCOPED()
330 	RebuildBodyIndex();
331 	RebuildSystemBodyIndex();
332 
333 	Json spaceObj({}); // Create JSON object to contain space data (all the bodies and things).
334 
335 	StarSystem::ToJson(spaceObj, m_starSystem.Get());
336 
337 	Json frameObj({});
338 	Frame::ToJson(frameObj, m_rootFrameId, this);
339 	spaceObj["frame"] = frameObj;
340 
341 	Json bodyArray = Json::array(); // Create JSON array to contain body data.
342 	for (Body *b : m_bodies) {
343 		Json bodyArrayEl({}); // Create JSON object to contain body.
344 		b->ToJson(bodyArrayEl, this);
345 		bodyArray.push_back(bodyArrayEl); // Append body object to array.
346 	}
347 	spaceObj["bodies"] = bodyArray; // Add body array to space object.
348 
349 	jsonObj["space"] = spaceObj; // Add space object to supplied object.
350 }
351 
GetBodyByIndex(Uint32 idx) const352 Body *Space::GetBodyByIndex(Uint32 idx) const
353 {
354 	assert(m_bodyIndexValid);
355 	assert(m_bodyIndex.size() > idx);
356 	if (idx == SDL_MAX_UINT32 || m_bodyIndex.size() <= idx) {
357 		Output("GetBodyByIndex passed bad index %u", idx);
358 		return nullptr;
359 	}
360 	return m_bodyIndex[idx];
361 }
362 
GetSystemBodyByIndex(Uint32 idx) const363 SystemBody *Space::GetSystemBodyByIndex(Uint32 idx) const
364 {
365 	assert(m_sbodyIndexValid);
366 	assert(m_sbodyIndex.size() > idx);
367 	return m_sbodyIndex[idx];
368 }
369 
GetIndexForBody(const Body * body) const370 Uint32 Space::GetIndexForBody(const Body *body) const
371 {
372 	assert(m_bodyIndexValid);
373 	for (Uint32 i = 0; i < m_bodyIndex.size(); i++)
374 		if (m_bodyIndex[i] == body) return i;
375 	assert(false);
376 	Output("GetIndexForBody passed unknown body");
377 	return SDL_MAX_UINT32;
378 }
379 
GetIndexForSystemBody(const SystemBody * sbody) const380 Uint32 Space::GetIndexForSystemBody(const SystemBody *sbody) const
381 {
382 	assert(m_sbodyIndexValid);
383 	for (Uint32 i = 0; i < m_sbodyIndex.size(); i++)
384 		if (m_sbodyIndex[i] == sbody) return i;
385 	assert(0);
386 	return SDL_MAX_UINT32;
387 }
388 
AddSystemBodyToIndex(SystemBody * sbody)389 void Space::AddSystemBodyToIndex(SystemBody *sbody)
390 {
391 	assert(sbody);
392 	m_sbodyIndex.push_back(sbody);
393 	for (Uint32 i = 0; i < sbody->GetNumChildren(); i++)
394 		AddSystemBodyToIndex(sbody->GetChildren()[i]);
395 }
396 
RebuildBodyIndex()397 void Space::RebuildBodyIndex()
398 {
399 	m_bodyIndex.clear();
400 	m_bodyIndex.push_back(nullptr);
401 
402 	for (Body *b : m_bodies) {
403 		m_bodyIndex.push_back(b);
404 		// also index ships inside clouds
405 		// XXX we should not have to know about this. move indexing grunt work
406 		// down into the bodies?
407 		if (b->IsType(ObjectType::HYPERSPACECLOUD)) {
408 			Ship *s = static_cast<HyperspaceCloud *>(b)->GetShip();
409 			if (s) m_bodyIndex.push_back(s);
410 		}
411 	}
412 
413 	Pi::SetAmountBackgroundStars(Pi::GetAmountBackgroundStars());
414 
415 	m_bodyIndexValid = true;
416 }
417 
RebuildSystemBodyIndex()418 void Space::RebuildSystemBodyIndex()
419 {
420 	m_sbodyIndex.clear();
421 	m_sbodyIndex.push_back(nullptr);
422 
423 	if (m_starSystem)
424 		AddSystemBodyToIndex(m_starSystem->GetRootBody().Get());
425 
426 	m_sbodyIndexValid = true;
427 }
428 
AddBody(Body * b)429 void Space::AddBody(Body *b)
430 {
431 	m_bodies.push_back(b);
432 }
433 
RemoveBody(Body * b)434 void Space::RemoveBody(Body *b)
435 {
436 #ifndef NDEBUG
437 	assert(!m_processingFinalizationQueue);
438 #endif
439 	m_assignedBodies.emplace_back(b, BodyAssignation::REMOVE);
440 }
441 
KillBody(Body * b)442 void Space::KillBody(Body *b)
443 {
444 #ifndef NDEBUG
445 	assert(!m_processingFinalizationQueue);
446 #endif
447 	if (!b->IsDead()) {
448 		b->MarkDead();
449 
450 		// player needs to stay alive so things like the death animation
451 		// (which uses a camera positioned relative to the player) can
452 		// continue to work. it will be cleaned up with the space is torn down
453 		// XXX this seems like the wrong way to do it. since its still "alive"
454 		// it still collides, moves, etc. better to just snapshot its position
455 		// elsewhere
456 		if (b != Pi::player)
457 			m_assignedBodies.emplace_back(b, BodyAssignation::KILL);
458 	}
459 }
460 
GetHyperspaceExitParams(const SystemPath & source,const SystemPath & dest,vector3d & pos,vector3d & vel) const461 void Space::GetHyperspaceExitParams(const SystemPath &source, const SystemPath &dest,
462 	vector3d &pos, vector3d &vel) const
463 {
464 	assert(m_starSystem);
465 	assert(source.IsSystemPath());
466 
467 	assert(dest.IsSameSystem(m_starSystem->GetPath()));
468 
469 	RefCountedPtr<const Sector> source_sec = m_sectorCache->GetCached(source);
470 	RefCountedPtr<const Sector> dest_sec = m_sectorCache->GetCached(dest);
471 
472 	Sector::System source_sys = source_sec->m_systems[source.systemIndex];
473 	Sector::System dest_sys = dest_sec->m_systems[dest.systemIndex];
474 
475 	const vector3d sourcePos = vector3d(source_sys.GetFullPosition());
476 	const vector3d destPos = vector3d(dest_sys.GetFullPosition());
477 
478 	Body *primary = 0;
479 	if (dest.IsBodyPath()) {
480 		assert(dest.bodyIndex < m_starSystem->GetNumBodies());
481 		primary = FindBodyForPath(&dest);
482 		while (primary && primary->GetSystemBody()->GetSuperType() != SystemBody::SUPERTYPE_STAR) {
483 			SystemBody *parent = primary->GetSystemBody()->GetParent();
484 			primary = parent ? FindBodyForPath(&parent->GetPath()) : 0;
485 		}
486 	}
487 	if (!primary) {
488 		// find the first non-gravpoint. should be the primary star
489 		for (Body *b : GetBodies())
490 			if (b->GetSystemBody()->GetType() != SystemBody::TYPE_GRAVPOINT) {
491 				primary = b;
492 				break;
493 			}
494 	}
495 	assert(primary);
496 
497 	// calculate distance to primary body relative to body's mass and radius
498 	const double max_orbit_vel = 100e3;
499 	double dist = G * primary->GetSystemBody()->GetMass() /
500 		(max_orbit_vel * max_orbit_vel);
501 
502 	// ensure an absolute minimum and an absolute maximum distance
503 	// the minimum distance from the center of the star should not be less than the radius of the star
504 	dist = Clamp(dist, primary->GetSystemBody()->GetRadius() * 1.1, std::max(primary->GetSystemBody()->GetRadius() * 1.1, 100 * AU));
505 
506 	// point velocity vector along the line from source to dest,
507 	// make exit position perpendicular to it,
508 	// add random component to exit position,
509 	// set velocity for (almost) circular orbit
510 	vel = (destPos - sourcePos).Normalized();
511 	{
512 		vector3d a{ MathUtil::OrthogonalDirection(vel) };
513 		vector3d b{ vel.Cross(a) };
514 		vector3d p{ MathUtil::RandomPointOnCircle(1.) };
515 		pos = p.x * a + p.y * b;
516 	}
517 	pos *= dist * Pi::rng.Double(0.95, 1.2);
518 	vel *= sqrt(G * primary->GetSystemBody()->GetMass() / dist);
519 
520 	assert(pos.Length() > primary->GetSystemBody()->GetRadius());
521 	pos += primary->GetPositionRelTo(GetRootFrame());
522 }
523 
FindNearestTo(const Body * b,ObjectType t) const524 Body *Space::FindNearestTo(const Body *b, ObjectType t) const
525 {
526 	Body *nearest = 0;
527 	double dist = FLT_MAX;
528 	for (Body *const body : m_bodies) {
529 		if (body->IsDead()) continue;
530 		if (body->IsType(t)) {
531 			double d = body->GetPositionRelTo(b).Length();
532 			if (d < dist) {
533 				dist = d;
534 				nearest = body;
535 			}
536 		}
537 	}
538 	return nearest;
539 }
540 
FindBodyForPath(const SystemPath * path) const541 Body *Space::FindBodyForPath(const SystemPath *path) const
542 {
543 	// it is a bit dumb that currentSystem is not part of Space...
544 	SystemBody *body = m_starSystem->GetBodyByPath(path);
545 
546 	if (!body) return 0;
547 
548 	for (Body *b : m_bodies) {
549 		if (b->GetSystemBody() == body) return b;
550 	}
551 	return 0;
552 }
553 
find_frame_with_sbody(FrameId fId,const SystemBody * b)554 static FrameId find_frame_with_sbody(FrameId fId, const SystemBody *b)
555 {
556 	Frame *f = Frame::GetFrame(fId);
557 	if (f->GetSystemBody() == b)
558 		return fId;
559 	else {
560 		for (FrameId kid : f->GetChildren()) {
561 			FrameId found = find_frame_with_sbody(kid, b);
562 			if (found.valid())
563 				return found;
564 		}
565 	}
566 	return FrameId::Invalid;
567 }
568 
GetFrameWithSystemBody(const SystemBody * b) const569 FrameId Space::GetFrameWithSystemBody(const SystemBody *b) const
570 {
571 	return find_frame_with_sbody(m_rootFrameId, b);
572 }
573 
574 // used to define a cube centred on your current location
575 static const int sectorRadius = 5;
576 
577 // sort using a custom function object
578 class SectorDistanceSort {
579 public:
operator ()(const SystemPath & a,const SystemPath & b)580 	bool operator()(const SystemPath &a, const SystemPath &b)
581 	{
582 		const float dist_a = vector3f(here.sectorX - a.sectorX, here.sectorY - a.sectorY, here.sectorZ - a.sectorZ).LengthSqr();
583 		const float dist_b = vector3f(here.sectorX - b.sectorX, here.sectorY - b.sectorY, here.sectorZ - b.sectorZ).LengthSqr();
584 		return dist_a < dist_b;
585 	}
SectorDistanceSort(const SystemPath * centre)586 	SectorDistanceSort(const SystemPath *centre) :
587 		here(centre)
588 	{}
589 
590 private:
SectorDistanceSort()591 	SectorDistanceSort() {}
592 	SystemPath here;
593 };
594 
GenSectorCache(RefCountedPtr<Galaxy> galaxy,const SystemPath * here)595 void Space::GenSectorCache(RefCountedPtr<Galaxy> galaxy, const SystemPath *here)
596 {
597 	PROFILE_SCOPED()
598 
599 	// current location
600 	if (!here) {
601 		if (!m_starSystem.Valid())
602 			return;
603 		here = &m_starSystem->GetPath();
604 	}
605 	const int here_x = here->sectorX;
606 	const int here_y = here->sectorY;
607 	const int here_z = here->sectorZ;
608 
609 	SectorCache::PathVector paths;
610 	// build all of the possible paths we'll need to build sectors for
611 	for (int x = here_x - sectorRadius; x <= here_x + sectorRadius; x++) {
612 		for (int y = here_y - sectorRadius; y <= here_y + sectorRadius; y++) {
613 			for (int z = here_z - sectorRadius; z <= here_z + sectorRadius; z++) {
614 				SystemPath path(x, y, z);
615 				paths.push_back(path);
616 			}
617 		}
618 	}
619 	// sort them so that those closest to the "here" path are processed first
620 	SectorDistanceSort SDS(here);
621 	std::sort(paths.begin(), paths.end(), SDS);
622 	m_sectorCache = galaxy->NewSectorSlaveCache();
623 	const SystemPath &center(*here);
624 	m_sectorCache->FillCache(paths, [this, center]() { UpdateStarSystemCache(&center); });
625 }
626 
WithinBox(const SystemPath & here,const int Xmin,const int Xmax,const int Ymin,const int Ymax,const int Zmin,const int Zmax)627 static bool WithinBox(const SystemPath &here, const int Xmin, const int Xmax, const int Ymin, const int Ymax, const int Zmin, const int Zmax)
628 {
629 	PROFILE_SCOPED()
630 	if (here.sectorX >= Xmin && here.sectorX <= Xmax) {
631 		if (here.sectorY >= Ymin && here.sectorY <= Ymax) {
632 			if (here.sectorZ >= Zmin && here.sectorZ <= Zmax) {
633 				return true;
634 			}
635 		}
636 	}
637 	return false;
638 }
639 
UpdateStarSystemCache(const SystemPath * here)640 void Space::UpdateStarSystemCache(const SystemPath *here)
641 {
642 	PROFILE_SCOPED()
643 
644 	// current location
645 	if (!here) {
646 		if (!m_starSystem.Valid())
647 			return;
648 		here = &m_starSystem->GetPath();
649 	}
650 	const int here_x = here->sectorX;
651 	const int here_y = here->sectorY;
652 	const int here_z = here->sectorZ;
653 
654 	// we're going to use these to determine if our StarSystems are within a range that we'll keep for later use
655 	static const int survivorRadius = sectorRadius * 3;
656 
657 	// min/max box limits
658 	const int xmin = here->sectorX - survivorRadius;
659 	const int xmax = here->sectorX + survivorRadius;
660 	const int ymin = here->sectorY - survivorRadius;
661 	const int ymax = here->sectorY + survivorRadius;
662 	const int zmin = here->sectorZ - survivorRadius;
663 	const int zmax = here->sectorZ + survivorRadius;
664 
665 #ifdef DEBUG_CACHE
666 	unsigned removed = 0;
667 #endif
668 	StarSystemCache::CacheMap::const_iterator i = m_starSystemCache->Begin();
669 	while (i != m_starSystemCache->End()) {
670 		if (!WithinBox(i->second->GetPath(), xmin, xmax, ymin, ymax, zmin, zmax)) {
671 			m_starSystemCache->Erase(i++);
672 #ifdef DEBUG_CACHE
673 			++removed;
674 #endif
675 		} else
676 			++i;
677 	}
678 #ifdef DEBUG_CACHE
679 	Output("%s: Erased %u entries.\n", StarSystemCache::CACHE_NAME.c_str(), removed);
680 #endif
681 
682 	SectorCache::PathVector paths;
683 	// build all of the possible paths we'll need to build star systems for
684 	for (int x = here_x - sectorRadius; x <= here_x + sectorRadius; x++) {
685 		for (int y = here_y - sectorRadius; y <= here_y + sectorRadius; y++) {
686 			for (int z = here_z - sectorRadius; z <= here_z + sectorRadius; z++) {
687 				SystemPath path(x, y, z);
688 				RefCountedPtr<Sector> sec(m_sectorCache->GetIfCached(path));
689 				assert(sec);
690 				for (const Sector::System &ss : sec->m_systems)
691 					paths.push_back(SystemPath(ss.sx, ss.sy, ss.sz, ss.idx));
692 			}
693 		}
694 	}
695 	m_starSystemCache->FillCache(paths);
696 }
697 
MakeFramesFor(const double at_time,SystemBody * sbody,Body * b,FrameId fId,std::vector<vector3d> & prevPositions)698 static FrameId MakeFramesFor(const double at_time, SystemBody *sbody, Body *b, FrameId fId, std::vector<vector3d> &prevPositions)
699 {
700 	PROFILE_SCOPED()
701 	if (!sbody->GetParent()) {
702 		if (b) b->SetFrame(fId);
703 		Frame *f = Frame::GetFrame(fId);
704 		f->SetBodies(sbody, b);
705 		return fId;
706 	}
707 
708 	if (sbody->GetType() == SystemBody::TYPE_GRAVPOINT) {
709 		FrameId orbFrameId = Frame::CreateFrame(fId,
710 			sbody->GetName().c_str(),
711 			Frame::FLAG_DEFAULT,
712 			sbody->GetMaxChildOrbitalDistance() * 1.1);
713 		Frame *orbFrame = Frame::GetFrame(orbFrameId);
714 		orbFrame->SetBodies(sbody, b);
715 		return orbFrameId;
716 	}
717 
718 	SystemBody::BodySuperType supertype = sbody->GetSuperType();
719 
720 	if ((supertype == SystemBody::SUPERTYPE_GAS_GIANT) ||
721 		(supertype == SystemBody::SUPERTYPE_ROCKY_PLANET)) {
722 		// for planets we want an non-rotating frame for a few radii
723 		// and a rotating frame with no radius to contain attached objects
724 		double frameRadius = std::max(4.0 * sbody->GetRadius(), sbody->GetMaxChildOrbitalDistance() * 1.05);
725 		FrameId orbFrameId = Frame::CreateFrame(fId,
726 			sbody->GetName().c_str(),
727 			Frame::FLAG_HAS_ROT,
728 			frameRadius);
729 		Frame *orbFrame = Frame::GetFrame(orbFrameId);
730 		orbFrame->SetBodies(sbody, b);
731 		//Output("\t\t\t%s has frame size %.0fkm, body radius %.0fkm\n", sbody->name.c_str(),
732 		//	(frameRadius ? frameRadius : 10*sbody->GetRadius())*0.001f,
733 		//	sbody->GetRadius()*0.001f);
734 
735 		assert(sbody->IsRotating() != 0);
736 		// rotating frame has atmosphere radius or feature height, whichever is larger
737 		FrameId rotFrameId = Frame::CreateFrame(orbFrameId,
738 			sbody->GetName().c_str(),
739 			Frame::FLAG_ROTATING,
740 			b->GetPhysRadius());
741 		Frame *rotFrame = Frame::GetFrame(rotFrameId);
742 		rotFrame->SetBodies(sbody, b);
743 
744 		matrix3x3d rotMatrix = matrix3x3d::RotateX(sbody->GetAxialTilt());
745 		double angSpeed = 2.0 * M_PI / sbody->GetRotationPeriod();
746 		rotFrame->SetAngSpeed(angSpeed);
747 
748 		if (sbody->HasRotationPhase())
749 			rotMatrix = rotMatrix * matrix3x3d::RotateY(sbody->GetRotationPhaseAtStart());
750 		rotFrame->SetInitialOrient(rotMatrix, at_time);
751 
752 		b->SetFrame(rotFrameId);
753 		return orbFrameId;
754 	} else if (supertype == SystemBody::SUPERTYPE_STAR) {
755 		// stars want a single small non-rotating frame
756 		// bigger than it's furtherest orbiting body.
757 		// if there are no orbiting bodies use a frame of several radii.
758 		FrameId orbFrameId = Frame::CreateFrame(fId, sbody->GetName().c_str());
759 		Frame *orbFrame = Frame::GetFrame(orbFrameId);
760 		orbFrame->SetBodies(sbody, b);
761 		const double bodyRadius = sbody->GetEquatorialRadius();
762 		double frameRadius = std::max(10.0 * bodyRadius, sbody->GetMaxChildOrbitalDistance() * 1.1);
763 		// Respect the frame of other stars in the multi-star system. We still make sure that the frame ends outside
764 		// the body. For a minimum separation of 1.236 radii, nothing will overlap (see StarSystem::StarSystem()).
765 		if (sbody->GetParent() && frameRadius > AU * 0.11 * sbody->GetOrbMin())
766 			frameRadius = std::max(1.1 * bodyRadius, AU * 0.11 * sbody->GetOrbMin());
767 		orbFrame->SetRadius(frameRadius);
768 		b->SetFrame(orbFrameId);
769 		return orbFrameId;
770 	} else if (sbody->GetType() == SystemBody::TYPE_STARPORT_ORBITAL) {
771 		// space stations want non-rotating frame to some distance
772 		FrameId orbFrameId = Frame::CreateFrame(fId,
773 			sbody->GetName().c_str(),
774 			Frame::FLAG_DEFAULT,
775 			20000.0);
776 		Frame *orbFrame = Frame::GetFrame(orbFrameId);
777 		orbFrame->SetBodies(sbody, b);
778 		b->SetFrame(orbFrameId);
779 		return orbFrameId;
780 	} else if (sbody->GetType() == SystemBody::TYPE_STARPORT_SURFACE) {
781 		// just put body into rotating frame of planet, not in its own frame
782 		// (because collisions only happen between objects in same frame,
783 		// and we want collisions on starport and on planet itself)
784 		FrameId rotFrameId = Frame::GetFrame(fId)->GetRotFrame();
785 		b->SetFrame(rotFrameId);
786 
787 		Frame *rotFrame = Frame::GetFrame(rotFrameId);
788 		assert(rotFrame->IsRotFrame());
789 		assert(rotFrame->GetBody()->IsType(ObjectType::PLANET));
790 		matrix3x3d rot;
791 		vector3d pos;
792 		Planet *planet = static_cast<Planet *>(rotFrame->GetBody());
793 		RelocateStarportIfNecessary(sbody, planet, pos, rot, prevPositions);
794 		sbody->SetOrbitPlane(rot);
795 		b->SetPosition(pos * planet->GetTerrainHeight(pos));
796 		b->SetOrient(rot);
797 		// accumulate for testing against
798 		prevPositions.push_back(pos);
799 		return rotFrameId;
800 	} else {
801 		assert(0);
802 	}
803 	return 0;
804 }
805 
GenBody(const double at_time,SystemBody * sbody,FrameId fId,std::vector<vector3d> & posAccum)806 void Space::GenBody(const double at_time, SystemBody *sbody, FrameId fId, std::vector<vector3d> &posAccum)
807 {
808 	PROFILE_START()
809 	Body *b = nullptr;
810 
811 	if (sbody->GetType() != SystemBody::TYPE_GRAVPOINT) {
812 		if (sbody->GetSuperType() == SystemBody::SUPERTYPE_STAR) {
813 			Star *star = new Star(sbody);
814 			b = star;
815 		} else if ((sbody->GetType() == SystemBody::TYPE_STARPORT_ORBITAL) ||
816 			(sbody->GetType() == SystemBody::TYPE_STARPORT_SURFACE)) {
817 			SpaceStation *ss = new SpaceStation(sbody);
818 			b = ss;
819 		} else {
820 			Planet *planet = new Planet(sbody);
821 			b = planet;
822 			// reset this
823 			posAccum.clear();
824 		}
825 		b->SetLabel(sbody->GetName().c_str());
826 		b->SetPosition(vector3d(0, 0, 0));
827 		AddBody(b);
828 	}
829 	fId = MakeFramesFor(at_time, sbody, b, fId, posAccum);
830 
831 	PROFILE_STOP()
832 	for (SystemBody *kid : sbody->GetChildren()) {
833 		GenBody(at_time, kid, fId, posAccum);
834 	}
835 }
836 
OnCollision(Body * o1,Body * o2,CollisionContact * c,double relativeVel)837 static bool OnCollision(Body *o1, Body *o2, CollisionContact *c, double relativeVel)
838 {
839 	/* XXX: if you create a new class inheriting from Object instead of Body, this code must be updated */
840 	if (o1 && !o1->OnCollision(o2, c->geomFlag, relativeVel)) return false;
841 	if (o2 && !o2->OnCollision(o1, c->geomFlag, relativeVel)) return false;
842 	return true;
843 }
844 
hitCallback(CollisionContact * c)845 static void hitCallback(CollisionContact *c)
846 {
847 	//Output("OUCH! %x (depth %f)\n", SDL_GetTicks(), c->depth);
848 
849 	Body *po1 = static_cast<Body *>(c->userData1);
850 	Body *po2 = static_cast<Body *>(c->userData2);
851 
852 	const bool po1_isDynBody = po1->IsType(ObjectType::DYNAMICBODY);
853 	const bool po2_isDynBody = po2->IsType(ObjectType::DYNAMICBODY);
854 	// collision response
855 	assert(po1_isDynBody || po2_isDynBody);
856 
857 	// Bounce factor
858 	const double coeff_rest = 0.35;
859 	// Allow stop due to friction
860 	const double coeff_slide = 0.700;
861 
862 	if (po1_isDynBody && po2_isDynBody) {
863 		DynamicBody *b1 = static_cast<DynamicBody *>(po1);
864 		DynamicBody *b2 = static_cast<DynamicBody *>(po2);
865 		const vector3d linVel1 = b1->GetVelocity();
866 		const vector3d linVel2 = b2->GetVelocity();
867 		const vector3d angVel1 = b1->GetAngVelocity();
868 		const vector3d angVel2 = b2->GetAngVelocity();
869 
870 		const double invMass1 = 1.0 / b1->GetMass();
871 		const double invMass2 = 1.0 / b2->GetMass();
872 		const vector3d hitPos1 = c->pos - b1->GetPosition();
873 		const vector3d hitPos2 = c->pos - b2->GetPosition();
874 		const vector3d hitVel1 = linVel1 + angVel1.Cross(hitPos1);
875 		const vector3d hitVel2 = linVel2 + angVel2.Cross(hitPos2);
876 		const double relVel = (hitVel1 - hitVel2).Dot(c->normal);
877 		// moving away so no collision
878 		if (relVel > 0) return;
879 		if (!OnCollision(po1, po2, c, -relVel)) return;
880 		const double invAngInert1 = 1.0 / b1->GetAngularInertia();
881 		const double invAngInert2 = 1.0 / b2->GetAngularInertia();
882 		const double numerator = -(1.0 + coeff_rest) * relVel;
883 		const double term1 = invMass1;
884 		const double term2 = invMass2;
885 		const double term3 = c->normal.Dot((hitPos1.Cross(c->normal) * invAngInert1).Cross(hitPos1));
886 		const double term4 = c->normal.Dot((hitPos2.Cross(c->normal) * invAngInert2).Cross(hitPos2));
887 
888 		const double j = numerator / (term1 + term2 + term3 + term4);
889 		const vector3d force = j * c->normal;
890 
891 		b1->SetVelocity(linVel1 * (1 - coeff_slide * c->timestep) + force * invMass1);
892 		b1->SetAngVelocity(angVel1 + hitPos1.Cross(force) * invAngInert1);
893 		b2->SetVelocity(linVel2 * (1 - coeff_slide * c->timestep) - force * invMass2);
894 		b2->SetAngVelocity(angVel2 - hitPos2.Cross(force) * invAngInert2);
895 	} else {
896 		// one body is static
897 		vector3d hitNormal;
898 		DynamicBody *mover;
899 
900 		if (po1_isDynBody) {
901 			mover = static_cast<DynamicBody *>(po1);
902 			hitNormal = c->normal;
903 		} else {
904 			mover = static_cast<DynamicBody *>(po2);
905 			hitNormal = -c->normal;
906 		}
907 
908 		const vector3d linVel1 = mover->GetVelocity();
909 		const vector3d angVel1 = mover->GetAngVelocity();
910 
911 		// step back
912 		//		mover->UndoTimestep();
913 
914 		const double invMass1 = 1.0 / mover->GetMass();
915 		const vector3d hitPos1 = c->pos - mover->GetPosition();
916 		const vector3d hitVel1 = linVel1 + angVel1.Cross(hitPos1);
917 		const double relVel = hitVel1.Dot(c->normal);
918 		// moving away so no collision
919 		if (relVel > 0) return;
920 		if (!OnCollision(po1, po2, c, -relVel)) return;
921 		const double invAngInert = 1.0 / mover->GetAngularInertia();
922 		const double numerator = -(1.0 + coeff_rest) * relVel;
923 		const double term1 = invMass1;
924 		const double term3 = c->normal.Dot((hitPos1.Cross(c->normal) * invAngInert).Cross(hitPos1));
925 
926 		const double j = numerator / (term1 + term3);
927 		const vector3d force = j * c->normal;
928 
929 		/*
930 		   "Linear projection reduces the penetration of two
931 		   objects by a small percentage, and this is performed
932 		   after the impulse is applied"
933 
934 		   From:
935 		   https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331
936 
937 		   Correction should never be more than c->depth (std::min) and never negative (std::max)
938 		   NOTE: usually instead of 'c->timestep' you find a 'percent',
939 		   but here we have a variable timestep and thus the upper limit,
940 		   which is intended to trigger a collision in the subsequent frame.
941 		   NOTE2: works (as intendend) at low timestep.
942 		   Further improvement could be:
943 				1) velocity should be projected relative to normal direction,
944 				   so bouncing and friction may act on the "correct" components
945 				   (though that a reason for fails and glitches are frames skipped
946 				   because relVel is quitting before any further calculation)
947 				2) with time accel at 10000x you end in space... probably in order
948 				   for that to works correctly some deeper change should kick in
949 		*/
950 		const float threshold = 0.005;
951 
952 		vector3d correction = std::min(std::max(c->depth - threshold, 0.0) * c->timestep, c->depth + threshold) * c->normal;
953 
954 		mover->SetPosition(mover->GetPosition() + correction);
955 
956 		const float reduction = std::max(1 - coeff_slide * c->timestep, 0.0);
957 		vector3d final_vel = linVel1 * reduction + force * invMass1;
958 		if (final_vel.LengthSqr() < 0.1) final_vel = vector3d(0.0);
959 
960 		mover->SetVelocity(final_vel);
961 		mover->SetAngVelocity(angVel1 + hitPos1.Cross(force) * invAngInert);
962 	}
963 }
964 
965 // temporary one-point version
CollideWithTerrain(Body * body,float timeStep)966 static void CollideWithTerrain(Body *body, float timeStep)
967 {
968 	PROFILE_SCOPED()
969 	if (!body->IsType(ObjectType::DYNAMICBODY))
970 		return;
971 	DynamicBody *dynBody = static_cast<DynamicBody *>(body);
972 	if (!dynBody->IsMoving())
973 		return;
974 
975 	Frame *f = Frame::GetFrame(body->GetFrame());
976 	if (!f || !f->GetBody() || f->GetId() != f->GetBody()->GetFrame())
977 		return;
978 	if (!f->GetBody()->IsType(ObjectType::TERRAINBODY))
979 		return;
980 	TerrainBody *terrain = static_cast<TerrainBody *>(f->GetBody());
981 
982 	const Aabb &aabb = dynBody->GetAabb();
983 	double altitude = body->GetPosition().Length() + aabb.min.y;
984 	if (altitude >= (terrain->GetMaxFeatureRadius() * 2.0))
985 		return;
986 
987 	double terrHeight = terrain->GetTerrainHeight(body->GetPosition().Normalized());
988 	if (altitude >= terrHeight)
989 		return;
990 
991 	CollisionContact c(body->GetPosition(), body->GetPosition().Normalized(), terrHeight - altitude, timeStep, static_cast<void *>(body), static_cast<void *>(f->GetBody()));
992 	hitCallback(&c);
993 }
994 
TimeStep(float step)995 void Space::TimeStep(float step)
996 {
997 	PROFILE_SCOPED()
998 
999 	if (Pi::MustRefreshBackgroundClearFlag())
1000 		RefreshBackground();
1001 
1002 	m_bodyIndexValid = m_sbodyIndexValid = false;
1003 
1004 	Frame::CollideFrames(&hitCallback);
1005 
1006 	for (Body *b : m_bodies)
1007 		CollideWithTerrain(b, step);
1008 
1009 	// update frames of reference
1010 	for (Body *b : m_bodies)
1011 		b->UpdateFrame();
1012 
1013 	// AI acts here, then move all bodies and frames
1014 	for (Body *b : m_bodies)
1015 		b->StaticUpdate(step);
1016 
1017 	Frame::UpdateOrbitRails(m_game->GetTime(), m_game->GetTimeStep());
1018 
1019 	for (Body *b : m_bodies)
1020 		b->TimeStepUpdate(step);
1021 
1022 	LuaEvent::Emit();
1023 	Pi::luaTimer->Tick();
1024 
1025 	UpdateBodies();
1026 
1027 	m_bodyNearFinder.Prepare();
1028 }
1029 
UpdateBodies()1030 void Space::UpdateBodies()
1031 {
1032 	PROFILE_SCOPED()
1033 #ifndef NDEBUG
1034 	m_processingFinalizationQueue = true;
1035 #endif
1036 
1037 	// removing or deleting bodies from space
1038 	for (const auto &b : m_assignedBodies) {
1039 		auto remove_iterator = m_bodies.end();
1040 		for (auto it = m_bodies.begin(); it != m_bodies.end(); ++it) {
1041 			if (*it != b.first)
1042 				(*it)->NotifyRemoved(b.first);
1043 			else
1044 				remove_iterator = it;
1045 		}
1046 		if (remove_iterator != m_bodies.end()) {
1047 			*remove_iterator = m_bodies.back();
1048 			m_bodies.pop_back();
1049 			if (b.second == BodyAssignation::KILL)
1050 				delete b.first;
1051 			else
1052 				b.first->SetFrame(FrameId::Invalid);
1053 		}
1054 	}
1055 
1056 	m_assignedBodies.clear();
1057 
1058 #ifndef NDEBUG
1059 	m_processingFinalizationQueue = false;
1060 #endif
1061 }
1062 
1063 static char space[256];
1064 
DebugDumpFrame(FrameId fId,bool details,unsigned int indent)1065 static void DebugDumpFrame(FrameId fId, bool details, unsigned int indent)
1066 {
1067 	Frame *f = Frame::GetFrame(fId);
1068 	Frame *parent = Frame::GetFrame(f->GetParent());
1069 
1070 	Output("%.*s%2i) %p (%s)%s\n", indent, space, static_cast<int>(fId), static_cast<void *>(f), f->GetLabel().c_str(), f->IsRotFrame() ? " [rotating]" : " [non rotating]");
1071 	if (f->GetParent().valid())
1072 		Output("%.*s parent %p (%s)\n", indent + 3, space, static_cast<void *>(parent), parent->GetLabel().c_str());
1073 	if (f->GetBody())
1074 		Output("%.*s body %p (%s)\n", indent + 3, space, static_cast<void *>(f->GetBody()), f->GetBody()->GetLabel().c_str());
1075 	if (Body *b = f->GetBody())
1076 		Output("%.*s bodyFor %p (%s)\n", indent + 3, space, static_cast<void *>(b), b->GetLabel().c_str());
1077 	Output("%.*s distance: %f radius: %f children: %u\n", indent + 3, space, f->GetPosition().Length(), f->GetRadius(), f->GetNumChildren());
1078 
1079 	for (FrameId kid : f->GetChildren())
1080 		DebugDumpFrame(kid, details, indent + 2);
1081 }
1082 
DebugDumpFrames(bool details)1083 void Space::DebugDumpFrames(bool details)
1084 {
1085 	memset(space, ' ', sizeof(space));
1086 
1087 	if (m_starSystem)
1088 		Output("Frame structure for '%s':\n", m_starSystem->GetName().c_str());
1089 	else
1090 		Output("Frame structure while in hyperspace:\n");
1091 	DebugDumpFrame(m_rootFrameId, details, 3);
1092 }
1093