Files
sbox-public/engine/Sandbox.Engine/Systems/SceneSystem/SceneModel.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

270 lines
7.5 KiB
C#

using NativeEngine;
using System.Runtime.InteropServices;
namespace Sandbox;
/// <summary>
/// A model scene object that supports animations and can be rendered within a <see cref="SceneWorld"/>.
/// </summary>
[Expose]
public sealed partial class SceneModel : SceneObject
{
internal CSceneAnimatableObject animNative;
DelegateFunctionPointer animGraphChangedCallback;
[UnmanagedFunctionPointer( CallingConvention.StdCall )]
internal delegate void AnimGraphChangedCallback( IntPtr p );
internal SceneModel() { }
internal SceneModel( HandleCreationData _ ) { }
public SceneModel( SceneWorld sceneWorld, string model, Transform transform ) : this( sceneWorld, Model.Load( model ), transform ) { }
public SceneModel( SceneWorld sceneWorld, Model model, Transform transform )
{
Assert.IsValid( sceneWorld );
Assert.NotNull( model );
if ( !model.HasRenderMeshes() ) model = Model.Error;
using ( var h = IHandle.MakeNextHandle( this ) )
{
var flags = Rendering.SceneObjectFlags.CastShadows | Rendering.SceneObjectFlags.IsLoaded;
var typeFlags = ESceneObjectTypeFlags.NONE;
MeshSystem.CreateSceneObject( model.native, transform, "AnimatableSceneObjectDesc", flags, typeFlags, sceneWorld, 0x1 );
if ( !animNative.IsValid )
{
Log.Warning( "SceneModel: Didn't get a valid native pointer" );
throw new System.ArgumentException( "Error creating AnimSceneObject - possible invalid model?" );
}
Transform = transform;
if ( animGraphChangedCallback == DelegateFunctionPointer.Null )
{
animGraphChangedCallback = DelegateFunctionPointer.Get<AnimGraphChangedCallback>( OnAnimGraphChanged );
}
// TODO
animNative.InitAnimGraph( animGraphChangedCallback );
}
}
private void OnAnimGraphChanged( IntPtr p )
{
_animationGraph = AnimationGraph.FromNative( animNative.GetAnimGraph() );
}
internal override void OnNativeInit( CSceneObject ptr )
{
base.OnNativeInit( ptr );
animNative = (NativeEngine.CSceneAnimatableObject)ptr;
}
internal override void OnNativeDestroy()
{
animNative = default;
base.OnNativeDestroy();
}
/// <summary>
/// Override the anim graph this scene model uses
/// </summary>
[System.Obsolete( "Can use AnimationGraph directly" )]
public void SetAnimGraph( string name )
{
animNative.SetAnimGraph( name );
}
private AnimationGraph _animationGraph;
public AnimationGraph AnimationGraph
{
get => _animationGraph;
set
{
animNative.SetAnimGraph( value?.native ?? default );
}
}
public float PlaybackRate
{
get => animNative.GetPlaybackRate();
set => animNative.SetPlaybackRate( value );
}
internal override void OnModelChanged()
{
base.OnModelChanged();
}
/// <summary>
/// Sets the world space bone transform of a bone by its index.
/// </summary>
/// <param name="boneIndex">Bone index to set transform of.</param>
/// <param name="transform"></param>
public void SetBoneWorldTransform( int boneIndex, Transform transform )
{
// TODO: Throw on index OOB
animNative.SetWorldSpaceRenderBoneTransform( boneIndex, transform );
}
/// <summary>
/// Returns the world space transform of a bone by its index.
/// </summary>
/// <param name="boneIndex">Index of the bone to calculate transform of.</param>
/// <returns>The world space transform, or an identity transform on failure.</returns>
public Transform GetBoneWorldTransform( int boneIndex )
{
// TODO: Throw on index OOB
// TODO: Returns nullable to match GetAttachment()?
return animNative.GetWorldSpaceRenderBoneTransform( boneIndex );
}
/// <summary>
/// Returns the world space transform of a bone by its name.
/// </summary>
/// <param name="boneName">Name of the bone to calculate transform of.</param>
/// <returns>The world space transform, or an identity transform on failure.</returns>
public Transform GetBoneWorldTransform( string boneName )
{
// TODO: Returns nullable to match GetAttachment()?
return animNative.GetWorldSpaceRenderBoneTransform( boneName );
}
/// <summary>
/// Returns the local space transform of a bone by its index.
/// </summary>
/// <param name="boneIndex">Index of the bone to calculate transform of.</param>
/// <returns>The local space transform, or an identity transform on failure.</returns>
public Transform GetBoneLocalTransform( int boneIndex )
{
// TODO: Throw on index OOB
// TODO: Returns nullable to match GetAttachment()?
return animNative.GetLocalSpaceRenderBoneTransform( boneIndex );
}
/// <summary>
/// Returns the local space transform of a bone by its name.
/// </summary>
/// <param name="boneName">Name of the bone to calculate transform of.</param>
/// <returns>The local space transform, or an identity transform on failure.</returns>
public Transform GetBoneLocalTransform( string boneName )
{
// TODO: Returns nullable to match GetAttachment()?
return animNative.GetLocalSpaceRenderBoneTransform( boneName );
}
internal Transform GetWorldSpaceAnimationTransform( int boneIndex )
{
return animNative.GetWorldSpaceAnimationTransform( boneIndex );
}
/// <summary>
/// Set material group to replace materials of the model as set up in ModelDoc.
/// </summary>
public new void SetMaterialGroup( string name ) // TODO - REMOVE ME
{
base.SetMaterialGroup( name );
}
/// <summary>
/// Set which body group to use.
/// </summary>
public void SetBodyGroup( string name, int value )
{
animNative.SetBodyGroup( name, value );
}
/// <summary>
/// Get attachment transform by name.
/// </summary>
/// <param name="name">Name of the attachment to calculate transform of.</param>
/// <param name="worldspace">Whether the transform should be in world space (relative to the scene world), or local space (relative to the scene object)</param>
/// <returns></returns>
public Transform? GetAttachment( string name, bool worldspace = true )
{
if ( animNative.SBox_GetAttachment( name, worldspace, out var tx ) )
return tx;
return null;
}
/// <summary>
/// Allows the scene model to not use the anim graph so it can play sequences directly
/// </summary>
public bool UseAnimGraph
{
get => animNative.GetShouldUseAnimGraph();
set
{
if ( UseAnimGraph == value ) return;
animNative.SetShouldUseAnimGraph( value );
}
}
/// <summary>
/// Get the calculated motion from animgraph since last frame
/// </summary>
public Transform RootMotion
{
get => animNative.GetRootMotion();
}
SceneObjectAnimationSequence _currentSequence;
/// <summary>
/// Allows playback of sequences directly, rather than using an animation graph.
/// Requires <see cref="UseAnimGraph"/> disabled if the scene model has one.
/// </summary>
public AnimationSequence CurrentSequence
{
get
{
// Create on first access
_currentSequence ??= new SceneObjectAnimationSequence( this );
return _currentSequence;
}
}
SceneObjectMorphCollection _morphs;
/// <summary>
/// Access this sceneobject's morph collection. Morphs are generally used in the model to control
/// the face, for things like emotions and lip sync.
/// </summary>
public MorphCollection Morphs
{
get
{
// Create on first access
_morphs ??= new SceneObjectMorphCollection( this );
return _morphs;
}
}
SceneObjectDirectPlayback _directPlayback;
/// <summary>
/// Access this sceneobject's direct playback. Direct playback is used to control the direct playback node in an animgraph
/// to play sequences directly in code
/// </summary>
public AnimGraphDirectPlayback DirectPlayback
{
get
{
// Create on first access
_directPlayback ??= new SceneObjectDirectPlayback( this );
return _directPlayback;
}
}
}