Skip to content
This repository was archived by the owner on Jun 28, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ END TEMPLATE-->
### Breaking changes

* Remove the duplicate serialization copy of components kept on ComponentRegistryEntry; now it only stores the deserialized component. To get the raw MappingDataNode for EntityPrototypes use PrototypeManager. This is expected to significantly reduce memory usage.
* Obsolete LocalRotation in favor of the system method. The angle is now also normalized to 2PI and no longer grows indefinitely.
* Removed the obsolete TransformComponent methods. The remaining methods have been marked as obsolete and are subject to removal in future.

### New features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,21 @@ public void WorldRotationTest()
Assert.That(result, new ApproxEqualityConstraint(Angle.FromDegrees(225)));
}

[Test]
public void LocalRotationNormalizesTest()
{
var entity = EntityManager.SpawnEntity(null, InitialPos);
var transform = EntityManager.GetComponent<TransformComponent>(entity);

XformSystem.SetLocalRotation(entity, Angle.FromDegrees(90), transform);

Assert.That(transform.LocalRotation, NUnit.Framework.Is.EqualTo(Angle.FromDegrees(90)));

XformSystem.SetWorldRotation(transform, Angle.FromDegrees(810));

Assert.That(transform.LocalRotation, NUnit.Framework.Is.EqualTo(Angle.FromDegrees(90)));
}

/// <summary>
/// Test that, in a chain A -> B -> C, if A is moved C's world position correctly updates.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,7 @@ public void Anchored_SetPosition_Nop()
sim.System<MoveEventTestSystem>().FailOnMove = true;

// Act
sim.Transform(ent1).WorldPosition = new Vector2(99, 99);
sim.Transform(ent1).LocalPosition = new Vector2(99, 99);
xformSys.SetLocalPosition(ent1, new Vector2(99, 99));

Assert.That(xformSys.GetMapCoordinates(ent1), Is.EqualTo(coordinates));
sim.System<MoveEventTestSystem>().FailOnMove = false;
Expand Down
2 changes: 1 addition & 1 deletion Robust.Shared/Containers/SharedContainerSystem.Insert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public bool Insert(Entity<TransformComponent?, MetaDataComponent?, PhysicsCompon
_transform.SetCoordinates(toInsert, transform, new EntityCoordinates(container.Owner, Vector2.Zero), Angle.Zero);
transform.Broadphase = old;

// the transform.AttachParent() could previously result in the flag being unset, so check that this hasn't happened.
// Re-parenting could previously result in the flag being unset, so check that this hasn't happened.
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0, "invalid metadata flags after insertion");

// Implementation specific insert logic
Expand Down
258 changes: 11 additions & 247 deletions Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public sealed partial class TransformComponent : Component, IComponentDebug
private Matrix3x2 _localMatrix = Matrix3x2.Identity;
private Matrix3x2 _invLocalMatrix = Matrix3x2.Identity;

