Adds debug visualizer for hitboxes

Summary:
Adds debug visualizer to render Collider and CollidableComponents.  You can choose their color in the entity defition even.
Tweaked how HitboxComponent stores its AABB, for performance. (Forgive me, I lost control!)
Fixed hitbox calculation that was shifting them down and to the right.

Collidables still seem to prefer going by the sprite size instead of looking for a HitboxComponent.  That will need to be fixed later.  As will the offset of the player sprite.

Test Plan: Hit F4 and caress the table.

Reviewers: #ss14_developers, volundr-

Reviewed By: #ss14_developers, volundr-

Projects: #space_station_14

Differential Revision: http://phab.nexisonline.net/D40
This commit is contained in:
volundr-
2015-01-22 16:42:24 -05:00
parent aa3a80ba9a
commit 6c85d8cd15
10 changed files with 150 additions and 95 deletions

View File

@@ -12,6 +12,8 @@ namespace CGO
{
public class CollidableComponent : Component, ICollidable
{
public Color DebugColor { get; set; }
private bool collisionEnabled = true;
private RectangleF currentAABB;
protected bool isHardCollidable = true;
@@ -24,6 +26,7 @@ namespace CGO
public CollidableComponent()
{
Family = ComponentFamily.Collidable;
DebugColor = Color.Red;
tweakAABB = new Vector4D(0,0,0,0);
}
@@ -176,6 +179,11 @@ namespace CGO
case "TweakAABBleft":
tweakAABB.W = parameter.GetValue<float>();
break;
case "DebugColor":
var color = ColorTranslator.FromHtml(parameter.GetValue<string>());
if (!color.IsEmpty)
DebugColor = color;
break;
}
}

View File

@@ -11,70 +11,44 @@ namespace CGO
{
public class ColliderComponent : Component
{
private RectangleF currentAABB
public Color DebugColor { get; set; }
private RectangleF AABB
{
get
{
if (Owner.HasComponent(ComponentFamily.Hitbox))
{
return Owner.GetComponent<HitboxComponent>(ComponentFamily.Hitbox).AABB;
}
if (Owner.HasComponent(ComponentFamily.Renderable))
{
else if (Owner.HasComponent(ComponentFamily.Renderable))
return Owner.GetComponent<IRenderableComponent>(ComponentFamily.Renderable).AverageAABB;
}
return RectangleF.Empty;
}
}
/// <summary>
/// X - Top | Y - Right | Z - Bottom | W - Left
/// </summary>
private Vector4D tweakAABB;
public ColliderComponent()
{
Family = ComponentFamily.Collider;
tweakAABB = new Vector4D(0, 0, 0, 0);
}
private Vector4D TweakAABB
{
get { return tweakAABB; }
set { tweakAABB = value; }
}
public RectangleF OffsetAABB
{
get
{
// Return tweaked AABB
var currAABB = currentAABB;
if (currAABB != null)
return
new RectangleF(
currAABB.Left +
Owner.GetComponent<TransformComponent>(ComponentFamily.Transform).Position.X -
(currAABB.Width / 2) + tweakAABB.W,
currAABB.Top +
Owner.GetComponent<TransformComponent>(ComponentFamily.Transform).Position.Y -
(currAABB.Height / 2) + tweakAABB.X,
currAABB.Width - (tweakAABB.W - tweakAABB.Y),
currAABB.Height - (tweakAABB.X - tweakAABB.Z));
else
return RectangleF.Empty;
}
}
public override void SetParameter(ComponentParameter parameter)
public ColliderComponent()
{
base.SetParameter(parameter);
Family = ComponentFamily.Collider;
DebugColor = Color.Lime;
}
switch (parameter.MemberName)
public RectangleF WorldAABB
{
get
{
case "TweakAABB":
TweakAABB = parameter.GetValue<Vector4D>();
break;
// Return tweaked AABB
var aabb = AABB;
var trans = Owner.GetComponent<TransformComponent>(ComponentFamily.Transform);
if (trans == null)
return aabb;
else if (aabb != null)
return new RectangleF(
aabb.Left + trans.X,
aabb.Top + trans.Y,
aabb.Width,
aabb.Height);
else
return RectangleF.Empty;
}
}
@@ -82,5 +56,15 @@ namespace CGO
{
return IoCManager.Resolve<ICollisionManager>().TryCollide(Owner, offset, bump);
}
public override void SetParameter(ComponentParameter parameter) {
switch (parameter.MemberName) {
case "DebugColor":
var color = ColorTranslator.FromHtml(parameter.GetValue<string>());
if (!color.IsEmpty)
DebugColor = color;
break;
}
}
}
}

