mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-05 04:48:19 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
354 lines
9.3 KiB
C#
354 lines
9.3 KiB
C#
using Sandbox.Rendering;
|
|
using System.Numerics;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// A scene object which is used to draw lines
|
|
/// </summary>
|
|
public class SceneLineObject : SceneCustomObject
|
|
{
|
|
public enum CapStyle
|
|
{
|
|
None = 0,
|
|
[Icon( "signal_cellular_4_bar" )]
|
|
Triangle = 1,
|
|
[Icon( "label_important" )]
|
|
Arrow = 2,
|
|
[Icon( "fiber_smart_record" )]
|
|
Rounded = 3
|
|
}
|
|
|
|
public enum FaceMode
|
|
{
|
|
[Icon( "settings_ethernet" )]
|
|
Camera = 0,
|
|
[Icon( "rotate_90_degrees_cw" )]
|
|
Normal = 1,
|
|
[Icon( "circle" )]
|
|
Cylinder = 2,
|
|
}
|
|
|
|
[Obsolete( "Use Material property instead", false )]
|
|
public Texture LineTexture
|
|
{
|
|
get => Attributes.GetTexture( "BaseTexture" );
|
|
set => Attributes.Set( "BaseTexture", value ?? Texture.White );
|
|
}
|
|
|
|
public Material Material = Material.FromShader( "line" );
|
|
|
|
public CapStyle StartCap
|
|
{
|
|
get => (CapStyle)Attributes.GetInt( "StartCap" );
|
|
set => Attributes.Set( "StartCap", (int)value );
|
|
}
|
|
|
|
public CapStyle EndCap
|
|
{
|
|
get => (CapStyle)Attributes.GetInt( "EndCap" );
|
|
set => Attributes.Set( "EndCap", (int)value );
|
|
}
|
|
|
|
public FaceMode Face
|
|
{
|
|
get => (FaceMode)_cs.Attributes.GetInt( "FaceMode" );
|
|
set => _cs.Attributes.Set( "FaceMode", (int)value );
|
|
}
|
|
|
|
public bool Wireframe
|
|
{
|
|
get => Attributes.GetComboBool( "D_WIREFRAME" );
|
|
set => Attributes.SetCombo( "D_WIREFRAME", value );
|
|
}
|
|
|
|
public bool Lighting
|
|
{
|
|
get => Attributes.GetComboBool( "D_ENABLE_LIGHTING" );
|
|
set => Attributes.SetCombo( "D_ENABLE_LIGHTING", value );
|
|
}
|
|
|
|
public bool Clamped
|
|
{
|
|
get => _clamped;
|
|
set
|
|
{
|
|
_clamped = value;
|
|
if ( _clamped )
|
|
{
|
|
Attributes.Set( "SamplerIndex", SamplerState.GetBindlessIndex( ClampSampler ) );
|
|
}
|
|
else
|
|
{
|
|
Attributes.Set( "SamplerIndex", SamplerState.GetBindlessIndex( WrapSampler ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool _clamped = false;
|
|
|
|
public int Smoothness
|
|
{
|
|
get => Attributes.GetInt( "Smoothness" );
|
|
set => Attributes.Set( "Smoothness", value );
|
|
}
|
|
|
|
public bool Opaque
|
|
{
|
|
set
|
|
{
|
|
Attributes.SetCombo( "D_OPAQUE", value ? 1 : 0 );
|
|
Flags.IsOpaque = value;
|
|
Flags.IsTranslucent = !value;
|
|
}
|
|
}
|
|
|
|
private int _roundedCapSegments = 8;
|
|
private int _tessellationLevel = 1; // Number of subdivisions between left and right sides of each segment
|
|
|
|
/// <summary>
|
|
/// Number of tessellation subdivisions across the width of each line segment.
|
|
/// 1 = no tessellation (just left and right), 2 = one subdivision in the middle, etc.
|
|
/// Higher values create smoother curves and more detailed geometry but use more vertices.
|
|
/// </summary>
|
|
public int TessellationLevel
|
|
{
|
|
get => _tessellationLevel;
|
|
set => _tessellationLevel = Math.Max( 1, value );
|
|
}
|
|
|
|
private struct LinePoint( uint offset, in Vector3 position, in Vector3 normal, Color color, float width, float textureCoord )
|
|
{
|
|
public uint Offset = offset;
|
|
public Vector3 Position = position;
|
|
public Vector3 Normal = normal;
|
|
public Color Color = color;
|
|
public float Width = width;
|
|
public float TextureCoord = textureCoord;
|
|
}
|
|
|
|
private struct LineVertex()
|
|
{
|
|
[VertexLayout.Position] public Vector3 Position = default;
|
|
[VertexLayout.Normal] public Vector3 Normal = default;
|
|
[VertexLayout.Tangent] public Vector3 Tangent = default;
|
|
[VertexLayout.Color] public Color Color = default;
|
|
[VertexLayout.TexCoord] public Vector2 TextureCoord = default;
|
|
}
|
|
|
|
private readonly ComputeShader _cs = new( "line_cs" );
|
|
private GpuBuffer<LinePoint> _pointBuffer;
|
|
private GpuBuffer<LineVertex> _vertexBuffer;
|
|
private GpuBuffer<uint> _indexBuffer;
|
|
private static readonly SamplerState WrapSampler = new() { Filter = FilterMode.Anisotropic, MaxAnisotropy = 8, AddressModeU = TextureAddressMode.Wrap, AddressModeV = TextureAddressMode.Wrap };
|
|
private static readonly SamplerState ClampSampler = new() { Filter = FilterMode.Anisotropic, MaxAnisotropy = 8, AddressModeU = TextureAddressMode.Clamp, AddressModeV = TextureAddressMode.Clamp };
|
|
private readonly List<LinePoint> _points = [];
|
|
private BBox _bounds;
|
|
|
|
private int _pointCount;
|
|
private int _pointCapacity;
|
|
|
|
private int _indexCapacity;
|
|
private int _vertexCapacity;
|
|
|
|
public SceneLineObject( SceneWorld sceneWorld ) : base( sceneWorld )
|
|
{
|
|
#pragma warning disable CS0618
|
|
LineTexture = Texture.White;
|
|
#pragma warning restore CS0618
|
|
|
|
managedNative.ExecuteOnMainThread = false;
|
|
}
|
|
|
|
public void StartLine()
|
|
{
|
|
_points.Clear();
|
|
|
|
_pointCount = 0;
|
|
|
|
_bounds = BBox.FromPositionAndSize( Transform.Position, 10 );
|
|
}
|
|
|
|
public void AddLinePoint( in Vector3 pos, Color color, float width )
|
|
{
|
|
AddLinePoint( pos, color, width, _pointCount / 20.0f );
|
|
}
|
|
|
|
public void AddLinePoint( in Vector3 pos, Color color, float width, float textureCoord )
|
|
{
|
|
AddLinePoint( pos, Vector3.Up, color, width, textureCoord );
|
|
}
|
|
|
|
public void AddLinePoint( in Vector3 pos, in Vector3 normal, Color color, float width, float textureCoord )
|
|
{
|
|
if ( _pointCount > 0 && _points[^1].Position.DistanceSquared( pos ) < 0.01f )
|
|
return;
|
|
|
|
var point = new LinePoint( 0, pos, normal, color, width, textureCoord );
|
|
_points.Add( point );
|
|
|
|
_pointCount++;
|
|
_bounds = _bounds.AddPoint( pos );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the number of vertices needed for a specific cap type
|
|
/// </summary>
|
|
private int GetCapVertexCount( CapStyle capStyle )
|
|
{
|
|
return capStyle switch
|
|
{
|
|
CapStyle.None => 0,
|
|
CapStyle.Triangle => 1, // 1 additional vertex (center)
|
|
CapStyle.Arrow => 3, // Additional triangle
|
|
CapStyle.Rounded => _roundedCapSegments + 1 + 1, // Multiple vertices for the rounded cap
|
|
_ => 0
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the number of indices needed for a specific cap type
|
|
/// </summary>
|
|
private int GetCapIndexCount( CapStyle capStyle )
|
|
{
|
|
return capStyle switch
|
|
{
|
|
CapStyle.None => 0,
|
|
CapStyle.Triangle => 3, // One triangle (3 indices)
|
|
CapStyle.Arrow => 3, // One triangle (3 indices)
|
|
CapStyle.Rounded => _roundedCapSegments * 3, // Multiple triangles (3 indices each)
|
|
_ => 0
|
|
};
|
|
}
|
|
|
|
public void EndLine()
|
|
{
|
|
Bounds = _bounds;
|
|
|
|
if ( _pointCount == 0 )
|
|
return;
|
|
|
|
var startCap = StartCap;
|
|
var endCap = EndCap;
|
|
|
|
if ( _points.Count > _pointCapacity )
|
|
{
|
|
MainThread.QueueDispose( _pointBuffer );
|
|
|
|
_pointCapacity = (int)(_points.Count * 1.5);
|
|
_pointBuffer = new GpuBuffer<LinePoint>( _pointCapacity, GpuBuffer.UsageFlags.Structured );
|
|
}
|
|
|
|
if ( CalculateIndexCount() > _indexCapacity )
|
|
{
|
|
MainThread.QueueDispose( _indexBuffer );
|
|
|
|
_indexCapacity = (int)(CalculateIndexCount() * 1.5);
|
|
_indexBuffer = new GpuBuffer<uint>( _indexCapacity, GpuBuffer.UsageFlags.Index | GpuBuffer.UsageFlags.Structured );
|
|
}
|
|
|
|
if ( CalculateVertexCount() > _vertexCapacity )
|
|
{
|
|
MainThread.QueueDispose( _vertexBuffer );
|
|
|
|
_vertexCapacity = (int)(CalculateVertexCount() * 1.5);
|
|
_vertexBuffer = new GpuBuffer<LineVertex>( _vertexCapacity, GpuBuffer.UsageFlags.Vertex | GpuBuffer.UsageFlags.Structured );
|
|
}
|
|
|
|
if ( _pointBuffer.IsValid() )
|
|
{
|
|
_pointBuffer.SetData( _points );
|
|
}
|
|
|
|
if ( Face == FaceMode.Cylinder )
|
|
{
|
|
// Unsupported on Cylinder
|
|
_cs.Attributes.Set( "StartCap", 0 );
|
|
_cs.Attributes.Set( "EndCap", 0 );
|
|
}
|
|
else
|
|
{
|
|
_cs.Attributes.Set( "StartCap", (int)startCap );
|
|
_cs.Attributes.Set( "EndCap", (int)endCap );
|
|
}
|
|
|
|
_cs.Attributes.Set( "TessellationLevel", _tessellationLevel );
|
|
_cs.Attributes.Set( "RoundedCapSegments", _roundedCapSegments );
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_points.Clear();
|
|
|
|
_pointCount = 0;
|
|
|
|
_pointCapacity = 0;
|
|
_vertexCapacity = 0;
|
|
_indexCapacity = 0;
|
|
|
|
MainThread.QueueDispose( _pointBuffer );
|
|
MainThread.QueueDispose( _vertexBuffer );
|
|
MainThread.QueueDispose( _indexBuffer );
|
|
|
|
_pointBuffer = default;
|
|
_vertexBuffer = default;
|
|
_indexBuffer = default;
|
|
}
|
|
|
|
private int CalculateIndexCount()
|
|
{
|
|
// With tessellation, each segment creates tessellationLevel * 2 triangles
|
|
// (tessellationLevel quads, each quad = 2 triangles, each triangle = 3 indices)
|
|
int indicesPerSegment = _tessellationLevel * 2 * 3;
|
|
int lineIndices = (_pointCount - 1) * indicesPerSegment;
|
|
|
|
// Add indices for caps
|
|
int capIndices = GetCapIndexCount( StartCap ) + GetCapIndexCount( EndCap );
|
|
|
|
return lineIndices + capIndices;
|
|
}
|
|
|
|
private int CalculateVertexCount()
|
|
{
|
|
// Basic line vertices (tessellationLevel + 1 vertices per point to create tessellationLevel segments across width)
|
|
int lineVertices = _pointCount * (_tessellationLevel + 1);
|
|
|
|
// Add vertices for caps
|
|
int capVertices = GetCapVertexCount( StartCap ) + GetCapVertexCount( EndCap );
|
|
|
|
return lineVertices + capVertices;
|
|
}
|
|
|
|
public override void RenderSceneObject()
|
|
{
|
|
base.RenderSceneObject();
|
|
|
|
// If we have less than a quad fuck off
|
|
if ( CalculateIndexCount() < 6 )
|
|
return;
|
|
|
|
if ( !_pointBuffer.IsValid() )
|
|
return;
|
|
|
|
if ( !_vertexBuffer.IsValid() )
|
|
return;
|
|
|
|
if ( !_indexBuffer.IsValid() )
|
|
return;
|
|
|
|
_cs.Attributes.Set( "PointBuffer", _pointBuffer );
|
|
_cs.Attributes.Set( "VertexBuffer", _vertexBuffer );
|
|
_cs.Attributes.Set( "IndexBuffer", _indexBuffer );
|
|
_cs.Attributes.Set( "PointCount", _pointCount );
|
|
_cs.Dispatch( _pointCount, 1, 1 );
|
|
|
|
Graphics.ResourceBarrierTransition( _vertexBuffer, ResourceState.UnorderedAccess, ResourceState.VertexOrIndexBuffer );
|
|
Graphics.ResourceBarrierTransition( _indexBuffer, ResourceState.UnorderedAccess, ResourceState.VertexOrIndexBuffer );
|
|
if ( Lighting )
|
|
{
|
|
Graphics.SetupLighting( this, Attributes );
|
|
}
|
|
Graphics.Draw( _vertexBuffer, _indexBuffer, Material, 0, CalculateIndexCount(), Attributes );
|
|
}
|
|
}
|