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 "Frame.h"
5
6 #include "GameSaveError.h"
7 #include "JsonUtils.h"
8 #include "Sfx.h"
9 #include "Space.h"
10 #include "collider/CollisionSpace.h"
11 #include "utils.h"
12
13 std::vector<Frame> Frame::s_frames;
14 std::vector<CollisionSpace> Frame::s_collisionSpaces;
15
Frame(const Dummy & d,FrameId parent,const char * label,unsigned int flags,double radius)16 Frame::Frame(const Dummy &d, FrameId parent, const char *label, unsigned int flags, double radius) :
17 m_parent(parent),
18 m_sbody(nullptr),
19 m_astroBody(nullptr),
20 m_pos(vector3d(0.0)),
21 m_initialOrient(matrix3x3d::Identity()),
22 m_orient(matrix3x3d::Identity()),
23 m_vel(vector3d(0.0)),
24 m_angSpeed(0.0),
25 m_radius(radius),
26 m_flags(flags)
27 {
28 if (!d.madeWithFactory)
29 Error("Frame ctor called directly!\n");
30
31 m_thisId = s_frames.size();
32
33 ClearMovement();
34
35 s_collisionSpaces.emplace_back();
36 m_collisionSpace = s_collisionSpaces.size() - 1;
37 // TODO: this hacks around TraceRay being called on an uninitialized collision space and crashing
38 // Need to further evaluate the impact of this and whether it should be called in the CollisionSpace constructor instead
39 // FIXME: this causes a crash when resizing the collisionSpace array because a collision space double-frees when move constructing
40 // s_collisionSpaces.back().RebuildObjectTrees();
41
42 if (m_parent.valid())
43 Frame::GetFrame(m_parent)->AddChild(m_thisId);
44 if (label)
45 m_label = label;
46 }
47
Frame(const Dummy & d,FrameId parent)48 Frame::Frame(const Dummy &d, FrameId parent) :
49 m_parent(parent),
50 m_sbody(nullptr),
51 m_astroBody(nullptr),
52 m_pos(vector3d(0.0)),
53 m_initialOrient(matrix3x3d::Identity()),
54 m_orient(matrix3x3d::Identity()),
55 m_vel(vector3d(0.0)),
56 m_angSpeed(0.0),
57 m_label("camera"),
58 m_radius(0.0),
59 m_flags(FLAG_ROTATING),
60 m_collisionSpace(-1)
61 {
62 if (!d.madeWithFactory)
63 Error("Frame ctor called directly!\n");
64
65 m_thisId = s_frames.size();
66
67 ClearMovement();
68 if (m_parent.valid())
69 Frame::GetFrame(m_parent)->AddChild(m_thisId);
70 }
71
Frame(Frame && other)72 Frame::Frame(Frame &&other) noexcept :
73 m_sfx(std::move(other.m_sfx)),
74 m_thisId(other.m_thisId),
75 m_parent(other.m_parent),
76 m_children(std::move(other.m_children)),
77 m_sbody(other.m_sbody),
78 m_astroBody(other.m_astroBody),
79 m_pos(other.m_pos),
80 m_oldPos(other.m_oldPos),
81 m_interpPos(other.m_interpPos),
82 m_initialOrient(other.m_initialOrient),
83 m_orient(other.m_orient),
84 m_vel(other.m_vel),
85 m_angSpeed(other.m_angSpeed),
86 m_oldAngDisplacement(other.m_oldAngDisplacement),
87 m_label(std::move(other.m_label)),
88 m_radius(other.m_radius),
89 m_flags(other.m_flags),
90 m_collisionSpace(other.m_collisionSpace),
91 m_rootVel(other.m_rootVel),
92 m_rootPos(other.m_rootPos),
93 m_rootOrient(other.m_rootOrient),
94 m_rootInterpPos(other.m_rootInterpPos),
95 m_rootInterpOrient(other.m_rootInterpOrient),
96 m_astroBodyIndex(other.m_astroBodyIndex),
97 d(other.d)
98 {
99 other.d.madeWithFactory = true;
100 }
101
operator =(Frame && other)102 Frame &Frame::operator=(Frame &&other)
103 {
104 m_sfx = std::move(other.m_sfx);
105 m_thisId = other.m_thisId;
106 m_parent = other.m_parent;
107 m_children = std::move(other.m_children);
108 m_sbody = other.m_sbody;
109 m_astroBody = other.m_astroBody;
110 m_pos = other.m_pos;
111 m_oldPos = other.m_oldPos;
112 m_interpPos = other.m_interpPos;
113 m_initialOrient = other.m_initialOrient;
114 m_orient = other.m_orient;
115 m_vel = other.m_vel;
116 m_angSpeed = other.m_angSpeed;
117 m_oldAngDisplacement = other.m_oldAngDisplacement;
118 m_label = std::move(other.m_label);
119 m_radius = other.m_radius;
120 m_flags = other.m_flags;
121 m_collisionSpace = other.m_collisionSpace;
122 m_rootVel = other.m_rootVel;
123 m_rootPos = other.m_rootPos;
124 m_rootOrient = other.m_rootOrient;
125 m_rootInterpPos = other.m_rootInterpPos;
126 m_rootInterpOrient = other.m_rootInterpOrient;
127 m_astroBodyIndex = other.m_astroBodyIndex;
128 d = other.d;
129 return *this;
130 }
131
ToJson(Json & frameObj,FrameId fId,Space * space)132 void Frame::ToJson(Json &frameObj, FrameId fId, Space *space)
133 {
134 Frame *f = Frame::GetFrame(fId);
135
136 assert(f != nullptr);
137
138 frameObj["frameId"] = f->m_thisId;
139 frameObj["flags"] = f->m_flags;
140 frameObj["radius"] = f->m_radius;
141 frameObj["label"] = f->m_label;
142 frameObj["pos"] = f->m_pos;
143 frameObj["ang_speed"] = f->m_angSpeed;
144 frameObj["init_orient"] = f->m_initialOrient;
145 frameObj["index_for_system_body"] = space->GetIndexForSystemBody(f->m_sbody);
146 frameObj["index_for_astro_body"] = space->GetIndexForBody(f->m_astroBody);
147
148 Json childFrameArray = Json::array(); // Create JSON array to contain child frame data.
149 for (FrameId kid : f->GetChildren()) {
150 Json childFrameArrayEl = Json::object(); // Create JSON object to contain child frame.
151 Frame::ToJson(childFrameArrayEl, kid, space);
152 childFrameArray.push_back(childFrameArrayEl); // Append child frame object to array.
153 }
154 if (!childFrameArray.empty())
155 frameObj["child_frames"] = childFrameArray; // Add child frame array to frame object.
156
157 // Add sfx array to supplied object.
158 SfxManager::ToJson(frameObj, f->m_thisId);
159 }
160
~Frame()161 Frame::~Frame()
162 {
163 if (!d.madeWithFactory) {
164 Error("Frame instance deletion outside 'DeleteFrame' [%zu]\n", m_thisId.id());
165 }
166 }
167
CreateFrame(FrameId parent,const char * label,unsigned int flags,double radius)168 FrameId Frame::CreateFrame(FrameId parent, const char *label, unsigned int flags, double radius)
169 {
170 Dummy dummy;
171 dummy.madeWithFactory = true;
172
173 s_frames.emplace_back(dummy, parent, label, flags, radius);
174 return (s_frames.size() - 1);
175 }
176
FromJson(const Json & frameObj,Space * space,FrameId parent,double at_time)177 FrameId Frame::FromJson(const Json &frameObj, Space *space, FrameId parent, double at_time)
178 {
179 Dummy dummy;
180 dummy.madeWithFactory = true;
181
182 // Set parent to nullptr here in order to avoid this frame
183 // being a child twice (due to ctor calling AddChild)
184 s_frames.emplace_back(dummy, FrameId(), nullptr);
185
186 Frame *f = &s_frames.back();
187
188 f->m_parent = parent;
189 f->d.madeWithFactory = false;
190
191 try {
192 f->m_thisId = frameObj["frameId"];
193
194 // Check if frames order in load and save are the same
195 assert(s_frames.size() == 0 || (s_frames.size() - 1) == f->m_thisId.id());
196
197 f->m_flags = frameObj["flags"];
198 f->m_radius = frameObj["radius"];
199 f->m_label = frameObj["label"].get<std::string>();
200
201 f->m_pos = frameObj["pos"];
202 f->m_angSpeed = frameObj["ang_speed"];
203 f->SetInitialOrient(frameObj["init_orient"], at_time);
204 f->m_sbody = space->GetSystemBodyByIndex(frameObj["index_for_system_body"]);
205 f->m_astroBodyIndex = frameObj["index_for_astro_body"];
206 f->m_vel = vector3d(0.0); // m_vel is set to zero.
207
208 if (frameObj.count("child_frames") && frameObj["child_frames"].is_array()) {
209 Json childFrameArray = frameObj["child_frames"];
210 f->m_children.reserve(childFrameArray.size());
211 for (unsigned int i = 0; i < childFrameArray.size(); ++i) {
212 // During 'FromJson' a reallocation may happens, invalidating 'f',
213 // thus store his FrameId and renew it
214 FrameId temp = f->m_thisId;
215 FrameId kidId = FromJson(childFrameArray[i], space, f->m_thisId, at_time);
216 f = &s_frames[temp];
217 f->m_children.push_back(kidId);
218 }
219 } else {
220 f->m_children.clear();
221 }
222 } catch (Json::type_error &) {
223 Output("Loading error in '%s'\n", typeid(f).name());
224 f->d.madeWithFactory = true;
225 throw SavedGameCorruptException();
226 }
227
228 SfxManager::FromJson(frameObj, f->m_thisId);
229
230 f->ClearMovement();
231 return f->GetId();
232 }
233
DeleteFrames()234 void Frame::DeleteFrames()
235 {
236 // for each set "madeWithFactory"...
237 std::for_each(begin(s_frames), end(s_frames), [](Frame &f) {
238 f.d.madeWithFactory = true;
239 });
240 // then delete it
241 s_frames.clear();
242
243 // remember to delete CollisionSpaces
244 s_collisionSpaces.clear();
245 }
246
GetFrame(FrameId fId)247 Frame *Frame::GetFrame(FrameId fId)
248 {
249 #ifndef NDEBUG
250 if (fId && fId.id() >= s_frames.size())
251 Error("In '%s': fId is valid but out of range (%zu)...\n", __func__, fId.id());
252 #endif
253
254 return fId.valid() ? &s_frames[fId] : nullptr;
255 }
256
CreateCameraFrame(FrameId parent)257 FrameId Frame::CreateCameraFrame(FrameId parent)
258 {
259 Dummy dummy;
260 dummy.madeWithFactory = true;
261
262 s_frames.emplace_back(dummy, parent);
263 return (s_frames.size() - 1);
264 }
265
DeleteCameraFrame(FrameId camera)266 void Frame::DeleteCameraFrame(FrameId camera)
267 {
268 if (!camera)
269 return;
270
271 // Detach camera from parent, then delete:
272 Frame *cameraFrame = Frame::GetFrame(camera);
273 Frame *parent = Frame::GetFrame(cameraFrame->GetParent());
274 if (parent)
275 parent->RemoveChild(camera);
276
277 // Call dtor "popping" element in vector
278 #ifndef NDEBUG
279 if (s_frames.size() > 0 && camera.id() < s_frames.size() - 1) {
280 Error("DeleteCameraFrame: seems camera frame is not the last frame!\n");
281 abort();
282 };
283 #endif // NDEBUG
284 s_frames.back().d.madeWithFactory = true;
285 s_frames.pop_back();
286 }
287
PostUnserializeFixup(FrameId fId,Space * space)288 void Frame::PostUnserializeFixup(FrameId fId, Space *space)
289 {
290 Frame *f = Frame::GetFrame(fId);
291 f->UpdateRootRelativeVars();
292 f->m_astroBody = space->GetBodyByIndex(f->m_astroBodyIndex);
293 // build the object trees once after loading so they're initialized while paused.
294 f->GetCollisionSpace()->RebuildObjectTrees();
295 for (FrameId kid : f->GetChildren())
296 PostUnserializeFixup(kid, space);
297 }
298
CollideFrames(void (* callback)(CollisionContact *))299 void Frame::CollideFrames(void (*callback)(CollisionContact *))
300 {
301 PROFILE_SCOPED()
302
303 std::for_each(begin(s_collisionSpaces), end(s_collisionSpaces), [&](CollisionSpace &cs) {
304 cs.Collide(callback);
305 });
306 }
307
RemoveChild(FrameId fId)308 void Frame::RemoveChild(FrameId fId)
309 {
310 PROFILE_SCOPED()
311 if (!fId.valid()) return;
312 Frame *f = Frame::GetFrame(fId);
313 if (f == nullptr) return;
314 const std::vector<FrameId>::iterator it = std::find(m_children.begin(), m_children.end(), fId);
315 if (it != m_children.end())
316 m_children.erase(it);
317 }
318
AddGeom(Geom * g)319 void Frame::AddGeom(Geom *g) { s_collisionSpaces.at(m_collisionSpace).AddGeom(g); }
RemoveGeom(Geom * g)320 void Frame::RemoveGeom(Geom *g) { s_collisionSpaces.at(m_collisionSpace).RemoveGeom(g); }
AddStaticGeom(Geom * g)321 void Frame::AddStaticGeom(Geom *g) { s_collisionSpaces.at(m_collisionSpace).AddStaticGeom(g); }
RemoveStaticGeom(Geom * g)322 void Frame::RemoveStaticGeom(Geom *g) { s_collisionSpaces.at(m_collisionSpace).RemoveStaticGeom(g); }
SetPlanetGeom(double radius,Body * obj)323 void Frame::SetPlanetGeom(double radius, Body *obj)
324 {
325 s_collisionSpaces.at(m_collisionSpace).SetSphere(vector3d(0, 0, 0), radius, static_cast<void *>(obj));
326 }
327
GetCollisionSpace() const328 CollisionSpace *Frame::GetCollisionSpace() const
329 {
330 if (m_collisionSpace >= 0)
331 return &s_collisionSpaces[m_collisionSpace];
332 else
333 return nullptr;
334 }
335
336 // doesn't consider stasis velocity
GetVelocityRelTo(FrameId relToId) const337 vector3d Frame::GetVelocityRelTo(FrameId relToId) const
338 {
339 if (m_thisId == relToId) return vector3d(0, 0, 0); // early-out to avoid unnecessary computation
340
341 const Frame *relTo = Frame::GetFrame(relToId);
342 vector3d diff = m_rootVel - relTo->m_rootVel;
343 if (relTo->IsRotFrame())
344 return diff * relTo->m_rootOrient;
345 else
346 return diff;
347 }
348
GetPositionRelTo(FrameId relToId) const349 vector3d Frame::GetPositionRelTo(FrameId relToId) const
350 {
351 // early-outs for simple cases, required for accuracy in large systems
352 if (m_thisId == relToId) return vector3d(0, 0, 0);
353
354 const Frame *relTo = Frame::GetFrame(relToId);
355
356 if (GetParent() == relToId) return m_pos; // relative to parent
357 if (relTo->GetParent() == m_thisId) { // relative to child
358 if (!relTo->IsRotFrame())
359 return -relTo->m_pos;
360 else
361 return -relTo->m_pos * relTo->m_orient;
362 }
363 if (relTo->GetParent() == GetParent()) { // common parent
364 if (!relTo->IsRotFrame())
365 return m_pos - relTo->m_pos;
366 else
367 return (m_pos - relTo->m_pos) * relTo->m_orient;
368 }
369
370 // use precalculated absolute position and orient
371 vector3d diff = m_rootPos - relTo->m_rootPos;
372 if (relTo->IsRotFrame())
373 return diff * relTo->m_rootOrient;
374 else
375 return diff;
376 }
377
GetInterpPositionRelTo(FrameId relToId) const378 vector3d Frame::GetInterpPositionRelTo(FrameId relToId) const
379 {
380 const Frame *relTo = Frame::GetFrame(relToId);
381
382 // early-outs for simple cases, required for accuracy in large systems
383 if (m_thisId == relToId) return vector3d(0, 0, 0);
384 if (GetParent() == relTo->GetId()) return m_interpPos; // relative to parent
385 if (relTo->GetParent() == m_thisId) { // relative to child
386 if (!relTo->IsRotFrame())
387 return -relTo->m_interpPos;
388 else
389 return -relTo->m_interpPos * relTo->m_interpOrient;
390 }
391 if (relTo->GetParent() == GetParent()) { // common parent
392 if (!relTo->IsRotFrame())
393 return m_interpPos - relTo->m_interpPos;
394 else
395 return (m_interpPos - relTo->m_interpPos) * relTo->m_interpOrient;
396 }
397
398 vector3d diff = m_rootInterpPos - relTo->m_rootInterpPos;
399 if (relTo->IsRotFrame())
400 return diff * relTo->m_rootInterpOrient;
401 else
402 return diff;
403 }
404
GetOrientRelTo(FrameId relToId) const405 matrix3x3d Frame::GetOrientRelTo(FrameId relToId) const
406 {
407 if (m_thisId == relToId) return matrix3x3d::Identity();
408 return Frame::GetFrame(relToId)->m_rootOrient.Transpose() * m_rootOrient;
409 }
410
GetInterpOrientRelTo(FrameId relToId) const411 matrix3x3d Frame::GetInterpOrientRelTo(FrameId relToId) const
412 {
413 if (m_thisId == relToId) return matrix3x3d::Identity();
414 return Frame::GetFrame(relToId)->m_rootInterpOrient.Transpose() * m_rootInterpOrient;
415 /* if (IsRotFrame()) {
416 if (relTo->IsRotFrame()) return m_interpOrient * relTo->m_interpOrient.Transpose();
417 else return m_interpOrient;
418 }
419 if (relTo->IsRotFrame()) return relTo->m_interpOrient.Transpose();
420 else return matrix3x3d::Identity();
421 */
422 }
423
GetTransformRelTo(FrameId relToId) const424 matrix4x4d Frame::GetTransformRelTo(FrameId relToId) const
425 {
426 return matrix4x4d(GetOrientRelTo(relToId), GetPositionRelTo(relToId));
427 }
428
GetInterpTransformRelTo(FrameId relToId) const429 matrix4x4d Frame::GetInterpTransformRelTo(FrameId relToId) const
430 {
431 return matrix4x4d(GetInterpOrientRelTo(relToId), GetInterpPositionRelTo(relToId));
432 }
433
UpdateInterpTransform(double alpha)434 void Frame::UpdateInterpTransform(double alpha)
435 {
436 PROFILE_SCOPED()
437 m_interpPos = alpha * m_pos + (1.0 - alpha) * m_oldPos;
438
439 double len = m_oldAngDisplacement * (1.0 - alpha);
440 if (!is_zero_exact(len)) { // very small values are normal here
441 matrix3x3d rot = matrix3x3d::RotateY(len); // RotateY is backwards
442 m_interpOrient = m_orient * rot;
443 } else
444 m_interpOrient = m_orient;
445
446 Frame *parent = Frame::GetFrame(m_parent);
447 if (!parent)
448 ClearMovement();
449 else {
450 m_rootInterpPos = parent->m_rootInterpOrient * m_interpPos + parent->m_rootInterpPos;
451 m_rootInterpOrient = parent->m_rootInterpOrient * m_interpOrient;
452 }
453
454 for (FrameId kid : m_children) {
455 Frame *kidFrame = Frame::GetFrame(kid);
456 kidFrame->UpdateInterpTransform(alpha);
457 }
458 }
459
GetFrameTransform(const FrameId fFromId,const FrameId fToId,matrix4x4d & m)460 void Frame::GetFrameTransform(const FrameId fFromId, const FrameId fToId, matrix4x4d &m)
461 {
462 matrix3x3d forient = Frame::GetFrame(fFromId)->GetOrientRelTo(fToId);
463 vector3d fpos = Frame::GetFrame(fFromId)->GetPositionRelTo(fToId);
464 m = forient;
465 m.SetTranslate(fpos);
466 }
467
ClearMovement()468 void Frame::ClearMovement()
469 {
470 UpdateRootRelativeVars();
471 m_rootInterpPos = m_rootPos;
472 m_rootInterpOrient = m_rootOrient;
473 m_oldPos = m_interpPos = m_pos;
474 m_interpOrient = m_orient;
475 m_oldAngDisplacement = 0.0;
476 }
477
UpdateOrbitRails(double time,double timestep)478 void Frame::UpdateOrbitRails(double time, double timestep)
479 {
480 PROFILE_SCOPED()
481 std::for_each(begin(s_frames), end(s_frames), [&time, ×tep](Frame &frame) {
482 frame.m_oldPos = frame.m_pos;
483 frame.m_oldAngDisplacement = frame.m_angSpeed * timestep;
484
485 // update frame position and velocity
486 if (frame.m_parent.valid() && frame.m_sbody && !frame.IsRotFrame()) {
487 frame.m_pos = frame.m_sbody->GetOrbit().OrbitalPosAtTime(time);
488 vector3d pos2 = frame.m_sbody->GetOrbit().OrbitalPosAtTime(time + timestep);
489 frame.m_vel = (pos2 - frame.m_pos) / timestep;
490 }
491 // temporary test thing
492 else
493 frame.m_pos = frame.m_pos + frame.m_vel * timestep;
494
495 // update frame rotation
496 double ang = fmod(frame.m_angSpeed * time, 2.0 * M_PI);
497 if (!is_zero_exact(ang)) { // frequently used with e^-10 etc
498 matrix3x3d rot = matrix3x3d::RotateY(-ang); // RotateY is backwards
499 frame.m_orient = frame.m_initialOrient * rot; // angvel always +y
500 }
501 frame.UpdateRootRelativeVars(); // update root-relative pos/vel/orient
502 });
503 /*
504 for (FrameId kid : m_children) {
505 Frame *kidFrame = Frame::GetFrame(kid);
506 kidFrame->UpdateOrbitRails(time, timestep);
507 }
508 */
509 }
510
SetInitialOrient(const matrix3x3d & m,double time)511 void Frame::SetInitialOrient(const matrix3x3d &m, double time)
512 {
513 m_initialOrient = m;
514 double ang = fmod(m_angSpeed * time, 2.0 * M_PI);
515 if (!is_zero_exact(ang)) { // frequently used with e^-10 etc
516 matrix3x3d rot = matrix3x3d::RotateY(-ang); // RotateY is backwards
517 m_orient = m_initialOrient * rot; // angvel always +y
518 } else {
519 m_orient = m_initialOrient;
520 }
521 }
522
SetOrient(const matrix3x3d & m,double time)523 void Frame::SetOrient(const matrix3x3d &m, double time)
524 {
525 m_orient = m;
526 double ang = fmod(m_angSpeed * time, 2.0 * M_PI);
527 if (!is_zero_exact(ang)) { // frequently used with e^-10 etc
528 matrix3x3d rot = matrix3x3d::RotateY(ang); // RotateY is backwards
529 m_initialOrient = m_orient * rot; // angvel always +y
530 } else {
531 m_initialOrient = m_orient;
532 }
533 }
534
UpdateRootRelativeVars()535 void Frame::UpdateRootRelativeVars()
536 {
537 // update pos & vel relative to parent frame
538 Frame *parent = Frame::GetFrame(m_parent);
539 if (!parent) {
540 m_rootPos = m_rootVel = vector3d(0, 0, 0);
541 m_rootOrient = matrix3x3d::Identity();
542 } else {
543 m_rootPos = parent->m_rootOrient * m_pos + parent->m_rootPos;
544 m_rootVel = parent->m_rootOrient * m_vel + parent->m_rootVel;
545 m_rootOrient = parent->m_rootOrient * m_orient;
546 }
547 }
548