Editor/Output/ArchCollision.cs
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public enum ArchCollisionMode {
None,
Solids,
Convex,
Complex
}
public interface IArchCollides {
ArchCollisionMode? Collision { get; set; }
}
public static class ArchCollision {
// Resolves: part → inherited up the layer stack → scope → kit
public static ArchCollisionMode Resolve( ArchBuiltPart part, ArchKit kit, ArchLayerTree layers ) {
var wanted = part.Asked ?? Inherited( part.Source, layers ) ?? part.Scope ?? kit?.Physics ?? ArchCollisionMode.Solids;
if ( wanted != ArchCollisionMode.Solids ) {
return wanted;
}
// Solids only valid when the generator fully described the part with them
if ( part.Canvas.Describes && part.Canvas.Solids.Any( solid => solid.IsUsable ) ) {
return ArchCollisionMode.Solids;
}
return ArchCollisionMode.Complex;
}
static ArchCollisionMode? Inherited( object source, ArchLayerTree layers ) {
return source is not null && layers?.Find( source ) is { } node ? Above( node ) : null;
}
public static ArchCollisionMode? Above( ArchLayerNode node ) {
for ( var above = node?.Parent; above is not null; above = above.Parent ) {
if ( above.Payload is IArchCollides { Collision: { } inherited } ) {
return inherited;
}
}
return null;
}
public static ArchCollisionMode Showing( ArchLayerNode node, ArchKit kit, out bool own ) {
var mine = (node?.Payload as IArchCollides)?.Collision;
own = mine.HasValue;
return mine ?? Above( node ) ?? kit?.Physics ?? ArchCollisionMode.Solids;
}
public static void Write( GameObject node, MeshComponent renderer, ArchMesh canvas, ArchCollisionMode mode ) {
renderer.Collision = mode switch {
ArchCollisionMode.Complex => MeshComponent.CollisionType.Mesh,
ArchCollisionMode.Convex => MeshComponent.CollisionType.Hull,
_ => MeshComponent.CollisionType.None
};
Clear( node );
if ( mode != ArchCollisionMode.Solids ) {
return;
}
foreach ( var solid in canvas.Solids.Where( solid => solid.IsUsable ) ) {
Shape( node, solid );
}
}
static void Clear( GameObject node ) {
foreach ( var collider in node.Components.GetAll<HullCollider>( FindMode.EverythingInSelf ).ToList() ) {
collider.Destroy();
}
}
static void Shape( GameObject node, ArchSolid solid ) {
var collider = node.Components.Create<HullCollider>();
switch ( solid.Form ) {
case ArchSolidForm.Box:
collider.Type = HullCollider.PrimitiveType.Box;
collider.Center = solid.Centre;
collider.BoxSize = solid.Size;
break;
case ArchSolidForm.Cylinder:
collider.Type = HullCollider.PrimitiveType.Cylinder;
collider.Center = solid.Centre;
collider.Radius = solid.Radius;
collider.Height = solid.Height;
collider.Slices = solid.Slices;
break;
default:
collider.Type = HullCollider.PrimitiveType.Points;
collider.Points = solid.Points.ToList();
break;
}
}
public static string Describe( ArchCollisionMode mode ) => mode switch {
ArchCollisionMode.None => "No collision — nothing to walk into",
ArchCollisionMode.Solids => "Convex pieces — one shape per solid the generator made",
ArchCollisionMode.Convex => "One hull round the whole part — fills its holes",
_ => "Exact mesh — every triangle, the expensive one"
};
public static string Glyph( ArchCollisionMode mode ) => mode switch {
ArchCollisionMode.None => "block",
ArchCollisionMode.Solids => "view_module",
ArchCollisionMode.Convex => "crop_square",
_ => "grain"
};
}