// these should just be system methods, but existing component functions like InvWorldMatrix still rely on
// these should just be system methods, but existing component functions still rely on
// getting these so those have to be fully ECS-ed first.
public Matrix3x2 LocalMatrix
{
Expand Down Expand Up @@ -135,14 +135,8 @@ public Matrix3x2 InvLocalMatrix
public bool NoLocalRotation
{
get => _noLocalRotation;
set
{
if (value)
LocalRotation = Angle.Zero;

_noLocalRotation = value;
_entMan.Dirty(Owner, this);
}
[Obsolete("Use SharedTransformSystem.SetNoLocalRotation instead.")]
set => _entMan.System<SharedTransformSystem>().SetNoLocalRotation(Owner, value, this);
}

/// <summary>
Expand All @@ -153,11 +147,14 @@ public bool NoLocalRotation
public Angle LocalRotation
{
get => _localRotation;
[Obsolete("Use SharedTransformSystem.SetLocalRotation")]
set
{
if(_noLocalRotation)
return;

value = SharedTransformSystem.NormalizeRotation(value);

if (_localRotation.EqualsApprox(value))
return;

Expand Down Expand Up @@ -216,94 +213,6 @@ public Angle WorldRotation
/// </summary>
public EntityUid ParentUid => _parent;

/// <summary>
/// Matrix for transforming points from local to world space.
/// </summary>
[Obsolete("Use the system method instead")]
public Matrix3x2 WorldMatrix
{
get
{
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
var parent = _parent;
var myMatrix = LocalMatrix;

while (parent.IsValid())
{
var parentXform = xformQuery.GetComponent(parent);
var parentMatrix = parentXform.LocalMatrix;
parent = parentXform.ParentUid;

var result = Matrix3x2.Multiply(myMatrix, parentMatrix);
myMatrix = result;
}

return myMatrix;
}
}

/// <summary>
/// Matrix for transforming points from world to local space.
/// </summary>
[Obsolete("Use the system method instead")]
public Matrix3x2 InvWorldMatrix
{
get
{
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
var parent = _parent;
var myMatrix = InvLocalMatrix;

while (parent.IsValid())
{
var parentXform = xformQuery.GetComponent(parent);
var parentMatrix = parentXform.InvLocalMatrix;
parent = parentXform.ParentUid;

var result = Matrix3x2.Multiply(parentMatrix, myMatrix);
myMatrix = result;
}

return myMatrix;
}
}

/// <summary>
/// Current position offset of the entity relative to the world.
/// Can de-parent from its parent if the parent is a grid.
/// </summary>
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete("Use the system method instead")]
public Vector2 WorldPosition
{
get
{
if (_parent.IsValid())
{
// parent coords to world coords
return Vector2.Transform(_localPosition, _entMan.GetComponent<TransformComponent>(ParentUid).WorldMatrix);
}
else
{
return Vector2.Zero;
}
}
set
{
if (!_parent.IsValid())
{
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
return;
}

// world coords to parent coords
var newPos = Vector2.Transform(value, _entMan.GetComponent<TransformComponent>(ParentUid).InvWorldMatrix);

LocalPosition = newPos;
}
}

/// <summary>
/// Position offset of this entity relative to its parent.
/// </summary>
Expand All @@ -319,45 +228,14 @@ public EntityCoordinates Coordinates
set => _entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetCoordinates(Owner, this, value);
}

/// <summary>
/// Current position offset of the entity relative to the world.
/// This is effectively a more complete version of <see cref="WorldPosition"/>
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete("Use TransformSystem.GetMapCoordinates")]
public MapCoordinates MapPosition => new(WorldPosition, MapID);

/// <summary>
/// Local offset of this entity relative to its parent
/// (<see cref="Parent"/> if it's not null, to <see cref="GridUid"/> otherwise).
/// </summary>
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
[ViewVariables]
public Vector2 LocalPosition
{
get => _localPosition;
[Obsolete("Use the system method instead")]
set
{
if(Anchored)
return;

if (_localPosition.EqualsApprox(value))
return;

var oldParent = _parent;
var oldPos = _localPosition;

_localPosition = value;
var meta = _entMan.GetComponent<MetaDataComponent>(Owner);
_entMan.Dirty(Owner, this, meta);
MatricesDirty = true;

if (!Initialized)
return;

_entMan.System<SharedTransformSystem>().RaiseMoveEvent((Owner, this, meta), oldParent, oldPos, _localRotation, MapUid);
}
}

/// <summary>
Expand All @@ -375,7 +253,7 @@ public bool Anchored
{
_anchored = value;
}
else if (value && !_anchored && _mapManager.TryFindGridAt(MapPosition, out _, out var grid))
else if (value && !_anchored && _mapManager.TryFindGridAt(_entMan.System<SharedTransformSystem>().GetMapCoordinates(this), out _, out var grid))
{
_anchored = _entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().AnchorEntity(Owner, this, grid);
}
Expand All @@ -396,122 +274,7 @@ public bool Anchored
[ViewVariables] public EntityUid LerpParent;
public bool PredictedLerp;