View File

@@ -6,47 +6,55 @@ using System.Text;
using GameObject;
using SS13_Shared.GO;
using SS13_Shared.GO.Component.Hitbox;
using ClientInterfaces.GOC;
namespace CGO
{
public class HitboxComponent : Component
{
public SizeF Size;
public PointF Offset;
public PointF UpperLeft
{
get
{
return new PointF(Offset.X - Size.Width / 2, Offset.Y - Size.Height / 2);
namespace CGO {
public class HitboxComponent : Component {
public RectangleF AABB { get; set; }
public SizeF Size {
get {
return AABB.Size;
}
set {
AABB = new RectangleF(
AABB.Left + (AABB.Width - value.Width),
AABB.Top + (AABB.Height - value.Height),
value.Width,
value.Height
);
}
}
public RectangleF AABB
{
get
{
return new RectangleF(UpperLeft, Size);
public PointF Offset {
get {
return new PointF(AABB.Left + AABB.Width / 2f, AABB.Top + AABB.Height / 2f);
}
set {
AABB = new RectangleF(
value.X - AABB.Width / 2f,
value.Y - AABB.Height / 2f,
AABB.Width,
AABB.Height
);
}
}
public HitboxComponent()
{
public HitboxComponent() {
Family = ComponentFamily.Hitbox;
Size = new SizeF();
Offset = new PointF();
}
public override Type StateType
{
get
{
public override Type StateType {
get {
return typeof(HitboxComponentState);
}
}
public override void HandleComponentState(dynamic state)
{
Size = state.Size;
Offset = state.Offset;
public override void HandleComponentState(dynamic state) {
AABB = state.AABB;
}
}
}

View File

@@ -100,7 +100,7 @@ namespace ClientServices.Collision
var collider = (ColliderComponent)entity.GetComponent(ComponentFamily.Collider);
if (collider == null) return false;
var ColliderAABB = collider.OffsetAABB;
var ColliderAABB = collider.WorldAABB;
if(offset.Length > 0)
ColliderAABB.Offset(offset.X, offset.Y);
@@ -113,7 +113,7 @@ namespace ClientServices.Collision
};
//Get the buckets that correspond to the collider's points.
List<CollidableBucket> buckets = points.Select(GetBucket).Distinct().ToList();
List<CollidableBucket> buckets = points.Select(GetBucket).Distinct().ToList(); // PERF: ToList() is absolutely unnecessary here.
//Get all of the points
var cpoints = new List<CollidablePoint>();
@@ -123,7 +123,7 @@ namespace ClientServices.Collision
}
//Expand points to distinct AABBs
List<CollidableAABB> aabBs = (cpoints.Select(cp => cp.ParentAABB)).Distinct().ToList();
List<CollidableAABB> aabBs = (cpoints.Select(cp => cp.ParentAABB)).Distinct().ToList(); // PERF: ToList() and Distinct() are absolutely unnecessary here.
//try all of the AABBs against the target rect.
bool collided = false;

View File

@@ -113,6 +113,7 @@ namespace ClientServices.State.States
private RenderImage shadowIntermediate;
private ShadowMapResolver shadowMapResolver;
private bool debugWallOccluders = false;
private bool debugHitboxes = false;
#endregion
@@ -627,6 +628,40 @@ namespace ClientServices.State.States
{
if(debugWallOccluders)
_occluderDebugTarget.Blit(0,0,_occluderDebugTarget.Width, _occluderDebugTarget.Height, Color.White, BlitterSizeMode.Crop);
if (debugHitboxes) {
var corner = ClientWindowData.Singleton.ScreenOrigin;
var colliders =
_entityManager.ComponentManager.GetComponents(ComponentFamily.Collider)
.OfType<ColliderComponent>()
.Select(c => new { Color = c.DebugColor, AABB = c.WorldAABB })
.Where(c => !c.AABB.IsEmpty && c.AABB.IntersectsWith(ClientWindowData.Singleton.ViewPort));
var collidables =
_entityManager.ComponentManager.GetComponents(ComponentFamily.Collidable)
.OfType<CollidableComponent>()
.Select(c => new { Color = c.DebugColor, AABB = c.AABB })
.Where(c => !c.AABB.IsEmpty && c.AABB.IntersectsWith(ClientWindowData.Singleton.ViewPort));
var destAbo = _baseTarget.DestinationBlend;
var srcAbo = _baseTarget.SourceBlend;
_baseTarget.DestinationBlend = AlphaBlendOperation.InverseSourceAlpha;
_baseTarget.SourceBlend = AlphaBlendOperation.SourceAlpha;
foreach (var hitbox in colliders.Concat(collidables)) {
_baseTarget.FilledRectangle(
hitbox.AABB.Left - corner.X, hitbox.AABB.Top - corner.Y,
hitbox.AABB.Width, hitbox.AABB.Height, Color.FromArgb(64, hitbox.Color));
_baseTarget.Rectangle(
hitbox.AABB.Left - corner.X, hitbox.AABB.Top - corner.Y,
hitbox.AABB.Width, hitbox.AABB.Height, Color.FromArgb(128, hitbox.Color));
}
_baseTarget.DestinationBlend = destAbo;
_baseTarget.SourceBlend = srcAbo;
}
}
public void FormResize()
@@ -662,6 +697,10 @@ namespace ClientServices.State.States
{
ToggleOccluderDebug();
}
if (e.Key == KeyboardKeys.F4)
{
debugHitboxes = !debugHitboxes;
}
if (e.Key == KeyboardKeys.F5)
{
PlayerManager.SendVerb("save", 0);

View File

@@ -11,19 +11,17 @@ namespace SGO
{
public class HitboxComponent : Component
{
public SizeF Size;
public PointF Offset;
public RectangleF AABB { get; set; }
public HitboxComponent()
{
Family = ComponentFamily.Hitbox;
Size = new SizeF();
Offset = new PointF();
AABB = new RectangleF();
}
public override ComponentState GetComponentState()
{
return new HitboxComponentState(Size, Offset);
return new HitboxComponentState(AABB);
}
/// <summary>
@@ -36,16 +34,20 @@ namespace SGO
switch (parameter.MemberName)
{
case "SizeX":
Size.Width = parameter.GetValue<float>();
var width = parameter.GetValue<float>();
AABB = new RectangleF(AABB.Left + (AABB.Width - width) / 2f, AABB.Top, width, AABB.Height);
break;
case "SizeY":
Size.Height = parameter.GetValue<float>();
var height = parameter.GetValue<float>();
AABB = new RectangleF(AABB.Left, AABB.Top + (AABB.Height - height) / 2f, AABB.Width, height);
break;
case "OffsetX":
Offset.X = parameter.GetValue<float>();
var x = parameter.GetValue<float>();
AABB = new RectangleF(x - AABB.Width / 2f, AABB.Top, AABB.Width, AABB.Height);
break;
case "OffsetY":
Offset.Y = parameter.GetValue<float>();
var y = parameter.GetValue<float>();
AABB = new RectangleF(AABB.Left, y - AABB.Height / 2f, AABB.Width, AABB.Height);
break;
}
}

View File

@@ -27,7 +27,11 @@
<Component name="PlayerActionComp"></Component>
<Component name="EntityStatsComp"></Component>
<Component name="ParticleSystemComponent"></Component>
<Component name="HitboxComponent"></Component>
<Component name="HitboxComponent">
<Parameters>
<Parameter name="DebugColor" type="string" value="Red" />
</Parameters>
</Component>
</Components>
</EntityTemplate>
</EntityTemplates>

View File

@@ -19,7 +19,12 @@
</Parameters>
</Component>
<Component name="ClickableComponent"></Component>
<Component name="CollidableComponent"></Component>
<Component name="HitboxComponent"></Component>
<Component name="CollidableComponent">
<Parameters>
<Parameter name="DebugColor" type="string" value="Blue" />
</Parameters>
</Component>
</Components>
</EntityTemplate>
</EntityTemplates>

View File

@@ -14,14 +14,12 @@ namespace SS13_Shared.GO.Component.Hitbox
[Serializable]
public class HitboxComponentState : ComponentState
{
public SizeF Size;
public PointF Offset;
public RectangleF AABB;
public HitboxComponentState(SizeF size, PointF offset)
public HitboxComponentState(RectangleF aabb)
:base(ComponentFamily.Hitbox)
{
Size = size;
Offset = offset;
AABB = aabb;
}
}
}

View File

@@ -11,6 +11,13 @@
<Component name="BasicInteractableComponent"></Component>
<Component name="BasicMoverComponent"></Component>
<Component name="WorktopComponent"></Component>
<Component name="HitboxComponent">
<Parameters>
<Parameter name="SizeX" type="float" value="64" />
<Parameter name="SizeY" type="float" value="47" />
<Parameter name="OffsetY" type="float" value="8" />
</Parameters>
</Component>
<Component name="CollidableComponent"></Component>
</Components>
</EntityTemplate>