Alright. So I realized that my previous statement on only needing "to scale the inertia wind when set in World mode" was wrong, as I need more control on the wind inertia forces. So this is my current hacked togeather solution;
Updated the manual update system to separate velocities
Added data and utility methods separate from Obi code to more easily upgrade.
Then some smaller things;
Add `BurstInertialFrameAdditionalData` as member to `BurstInertialFrame` and `ApplyInertialForcesJobAdditionalData` to `ApplyInertialForcesJob`.
Replaced `ApplyInertialForcesJob`s velocity and wind calculate with utilities from above.
Added `SetAdditionalData` to the ISolverImpl etc (only supporting bursted for now).
In `ApplyFrame` create and add `ApplyInertialForcesJobAdditionalData`.
With this I can handle the forces separatley and use two different factors `inertiaScaleFromLocal` and `inertiaScaleFromLocal` to control how much it should affect. (I need this because the parent can move very fast, and might want to modify it based on if you're in shade of the air in the velocity direction etc).
Now this all works fine.
One thing maybe you can explain to me is why you are using prevFrame.translation for VelocityAtPoint and not the current one (I changed it to that)?
Consider a case where the point is in front of the solver in direction of movement, you move with a velocity which in turn moves the solver twice the distance of the point to the solver + the solver rotates. Now the point is in the new frame world space - but you take the vector from previous solver world pos, the vector will be pointing in the opposite direction than its relative point to the solver. After crossing this would now result in a velocity in the opposite the direction compared if the solver was standing still?
Also if you have any thoughts of a neater way to integrate custom logic like this into obi.
Cheers
Updated the manual update system to separate velocities
Code:
public void OnUpdate(ref SystemState state)
{
var system = state.World.GetExistingSystemManaged<GhostPresentationGameObjectSystem>();
SystemAPI.TryGetSingleton<ObiRuntimeData>(out var obiRuntimeData);
var timeAhead = (float)(SystemAPI.Time.ElapsedTime - obiRuntimeData.elapsedTime);
var timeStep = (float)obiRuntimeData.deltaTime;
if (timeAhead < 0f || timeStep == 0f)
{
return;
}
// Assumption we do 1:1 physics ticks with tick rate
var steps = _lastFixedTick.IsValid ? obiRuntimeData.lastFixedTick.TicksSince(_lastFixedTick) : 1;
_lastFixedTick = obiRuntimeData.lastFixedTick;
foreach (var (obiSolverSettingsDataRef, obiSolverSimulateHistoryDataRef, localTransformRef,
parentRelativeTransformDataRef, parentRelativeTransformCacheDataRef, entity)
in SystemAPI.Query<RefRO<ObiSolverSettingsData>, RefRW<ObiSolverSimulateHistoryData>,
RefRO<LocalTransform>, RefRO<ParentRelativeTransformData>,
RefRO<ParentRelativeTransformCacheData>>().WithEntityAccess())
{
var gameObject = system.GetGameObjectForEntity(state.EntityManager, entity);
var obiSolver = gameObject?.GetComponent<ObiSolver>();
if (obiSolver == null)
{
continue;
}
#if UNITY_EDITOR
if (obiSolver.isActiveAndEnabled)
{
Debug.LogError($"ObiSolver {gameObject.name} is enabled but running in manual mode. Please disable the component to avoid unexpected behavior.", gameObject);
}
#endif
// Use localTransform for fixed updated position compared to transform (which is updated from localToWorld)
var interpolatedLocalPosition = gameObject.transform.localPosition;
var interpolatedLocalRotation = gameObject.transform.localRotation;
var worldPosition = new float4(localTransformRef.ValueRO.Position, 0);
var worldRotation = localTransformRef.ValueRO.Rotation;
var localPosition = new float4(parentRelativeTransformDataRef.ValueRO.position, 0);
var localRotation = parentRelativeTransformDataRef.ValueRO.rotation;
var parentEntity = parentRelativeTransformDataRef.ValueRO.parentEntity;
var parentTransform = parentRelativeTransformCacheDataRef.ValueRO.parentLocalTransform;
gameObject.transform.localPosition = worldPosition.xyz;
gameObject.transform.localRotation = worldRotation;
ref var obiSolverData = ref obiSolverSimulateHistoryDataRef.ValueRW;
if (!obiSolverData.initialized || obiSolverData.previousParent != parentEntity)
{
obiSolverData.initialized = true;
obiSolverData.previousPositionLocal = localPosition;
obiSolverData.previousRotationLocal = localRotation;
obiSolverData.previousPosition = worldPosition;
obiSolverData.previousRotation = worldRotation;
obiSolverData.previousParent = parentEntity;
}
if (steps > 0)
{
obiSolver.Initialize();
var dt = timeStep * steps;
var velocityFromLocal = BurstIntegration.DifferentiateLinear(localPosition, obiSolverData.previousPositionLocal, dt);
velocityFromLocal = new float4(parentTransform.TransformDirection(velocityFromLocal.xyz), 0);
var totalVelocity = BurstIntegration.DifferentiateLinear(worldPosition, obiSolverData.previousPosition, dt);
var velocityFromParent = totalVelocity - velocityFromLocal;
var angularVelocityFromLocal = BurstIntegration.DifferentiateAngular(parentTransform.TransformRotation(localRotation), parentTransform.TransformRotation(obiSolverData.previousRotationLocal), dt);
var angularVelocityTotal = BurstIntegration.DifferentiateAngular(worldRotation, obiSolverData.previousRotation, dt);
var angularVelocityFromParent = angularVelocityTotal - angularVelocityFromLocal;
obiSolverData.previousPositionLocal = localPosition;
obiSolverData.previousRotationLocal = localRotation;
obiSolverData.previousPosition = worldPosition;
obiSolverData.previousRotation = worldRotation;
var inputData = new BurstInertialFrameAdditionalInputData()
{
velocityFromLocal = velocityFromLocal,
velocityFromParent = velocityFromParent,
angularVelocityFromLocal = angularVelocityFromLocal,
angularVelocityFromParent = angularVelocityFromParent,
inertiaScaleFromLocal = obiSolverSettingsDataRef.ValueRO.inertiaScaleFromLocal,
inertiaScaleFromParent = obiSolverSettingsDataRef.ValueRO.inertiaScaleFromParent,
};
obiSolver.implementation.SetAdditionalData(inputData,dt);
obiSolver.StartSimulation(timeStep, steps);
}
obiSolver.Render(timeAhead);
gameObject.transform.localPosition = interpolatedLocalPosition;
gameObject.transform.localRotation = interpolatedLocalRotation;
}
}Added data and utility methods separate from Obi code to more easily upgrade.
Code:
namespace Obi
{
public struct BurstInertialFrameAdditionalInputData
{
public float4 velocityFromLocal;
public float4 velocityFromParent;
public float4 angularVelocityFromLocal;
public float4 angularVelocityFromParent;
public float inertiaScaleFromLocal;
public float inertiaScaleFromParent;
}
public struct BurstInertialFrameAdditionalData
{
public BurstInertialFrameAdditionalInputData inputData;
public float4 accelerationFromLocal;
public float4 accelerationFromParent;
public float4 angularAccelerationFromLocal;
public float4 angularAccelerationFromParent;
}
public struct ApplyInertialForcesJobAdditionalData
{
public float4 localAngularVel;
public float4 localInertialAccel;
public float4 localEulerAccel;
public float4 parentAngularVel;
public float4 parentInertialAccel;
public float4 parentEulerAccel;
}
public class InertialForcesUtility
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UpdateAdditionalData(ref BurstInertialFrameAdditionalData additionalData, in BurstInertialFrameAdditionalInputData inputData, float dt)
{
var prevLocalVelocity = additionalData.inputData.velocityFromLocal;
var prevParentVelocity = additionalData.inputData.velocityFromParent;
var prevLocalAngularVelocity = additionalData.inputData.angularVelocityFromLocal;
var prevParentAngularVelocity = additionalData.inputData.angularVelocityFromParent;
additionalData.accelerationFromLocal = BurstIntegration.DifferentiateLinear(inputData.velocityFromLocal, prevLocalVelocity, dt);
additionalData.accelerationFromParent = BurstIntegration.DifferentiateLinear(inputData.velocityFromParent, prevParentVelocity, dt);
additionalData.angularAccelerationFromLocal = BurstIntegration.DifferentiateLinear(inputData.angularVelocityFromLocal, prevLocalAngularVelocity, dt);
additionalData.angularAccelerationFromParent = BurstIntegration.DifferentiateLinear(inputData.angularVelocityFromParent, prevParentAngularVelocity, dt);
additionalData.inputData = inputData;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ApplyInertialForcesJobAdditionalData CreateApplyInertiaForcesAdditionalData(in BurstInertialFrame inertialFrame,
in float4x4 linear, in float4x4 linearInv)
{
return new ApplyInertialForcesJobAdditionalData()
{
localAngularVel = math.mul(linearInv, math.mul(float4x4.Scale(inertialFrame.additionalData.inputData.angularVelocityFromLocal.xyz), linear)).diagonal(),
localEulerAccel = math.mul(linearInv, math.mul(float4x4.Scale(inertialFrame.additionalData.angularAccelerationFromLocal.xyz), linear)).diagonal(),
localInertialAccel = math.mul(linearInv, inertialFrame.additionalData.accelerationFromLocal),
parentAngularVel = math.mul(linearInv, math.mul(float4x4.Scale(inertialFrame.additionalData.inputData.angularVelocityFromParent.xyz), linear)).diagonal(),
parentEulerAccel = math.mul(linearInv, math.mul(float4x4.Scale(inertialFrame.additionalData.angularAccelerationFromParent.xyz), linear)).diagonal(),
parentInertialAccel = math.mul(linearInv, inertialFrame.additionalData.accelerationFromParent),
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float4 CalculateWorldInertialForces(in ApplyInertialForcesJobAdditionalData additionalData,
in BurstInertialFrame inertialFrame, float4 position, float4 velocity, float worldLinearInertiaScale,
float worldAngularInertiaScale, float deltaTime)
{
var fromLocalForce = CalculateInertialForce(additionalData.localEulerAccel, additionalData.localAngularVel,
velocity, additionalData.localInertialAccel, position, worldLinearInertiaScale, worldAngularInertiaScale,
inertialFrame.additionalData.inputData.inertiaScaleFromLocal, deltaTime);
var fromParentForce = CalculateInertialForce(additionalData.parentEulerAccel, additionalData.parentAngularVel,
velocity, additionalData.parentInertialAccel, position, worldLinearInertiaScale, worldAngularInertiaScale,
inertialFrame.additionalData.inputData.inertiaScaleFromParent, deltaTime);
return fromLocalForce + fromParentForce;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float4 CalculateInertialForce(float4 eulerAccel, float4 angularVel, float4 velocity,
float4 inertialAccel, float4 position, float worldLinearInertiaScale, float worldAngularInertiaScale,
float inertiaScale, float deltaTime)
{
float4 euler = new float4(math.cross(eulerAccel.xyz, position.xyz), 0);
float4 centrifugal = new float4(math.cross(angularVel.xyz, math.cross(angularVel.xyz, position.xyz)), 0);
float4 coriolis = 2 * new float4(math.cross(angularVel.xyz, velocity.xyz), 0);
float4 angularAccel = euler + coriolis + centrifugal;
return (inertialAccel * worldLinearInertiaScale + angularAccel * worldAngularInertiaScale) * inertiaScale * deltaTime;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float4 CalculateInertialWindForces(in BurstInertialFrame inertialFrame, float4 position)
{
float4 wsPos = inertialFrame.frame.TransformPoint(position);
var worldInertialWindForceFromLocal = VelocityAtPoint(wsPos, inertialFrame.frame.translation,
inertialFrame.additionalData.inputData.velocityFromLocal, inertialFrame.additionalData.inputData.angularVelocityFromLocal);
var worldInertialWindForceFromParent = VelocityAtPoint(wsPos, inertialFrame.frame.translation,
inertialFrame.additionalData.inputData.velocityFromParent, inertialFrame.additionalData.inputData.angularVelocityFromParent);
var totalWorldInertialWindForce = worldInertialWindForceFromLocal * inertialFrame.additionalData.inputData.inertiaScaleFromLocal +
worldInertialWindForceFromParent * inertialFrame.additionalData.inputData.inertiaScaleFromParent;
return inertialFrame.frame.InverseTransformVector(totalWorldInertialWindForce);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float4 VelocityAtPoint(float4 worldSpacePoint, float4 prevFrameOrigin, float4 velocity, float4 angularVelocity)
{
return velocity + new float4(math.cross(angularVelocity.xyz, (worldSpacePoint - prevFrameOrigin).xyz), 0);
}
}
}Then some smaller things;
Add `BurstInertialFrameAdditionalData` as member to `BurstInertialFrame` and `ApplyInertialForcesJobAdditionalData` to `ApplyInertialForcesJob`.
Replaced `ApplyInertialForcesJob`s velocity and wind calculate with utilities from above.
Added `SetAdditionalData` to the ISolverImpl etc (only supporting bursted for now).
In `ApplyFrame` create and add `ApplyInertialForcesJobAdditionalData`.
With this I can handle the forces separatley and use two different factors `inertiaScaleFromLocal` and `inertiaScaleFromLocal` to control how much it should affect. (I need this because the parent can move very fast, and might want to modify it based on if you're in shade of the air in the velocity direction etc).
Now this all works fine.
One thing maybe you can explain to me is why you are using prevFrame.translation for VelocityAtPoint and not the current one (I changed it to that)?
Consider a case where the point is in front of the solver in direction of movement, you move with a velocity which in turn moves the solver twice the distance of the point to the solver + the solver rotates. Now the point is in the new frame world space - but you take the vector from previous solver world pos, the vector will be pointing in the opposite direction than its relative point to the solver. After crossing this would now result in a velocity in the opposite the direction compared if the solver was standing still?
Also if you have any thoughts of a neater way to integrate custom logic like this into obi.
Cheers

