mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-21 04:40:00 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
577 lines
16 KiB
C#
577 lines
16 KiB
C#
using NativeEngine;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace Sandbox
|
|
{
|
|
/// <summary>
|
|
/// Provides ability to generate <see cref="Model"/>s at runtime.
|
|
/// A static instance of this class is available at <see cref="Model.Builder"/>
|
|
/// </summary>
|
|
public sealed partial class ModelBuilder
|
|
{
|
|
private readonly List<Mesh> meshes = new();
|
|
private readonly List<Vector3> vertices = new();
|
|
private readonly List<int> indices = new();
|
|
private readonly List<BoxDesc> boxes = new();
|
|
private readonly List<SphereDesc> spheres = new();
|
|
private readonly List<CapsuleDesc> capsules = new();
|
|
private readonly List<HullDesc> hulls = new();
|
|
private readonly List<MeshDesc> meshShapes = new();
|
|
private readonly List<int> lods = new();
|
|
private readonly List<ulong> bodyGroups = new();
|
|
private readonly List<MeshGroupDesc> meshGroups = new();
|
|
private readonly List<BoneDesc> bones = new();
|
|
private readonly StringBuilder names = new();
|
|
private int startTraceVertex;
|
|
private int startTraceIndex;
|
|
private int numTraceVertices;
|
|
private int numTraceIndices;
|
|
private string modelName;
|
|
|
|
private float mass = 1000;
|
|
private string surfaceProperty = "";
|
|
private readonly float[] lodSwitchDistance = Enumerable.Range( 0, 8 )
|
|
.Select( i => i * 50.0f )
|
|
.ToArray();
|
|
|
|
private struct BoxDesc
|
|
{
|
|
public Transform transform;
|
|
public Vector3 extents;
|
|
}
|
|
|
|
private struct SphereDesc
|
|
{
|
|
public Vector3 center;
|
|
public float radius;
|
|
}
|
|
|
|
private struct CapsuleDesc
|
|
{
|
|
public Vector3 center0;
|
|
public Vector3 center1;
|
|
public float radius;
|
|
}
|
|
|
|
private struct HullDesc
|
|
{
|
|
public Transform transform;
|
|
public int startVertex;
|
|
public int numVertex;
|
|
}
|
|
|
|
private struct MeshDesc
|
|
{
|
|
public int startVertex;
|
|
public int numVertex;
|
|
public int startIndex;
|
|
public int numIndex;
|
|
}
|
|
|
|
private struct BoneDesc
|
|
{
|
|
public int nameOffset;
|
|
public int nameLength;
|
|
public int parentNameOffset;
|
|
public int parentNameLength;
|
|
public Vector3 position;
|
|
public Rotation rotation;
|
|
public float radius;
|
|
public bool attachment;
|
|
}
|
|
|
|
private struct MeshGroupDesc
|
|
{
|
|
public int nameOffset;
|
|
public int nameLength;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Total mass of the physics body (Default is 1000)
|
|
/// </summary>
|
|
public ModelBuilder WithMass( float mass )
|
|
{
|
|
this.mass = mass;
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Surface property to use for collision
|
|
/// </summary>
|
|
public ModelBuilder WithSurface( string name )
|
|
{
|
|
surfaceProperty = name;
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// LOD switch distance increment for each Level of Detail (LOD) level. (Default is 50)
|
|
/// </summary>
|
|
public ModelBuilder WithLodDistance( int lod, float distance )
|
|
{
|
|
if ( lod >= 8 )
|
|
throw new ArgumentException( "Max LOD count is 8" );
|
|
|
|
lodSwitchDistance[lod] = distance;
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add box collision shape.
|
|
/// </summary>
|
|
public ModelBuilder AddCollisionBox( Vector3 extents, Vector3? center = default, Rotation? rotation = default )
|
|
{
|
|
boxes.Add( new()
|
|
{
|
|
extents = extents,
|
|
transform = new Transform( center ?? Vector3.Zero, rotation ?? Rotation.Identity )
|
|
} );
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add sphere collision shape.
|
|
/// </summary>
|
|
public ModelBuilder AddCollisionSphere( float radius, Vector3 center = default )
|
|
{
|
|
spheres.Add( new()
|
|
{
|
|
center = center,
|
|
radius = radius
|
|
} );
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add capsule collision shape.
|
|
/// </summary>
|
|
public ModelBuilder AddCollisionCapsule( Vector3 center0, Vector3 center1, float radius )
|
|
{
|
|
capsules.Add( new()
|
|
{
|
|
center0 = center0,
|
|
center1 = center1,
|
|
radius = radius
|
|
} );
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a CONVEX hull collision shape.
|
|
/// </summary>
|
|
public ModelBuilder AddCollisionHull( List<Vector3> vertices, Vector3? center = default, Rotation? rotation = default )
|
|
{
|
|
return AddCollisionHull( CollectionsMarshal.AsSpan( vertices ), center, rotation );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a CONVEX hull collision shape.
|
|
/// </summary>
|
|
public ModelBuilder AddCollisionHull( Span<Vector3> vertices, Vector3? center = default, Rotation? rotation = default )
|
|
{
|
|
if ( vertices.IsEmpty ) return this;
|
|
|
|
var startVertex = this.vertices.Count;
|
|
|
|
hulls.Add( new()
|
|
{
|
|
startVertex = startVertex,
|
|
numVertex = vertices.Length,
|
|
transform = new Transform( center ?? Vector3.Zero, rotation ?? Rotation.Identity )
|
|
} );
|
|
|
|
this.vertices.AddRange( vertices );
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a CONCAVE mesh collision shape. (This shape can NOT be physically simulated)
|
|
/// </summary>
|
|
public ModelBuilder AddCollisionMesh( List<Vector3> vertices, List<int> indices )
|
|
{
|
|
return AddCollisionMesh( CollectionsMarshal.AsSpan( vertices ), CollectionsMarshal.AsSpan( indices ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a CONCAVE mesh collision shape. (This shape can NOT be physically simulated)
|
|
/// </summary>
|
|
public ModelBuilder AddCollisionMesh( Span<Vector3> vertices, Span<int> indices )
|
|
{
|
|
if ( vertices.Length < 3 )
|
|
return this;
|
|
|
|
if ( indices.Length < 3 )
|
|
return this;
|
|
|
|
// Validate indices are in range, creating collision mesh is expensive anyway so no harm in being safe.
|
|
int numVertices = vertices.Length;
|
|
foreach ( var i in indices )
|
|
{
|
|
if ( i < 0 || i >= numVertices )
|
|
throw new ArgumentOutOfRangeException( nameof( indices ), $"Tried to access out of range vertex {i}, range is 0-{numVertices - 1}" );
|
|
}
|
|
|
|
var startVertex = this.vertices.Count;
|
|
var startIndex = this.indices.Count;
|
|
|
|
meshShapes.Add( new()
|
|
{
|
|
startVertex = startVertex,
|
|
numVertex = vertices.Length,
|
|
startIndex = startIndex,
|
|
numIndex = indices.Length
|
|
} );
|
|
|
|
this.vertices.AddRange( vertices );
|
|
this.indices.AddRange( indices );
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add trace vertices for tracing against mesh
|
|
/// </summary>
|
|
public ModelBuilder AddTraceMesh( List<Vector3> vertices, List<int> indices )
|
|
{
|
|
return AddTraceMesh( CollectionsMarshal.AsSpan( vertices ), CollectionsMarshal.AsSpan( indices ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add trace vertices for tracing against mesh
|
|
/// </summary>
|
|
public ModelBuilder AddTraceMesh( Span<Vector3> vertices, Span<int> indices )
|
|
{
|
|
if ( vertices.Length < 3 )
|
|
return this;
|
|
|
|
if ( indices.Length < 3 )
|
|
return this;
|
|
|
|
// Validate indices are in range
|
|
int numVertices = vertices.Length;
|
|
foreach ( var i in indices )
|
|
{
|
|
if ( i < 0 || i >= numVertices )
|
|
throw new ArgumentOutOfRangeException( nameof( indices ), $"Tried to access out of range vertex {i}, range is 0-{numVertices - 1}" );
|
|
}
|
|
|
|
var startVertex = this.vertices.Count;
|
|
var startIndex = this.indices.Count;
|
|
|
|
startTraceVertex = startVertex;
|
|
startTraceIndex = startIndex;
|
|
numTraceVertices = vertices.Length;
|
|
numTraceIndices = indices.Length;
|
|
|
|
this.vertices.AddRange( vertices );
|
|
this.indices.AddRange( indices );
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a mesh.
|
|
/// </summary>
|
|
public ModelBuilder AddMesh( Mesh mesh )
|
|
{
|
|
AddMesh( mesh, 255, ulong.MaxValue );
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a bunch of meshes.
|
|
/// </summary>
|
|
public ModelBuilder AddMeshes( Mesh[] meshes )
|
|
{
|
|
AddMeshes( meshes, 255, ulong.MaxValue );
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a mesh to a Level of Detail (LOD) group.
|
|
/// </summary>
|
|
public ModelBuilder AddMesh( Mesh mesh, int lod )
|
|
{
|
|
if ( lod < 0 ) lod = 0;
|
|
var lodMask = 1 << lod;
|
|
|
|
AddMesh( mesh, lodMask, ulong.MaxValue );
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a bunch of meshes to a Level of Detail (LOD) group.
|
|
/// </summary>
|
|
public ModelBuilder AddMeshes( Mesh[] meshes, int lod )
|
|
{
|
|
if ( lod < 0 ) lod = 0;
|
|
var lodMask = 1 << lod;
|
|
|
|
AddMeshes( meshes, lodMask, ulong.MaxValue );
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a mesh to a body group choice.
|
|
/// </summary>
|
|
public ModelBuilder AddMesh( Mesh mesh, string groupName, int choiceIndex )
|
|
{
|
|
return AddMesh( mesh, 0, groupName, choiceIndex );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a mesh to a Level of Detail (LOD) and a body group choice.
|
|
/// </summary>
|
|
public ModelBuilder AddMesh( Mesh mesh, int lod, string groupName, int choiceIndex )
|
|
{
|
|
var groupIndex = EnsureGroupChoice( groupName, choiceIndex );
|
|
var groupMask = 1UL << groupIndex;
|
|
|
|
if ( lod < 0 ) lod = 0;
|
|
var lodMask = 1 << lod;
|
|
|
|
return AddMesh( mesh, lodMask, groupMask );
|
|
}
|
|
|
|
private ModelBuilder AddMesh( Mesh mesh, int lodMask, ulong bodyGroupMask )
|
|
{
|
|
if ( mesh == null || !mesh.IsValid )
|
|
return this;
|
|
|
|
if ( !mesh.HasVertexBuffer )
|
|
throw new ArgumentException( "Mesh has invalid vertex buffer" );
|
|
|
|
meshes.Add( mesh );
|
|
lods.Add( lodMask );
|
|
bodyGroups.Add( bodyGroupMask );
|
|
|
|
return this;
|
|
}
|
|
|
|
private ModelBuilder AddMeshes( Mesh[] meshes, int lodMask, ulong bodyGroupMask )
|
|
{
|
|
if ( meshes == null || meshes.Length == 0 )
|
|
return this;
|
|
|
|
int numMeshes = 0;
|
|
foreach ( var mesh in meshes )
|
|
{
|
|
if ( mesh == null || !mesh.IsValid )
|
|
continue;
|
|
|
|
if ( !mesh.HasVertexBuffer )
|
|
throw new ArgumentException( "Mesh has invalid vertex buffer" );
|
|
|
|
this.meshes.Add( mesh );
|
|
numMeshes++;
|
|
}
|
|
|
|
if ( numMeshes == 0 )
|
|
return this;
|
|
|
|
lods.AddRange( Enumerable.Repeat( lodMask, numMeshes ) );
|
|
bodyGroups.AddRange( Enumerable.Repeat( bodyGroupMask, numMeshes ) );
|
|
|
|
return this;
|
|
}
|
|
|
|
private readonly Dictionary<(string, int), int> _groupChoiceToIndex = new();
|
|
private readonly HashSet<string> _groupNames = new( StringComparer.Ordinal );
|
|
private ulong _defaultMeshGroupMask;
|
|
|
|
private ulong DefaultMeshGroupMask => _groupNames.Count > 0 ? _defaultMeshGroupMask : ulong.MaxValue;
|
|
|
|
private int EnsureGroupChoice( string name, int choice )
|
|
{
|
|
if ( string.IsNullOrWhiteSpace( name ) ) throw new ArgumentException( null, nameof( name ) );
|
|
ArgumentOutOfRangeException.ThrowIfNegative( choice );
|
|
|
|
var key = (name, choice);
|
|
if ( _groupChoiceToIndex.TryGetValue( key, out var index ) ) return index;
|
|
|
|
if ( meshGroups.Count >= 64 )
|
|
throw new InvalidOperationException( "Total bodygroup choices exceed 64 bits." );
|
|
|
|
var groupName = $"{name}_@{choice}";
|
|
var nameOffset = names.Length;
|
|
index = meshGroups.Count;
|
|
meshGroups.Add( new MeshGroupDesc { nameLength = groupName.Length, nameOffset = nameOffset } );
|
|
names.Append( groupName );
|
|
_groupChoiceToIndex[key] = index;
|
|
|
|
if ( _groupNames.Add( name ) )
|
|
{
|
|
_defaultMeshGroupMask |= (1UL << index);
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A bone definition for use with <see cref="ModelBuilder"/>.
|
|
/// </summary>
|
|
/// <param name="Name">Name of the bone.</param>
|
|
/// <param name="ParentName">Name of the parent bone.</param>
|
|
/// <param name="Position">Position of the bone, relative to its parent.</param>
|
|
/// <param name="Rotation">Rotation of the bone, relative to its parent.</param>
|
|
public readonly record struct Bone( string Name, string ParentName, Vector3 Position, Rotation Rotation );
|
|
|
|
/// <summary>
|
|
/// Add a bone to the skeleton via a <see cref="Bone"/> struct.
|
|
/// </summary>
|
|
public void AddBone( Bone bone )
|
|
{
|
|
AddBone( bone.Name, bone.Position, bone.Rotation, bone.ParentName );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add multiple bones to the skeleton.
|
|
/// </summary>
|
|
public void AddBones( Bone[] bones )
|
|
{
|
|
if ( bones == null )
|
|
return;
|
|
|
|
foreach ( var bone in bones )
|
|
AddBone( bone.Name, bone.Position, bone.Rotation, bone.ParentName );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a bone to the skeleton.
|
|
/// </summary>
|
|
public ModelBuilder AddBone( string name, Vector3 position, Rotation rotation, string parentName = null )
|
|
{
|
|
return AddBone( name, position, rotation, parentName, false );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an attachment to the skeleton.
|
|
/// </summary>
|
|
public ModelBuilder AddAttachment( string name, Vector3 position, Rotation rotation, string parentName = null )
|
|
{
|
|
return AddBone( name, position, rotation, parentName, true );
|
|
}
|
|
|
|
internal ModelBuilder AddBone( string name, Vector3 position, Rotation rotation, string parentName, bool attachment )
|
|
{
|
|
var nameOffset = -1;
|
|
if ( !string.IsNullOrWhiteSpace( name ) )
|
|
{
|
|
nameOffset = names.Length;
|
|
names.Append( name );
|
|
}
|
|
|
|
var parentNameOffset = -1;
|
|
if ( !string.IsNullOrWhiteSpace( parentName ) )
|
|
{
|
|
parentNameOffset = names.Length;
|
|
names.Append( parentName );
|
|
}
|
|
|
|
bones.Add( new BoneDesc
|
|
{
|
|
nameOffset = nameOffset,
|
|
nameLength = name != null ? name.Length : 0,
|
|
parentNameOffset = parentNameOffset,
|
|
parentNameLength = parentName != null ? parentName.Length : 0,
|
|
position = position,
|
|
rotation = rotation,
|
|
radius = -1,
|
|
attachment = attachment
|
|
} );
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provide a name to identify the model by
|
|
/// </summary>
|
|
/// <param name="name">Desired model name</param>
|
|
public ModelBuilder WithName( string name )
|
|
{
|
|
modelName = name;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finish creation of the model.
|
|
/// </summary>
|
|
public unsafe Model Create()
|
|
{
|
|
var renderMeshes = meshes
|
|
.Where( x => x != null && x.IsValid )
|
|
.Select( x => x.native )
|
|
.ToArray();
|
|
|
|
var vertices_span = CollectionsMarshal.AsSpan( vertices );
|
|
var indices_span = CollectionsMarshal.AsSpan( indices );
|
|
var spheres_span = CollectionsMarshal.AsSpan( spheres );
|
|
var capsules_span = CollectionsMarshal.AsSpan( capsules );
|
|
var boxes_span = CollectionsMarshal.AsSpan( boxes );
|
|
var hulls_span = CollectionsMarshal.AsSpan( hulls );
|
|
var meshes_span = CollectionsMarshal.AsSpan( meshShapes );
|
|
var lods_span = CollectionsMarshal.AsSpan( lods );
|
|
var bodygroups_span = CollectionsMarshal.AsSpan( bodyGroups );
|
|
var meshgroups_span = CollectionsMarshal.AsSpan( meshGroups );
|
|
var bones_span = CollectionsMarshal.AsSpan( bones );
|
|
|
|
fixed ( IMesh* meshes_ptr = renderMeshes )
|
|
fixed ( Vector3* vertices_ptr = vertices_span )
|
|
fixed ( int* indices_ptr = indices_span )
|
|
fixed ( SphereDesc* spheres_ptr = spheres_span )
|
|
fixed ( CapsuleDesc* capsule_ptr = capsules_span )
|
|
fixed ( BoxDesc* boxes_ptr = boxes_span )
|
|
fixed ( HullDesc* hulls_ptr = hulls_span )
|
|
fixed ( MeshDesc* meshShapes_ptr = meshes_span )
|
|
fixed ( int* lods_ptr = lods_span )
|
|
fixed ( ulong* bodygroups_ptr = bodygroups_span )
|
|
fixed ( MeshGroupDesc* meshgroups_ptr = meshgroups_span )
|
|
fixed ( BoneDesc* bones_ptr = bones_span )
|
|
fixed ( float* pLodSwitchDistance = &lodSwitchDistance[0] )
|
|
{
|
|
var anim = CreateAnimationGroup();
|
|
var bodies = CreatePhysBodyDesc();
|
|
var materialGroups = CreateMaterialGroups();
|
|
|
|
var model = MeshGlue.CreateModel(
|
|
anim,
|
|
bodies,
|
|
materialGroups,
|
|
mass,
|
|
surfaceProperty,
|
|
(IntPtr)pLodSwitchDistance,
|
|
(IntPtr)meshes_ptr, renderMeshes.Length,
|
|
(IntPtr)lods_ptr,
|
|
(IntPtr)bodygroups_ptr,
|
|
(IntPtr)meshgroups_ptr, meshGroups.Count,
|
|
(IntPtr)vertices_ptr, vertices.Count,
|
|
(IntPtr)indices_ptr, indices.Count,
|
|
(IntPtr)spheres_ptr, spheres.Count,
|
|
(IntPtr)capsule_ptr, capsules.Count,
|
|
(IntPtr)boxes_ptr, boxes.Count,
|
|
(IntPtr)hulls_ptr, hulls.Count,
|
|
(IntPtr)meshShapes_ptr, meshShapes.Count,
|
|
(IntPtr)bones_ptr, bones.Count,
|
|
names.Length > 0 ? names.ToString() : null,
|
|
startTraceVertex, startTraceIndex,
|
|
numTraceVertices, numTraceIndices,
|
|
DefaultMeshGroupMask );
|
|
|
|
if ( anim.IsValid ) anim.DeleteThis();
|
|
if ( bodies.IsValid ) bodies.DeleteThis();
|
|
if ( materialGroups.IsValid ) materialGroups.DeleteThis();
|
|
|
|
return Model.FromNative( model, true, modelName );
|
|
}
|
|
}
|
|
}
|
|
}
|