Variable Center of Mass
NWH.Common.CoM.VariableCenterOfMass applies mass, local center of mass, and inertia settings to
the Rigidbody on the same GameObject. It can also include child components that implement
IMassAffector.
Setup
- Add
VariableCenterOfMassto the physics root. Unity also ensures that aRigidbodyexists. - Choose whether each Rigidbody value comes from Unity or from this component:
useDefaultMassuseDefaultCenterOfMassuseDefaultInertia
- When a default option is disabled, configure its corresponding value:
baseMasscenterOfMassinertiaTensor
- Enable
useMassAffectorswhen childIMassAffectorcomponents should contribute, and turnuseDefaultMassoff. With both on, affector mass never reaches the Rigidbody and the component logs a warning when it initializes.
The centerOfMass value is in the component's local space. The current API has no
centerOfMassOffset or autoCalculate field.
Serialized Fields
| Field | Current behavior |
|---|---|
baseMass |
Base Rigidbody mass before active mass affectors are added |
centerOfMass |
Base local center of mass when useDefaultCenterOfMass is false |
inertiaTensor |
Base inertia tensor when useDefaultInertia is false |
dimensions |
Dimensions detected by Reset and used by the static cuboid CalculateInertia helper |
useDefaultMass |
Leaves Rigidbody mass under Unity/default ownership |
useDefaultCenterOfMass |
Leaves Rigidbody center of mass under Unity/default ownership |
useDefaultInertia |
Leaves Rigidbody inertia under Unity/default ownership |
useMassAffectors |
Includes active child IMassAffector values in custom calculations |
isDirty |
Requests recalculation during the next FixedUpdate |
combinedMass, combinedCenterOfMass, and combinedInertiaTensor expose the most recently
calculated custom values. They are not replacement setup fields.
Initialization and Updates
During Awake, the component:
- caches the local
Rigidbody; - copies default Rigidbody values into the corresponding base fields when their default flags are enabled;
- discovers child
IMassAffectorimplementations, including inactive children; and - calls
UpdateAllProperties().
At runtime, recalculation happens in FixedUpdate only when isDirty is true. The same
FixedUpdate also compares centerOfMass, inertiaTensor, baseMass and the total affector mass
against the values it last applied, and sets isDirty itself when any of them moved. A script that
writes those fields directly therefore takes effect on the next physics step without calling
MarkDirty().
MarkDirty() remains the cheaper and more explicit path, and is still the right call after adding
or removing an affector.
using NWH.Common.CoM;
using UnityEngine;
public sealed class CenterOfMassSetup : MonoBehaviour
{
[SerializeField] private VariableCenterOfMass variableCenterOfMass;
public void SetLocalCenterOfMass(Vector3 localCenter)
{
variableCenterOfMass.useDefaultCenterOfMass = false;
variableCenterOfMass.centerOfMass = localCenter;
variableCenterOfMass.MarkDirty();
}
public void RefreshAffectors()
{
variableCenterOfMass.GetMassAffectors();
variableCenterOfMass.MarkDirty();
}
public void UseFixedCenterOfMass(Vector3 localCenter)
{
variableCenterOfMass.useDefaultCenterOfMass = false;
variableCenterOfMass.useMassAffectors = false;
variableCenterOfMass.centerOfMass = localCenter;
variableCenterOfMass.MarkDirty();
}
}
Mass Affectors
When useMassAffectors is enabled:
CalculateMass()adds the mass of every active discovered affector tobaseMass;CalculateRelativeCenterOfMassOffset()produces their weighted local offset;CalculateCompositeInertiaTensorOffset(...)applies their parallel-axis contributions; and- inactive or missing affectors are skipped.
If affectors are added or removed at runtime, call GetMassAffectors() before marking the
component dirty, as shown by RefreshAffectors() in the complete example above. The method both
rescans the children and stores the result in affectors, so its return value does not have to be
assigned.
An existing affector that only changes mass does not need to be rediscovered. FixedUpdate watches
the affector mass total and recalculates on its own.
Public Update Methods
| Method | Effect |
|---|---|
MarkDirty() |
Recalculate enabled custom properties on the next physics update |
UpdateAllProperties() |
Immediately update every property whose default flag is disabled |
UpdateMass() |
Calculate and assign Rigidbody.mass |
UpdateCoM() |
Calculate and assign Rigidbody.centerOfMass |
UpdateInertia() |
Calculate and assign a positive inertia tensor |
UpdateInertia(bool) |
Obsolete compatibility overload; the argument was never used |
GetMassAffectors() |
Rescan child IMassAffector implementations, store them in affectors and return them |
GetWorldCenterOfMass() |
Transform combinedCenterOfMass to world space |
CalculateInertia(Vector3, float) |
Return cuboid inertia for dimensions and mass |
CalculateCompositeInertiaTensorOffset(Vector3) |
Correct parallel-axis contribution about a local combined center of mass |
CalculateInertiaTensorOffset(Vector3) |
Obsolete pre-14.2 calculation; its argument remains ignored and its legacy result is preserved |
Prefer MarkDirty() for ordinary runtime changes so all enabled custom properties update together
at the component's normal physics point.
The obsolete inertia-offset method deliberately does not forward to the corrected calculation:
doing so would silently change physics for compiled upgrade code. Migrate explicitly to
CalculateCompositeInertiaTensorOffset(variableCenterOfMass.combinedCenterOfMass).
Default Versus Custom Values
When a useDefault... flag is true, UpdateAllProperties() deliberately skips that property.
Changing baseMass, centerOfMass, or inertiaTensor while its default flag remains enabled does
not overwrite the Rigidbody value.
For a fixed custom center of mass without affectors, use the same sequence as
UseFixedCenterOfMass() in the complete example above.
For Unity-calculated mass and inertia with only a custom center of mass, leave
useDefaultMass and useDefaultInertia enabled.
Visualization
The component draws the center of mass as a yellow sphere while gizmos are visible. Mass affectors are drawn in cyan. When custom inertia is enabled, colored dimension lines are also drawn.
Troubleshooting
A custom value has no effect
Disable the matching useDefault... flag. The value is picked up on the next physics step.
A runtime-created affector is ignored
Call GetMassAffectors(), then MarkDirty().
Affector mass does not change the Rigidbody
useDefaultMass is still enabled. Turn it off; the warning logged during initialization names the
same cause.
The center of mass appears in the wrong place
centerOfMass is local to the GameObject that holds the Rigidbody.
GetWorldCenterOfMass() transforms the most recently calculated custom
combinedCenterOfMass. When useDefaultCenterOfMass is enabled, use the attached
Rigidbody's worldCenterOfMass instead.