Open NathanKell opened 8 months ago
This implementation is now correct (in RO): https://github.com/KSP-RO/RealismOverhaul/commit/0e7c715bb15ad7d737aa928eb6a7d5794e89b55e
I'm trying to wrap my head around this, so I will try to just reformulate, let me know if there is something I didn't get right. So I see 3 somewhat separate issues :
ProtoVessel.rotation
, equal to Vessel.srfRelRotation
, defined by :
Quaternion.Inverse(vessel.mainBody.bodyTransform.rotation) * vessel.transform.rotation
.
That persistence mechanism doesn't work because :
bodyTransform.rotation
isn't a fixed surface relative reference when inverse rotation is active, making the persisted value useless garbage as soon as inverse rotation has been engaged after that value has been persisted.vessel.transform.rotation
nor protovessel.rotation
), beside setting calling vessel.transform.rotation = vessel.orbit.referenceBody.bodyTransform.rotation * protoVessel.rotation
once on ProtoVessel.Load()
when the vessel is instantiated on a full game reload.The solutions would be :
Difficulties / implementation :
ProtoVessel.rotation
field to store a zup-relative rotation would require patches on ProtoVessel(Vessel VesselRef, bool preCreate)
(where the value is set) and ProtoVessel.Load()
(where the value is copied to vessel.transform.rotation
). That seems like something sensible, but would cause weird side effects to any code relying on that field being a unity world rotation. From a quick github search, this would at the very least break the LMP/DMP multiplayer mods, possibly a few others. The ProtoVessel
objects lifecycle is almost impossible to track, so storing the value in a Dictionary<ProtoVessel, Quaternion>
would be a memory leak mess. That leaves us with the VesselModule option which is quite overkill, or leveraging the weird Dictionary<string, KSPParseable> vesselStateValues
"random data store" field in ProtoVessel, which has the drawback of being quite inefficient to read from.OrbitDriver.updateFromParameters()
, by updating vesselTransform.rotation
prior to the Vessel.SetPosition()
call. OrbitDriver.updateFromParameters()
, to update vesselTransform.rotation
should also take care of the unloaded vessels case.In any case, setting vesselTransform.rotation
for packed and unloaded vessels is something that stock never do past initial instantiation, but a bunch of mods are definitely doing it, so overwriting the value continuously with our own "source of truth" will surely cause a lot of issues.
A safer alternative would to be to apply the inverse rotation induced rotation offset to the current vesselTransform.rotation
value, but given how small these offsets are likely to be, and the fact that we will be applying them on a 32 bit floats backed quaternion, I fear there will be significant FP precision induced drift over time, but this should probably be tested. Applying an offset also mean we should be extra careful to not miss a vessel or apply it multiple times to the same vessel, which will likely prove tricky if we want to reuse the existing stock SetPosition()
calls in OrbitDriver and FloatingOrigin to avoid the performance overhead of calling it again in a component of our own.
Yeah, it's complex indeed! Your reformulation looks broadly right based on my current understanding; here are a couple additions/modifications:
protovessel.rotation
no longer matches vessel.srfRelRotation
.On solutions and difficulties:
vessel.SetRotation
. So long as this is done as part of Precalculate, it should not interfere with other mods, because it will run before their FixedUpdates. (Principia is a slightly special case, because it hijacks precalculate in a different way, so coordination with egg is probably needed, or just force-disabling the patch as unnecessary so long as Principia is installed).I agree that it's likely somewhat dangerous to straightforwardly patch orbitdriver because updateFromParameters might be called multiple times. Patching precalculate however seems safer not least because a mod that digs deeper would, I would have thought, replaced that with its own subclass (which is why I left the methods virtual--I'm not entirely sure why egg didn't do that).
I also agree float precision will bite us if we're applying per-frame inverse rotation changes at low warp, which is why I tend towards the approach of storing a fixed orientation and setting based on that each frame (and if at end of frame it differs, storing the new orientation).
Okay, did a bunch of experimentation trying to hijack updateFromParameters()
, tracking vessels rotation with a QuaternionD
independently or storing a fixed orientation, but this is becoming quite a fragile mess and I'm facing weird issues, and in both cases I need the "and if at end of frame it differs, storing the new orientation" part, but I'm starting to doubt this can be done reliably.
Or maybe this was just a bad day.
Will try to poke at this another time.
Did the hijacking in RO since we can basically guarantee that it's safe in that ecosystem (since we disable in the presence of Principia, which is the only other relevant supported mod).
Per discord I'm not really sure what the correct stock-ecosystem approach is though because as you say there's a whole bunch of stuff that can be happening during the frame if you can't rely on yourself to be the canonical source of vessel orientation (a github search for .setrotation vessel path:*.cs
turns up more than enough hits, alas).
Due to looking into RO's packed-rotation code, I ended up looking at KSP itself and found these issues:
Suggested fixes:
Reapplying an invariant rotation (rotated to whatever the current localspace coordinate system is) whenever a vessel is packed--including for unloaded vessels!--should solve (a) the inverse-rotation issue for loaded but packed vessels, (b) the inverse-rotation issue for unloaded vessels, (c) SOI transitions for unloaded vessels, and (d) unloaded vessels not having their current rotations saved as part of BackupVessel