using NativeEngine; using System.Runtime.InteropServices; namespace Sandbox; /// /// A model scene object that supports animations and can be rendered within a . /// [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( 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(); } /// /// Override the anim graph this scene model uses /// [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(); } /// /// Sets the world space bone transform of a bone by its index. /// /// Bone index to set transform of. /// public void SetBoneWorldTransform( int boneIndex, Transform transform ) { // TODO: Throw on index OOB animNative.SetWorldSpaceRenderBoneTransform( boneIndex, transform ); } /// /// Returns the world space transform of a bone by its index. /// /// Index of the bone to calculate transform of. /// The world space transform, or an identity transform on failure. public Transform GetBoneWorldTransform( int boneIndex ) { // TODO: Throw on index OOB // TODO: Returns nullable to match GetAttachment()? return animNative.GetWorldSpaceRenderBoneTransform( boneIndex ); } /// /// Returns the world space transform of a bone by its name. /// /// Name of the bone to calculate transform of. /// The world space transform, or an identity transform on failure. public Transform GetBoneWorldTransform( string boneName ) { // TODO: Returns nullable to match GetAttachment()? return animNative.GetWorldSpaceRenderBoneTransform( boneName ); } /// /// Returns the local space transform of a bone by its index. /// /// Index of the bone to calculate transform of. /// The local space transform, or an identity transform on failure. public Transform GetBoneLocalTransform( int boneIndex ) { // TODO: Throw on index OOB // TODO: Returns nullable to match GetAttachment()? return animNative.GetLocalSpaceRenderBoneTransform( boneIndex ); } /// /// Returns the local space transform of a bone by its name. /// /// Name of the bone to calculate transform of. /// The local space transform, or an identity transform on failure. public Transform GetBoneLocalTransform( string boneName ) { // TODO: Returns nullable to match GetAttachment()? return animNative.GetLocalSpaceRenderBoneTransform( boneName ); } internal Transform GetWorldSpaceAnimationTransform( int boneIndex ) { return animNative.GetWorldSpaceAnimationTransform( boneIndex ); } /// /// Set material group to replace materials of the model as set up in ModelDoc. /// public new void SetMaterialGroup( string name ) // TODO - REMOVE ME { base.SetMaterialGroup( name ); } /// /// Set which body group to use. /// public void SetBodyGroup( string name, int value ) { animNative.SetBodyGroup( name, value ); } /// /// Get attachment transform by name. /// /// Name of the attachment to calculate transform of. /// Whether the transform should be in world space (relative to the scene world), or local space (relative to the scene object) /// public Transform? GetAttachment( string name, bool worldspace = true ) { if ( animNative.SBox_GetAttachment( name, worldspace, out var tx ) ) return tx; return null; } /// /// Allows the scene model to not use the anim graph so it can play sequences directly /// public bool UseAnimGraph { get => animNative.GetShouldUseAnimGraph(); set { if ( UseAnimGraph == value ) return; animNative.SetShouldUseAnimGraph( value ); } } /// /// Get the calculated motion from animgraph since last frame /// public Transform RootMotion { get => animNative.GetRootMotion(); } SceneObjectAnimationSequence _currentSequence; /// /// Allows playback of sequences directly, rather than using an animation graph. /// Requires disabled if the scene model has one. /// public AnimationSequence CurrentSequence { get { // Create on first access _currentSequence ??= new SceneObjectAnimationSequence( this ); return _currentSequence; } } SceneObjectMorphCollection _morphs; /// /// Access this sceneobject's morph collection. Morphs are generally used in the model to control /// the face, for things like emotions and lip sync. /// public MorphCollection Morphs { get { // Create on first access _morphs ??= new SceneObjectMorphCollection( this ); return _morphs; } } SceneObjectDirectPlayback _directPlayback; /// /// 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 /// public AnimGraphDirectPlayback DirectPlayback { get { // Create on first access _directPlayback ??= new SceneObjectDirectPlayback( this ); return _directPlayback; } } }