(16-02-2026, 09:21 AM)josemendez Wrote: Hi,
What Physics.SyncTransforms() does is it immediately commits any changes done by to collider/rigidbody transforms by the main thread to Unity's physics engine (PhysX). Even if you don't call it manually, Unity does call it automatically right before each FixedUpdate() (see the manual):
https://docs.unity3d.com/6000.3/Document...forms.html
So in case your animation system is updating animation before physics (which is the correct approach for anything physics related) then it's not necessary to call Physics.SyncTransforms manually in any case.
Obi doesn't "read" this at all, since it's a separate, standalone physics engine. Calling Physics.SyncTransforms() has no effect on Obi at all as it only affects Unity's own physics engine.
I've been unable to reproduce this issue using Unity's built-in animation system. I'd need to take a closer look to a project that exhibits this problem in order to help. Would it be possible for you to share your project (or any project that reproduces the same problem) by sending it to support(at)virtualmethodstudio.com so that I can diagnose it?
kind regards
From the physics behavior and the physics debugger it seems I do need the sync transforms to get the colliders to follow the character properly, but it seems to vary by config.
Anyway, yes I'll try isolating this and giving a example project, the issue isn't what I thought from further experimenting.
Whats giving me the wrong impression is the "attach" behavior I scripted, with certain animations (i believe when the character is heavily offset from the origin or turned around) the collision position is processed wrong.
If the particle sticks to the "Head" collider, the position is offset by a whole unit but it is still following the transform. This only happens during animation, the collision triggers at the right position, but the position it "sticks" to is wrong. This is why I thought the collision was offset.
Code:
private static void Global_OnCollision(ObiSolver solver, ObiNativeContactList contacts)
{
for (int s = 0; s < activeStrings.Count; s++)
{
var str = activeStrings[s];
if (str.solver == solver)
str.Internal_OnCollision(solver, contacts);
}
}
protected virtual void Internal_OnCollision(ObiSolver solverInstance, ObiNativeContactList contacts)
{
if (!stickToSurfaces) return;
if (rope == null || !rope.isLoaded || rope.solverIndices == null || rope.activeParticleCount == 0) return;
for (int i = 0; i < contacts.count; ++i)
{
Oni.Contact contact = contacts[i];
if (contact.distance < 0.05f)
{
// In Obi 7.1, bodyA is a simplex index. Resolve it to a particle index. (AI fix)
int simplexStart = solverInstance.m_SimplexCounts.GetSimplexStartAndSize(contact.bodyA, out int simplexSize);
if (simplexSize != 1) continue; // We only care about particle (point) collisions for sticking
int solverIndex = solverInstance.simplices[simplexStart];
bool belongsToRope = false;
for (int j = 0; j < rope.activeParticleCount; ++j)
{
if (rope.solverIndices[j] == solverIndex)
{
belongsToRope = true;
break;
}
}
if (!belongsToRope) continue;
bool alreadyStuck = false;
for (int j = 0; j < stuckParticles.Count; ++j)
{
if (stuckParticles[j].solverIndex == solverIndex)
{
alreadyStuck = true;
break;
}
}
if (alreadyStuck) continue;
var colliderHandles = ObiColliderWorld.GetInstance().colliderHandles;
if (contact.bodyB >= 0 && contact.bodyB < colliderHandles.Count)
{
var colHandle = colliderHandles[contact.bodyB];
if (colHandle != null && colHandle.owner != null)
{
Vector3 worldContactPoint = solverInstance.transform.TransformPoint(contact.pointB);
Vector3 worldNormal = solverInstance.transform.TransformDirection(contact.normal);
AttachToTransform(solverIndex, colHandle.owner.transform, worldContactPoint, worldNormal);
}
}
}
}
}
protected virtual void AttachToTransform(int solverIndex, Transform target, Vector3 worldContactPoint, Vector3 worldNormal)
{
StuckParticle sp = new StuckParticle();
sp.solverIndex = solverIndex;
sp.target = target;
sp.isVertexStuck = false;
sp.localPoint = target.InverseTransformPoint(worldContactPoint);
sp.localNormal = target.InverseTransformDirection(worldNormal).normalized;
FinalizeAttachment(sp);
}
protected virtual void FinalizeAttachment(StuckParticle sp)
{
sp.shouldSpawnDecal = Random.value < decalSpawnChance;
stuckParticles.Add(sp);
solver.invMasses[sp.solverIndex] = 0;
if (solver.invRotationalMasses != null && sp.solverIndex < solver.invRotationalMasses.count)
solver.invRotationalMasses[sp.solverIndex] = 0;
#if UNITY_EDITOR
if (showDebugLogs)
{
string type = sp.isVertexStuck ? "SkinnedMesh" : "Transform";
Debug.Log($"[DEBUG_LOG] [{GetType().Name}] Stuck to {type}: {sp.target.name}. SolverIdx: {sp.solverIndex}. LocalNormal: {sp.localNormal}");
}
#endif
}