using System; using System.Diagnostics.Contracts; using System.Numerics; using System.Runtime.CompilerServices; using Robust.Shared.Map; namespace Robust.Shared.GameObjects; public abstract partial class SharedTransformSystem { /* * Helper methods for working with EntityCoordinates / MapCoordinates. * For grid methods see SharedMapSystem.Coordinates */ /// /// Verifies that this set of coordinates can be currently resolved to a location. /// /// if this set of coordinates can be currently resolved to a location, otherwise . public bool IsValid(EntityCoordinates coordinates) { var entity = coordinates.EntityId; if (!entity.IsValid() || !Exists(entity)) return false; if (!float.IsFinite(coordinates.Position.X) || !float.IsFinite(coordinates.Position.Y)) return false; return true; } /// /// Returns a new set of EntityCoordinates local to a new entity. /// /// The entity that the new coordinates will be local to /// A new set of EntityCoordinates local to a new entity. public EntityCoordinates WithEntityId(EntityCoordinates coordinates, EntityUid entity) { return entity == coordinates.EntityId ? coordinates : ToCoordinates(entity, ToMapCoordinates(coordinates)); } /// /// Converts entity-local coordinates into map terms. /// public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates, bool logError = true) { if (!TryComp(coordinates.EntityId, out TransformComponent? xform)) { if (logError) Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}. Trace: {Environment.StackTrace}"); return MapCoordinates.Nullspace; } Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition; while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid()) { xform = XformQuery.GetComponent(xform.ParentUid); pos = xform._localRotation.RotateVec(pos) + xform._localPosition; } return new MapCoordinates(pos, xform.MapID); } /// /// Converts entity-local coordinates into map terms. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public MapCoordinates ToMapCoordinates(NetCoordinates coordinates) { var eCoords = GetCoordinates(coordinates); return ToMapCoordinates(eCoords); } /// /// Converts entity-local coordinates into map terms. /// The same as ToMapCoordinates(coordinates, logError).Position, but doesn't have to construct the MapCoordinates first. /// public Vector2 ToWorldPosition(EntityCoordinates coordinates, bool logError = true) { if (!TryComp(coordinates.EntityId, out TransformComponent? xform)) { if (logError) Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}. Trace: {Environment.StackTrace}"); return Vector2.Zero; } Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition; while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid()) { xform = XformQuery.GetComponent(xform.ParentUid); pos = xform._localRotation.RotateVec(pos) + xform._localPosition; } return pos; } /// /// Converts entity-local coordinates into map terms. /// The same as ToMapCoordinates(coordinates).Position, but doesn't have to construct the MapCoordinates first. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToWorldPosition(NetCoordinates coordinates) { var eCoords = GetCoordinates(coordinates); return ToWorldPosition(eCoords); } /// /// Creates EntityCoordinates given an entity and some MapCoordinates. /// public EntityCoordinates ToCoordinates(Entity entity, MapCoordinates coordinates) { if (!Resolve(entity, ref entity.Comp, false)) { Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}. Trace: {Environment.StackTrace}"); return default; } if (entity.Comp.MapID != coordinates.MapId) { Log.Error($"Attempted to convert map coordinates {coordinates} to entity coordinates on a different map. Entity: {ToPrettyString(entity)}. Trace: {Environment.StackTrace}"); return default; } var localPos = Vector2.Transform(coordinates.Position, GetInvWorldMatrix(entity.Comp)); return new EntityCoordinates(entity, localPos); } /// /// Creates map-relative given some . /// [Pure] public EntityCoordinates ToCoordinates(MapCoordinates coordinates) { if (_map.TryGetMap(coordinates.MapId, out var uid)) return ToCoordinates(uid.Value, coordinates); Log.Error($"Attempted to convert map coordinates with unknown map id: {coordinates}. Trace: {Environment.StackTrace}"); return default; } /// /// Returns the grid that the entity whose position the coordinates are relative to is on. /// [Pure] public EntityUid? GetGrid(EntityCoordinates coordinates) { return GetGrid(coordinates.EntityId); } [Pure] public EntityUid? GetGrid(Entity entity) { return !Resolve(entity, ref entity.Comp, logMissing:false) ? null : entity.Comp.GridUid; } /// /// Returns the Map Id these coordinates are on. /// [Pure] public MapId GetMapId(EntityCoordinates coordinates) { return GetMapId(coordinates.EntityId); } public MapId GetMapId(Entity entity) { return !Resolve(entity, ref entity.Comp, logMissing: false) ? MapId.Nullspace : entity.Comp.MapID; } /// /// Returns the Map that these coordinates are on. /// public EntityUid? GetMap(EntityCoordinates coordinates) { return GetMap(coordinates.EntityId); } public EntityUid? GetMap(Entity entity) { return !Resolve(entity, ref entity.Comp, logMissing: false) ? null : entity.Comp.MapUid; } /// /// Compares two sets of coordinates to see if they are in range of each other. /// /// maximum distance between the two sets of coordinates. /// True if the two points are within a given range. public bool InRange(EntityCoordinates coordA, EntityCoordinates coordB, float range) { if (!coordA.EntityId.IsValid() || !coordB.EntityId.IsValid()) return false; if (coordA.EntityId == coordB.EntityId) return (coordA.Position - coordB.Position).LengthSquared() < range * range; var mapA = ToMapCoordinates(coordA, logError:false); var mapB = ToMapCoordinates(coordB, logError:false); if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace) return false; return mapA.InRange(mapB, range); } /// /// Compares the positions of two entities to see if they are within some specified distance of each other. /// public bool InRange(Entity entA, Entity entB, float range) { if (!Resolve(entA, ref entA.Comp, logMissing: false)) return false; if (!Resolve(entB, ref entB.Comp, logMissing: false)) return false; if (!entA.Comp.ParentUid.IsValid() || !entB.Comp.ParentUid.IsValid()) return false; if (entA.Comp.ParentUid == entB.Comp.ParentUid) return (entA.Comp.LocalPosition - entB.Comp.LocalPosition).LengthSquared() < range * range; if (entA.Comp.ParentUid == entB.Owner) return entA.Comp.LocalPosition.LengthSquared() < range * range; if (entB.Comp.ParentUid == entA.Owner) return entB.Comp.LocalPosition.LengthSquared() < range * range; var mapA = GetMapCoordinates(entA!); var mapB = GetMapCoordinates(entB!); if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace) return false; return mapA.InRange(mapB, range); } /// /// Returns a readable string with the entity's local and world position and rotation. Useful for debugging. /// public string GetDebugString(Entity ent) { if (!Resolve(ent, ref ent.Comp, logMissing: false)) return "invalid"; return $"pos/rot/wpos/wrot: {ent.Comp.Coordinates}/{ent.Comp.LocalRotation}/{GetWorldPosition(ent.Comp)}/{GetWorldRotation(ent.Comp)}"; } }