Skip to content
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
39 changes: 38 additions & 1 deletion OpenDreamClient/Input/ContextMenu/ContextMenuPopup.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using OpenDreamShared.Dream;
using OpenDreamShared.Rendering;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
Expand All @@ -23,6 +24,7 @@ internal sealed partial class ContextMenuPopup : Popup {
private readonly DMISpriteSystem _spriteSystem;
private readonly EntityLookupSystem _lookupSystem;
private readonly MouseInputSystem _mouseInputSystem;
private readonly TransformSystem _transformSystem;
private readonly EntityQuery<DMISpriteComponent> _spriteQuery;
private readonly EntityQuery<TransformComponent> _xformQuery;
private readonly EntityQuery<DreamMobSightComponent> _mobSightQuery;
Expand All @@ -40,6 +42,7 @@ public ContextMenuPopup() {
_spriteSystem = _entitySystemManager.GetEntitySystem<DMISpriteSystem>();
_lookupSystem = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
_mouseInputSystem = _entitySystemManager.GetEntitySystem<MouseInputSystem>();
_transformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
_spriteQuery = _entityManager.GetEntityQuery<DMISpriteComponent>();
_xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
_mobSightQuery = _entityManager.GetEntityQuery<DreamMobSightComponent>();
Expand All @@ -49,8 +52,9 @@ public void RepopulateEntities(ScalingViewport viewport, Vector2 relativePos, Sc
ContextMenu.RemoveAllChildren();

var mapCoords = viewport.ScreenToMap(pointerLocation.Position);
var entities = _lookupSystem.GetEntitiesInRange(mapCoords, 0.01f, LookupFlags.Uncontained | LookupFlags.Approximate);

// TODO: Even quite distant entities could have transformed bounding boxes that intersect with mapCoords. 10 tile radius is good for now...
var entities = _lookupSystem.GetEntitiesInRange(mapCoords, 10f, LookupFlags.Uncontained | LookupFlags.Approximate);
foreach (var uid in entities) {
if (_xformQuery.TryGetComponent(uid, out var transform) && !_entityManager.HasComponent<MapGridComponent>(transform.ParentUid)) // Not a child of another entity
continue;
Expand All @@ -60,6 +64,8 @@ public void RepopulateEntities(ScalingViewport viewport, Vector2 relativePos, Sc
continue;
if (!_spriteSystem.IsVisible(sprite, transform, GetSeeInvisible(), null)) // Not invisible
continue;
if (!IconTransformedBoundingBoxContainsPoint(transform!, sprite, mapCoords))
continue;

var reference = new ClientObjectReference(_entityManager.GetNetEntity(uid));
var name = _appearanceSystem.GetName(reference);
Expand Down Expand Up @@ -95,6 +101,37 @@ public void RepopulateEntities(ScalingViewport viewport, Vector2 relativePos, Sc
//because BYOND sure loves redundancy
}

// Determines if the given point falls inside the transformed bounding box of the given sprite's icon and its entity transform
private bool IconTransformedBoundingBoxContainsPoint(TransformComponent transform, DMISpriteComponent sprite, MapCoordinates mapCoords) {
var worldPos = _transformSystem.GetWorldPosition(transform);

// Find center of icon in case it's not the same size as a tile
if (!sprite.Icon.TryGetSizeInTiles(out var size))
return false;

if (!sprite.Icon.TryGetOffsetInTiles(out var offset))
return false;

var centerWorldPos = worldPos + offset.Value + (size.Value - new Vector2(1, 1)) * 0.5f;

// Get the icon's AABB centered at its worldcenter
Box2? iconAABB = null;
sprite.Icon.GetWorldAABB(centerWorldPos, ref iconAABB);
if (!iconAABB.HasValue)
return false;

// Inverse transform on the point and check if it's in the icon's AABB
var iconTransform = sprite.Icon.Appearance!.Transform;
Matrix3x2 t = new(iconTransform[0], iconTransform[1], iconTransform[2], iconTransform[3], iconTransform[4], iconTransform[5]);
if (Matrix3x2.Invert(t, out var invT)) {
// Origin of transform is centerWorldPos so remove it before transforming, then put it back before checking
var xformedPoint = Vector2.Transform(mapCoords.Position - centerWorldPos, invT) + centerWorldPos;
return iconAABB.Value.Contains(xformedPoint);
}

return false;
}

public void SetActiveItem(ContextMenuItem item) {
if (_currentVerbMenu != null) {
_currentVerbMenu.Close();
Expand Down
29 changes: 23 additions & 6 deletions OpenDreamClient/Rendering/DreamIcon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,31 @@ private void EndAppearanceAnimation(AppearanceAnimation? appearanceAnimation) {
}
}

public void GetWorldAABB(Vector2 worldPos, ref Box2? aabb) {
if (DMI != null && Appearance != null) {
Vector2 size = DMI.IconSize / (float)interfaceManager.IconSize;
Vector2 pixelOffset = Appearance.TotalPixelOffset / (float)interfaceManager.IconSize;
public bool TryGetSizeInTiles([NotNullWhen(true)] out Vector2? effectiveSize) {
if (DMI != null) {
effectiveSize = DMI.IconSize / (float)interfaceManager.IconSize;
return true;
}

worldPos += pixelOffset;
effectiveSize = null;
return false;
}

public bool TryGetOffsetInTiles([NotNullWhen(true)] out Vector2? effectiveOffset) {
if (Appearance != null) {
effectiveOffset = Appearance!.TotalPixelOffset / (float)interfaceManager.IconSize;
return true;
}

effectiveOffset = null;
return false;
}

public void GetWorldAABB(Vector2 worldPos, ref Box2? aabb) {
if (TryGetOffsetInTiles(out var effectiveOffset) && TryGetSizeInTiles(out var size)) {
worldPos += effectiveOffset.Value;

Box2 thisAABB = Box2.CenteredAround(worldPos, size);
Box2 thisAABB = Box2.CenteredAround(worldPos, size.Value);
aabb = aabb?.Union(thisAABB) ?? thisAABB;
}

Expand Down
Loading