From c2f19a486de06e1ff5e9461e28daa87f84694c57 Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Sat, 16 May 2026 11:07:47 -0400 Subject: [PATCH 1/8] WIP GravityGimbal module --- FreeIva/InternalModules/GravityGimbal.cs | 56 ++++++++++++++++++++++++ GameData/FreeIva/CrashCouchTest.cfg | 10 +++++ 2 files changed, 66 insertions(+) create mode 100644 FreeIva/InternalModules/GravityGimbal.cs create mode 100644 GameData/FreeIva/CrashCouchTest.cfg diff --git a/FreeIva/InternalModules/GravityGimbal.cs b/FreeIva/InternalModules/GravityGimbal.cs new file mode 100644 index 00000000..ebd52ef8 --- /dev/null +++ b/FreeIva/InternalModules/GravityGimbal.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace FreeIva.InternalModules +{ + internal class GravityGimbal : InternalModule + { + [KSPField] + public Vector3 upAxis = new Vector3(0, 1, 0); + + [KSPField] + public string transformName = string.Empty; + + [SerializeField] + public Transform controlledTransform; + + InternalModuleFreeIva m_freeIvaModule; + + public override void OnLoad(ConfigNode node) + { + base.OnLoad(node); + + if (HighLogic.LoadedScene == GameScenes.LOADING) + { + if (transformName != string.Empty) + { + controlledTransform = TransformUtil.FindPropTransform(internalProp, transformName); + } + else + { + controlledTransform = internalProp.hasModel ? controlledTransform : internalModel.transform; + } + } + } + + protected void Start() + { + m_freeIvaModule = InternalModuleFreeIva.GetForModel(internalModel); + } + + public override void OnFixedUpdate() + { + base.OnFixedUpdate(); + + if (controlledTransform != null) + { + Vector3 subjectiveGravity = FreeIva.GetInternalSubjectiveAcceleration(m_freeIvaModule, controlledTransform.position); + controlledTransform.up = -subjectiveGravity; + } + } + } +} diff --git a/GameData/FreeIva/CrashCouchTest.cfg b/GameData/FreeIva/CrashCouchTest.cfg new file mode 100644 index 00000000..57b878be --- /dev/null +++ b/GameData/FreeIva/CrashCouchTest.cfg @@ -0,0 +1,10 @@ +@INTERNAL[mk2InlineInternal]:FOR[FreeIva] +{ + @PROP[Seat_Pilot] + { + MODULE + { + name = GravityGimbal + } + } +} \ No newline at end of file From db5c2a4cd8ba203cd6abdda75ce6a4f7a7bc01f4 Mon Sep 17 00:00:00 2001 From: Jon Cable Date: Sat, 16 May 2026 15:58:52 -0400 Subject: [PATCH 2/8] GravityGimbal proof of conecpt working --- FreeIva.sln | 3 ++- FreeIva/InternalModules/GravityGimbal.cs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/FreeIva.sln b/FreeIva.sln index 319c589c..a8ee8f33 100644 --- a/FreeIva.sln +++ b/FreeIva.sln @@ -3,12 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32210.238 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FreeIva", "FreeIva\FreeIva.csproj", "{AEB6EEAB-46BC-49EE-8941-3833E1B1171B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeIva", "FreeIva\FreeIva.csproj", "{AEB6EEAB-46BC-49EE-8941-3833E1B1171B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4D82D2DE-C252-46FC-852E-3CFB355CA3A6}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig CHANGELOG.md = CHANGELOG.md + FreeIva\FreeIva.csproj.user = FreeIva\FreeIva.csproj.user GameData\FreeIva\FreeIva.version = GameData\FreeIva\FreeIva.version README.md = README.md EndProjectSection diff --git a/FreeIva/InternalModules/GravityGimbal.cs b/FreeIva/InternalModules/GravityGimbal.cs index ebd52ef8..fd53184e 100644 --- a/FreeIva/InternalModules/GravityGimbal.cs +++ b/FreeIva/InternalModules/GravityGimbal.cs @@ -7,6 +7,11 @@ namespace FreeIva.InternalModules { + // TODO: limit rotation to specific axes + // TODO: provide angle limits for rotation + // TODO: smoothing + // TODO: return to neutral when weightless? + internal class GravityGimbal : InternalModule { [KSPField] @@ -19,6 +24,7 @@ internal class GravityGimbal : InternalModule public Transform controlledTransform; InternalModuleFreeIva m_freeIvaModule; + Quaternion m_defaultRotation; public override void OnLoad(ConfigNode node) { @@ -32,22 +38,22 @@ public override void OnLoad(ConfigNode node) } else { - controlledTransform = internalProp.hasModel ? controlledTransform : internalModel.transform; + controlledTransform = internalProp.hasModel ? transform : internalModel.transform; } } } protected void Start() { + m_defaultRotation = controlledTransform?.rotation ?? Quaternion.identity; m_freeIvaModule = InternalModuleFreeIva.GetForModel(internalModel); } - public override void OnFixedUpdate() + void FixedUpdate() { - base.OnFixedUpdate(); - if (controlledTransform != null) { + // TODO: use upAxis Vector3 subjectiveGravity = FreeIva.GetInternalSubjectiveAcceleration(m_freeIvaModule, controlledTransform.position); controlledTransform.up = -subjectiveGravity; } From f01aaa0bff47a13632104e9457381ced9e70e537 Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Sun, 17 May 2026 10:54:54 -0400 Subject: [PATCH 3/8] gravity gimbal is somewhat functional --- FreeIva/InternalModules/GravityGimbal.cs | 40 ++++++++++++++++++------ FreeIva/KerbalIvaController.cs | 15 ++++++--- GameData/FreeIva/CrashCouchTest.cfg | 11 ++++++- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/FreeIva/InternalModules/GravityGimbal.cs b/FreeIva/InternalModules/GravityGimbal.cs index fd53184e..914e7649 100644 --- a/FreeIva/InternalModules/GravityGimbal.cs +++ b/FreeIva/InternalModules/GravityGimbal.cs @@ -15,15 +15,21 @@ namespace FreeIva.InternalModules internal class GravityGimbal : InternalModule { [KSPField] - public Vector3 upAxis = new Vector3(0, 1, 0); + public Vector3 rotationAxis = new Vector3(1, 0, 0); // in prop space [KSPField] public string transformName = string.Empty; - [SerializeField] - public Transform controlledTransform; + [KSPField] + public float minAccel = 0.05f; + + [KSPField] + public float smoothingFactor = 0.2f; + + [SerializeField] Transform m_controlledTransform; InternalModuleFreeIva m_freeIvaModule; + Vector3 m_rotationAxisInternalSpace; Quaternion m_defaultRotation; public override void OnLoad(ConfigNode node) @@ -34,28 +40,42 @@ public override void OnLoad(ConfigNode node) { if (transformName != string.Empty) { - controlledTransform = TransformUtil.FindPropTransform(internalProp, transformName); + m_controlledTransform = TransformUtil.FindPropTransform(internalProp, transformName); } else { - controlledTransform = internalProp.hasModel ? transform : internalModel.transform; + m_controlledTransform = internalProp.hasModel ? transform : internalModel.transform; } } } protected void Start() { - m_defaultRotation = controlledTransform?.rotation ?? Quaternion.identity; m_freeIvaModule = InternalModuleFreeIva.GetForModel(internalModel); + m_rotationAxisInternalSpace = transform.TransformDirection(rotationAxis); + m_defaultRotation = transform.rotation; } void FixedUpdate() { - if (controlledTransform != null) + if (m_controlledTransform != null) { - // TODO: use upAxis - Vector3 subjectiveGravity = FreeIva.GetInternalSubjectiveAcceleration(m_freeIvaModule, controlledTransform.position); - controlledTransform.up = -subjectiveGravity; + Vector3 subjectiveGravity = FreeIva.GetInternalSubjectiveAcceleration(m_freeIvaModule, m_controlledTransform.position); + Quaternion targetRotation; + + if (KerbalIvaController.UseHorizon(subjectiveGravity, m_freeIvaModule.Centrifuge != null)) + { + Vector3 forward = Vector3.Cross(subjectiveGravity, m_rotationAxisInternalSpace); + Vector3 up = Vector3.Cross(forward, m_rotationAxisInternalSpace); + + targetRotation = Quaternion.LookRotation(forward, up); + } + else + { + targetRotation = m_defaultRotation; + } + + m_controlledTransform.rotation = Quaternion.Lerp(m_controlledTransform.rotation, targetRotation, smoothingFactor); } } } diff --git a/FreeIva/KerbalIvaController.cs b/FreeIva/KerbalIvaController.cs index 640807a4..15321983 100644 --- a/FreeIva/KerbalIvaController.cs +++ b/FreeIva/KerbalIvaController.cs @@ -426,15 +426,20 @@ public void UpdatePosition(Vector3 flightAccel, Vector3 movementThrottle, bool j #endif } - Vector3 UpdateGravity() + internal static bool UseHorizon(Vector3 flightAccel, bool inCentrifuge) { - Vector3 flightAccel = FreeIva.GetInternalSubjectiveAcceleration(FreeIva.CurrentInternalModuleFreeIva, transform.position); - - float minAccelForHorizon = (FlightGlobals.ActiveVessel.LandedOrSplashed || currentCentrifuge != null) + float minAccelForHorizon = (FlightGlobals.ActiveVessel.LandedOrSplashed || inCentrifuge) ? MIN_ACCEL_FOR_HORIZON_LANDED : MIN_ACCEL_FOR_HORIZON_AIRBORNE; - usingRelativeMovement = flightAccel.magnitude >= minAccelForHorizon; + return flightAccel.magnitude >= minAccelForHorizon; + } + + Vector3 UpdateGravity() + { + Vector3 flightAccel = FreeIva.GetInternalSubjectiveAcceleration(FreeIva.CurrentInternalModuleFreeIva, transform.position); + + usingRelativeMovement = UseHorizon(flightAccel, currentCentrifuge != null); horizonDownVector = usingRelativeMovement ? flightAccel : Vector3.zero; // update attaching/detaching to things (note that detaching from a centrifuge is handled in ExitCentrifuge) diff --git a/GameData/FreeIva/CrashCouchTest.cfg b/GameData/FreeIva/CrashCouchTest.cfg index 57b878be..6325183b 100644 --- a/GameData/FreeIva/CrashCouchTest.cfg +++ b/GameData/FreeIva/CrashCouchTest.cfg @@ -1,10 +1,19 @@ @INTERNAL[mk2InlineInternal]:FOR[FreeIva] { - @PROP[Seat_Pilot] + !MODULE[InternalSeat],* {} + + @PROP[Seat_Pilot],* { MODULE { name = GravityGimbal } + + MODULE + { + name = InternalSeat + allowCrewHelmet = false + seatTransformName = model + } } } \ No newline at end of file From c6465f402016abc85c8956c30fc4c6b63f749c7d Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Mon, 18 May 2026 14:10:41 -0400 Subject: [PATCH 4/8] fix incorrect model rotations in some htprops cfgs --- GameData/FreeIva/HabTechProps/htProps_HatchCBM.cfg | 2 -- GameData/FreeIva/HabTechProps/htProps_HatchDoor.cfg | 1 - 2 files changed, 3 deletions(-) diff --git a/GameData/FreeIva/HabTechProps/htProps_HatchCBM.cfg b/GameData/FreeIva/HabTechProps/htProps_HatchCBM.cfg index 70f65d7c..096823d3 100644 --- a/GameData/FreeIva/HabTechProps/htProps_HatchCBM.cfg +++ b/GameData/FreeIva/HabTechProps/htProps_HatchCBM.cfg @@ -11,7 +11,6 @@ PROP { model = FreeIva/Props/HatchBoxCollider position = 0, 0.00826506782, 0 - rotation = 0, 0, 0, -1 //quaternion scale = 0.604978085, 0.0175083745, 0.605912209 } @@ -19,7 +18,6 @@ PROP { model = HabTechProps/Props/doorHatchWindowPlug position = 0, 0.0130386753, -0.221190825 - rotation = 0, 0, 0, -1 //quaternion } } diff --git a/GameData/FreeIva/HabTechProps/htProps_HatchDoor.cfg b/GameData/FreeIva/HabTechProps/htProps_HatchDoor.cfg index f5da171c..03c081cb 100644 --- a/GameData/FreeIva/HabTechProps/htProps_HatchDoor.cfg +++ b/GameData/FreeIva/HabTechProps/htProps_HatchDoor.cfg @@ -10,7 +10,6 @@ PROP MODEL { model = FreeIva/Props/HatchBoxCollider - rotation = 0, 0, 0, -1 scale = 0.774956346, 0.0422763638, 1 } From e3aa1fced72143fcc37e1339e3493162ea6a1ec9 Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Mon, 18 May 2026 16:56:05 -0400 Subject: [PATCH 5/8] functional InstanceInternal module --- FreeIva/InternalModules/InstanceInternal.cs | 60 +++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 FreeIva/InternalModules/InstanceInternal.cs diff --git a/FreeIva/InternalModules/InstanceInternal.cs b/FreeIva/InternalModules/InstanceInternal.cs new file mode 100644 index 00000000..745aff93 --- /dev/null +++ b/FreeIva/InternalModules/InstanceInternal.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace FreeIva.InternalModules +{ + class ConfigNodeHolder : ScriptableObject + { + public ConfigNode Node; + } + + internal class InstanceInternal : InternalModule + { + [SerializeReference] + ConfigNodeHolder m_prefabInternalNode; + ConfigNode m_internalNode; + + public override void OnLoad(ConfigNode moduleNode) + { + base.OnLoad(moduleNode); + + var internalNode = moduleNode.GetNode("INTERNAL"); + + if (HighLogic.LoadedScene == GameScenes.LOADING) + { + m_prefabInternalNode = ScriptableObject.CreateInstance(); + m_prefabInternalNode.Node = internalNode; + } + + if (internalNode != null) + { + m_internalNode = internalNode; + } + } + + void Start() + { + m_internalNode = m_internalNode ?? m_prefabInternalNode?.Node; + string internalName = m_internalNode?.GetValue("name"); + + if (internalName == null) + { + return; + } + + var internalPrefab = PartLoader.GetInternalPart(internalName); + if (internalPrefab != null) + { + var internalModel = GameObject.Instantiate(internalPrefab); + internalModel.transform.SetParent(transform, false); + internalModel.part = part; + internalModel.gameObject.SetActive(true); + internalModel.Load(m_internalNode); + } + } + } +} From 8c1ee6a05032c176f08d52056d17ee951415908c Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Mon, 18 May 2026 17:06:03 -0400 Subject: [PATCH 6/8] remove crash couch test file --- GameData/FreeIva/CrashCouchTest.cfg | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 GameData/FreeIva/CrashCouchTest.cfg diff --git a/GameData/FreeIva/CrashCouchTest.cfg b/GameData/FreeIva/CrashCouchTest.cfg deleted file mode 100644 index 6325183b..00000000 --- a/GameData/FreeIva/CrashCouchTest.cfg +++ /dev/null @@ -1,19 +0,0 @@ -@INTERNAL[mk2InlineInternal]:FOR[FreeIva] -{ - !MODULE[InternalSeat],* {} - - @PROP[Seat_Pilot],* - { - MODULE - { - name = GravityGimbal - } - - MODULE - { - name = InternalSeat - allowCrewHelmet = false - seatTransformName = model - } - } -} \ No newline at end of file From f3281b3ee547431e6a242ed622e7971d25c7af0e Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Mon, 18 May 2026 17:07:05 -0400 Subject: [PATCH 7/8] remove some TODOs --- FreeIva/InternalModules/GravityGimbal.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/FreeIva/InternalModules/GravityGimbal.cs b/FreeIva/InternalModules/GravityGimbal.cs index 914e7649..0a0d9338 100644 --- a/FreeIva/InternalModules/GravityGimbal.cs +++ b/FreeIva/InternalModules/GravityGimbal.cs @@ -7,10 +7,7 @@ namespace FreeIva.InternalModules { - // TODO: limit rotation to specific axes // TODO: provide angle limits for rotation - // TODO: smoothing - // TODO: return to neutral when weightless? internal class GravityGimbal : InternalModule { From 9490029fb51fbdb15f974937f1ba9bb288284f5e Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Mon, 18 May 2026 17:18:05 -0400 Subject: [PATCH 8/8] support attaching instanced internals to a named transform --- FreeIva/InternalModules/InstanceInternal.cs | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/FreeIva/InternalModules/InstanceInternal.cs b/FreeIva/InternalModules/InstanceInternal.cs index 745aff93..e8cdbb8b 100644 --- a/FreeIva/InternalModules/InstanceInternal.cs +++ b/FreeIva/InternalModules/InstanceInternal.cs @@ -18,9 +18,13 @@ internal class InstanceInternal : InternalModule ConfigNodeHolder m_prefabInternalNode; ConfigNode m_internalNode; - public override void OnLoad(ConfigNode moduleNode) - { - base.OnLoad(moduleNode); + [KSPField] + public string parentTransformName = string.Empty; + [SerializeField] Transform m_parentTransform; + + public override void OnLoad(ConfigNode moduleNode) + { + base.OnLoad(moduleNode); var internalNode = moduleNode.GetNode("INTERNAL"); @@ -28,6 +32,15 @@ public override void OnLoad(ConfigNode moduleNode) { m_prefabInternalNode = ScriptableObject.CreateInstance(); m_prefabInternalNode.Node = internalNode; + + if (parentTransformName != string.Empty) + { + m_parentTransform = TransformUtil.FindPropTransform(internalProp, parentTransformName); + } + else + { + m_parentTransform = internalProp.hasModel ? transform : internalModel.transform; + } } if (internalNode != null) @@ -41,7 +54,7 @@ void Start() m_internalNode = m_internalNode ?? m_prefabInternalNode?.Node; string internalName = m_internalNode?.GetValue("name"); - if (internalName == null) + if (internalName == null || m_parentTransform == null) { return; } @@ -50,7 +63,7 @@ void Start() if (internalPrefab != null) { var internalModel = GameObject.Instantiate(internalPrefab); - internalModel.transform.SetParent(transform, false); + internalModel.transform.SetParent(m_parentTransform, false); internalModel.part = part; internalModel.gameObject.SetActive(true); internalModel.Load(m_internalNode);