/// <summary>
/// Detaches this entity from its parent.
/// </summary>
[Obsolete("Use the system's method instead.")]
public void AttachToGridOrMap()
{
_entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().AttachToGridOrMap(Owner, this);
}

[Obsolete("Use TransformSystem.SetParent() instead")]
public void AttachParent(EntityUid parent)
{
_entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(Owner, this, parent, _entMan.GetEntityQuery<TransformComponent>());
}

/// <summary>
/// Get the WorldPosition and WorldRotation of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation()
{
// Worldmatrix needs calculating anyway for worldpos so we'll just drop it.
var (worldPos, worldRot, _) = GetWorldPositionRotationMatrix();
return (worldPos, worldRot);
}

/// <summary>
/// Get the WorldPosition, WorldRotation, and WorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix) GetWorldPositionRotationMatrix(EntityQuery<TransformComponent> xforms)
{
var parent = _parent;
var worldRot = _localRotation;
var worldMatrix = LocalMatrix;

// By doing these all at once we can elide multiple IsValid + GetComponent calls
while (parent.IsValid())
{
var xform = xforms.GetComponent(parent);
worldRot += xform.LocalRotation;
var parentMatrix = xform.LocalMatrix;
var result = Matrix3x2.Multiply(worldMatrix, parentMatrix);
worldMatrix = result;
parent = xform.ParentUid;
}

var worldPosition = worldMatrix.Translation;

return (worldPosition, worldRot, worldMatrix);
}

/// <summary>
/// Get the WorldPosition, WorldRotation, and WorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix) GetWorldPositionRotationMatrix()
{
var xforms = _entMan.GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrix(xforms);
}

/// <summary>
/// Get the WorldPosition, WorldRotation, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationInvMatrix(EntityQuery<TransformComponent> xformQuery)
{
var (worldPos, worldRot, _, invWorldMatrix) = GetWorldPositionRotationMatrixWithInv(xformQuery);
return (worldPos, worldRot, invWorldMatrix);
}

/// <summary>
/// Get the WorldPosition, WorldRotation, WorldMatrix, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationMatrixWithInv()
{
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrixWithInv(xformQuery);
}

/// <summary>
/// Get the WorldPosition, WorldRotation, WorldMatrix, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationMatrixWithInv(EntityQuery<TransformComponent> xformQuery)
{
var parent = _parent;
var worldRot = _localRotation;
var invMatrix = InvLocalMatrix;
var worldMatrix = LocalMatrix;

// By doing these all at once we can avoid multiple IsValid + GetComponent calls
while (parent.IsValid())
{
var xform = xformQuery.GetComponent(parent);
worldRot += xform.LocalRotation;

var parentMatrix = xform.LocalMatrix;
var result = Matrix3x2.Multiply(worldMatrix, parentMatrix);
worldMatrix = result;

var parentInvMatrix = xform.InvLocalMatrix;
var invResult = Matrix3x2.Multiply(parentInvMatrix, invMatrix);
invMatrix = invResult;

parent = xform.ParentUid;
}

var worldPosition = worldMatrix.Translation;

return (worldPosition, worldRot, worldMatrix, invMatrix);
}

public void RebuildMatrices()
private void RebuildMatrices()
{
MatricesDirty = false;

Expand All @@ -527,7 +290,8 @@ public void RebuildMatrices()

public string GetDebugString()
{
return $"pos/rot/wpos/wrot: {Coordinates}/{LocalRotation}/{WorldPosition}/{WorldRotation}";
var xform = _entMan.System<SharedTransformSystem>();
return $"pos/rot/wpos/wrot: {Coordinates}/{LocalRotation}/{xform.GetWorldPosition(this)}/{xform.GetWorldRotation(this)}";
}
}

Expand Down
Loading
Loading