From 2ed1d7329c0c94554b4b226df32a218c485f09a8 Mon Sep 17 00:00:00 2001 From: mischa Date: Fri, 16 Feb 2024 10:59:45 +0100 Subject: [PATCH] fix(Prediction): ConfigurableJoints' range of motion is now moved correctly --- .../PredictedRigidbody/PredictedRigidbody.cs | 52 +++++++++++++++++-- .../PredictedRigidbody/PredictionUtils.cs | 12 +++-- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs b/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs index 3a8f9465b..3f896169f 100644 --- a/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs +++ b/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs @@ -102,6 +102,11 @@ public class PredictedRigidbody : NetworkBehaviour // we also create one extra ghost for the exact known server state. protected GameObject remoteCopy; + // joints + Vector3 initialPosition; + Quaternion initialRotation; + Vector3 initialScale; + void Awake() { tf = transform; @@ -111,6 +116,11 @@ void Awake() // cache some threshold to avoid calculating them in LateUpdate float colliderSize = GetComponentInChildren().bounds.size.magnitude; smoothFollowThreshold = colliderSize * teleportDistanceMultiplier; + + // cache initial position/rotation/scale to be used when moving physics components (configurable joints' range of motion) + initialPosition = tf.position; + initialRotation = tf.rotation; + initialScale = tf.localScale; } protected virtual void CopyRenderersAsGhost(GameObject destination, Material material) @@ -164,9 +174,6 @@ protected virtual void CreateGhosts() // if we copy localScale then the copy has scale=0.5, where as the // original would have a global scale of ~1.0. physicsCopy = new GameObject($"{name}_Physical"); - physicsCopy.transform.position = tf.position; // world position! - physicsCopy.transform.rotation = tf.rotation; // world rotation! - physicsCopy.transform.localScale = tf.lossyScale; // world scale! // assign the same Layer for the physics copy. // games may use a custom physics collision matrix, layer matters. @@ -176,8 +183,25 @@ protected virtual void CreateGhosts() PredictedRigidbodyPhysicsGhost physicsGhostRigidbody = physicsCopy.AddComponent(); physicsGhostRigidbody.target = tf; - // move the rigidbody component & all colliders to the physics GameObject + // when moving (Configurable)Joints, their range of motion is + // relative to the initial position. if we move them after the + // GameObject rotated, the range of motion is wrong. + // the easiest solution is to move to initial position, + // then move physics components, then move back. + // => remember previous + Vector3 position = tf.position; + Quaternion rotation = tf.rotation; + Vector3 scale = tf.localScale; + // => reset to initial + physicsGhostRigidbody.transform.position = tf.position = initialPosition; + physicsGhostRigidbody.transform.rotation = tf.rotation = initialRotation; + physicsGhostRigidbody.transform.localScale = tf.localScale = initialScale; + // => move physics components PredictionUtils.MovePhysicsComponents(gameObject, physicsCopy); + // => reset previous + physicsGhostRigidbody.transform.position = tf.position = position; + physicsGhostRigidbody.transform.rotation = tf.rotation = rotation; + physicsGhostRigidbody.transform.localScale = tf.localScale = scale; // show ghost by copying all renderers / materials with ghost material applied if (showGhost) @@ -213,7 +237,27 @@ protected virtual void DestroyGhosts() // otherwise next time they wouldn't have a collider anymore. if (physicsCopy != null) { + // when moving (Configurable)Joints, their range of motion is + // relative to the initial position. if we move them after the + // GameObject rotated, the range of motion is wrong. + // the easiest solution is to move to initial position, + // then move physics components, then move back. + // => remember previous + Vector3 position = tf.position; + Quaternion rotation = tf.rotation; + Vector3 scale = tf.localScale; + // => reset to initial + physicsCopy.transform.position = tf.position = initialPosition; + physicsCopy.transform.rotation = tf.rotation = initialRotation; + physicsCopy.transform.localScale = tf.localScale = initialScale; + // => move physics components PredictionUtils.MovePhysicsComponents(physicsCopy, gameObject); + // => reset previous + tf.position = position; + tf.rotation = rotation; + tf.localScale = scale; + + // when moving components back, we need to undo the joints initial-delta rotation that we added. Destroy(physicsCopy); // reassign our Rigidbody reference diff --git a/Assets/Mirror/Components/PredictedRigidbody/PredictionUtils.cs b/Assets/Mirror/Components/PredictedRigidbody/PredictionUtils.cs index 335ce0bf2..9dc2b51d2 100644 --- a/Assets/Mirror/Components/PredictedRigidbody/PredictionUtils.cs +++ b/Assets/Mirror/Components/PredictedRigidbody/PredictionUtils.cs @@ -30,8 +30,12 @@ public static void MoveRigidbody(GameObject source, GameObject destination) rigidbodyCopy.constraints = original.constraints; rigidbodyCopy.sleepThreshold = original.sleepThreshold; rigidbodyCopy.freezeRotation = original.freezeRotation; - rigidbodyCopy.position = original.position; - rigidbodyCopy.rotation = original.rotation; + + // moving (Configurable)Joints messes up their range of motion unless + // we reset to initial position first (we do this in PredictedRigibody.cs). + // so here we don't set the Rigidbody's physics position at all. + // rigidbodyCopy.position = original.position; + // rigidbodyCopy.rotation = original.rotation; // projects may keep Rigidbodies as kinematic sometimes. in that case, setting velocity would log an error if (!original.isKinematic) @@ -250,10 +254,10 @@ public static void MoveConfigurableJoints(GameObject source, GameObject destinat jointCopy.connectedMassScale = sourceJoint.connectedMassScale; jointCopy.enableCollision = sourceJoint.enableCollision; jointCopy.enablePreprocessing = sourceJoint.enablePreprocessing; - jointCopy.highAngularXLimit = sourceJoint.highAngularXLimit; + jointCopy.highAngularXLimit = sourceJoint.highAngularXLimit; // moving this only works if the object is at initial position/rotation/scale, see PredictedRigidbody.cs jointCopy.linearLimitSpring = sourceJoint.linearLimitSpring; jointCopy.linearLimit = sourceJoint.linearLimit; - jointCopy.lowAngularXLimit = sourceJoint.lowAngularXLimit; + jointCopy.lowAngularXLimit = sourceJoint.lowAngularXLimit; // moving this only works if the object is at initial position/rotation/scale, see PredictedRigidbody.cs jointCopy.massScale = sourceJoint.massScale; jointCopy.projectionAngle = sourceJoint.projectionAngle; jointCopy.projectionDistance = sourceJoint.projectionDistance;