namespace Sandbox;
///
/// Ticks the physics in FrameStage.PhysicsStep
///
[Expose]
sealed class HitboxSystem : GameObjectSystem, GameObjectSystem.ITraceProvider
{
[ConVar( "debug_hitbox", ConVarFlags.Protected )]
static bool Debug { get; set; }
private PhysicsWorld _physicsWorld;
public PhysicsWorld PhysicsWorld => _physicsWorld ??= new PhysicsWorld();
public HitboxSystem( Scene scene ) : base( scene )
{
Listen( Stage.UpdateBones, 100, InvalidateHitboxes, "InvalidateHitboxes" );
Listen( Stage.FinishUpdate, -1, DrawDebug, "DrawDebug" );
}
public override void Dispose()
{
_physicsWorld?.Delete();
_physicsWorld = null;
}
bool hitboxesDirty;
void InvalidateHitboxes()
{
hitboxesDirty = true;
}
void UpdateHitboxPositions()
{
if ( !hitboxesDirty )
return;
lock ( this )
{
if ( !hitboxesDirty )
return;
hitboxesDirty = false;
// these could be foreach parallel!!
foreach ( var group in Scene.GetAllComponents() )
{
group.UpdatePositions();
}
foreach ( var group in Scene.GetAllComponents() )
{
group.UpdatePositions();
}
}
}
public void DoTrace( in SceneTrace trace, List results )
{
if ( !trace.IncludeHitboxes )
return;
UpdateHitboxPositions();
var traceResults = PhysicsWorld.RunTraceAll( trace.PhysicsTrace );
foreach ( var traceResult in traceResults )
{
var sceneResult = SceneTraceResult.From( Scene, traceResult );
sceneResult.Hitbox = traceResult.Body.Hitbox as Hitbox;
sceneResult.Body = null;
sceneResult.Shape = null;
sceneResult.GameObject = sceneResult.Hitbox.GameObject;
results.Add( sceneResult );
}
}
public SceneTraceResult? DoTrace( in SceneTrace trace )
{
if ( !trace.IncludeHitboxes )
return null;
UpdateHitboxPositions();
var tr = PhysicsWorld.RunTrace( trace.PhysicsTrace );
if ( !tr.Hit )
return null;
var result = SceneTraceResult.From( Scene, tr );
result.Hitbox = (Hitbox)tr.Body.Hitbox;
result.Body = null;
result.Shape = null;
result.GameObject = result.Hitbox.GameObject;
result.Bone = result.Hitbox?.Bone?.Index ?? tr.Bone;
return result;
}
private void DrawDebug()
{
if ( !Debug ) return;
foreach ( var group in Scene.GetAllComponents() )
{
DrawDebug( group );
}
foreach ( var group in Scene.GetAllComponents() )
{
DrawDebug( group );
}
}
private void DrawDebug( ModelHitboxes hitboxes )
{
using var _ = Gizmo.Scope();
Gizmo.Draw.Color = Color.Orange;
hitboxes.UpdatePositions();
foreach ( var hitbox in hitboxes.Hitboxes )
{
DrawDebug( hitbox );
}
}
private void DrawDebug( ManualHitbox hitbox )
{
using var _ = Gizmo.Scope();
Gizmo.Draw.Color = Color.Orange;
hitbox.UpdatePositions();
if ( hitbox.Hitbox is null )
return;
DrawDebug( hitbox.Hitbox );
}
private void DrawDebug( Hitbox hitbox )
{
using ( Gizmo.ObjectScope( hitbox, hitbox.Body.Transform ) )
{
foreach ( var shape in hitbox.Body.Shapes )
{
if ( shape.ShapeType == PhysicsShapeType.SHAPE_SPHERE )
{
Gizmo.Draw.LineSphere( shape.Sphere.Center, shape.Sphere.Radius );
}
else if ( shape.ShapeType == PhysicsShapeType.SHAPE_CAPSULE )
{
Gizmo.Draw.LineCapsule( new( shape.Capsule.CenterA, shape.Capsule.CenterB, shape.Capsule.Radius ) );
}
else if ( shape.ShapeType == PhysicsShapeType.SHAPE_HULL )
{
Gizmo.Draw.Lines( shape.GetOutline() );
}
}
}
}
}