diff --git a/Assets/Scripts/Core/DataReader/Cvd/CvdFileReader.cs b/Assets/Scripts/Core/DataReader/Cvd/CvdFileReader.cs index 1188824b9..ca7ae9d47 100644 --- a/Assets/Scripts/Core/DataReader/Cvd/CvdFileReader.cs +++ b/Assets/Scripts/Core/DataReader/Cvd/CvdFileReader.cs @@ -501,18 +501,18 @@ private static (List triangles, List indexBuffer) CalculateTriangles( var triangles = new List(); var index = 0; - for (var j = 0; j < allIndices.Length; j++) + for (var i = 0; i < allIndices.Length; i++) { var indices = new[] { - allIndices[j].x, - allIndices[j].y, - allIndices[j].z + allIndices[i].x, + allIndices[i].y, + allIndices[i].z }; - for (var k = 0; k < 3; k++) + for (var j = 0; j < 3; j++) { - indexBuffer.Add(indices[k]); + indexBuffer.Add(indices[j]); triangles.Add(index++); } } diff --git a/Assets/Scripts/Core/DataReader/Mov/MovFileReader.cs b/Assets/Scripts/Core/DataReader/Mov/MovFileReader.cs index 65a383f49..57acb705d 100644 --- a/Assets/Scripts/Core/DataReader/Mov/MovFileReader.cs +++ b/Assets/Scripts/Core/DataReader/Mov/MovFileReader.cs @@ -86,7 +86,7 @@ private static MovAnimationEvent ReadAnimationEvent(BinaryReader reader, int cod { return new MovAnimationEvent() { - Tick = GameBoxInterpreter.GameBoxSecondsToTick(reader.ReadSingle()), + Tick = GameBoxInterpreter.SecondsToTick(reader.ReadSingle()), Name = reader.ReadString(16, codepage) }; } @@ -110,7 +110,7 @@ private static MovBoneAnimationTrack ReadBoneAnimationTrack(BinaryReader reader, { animationKeyFrames[i] = new MovAnimationKeyFrame() { - Tick = GameBoxInterpreter.GameBoxSecondsToTick(reader.ReadSingle()), + Tick = GameBoxInterpreter.SecondsToTick(reader.ReadSingle()), Translation = GameBoxInterpreter.ToUnityPosition(reader.ReadVector3()), Rotation = GameBoxInterpreter.MovQuaternionToUnityQuaternion(new GameBoxQuaternion() { diff --git a/Assets/Scripts/Core/GameBox/GameBoxInterpreter.cs b/Assets/Scripts/Core/GameBox/GameBoxInterpreter.cs index 35df1d9c0..b50e57e14 100644 --- a/Assets/Scripts/Core/GameBox/GameBoxInterpreter.cs +++ b/Assets/Scripts/Core/GameBox/GameBoxInterpreter.cs @@ -203,13 +203,13 @@ public static Matrix4x4 ToUnityMatrix4x4(GameBoxMatrix4X4 matrix) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint GameBoxSecondsToTick(float seconds) + public static uint SecondsToTick(float seconds) { return (uint)(seconds * GameBoxTicksPerSecond); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float TickToGameBoxSeconds(uint tick) + public static float TickToSeconds(uint tick) { return (float)tick / GameBoxTicksPerSecond; } diff --git a/Assets/Scripts/Core/Utils/UnitySupportedVideoFormats.cs b/Assets/Scripts/Core/Utils/UnitySupportedVideoFormats.cs index 56e465623..e84aa32fe 100644 --- a/Assets/Scripts/Core/Utils/UnitySupportedVideoFormats.cs +++ b/Assets/Scripts/Core/Utils/UnitySupportedVideoFormats.cs @@ -62,18 +62,11 @@ public static HashSet GetSupportedVideoFormats(RuntimePlatform platform) { return platform switch { - RuntimePlatform.WindowsPlayer => WindowsSupportedVideoFormats, - RuntimePlatform.WindowsEditor => WindowsSupportedVideoFormats, - - RuntimePlatform.OSXPlayer => MacOSSupportedVideoFormats, - RuntimePlatform.OSXEditor => MacOSSupportedVideoFormats, - - RuntimePlatform.LinuxPlayer => LinuxSupportedVideoFormats, - RuntimePlatform.LinuxEditor => LinuxSupportedVideoFormats, - + RuntimePlatform.WindowsPlayer or RuntimePlatform.WindowsEditor => WindowsSupportedVideoFormats, + RuntimePlatform.OSXPlayer or RuntimePlatform.OSXEditor => MacOSSupportedVideoFormats, + RuntimePlatform.LinuxPlayer or RuntimePlatform.LinuxEditor => LinuxSupportedVideoFormats, RuntimePlatform.IPhonePlayer => iOSSupportedVideoFormats, RuntimePlatform.Android => AndroidSupportedVideoFormats, - _ => new HashSet {".mp4" } }; } diff --git a/Assets/Scripts/Pal3/Actor/ActorBase.cs b/Assets/Scripts/Pal3/Actor/ActorBase.cs index c93d0aa85..9bce4c147 100644 --- a/Assets/Scripts/Pal3/Actor/ActorBase.cs +++ b/Assets/Scripts/Pal3/Actor/ActorBase.cs @@ -14,8 +14,8 @@ namespace Pal3.Actor public enum ActorAnimationType { - Mv3 = 0, - Mov + Vertex = 0, // .mv3 animation + Skeletal = 1, // .msh + .mov animation } public abstract class ActorBase @@ -50,7 +50,7 @@ private void InitActorConfig(string name) if (defaultConfig is Mv3ActionConfig mv3Config) { AddActorConfig(mv3Config); - AnimationType = ActorAnimationType.Mv3; + AnimationType = ActorAnimationType.Vertex; return; } @@ -71,7 +71,7 @@ private void InitActorConfig(string name) } } - AnimationType = ActorAnimationType.Mov; + AnimationType = ActorAnimationType.Skeletal; } private void AddActorConfig(ActorActionConfig config) @@ -118,14 +118,14 @@ public string GetActionFilePath(string actionName) public string GetMeshFilePath(string actionName) { - if (AnimationType != ActorAnimationType.Mov) + if (AnimationType != ActorAnimationType.Skeletal) { - throw new InvalidOperationException("Mesh file path is only available for MOV animation type."); + throw new InvalidOperationException("Msh file path is only available for skeletal animation type."); } if (!_actionNameToMeshFileNameMap.TryGetValue(actionName, out var meshFileName)) { - throw new ArgumentException($"Mesh file name not found for action name {actionName}"); + throw new ArgumentException($"Msh file name not found for action name {actionName}"); } char separator = CpkConstants.DirectorySeparator; diff --git a/Assets/Scripts/Pal3/Actor/ActorFactory.cs b/Assets/Scripts/Pal3/Actor/ActorFactory.cs index 787213838..374b49d48 100644 --- a/Assets/Scripts/Pal3/Actor/ActorFactory.cs +++ b/Assets/Scripts/Pal3/Actor/ActorFactory.cs @@ -40,17 +40,26 @@ public static GameObject CreateActorGameObject(GameResourceProvider resourceProv #endif ActorActionController actionController; - if (actor.AnimationType == ActorAnimationType.Mv3) + switch (actor.AnimationType) { - Mv3ActorActionController mv3ActionController = actorGameObject.AddComponent(); - mv3ActionController.Init(resourceProvider, actor, hasColliderAndRigidBody, isDropShadowEnabled, tintColor); - actionController = mv3ActionController; - } - else - { - MovActorActionController movActionController = actorGameObject.AddComponent(); - movActionController.Init(resourceProvider, actor, hasColliderAndRigidBody, isDropShadowEnabled, tintColor); - actionController = movActionController; + case ActorAnimationType.Vertex: + { + VertexAnimationActorActionController vertexActionController = + actorGameObject.AddComponent(); + vertexActionController.Init(resourceProvider, actor, hasColliderAndRigidBody, isDropShadowEnabled, tintColor); + actionController = vertexActionController; + break; + } + case ActorAnimationType.Skeletal: + { + SkeletalAnimationActorActionController skeletalActionController = + actorGameObject.AddComponent(); + skeletalActionController.Init(resourceProvider, actor, hasColliderAndRigidBody, isDropShadowEnabled, tintColor); + actionController = skeletalActionController; + break; + } + default: + throw new NotSupportedException($"Unsupported actor animation type: {actor.AnimationType}"); } var movementController = actorGameObject.AddComponent(); diff --git a/Assets/Scripts/Pal3/Actor/MovActorActionController.cs b/Assets/Scripts/Pal3/Actor/MovActorActionController.cs deleted file mode 100644 index 2c0342964..000000000 --- a/Assets/Scripts/Pal3/Actor/MovActorActionController.cs +++ /dev/null @@ -1,224 +0,0 @@ -// --------------------------------------------------------------------------------------------- -// Copyright (c) 2021-2023, Jiaqi Liu. All rights reserved. -// See LICENSE file in the project root for license information. -// --------------------------------------------------------------------------------------------- - -namespace Pal3.Actor -{ - using System; - using Command; - using Command.InternalCommands; - using Command.SceCommands; - using Core.DataLoader; - using Core.DataReader.Mov; - using Core.DataReader.Msh; - using Core.Utils; - using Data; - using MetaData; - using Renderer; - using Script.Waiter; - using UnityEngine; - - public class MovActorActionController : ActorActionController, - ICommandExecutor, - ICommandExecutor - { - private GameResourceProvider _resourceProvider; - private IMaterialFactory _materialFactory; - private Actor _actor; - private Color _tintColor; - - private bool _autoStand = true; - - // private MovModelRenderer _movAnimationRenderer; - // private WaitUntilCanceled _animationLoopPointWaiter; - - private Bounds _rendererBounds; - private Bounds _meshBounds; - - public void Init(GameResourceProvider resourceProvider, - Actor actor, - bool hasColliderAndRigidBody, - bool isDropShadowEnabled, - Color tintColor) - { - base.Init(resourceProvider, actor, hasColliderAndRigidBody, isDropShadowEnabled); - - _resourceProvider = resourceProvider; - _actor = actor; - _tintColor = tintColor; - _materialFactory = resourceProvider.GetMaterialFactory(); - } - - private void OnEnable() - { - CommandExecutorRegistry.Instance.Register(this); - } - - private void OnDisable() - { - CommandExecutorRegistry.Instance.UnRegister(this); - DeActivate(); - base.DeActivate(); - } - - public override void PerformAction(string actionName, - bool overwrite = false, - int loopCount = -1, - WaitUntilCanceled waiter = null) - { - if (!overwrite && string.Equals(GetCurrentAction(), actionName, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - if (!_actor.HasAction(actionName)) - { - Debug.LogError($"Action {actionName} not found for actor {_actor.Info.Name}."); - waiter?.CancelWait(); - return; - } - - Debug.LogWarning($"Actor {_actor.Info.Name} is performing bone action {actionName}."); - - MovFile movFile; - MshFile mshFile; - ITextureResourceProvider textureProvider; - try - { - string movFilePath = _actor.GetActionFilePath(actionName); - movFile = _resourceProvider.GetGameResourceFile(movFilePath); - textureProvider = _resourceProvider.CreateTextureResourceProvider( - Utility.GetRelativeDirectoryPath(movFilePath)); - - string mshFilePath = _actor.GetMeshFilePath(actionName); - mshFile = _resourceProvider.GetGameResourceFile(mshFilePath); - } - catch (Exception ex) - { - Debug.LogError(ex); - waiter?.CancelWait(); - return; - } - - DisposeCurrentAction(); - - // _animationLoopPointWaiter = waiter; - // _movAnimationRenderer = gameObject.GetOrAddComponent(); - // - // ActorActionType actionType = ActorConstants.ActionNames - // .FirstOrDefault(_ => string.Equals(_.Value, actionName, StringComparison.OrdinalIgnoreCase)).Key; - // - // if (movFile.TagNodes is {Length: > 0} && _actor.GetWeaponName() is {} weaponName && - // ActorConstants.ActionNameToWeaponArmTypeMap[actionType] != WeaponArmType.None) - // { - // var separator = CpkConstants.DirectorySeparator; - // - // var weaponPath = $"{FileConstants.BaseDataCpkPathInfo.cpkName}{separator}" + - // $"{FileConstants.WeaponFolderName}{separator}{weaponName}{separator}{weaponName}.pol"; - // - // (PolFile polFile, ITextureResourceProvider weaponTextureProvider) = _resourceProvider.GetPol(weaponPath); - // _movAnimationRenderer.Init(movFile, - // _materialFactory, - // textureProvider, - // _tintColor, - // polFile, - // weaponTextureProvider); - // } - // else - // { - // _movAnimationRenderer.Init(movFile, - // _materialFactory, - // textureProvider, - // _tintColor); - // } - // - // _movAnimationRenderer.AnimationLoopPointReached += AnimationLoopPointReached; - // _movAnimationRenderer.StartAnimation(loopCount); - // - // _rendererBounds = _movAnimationRenderer.GetRendererBounds(); - // _meshBounds = _movAnimationRenderer.GetMeshBounds(); - - base.PerformAction(actionName, overwrite, loopCount, waiter); - } - - // public override float GetActorHeight() - // { - // if (_movAnimationRenderer == null || !_movAnimationRenderer.IsVisible()) - // { - // return _meshBounds.size.y; - // } - // - // return _movAnimationRenderer.GetMeshBounds().size.y; - // } - // - // public override Bounds GetRendererBounds() - // { - // return (_movAnimationRenderer == null || !_movAnimationRenderer.IsVisible()) ? _rendererBounds : - // _movAnimationRenderer.GetRendererBounds(); - // } - // - // public override Bounds GetMeshBounds() - // { - // return (_movAnimationRenderer == null || !_movAnimationRenderer.IsVisible()) ? _meshBounds : - // _movAnimationRenderer.GetMeshBounds(); - // } - - internal override void DisposeCurrentAction() - { - // _animationLoopPointWaiter?.CancelWait(); - // - // if (_movAnimationRenderer != null) - // { - // _movAnimationRenderer.AnimationLoopPointReached -= AnimationLoopPointReached; - // _movAnimationRenderer.DisposeAnimation(); - // } - - base.DisposeCurrentAction(); - } - - internal override void DeActivate() - { - DisposeCurrentAction(); - - // if (_movAnimationRenderer != null) - // { - // Destroy(_movAnimationRenderer); - // } - - base.DeActivate(); - } - - public void Execute(ActorAutoStandCommand command) - { - if (command.ActorId == _actor.Info.Id) _autoStand = (command.AutoStand == 1); - } - - public void Execute(ActorStopActionCommand command) - { - // if (command.ActorId != _actor.Info.Id || - // _movAnimationRenderer == null || - // !_movAnimationRenderer.IsVisible()) return; - // - // if (_movAnimationRenderer.IsActionInHoldState()) - // { - // _animationLoopPointWaiter?.CancelWait(); - // _animationLoopPointWaiter = new WaitUntilCanceled(); - // CommandDispatcher.Instance.Dispatch( - // new ScriptRunnerAddWaiterRequest(_animationLoopPointWaiter)); - // - // _movAnimationRenderer.ResumeAction(); - // } - // else - // { - // _movAnimationRenderer.PauseAnimation(); - // _animationLoopPointWaiter?.CancelWait(); - // - // if (_autoStand && _movAnimationRenderer.IsVisible()) - // { - // PerformAction(_actor.GetIdleAction()); - // } - // } - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Pal3/Actor/SkeletalAnimationActorActionController.cs b/Assets/Scripts/Pal3/Actor/SkeletalAnimationActorActionController.cs new file mode 100644 index 000000000..90e641a83 --- /dev/null +++ b/Assets/Scripts/Pal3/Actor/SkeletalAnimationActorActionController.cs @@ -0,0 +1,181 @@ +// --------------------------------------------------------------------------------------------- +// Copyright (c) 2021-2023, Jiaqi Liu. All rights reserved. +// See LICENSE file in the project root for license information. +// --------------------------------------------------------------------------------------------- + +namespace Pal3.Actor +{ + using System; + using Command; + using Command.SceCommands; + using Core.DataLoader; + using Core.DataReader.Mov; + using Core.DataReader.Msh; + using Core.Extensions; + using Core.Utils; + using Data; + using Renderer; + using Script.Waiter; + using UnityEngine; + + public class SkeletalAnimationActorActionController : ActorActionController, + ICommandExecutor, + ICommandExecutor + { + private GameResourceProvider _resourceProvider; + private IMaterialFactory _materialFactory; + private Actor _actor; + private Color _tintColor; + + private bool _autoStand = true; + + private SkeletalModelRenderer _skeletalModelRenderer; + + private Bounds _rendererBounds; + private Bounds _meshBounds; + + public void Init(GameResourceProvider resourceProvider, + Actor actor, + bool hasColliderAndRigidBody, + bool isDropShadowEnabled, + Color tintColor) + { + base.Init(resourceProvider, actor, hasColliderAndRigidBody, isDropShadowEnabled); + + _resourceProvider = resourceProvider; + _actor = actor; + _tintColor = tintColor; + _materialFactory = resourceProvider.GetMaterialFactory(); + } + + private void OnEnable() + { + CommandExecutorRegistry.Instance.Register(this); + } + + private void OnDisable() + { + CommandExecutorRegistry.Instance.UnRegister(this); + DeActivate(); + base.DeActivate(); + } + + public override void PerformAction(string actionName, + bool overwrite = false, + int loopCount = -1, + WaitUntilCanceled waiter = null) + { + if (!overwrite && string.Equals(GetCurrentAction(), actionName, StringComparison.OrdinalIgnoreCase)) + { + return; + } + + if (!_actor.HasAction(actionName)) + { + Debug.LogError($"Action {actionName} not found for actor {_actor.Info.Name}."); + waiter?.CancelWait(); + return; + } + + MovFile movFile; + MshFile mshFile; + ITextureResourceProvider textureProvider; + try + { + string movFilePath = _actor.GetActionFilePath(actionName); + movFile = _resourceProvider.GetGameResourceFile(movFilePath); + textureProvider = _resourceProvider.CreateTextureResourceProvider( + Utility.GetRelativeDirectoryPath(movFilePath)); + + string mshFilePath = _actor.GetMeshFilePath(actionName); + mshFile = _resourceProvider.GetGameResourceFile(mshFilePath); + } + catch (Exception ex) + { + Debug.LogError(ex); + waiter?.CancelWait(); + return; + } + + DisposeCurrentAction(); + + _skeletalModelRenderer = gameObject.GetOrAddComponent(); + + _skeletalModelRenderer.Init(mshFile, + _materialFactory, + textureProvider, + _actor.Info.Name + ".tga", // TODO: Read texture name from MTL file + _tintColor); + + _skeletalModelRenderer.StartAnimation(movFile, loopCount); + + _rendererBounds = _skeletalModelRenderer.GetRendererBounds(); + _meshBounds = _skeletalModelRenderer.GetMeshBounds(); + + base.PerformAction(actionName, overwrite, loopCount, waiter); + } + + public override float GetActorHeight() + { + if (_skeletalModelRenderer == null || !_skeletalModelRenderer.IsVisible()) + { + return _meshBounds.size.y; + } + + return _skeletalModelRenderer.GetMeshBounds().size.y; + } + + public override Bounds GetRendererBounds() + { + return (_skeletalModelRenderer == null || !_skeletalModelRenderer.IsVisible()) ? _rendererBounds : + _skeletalModelRenderer.GetRendererBounds(); + } + + public override Bounds GetMeshBounds() + { + return (_skeletalModelRenderer == null || !_skeletalModelRenderer.IsVisible()) ? _meshBounds : + _skeletalModelRenderer.GetMeshBounds(); + } + + internal override void DisposeCurrentAction() + { + if (_skeletalModelRenderer != null) + { + _skeletalModelRenderer.Dispose(); + } + + base.DisposeCurrentAction(); + } + + internal override void DeActivate() + { + DisposeCurrentAction(); + + if (_skeletalModelRenderer != null) + { + Destroy(_skeletalModelRenderer); + } + + base.DeActivate(); + } + + public void Execute(ActorAutoStandCommand command) + { + if (command.ActorId == _actor.Info.Id) _autoStand = (command.AutoStand == 1); + } + + public void Execute(ActorStopActionCommand command) + { + if (command.ActorId != _actor.Info.Id || + _skeletalModelRenderer == null || + !_skeletalModelRenderer.IsVisible()) return; + + _skeletalModelRenderer.PauseAnimation(); + + if (_autoStand && _skeletalModelRenderer.IsVisible()) + { + PerformAction(_actor.GetIdleAction()); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Pal3/Actor/MovActorActionController.cs.meta b/Assets/Scripts/Pal3/Actor/SkeletalAnimationActorActionController.cs.meta similarity index 100% rename from Assets/Scripts/Pal3/Actor/MovActorActionController.cs.meta rename to Assets/Scripts/Pal3/Actor/SkeletalAnimationActorActionController.cs.meta diff --git a/Assets/Scripts/Pal3/Actor/Mv3ActorActionController.cs b/Assets/Scripts/Pal3/Actor/VertexAnimationActorActionController.cs similarity index 79% rename from Assets/Scripts/Pal3/Actor/Mv3ActorActionController.cs rename to Assets/Scripts/Pal3/Actor/VertexAnimationActorActionController.cs index 5ede6daff..04f467b64 100644 --- a/Assets/Scripts/Pal3/Actor/Mv3ActorActionController.cs +++ b/Assets/Scripts/Pal3/Actor/VertexAnimationActorActionController.cs @@ -22,7 +22,7 @@ namespace Pal3.Actor using Script.Waiter; using UnityEngine; - public class Mv3ActorActionController : ActorActionController, + public class VertexAnimationActorActionController : ActorActionController, ICommandExecutor, ICommandExecutor, ICommandExecutor @@ -34,7 +34,7 @@ public class Mv3ActorActionController : ActorActionController, private bool _autoStand = true; - private Mv3ModelRenderer _mv3AnimationRenderer; + private Mv3ModelRenderer _mv3ModelRenderer; private WaitUntilCanceled _animationLoopPointWaiter; private Bounds _rendererBounds; @@ -124,7 +124,7 @@ public override void PerformAction(string actionName, DisposeCurrentAction(); _animationLoopPointWaiter = waiter; - _mv3AnimationRenderer = gameObject.GetOrAddComponent(); + _mv3ModelRenderer = gameObject.GetOrAddComponent(); ActorActionType? actionType = ActorConstants.NameToActionMap.ContainsKey(actionName.ToLower()) ? ActorConstants.NameToActionMap[actionName.ToLower()] : null; @@ -142,7 +142,7 @@ public override void PerformAction(string actionName, PolFile polFile = _resourceProvider.GetGameResourceFile(weaponPath); ITextureResourceProvider weaponTextureProvider = _resourceProvider.CreateTextureResourceProvider( Utility.GetRelativeDirectoryPath(weaponPath)); - _mv3AnimationRenderer.Init(mv3File, + _mv3ModelRenderer.Init(mv3File, _materialFactory, textureProvider, _tintColor, @@ -151,49 +151,49 @@ public override void PerformAction(string actionName, } else { - _mv3AnimationRenderer.Init(mv3File, + _mv3ModelRenderer.Init(mv3File, _materialFactory, textureProvider, _tintColor); } - _mv3AnimationRenderer.AnimationLoopPointReached += AnimationLoopPointReached; - _mv3AnimationRenderer.StartAnimation(loopCount); + _mv3ModelRenderer.AnimationLoopPointReached += AnimationLoopPointReached; + _mv3ModelRenderer.StartAnimation(loopCount); - _rendererBounds = _mv3AnimationRenderer.GetRendererBounds(); - _meshBounds = _mv3AnimationRenderer.GetMeshBounds(); + _rendererBounds = _mv3ModelRenderer.GetRendererBounds(); + _meshBounds = _mv3ModelRenderer.GetMeshBounds(); base.PerformAction(actionName, overwrite, loopCount, waiter); } public override void PauseAnimation() { - if (_mv3AnimationRenderer != null) + if (_mv3ModelRenderer != null) { - _mv3AnimationRenderer.PauseAnimation(); + _mv3ModelRenderer.PauseAnimation(); } } public override float GetActorHeight() { - if (_mv3AnimationRenderer == null || !_mv3AnimationRenderer.IsVisible()) + if (_mv3ModelRenderer == null || !_mv3ModelRenderer.IsVisible()) { return _meshBounds.size.y; } - return _mv3AnimationRenderer.GetMeshBounds().size.y; + return _mv3ModelRenderer.GetMeshBounds().size.y; } public override Bounds GetRendererBounds() { - return (_mv3AnimationRenderer == null || !_mv3AnimationRenderer.IsVisible()) ? _rendererBounds : - _mv3AnimationRenderer.GetRendererBounds(); + return (_mv3ModelRenderer == null || !_mv3ModelRenderer.IsVisible()) ? _rendererBounds : + _mv3ModelRenderer.GetRendererBounds(); } public override Bounds GetMeshBounds() { - return (_mv3AnimationRenderer == null || !_mv3AnimationRenderer.IsVisible()) ? _meshBounds : - _mv3AnimationRenderer.GetMeshBounds(); + return (_mv3ModelRenderer == null || !_mv3ModelRenderer.IsVisible()) ? _meshBounds : + _mv3ModelRenderer.GetMeshBounds(); } private void AnimationLoopPointReached(object _, int loopCount) @@ -203,10 +203,10 @@ private void AnimationLoopPointReached(object _, int loopCount) _animationLoopPointWaiter?.CancelWait(); } - if (_autoStand && _mv3AnimationRenderer.IsVisible()) + if (_autoStand && _mv3ModelRenderer.IsVisible()) { if (loopCount is 0 || - (loopCount is -2 && !_mv3AnimationRenderer.IsActionInHoldState())) + (loopCount is -2 && !_mv3ModelRenderer.IsActionInHoldState())) { PerformAction(_actor.GetIdleAction()); } @@ -217,10 +217,10 @@ internal override void DisposeCurrentAction() { _animationLoopPointWaiter?.CancelWait(); - if (_mv3AnimationRenderer != null) + if (_mv3ModelRenderer != null) { - _mv3AnimationRenderer.AnimationLoopPointReached -= AnimationLoopPointReached; - _mv3AnimationRenderer.Dispose(); + _mv3ModelRenderer.AnimationLoopPointReached -= AnimationLoopPointReached; + _mv3ModelRenderer.Dispose(); } base.DisposeCurrentAction(); @@ -230,9 +230,9 @@ internal override void DeActivate() { DisposeCurrentAction(); - if (_mv3AnimationRenderer != null) + if (_mv3ModelRenderer != null) { - Destroy(_mv3AnimationRenderer); + Destroy(_mv3ModelRenderer); } base.DeActivate(); @@ -246,23 +246,23 @@ public void Execute(ActorAutoStandCommand command) public void Execute(ActorStopActionCommand command) { if (command.ActorId != _actor.Info.Id || - _mv3AnimationRenderer == null || - !_mv3AnimationRenderer.IsVisible()) return; + _mv3ModelRenderer == null || + !_mv3ModelRenderer.IsVisible()) return; - if (_mv3AnimationRenderer.IsActionInHoldState()) + if (_mv3ModelRenderer.IsActionInHoldState()) { _animationLoopPointWaiter?.CancelWait(); _animationLoopPointWaiter = new WaitUntilCanceled(); CommandDispatcher.Instance.Dispatch( new ScriptRunnerAddWaiterRequest(_animationLoopPointWaiter)); - _mv3AnimationRenderer.ResumeAction(); + _mv3ModelRenderer.ResumeAction(); } else { - _mv3AnimationRenderer.PauseAnimation(); + _mv3ModelRenderer.PauseAnimation(); _animationLoopPointWaiter?.CancelWait(); - if (_autoStand && _mv3AnimationRenderer.IsVisible()) + if (_autoStand && _mv3ModelRenderer.IsVisible()) { PerformAction(_actor.GetIdleAction()); } @@ -272,7 +272,7 @@ public void Execute(ActorStopActionCommand command) public void Execute(ActorChangeTextureCommand command) { if (_actor.Info.Id != command.ActorId) return; - _mv3AnimationRenderer.ChangeTexture(command.TextureName); + _mv3ModelRenderer.ChangeTexture(command.TextureName); } } } \ No newline at end of file diff --git a/Assets/Scripts/Pal3/Actor/Mv3ActorActionController.cs.meta b/Assets/Scripts/Pal3/Actor/VertexAnimationActorActionController.cs.meta similarity index 100% rename from Assets/Scripts/Pal3/Actor/Mv3ActorActionController.cs.meta rename to Assets/Scripts/Pal3/Actor/VertexAnimationActorActionController.cs.meta diff --git a/Assets/Scripts/Pal3/Data/GameResourceProvider.cs b/Assets/Scripts/Pal3/Data/GameResourceProvider.cs index e74787610..10a9d6662 100644 --- a/Assets/Scripts/Pal3/Data/GameResourceProvider.cs +++ b/Assets/Scripts/Pal3/Data/GameResourceProvider.cs @@ -22,6 +22,7 @@ namespace Pal3.Data using Core.DataReader.Ini; using Core.DataReader.Lgt; using Core.DataReader.Mov; + using Core.DataReader.Msh; using Core.DataReader.Mv3; using Core.DataReader.Nav; using Core.DataReader.Pol; @@ -669,6 +670,11 @@ public void Execute(ScenePreLoadingNotification notification) _gameResourceFileCache[typeof(CvdFile)]?.Clear(); } + if (_gameResourceFileCache.ContainsKey(typeof(MshFile))) + { + _gameResourceFileCache[typeof(MshFile)]?.Clear(); + } + if (_gameResourceFileCache.ContainsKey(typeof(MovFile))) { _gameResourceFileCache[typeof(MovFile)]?.Clear(); diff --git a/Assets/Scripts/Pal3/Renderer/Mv3ModelRenderer.cs b/Assets/Scripts/Pal3/Renderer/Mv3ModelRenderer.cs index 9c7c1ef06..61835a095 100644 --- a/Assets/Scripts/Pal3/Renderer/Mv3ModelRenderer.cs +++ b/Assets/Scripts/Pal3/Renderer/Mv3ModelRenderer.cs @@ -26,7 +26,6 @@ public class Mv3ModelRenderer : MonoBehaviour, IDisposable private const string MV3_ANIMATION_HOLD_EVENT_NAME = "hold"; private const string MV3_MODEL_DEFAULT_TEXTURE_EXTENSION = ".tga"; - private const float TIME_TO_TICK_SCALE = 5000f; private ITextureResourceProvider _textureProvider; private IMaterialFactory _materialFactory; @@ -219,7 +218,7 @@ public void StartAnimation(int loopCount = -1, float fps = -1f) { if (_renderMeshComponents == null) { - throw new Exception("Animation not initialized."); + throw new Exception("Animation model not initialized."); } PauseAnimation(); @@ -406,8 +405,7 @@ private IEnumerator PlayOneTimeAnimationInternalAsync(uint startTick, while (!cancellationToken.IsCancellationRequested) { - var tick = ((Time.timeSinceLevelLoad - startTime) * TIME_TO_TICK_SCALE - + startTick); + uint tick = GameBoxInterpreter.SecondsToTick(Time.timeSinceLevelLoad - startTime) + startTick; if (tick >= endTick) { @@ -421,12 +419,12 @@ private IEnumerator PlayOneTimeAnimationInternalAsync(uint startTick, var frameTicks = _frameTicks[i]; - var currentFrameIndex = Utility.GetFloorIndex(frameTicks, (uint) tick); + var currentFrameIndex = Utility.GetFloorIndex(frameTicks, tick); var currentFrameTick = _frameTicks[i][currentFrameIndex]; var nextFrameIndex = currentFrameIndex < frameTicks.Length - 1 ? currentFrameIndex + 1 : 0; var nextFrameTick = nextFrameIndex == 0 ? endTick : _frameTicks[i][nextFrameIndex]; - var influence = (tick - currentFrameTick) / (nextFrameTick - currentFrameTick); + var influence = (float)(tick - currentFrameTick) / (nextFrameTick - currentFrameTick); var vertices = meshComponent.MeshDataBuffer.VertexBuffer; for (var j = 0; j < vertices.Length; j++) @@ -452,7 +450,7 @@ private IEnumerator PlayOneTimeAnimationInternalAsync(uint startTick, var nextFrameIndex = currentFrameIndex < frameTicks.Length - 1 ? currentFrameIndex + 1 : 0; var nextFrameTick = nextFrameIndex == 0 ? endTick : _tagNodeFrameTicks[i][nextFrameIndex]; - var influence = (tick - currentFrameTick) / (nextFrameTick - currentFrameTick); + var influence = (float)(tick - currentFrameTick) / (nextFrameTick - currentFrameTick); Vector3 position = Vector3.Lerp(_tagNodesInfo[i].TagFrames[currentFrameIndex].Position, _tagNodesInfo[i].TagFrames[nextFrameIndex].Position, influence); diff --git a/Assets/Scripts/Pal3/Renderer/RendererType.cs b/Assets/Scripts/Pal3/Renderer/RendererType.cs index ae81a47c6..7c508d1f9 100644 --- a/Assets/Scripts/Pal3/Renderer/RendererType.cs +++ b/Assets/Scripts/Pal3/Renderer/RendererType.cs @@ -10,6 +10,6 @@ public enum RendererType Cvd, Pol, Mv3, - Mov, + Msh, } } \ No newline at end of file diff --git a/Assets/Scripts/Pal3/Renderer/SkeletalModelRenderer.cs b/Assets/Scripts/Pal3/Renderer/SkeletalModelRenderer.cs new file mode 100644 index 000000000..bf0899389 --- /dev/null +++ b/Assets/Scripts/Pal3/Renderer/SkeletalModelRenderer.cs @@ -0,0 +1,471 @@ +// --------------------------------------------------------------------------------------------- +// Copyright (c) 2021-2023, Jiaqi Liu. All rights reserved. +// See LICENSE file in the project root for license information. +// --------------------------------------------------------------------------------------------- + +namespace Pal3.Renderer +{ + using System; + using System.Collections; + using Core.DataReader.Msh; + using Core.DataReader.Mov; + using UnityEngine; + using System.Collections.Generic; + using System.Threading; + using Core.DataLoader; + using Core.GameBox; + using Core.Renderer; + using Core.Utils; + + internal class Bone + { + public string Name { get; } + public GameObject GameObject { get; } + public BoneNode BoneNode { get; } + + public Matrix4x4 BindPoseModelToBoneSpace { get; set; } + public Matrix4x4 CurrentPoseToModelMatrix { get; set; } + + public MovBoneAnimationTrack? AnimationTrack { get; private set; } + + public uint[] FrameTicks { get; private set; } + + public Bone(string name, GameObject gameObject, BoneNode boneNode) + { + Name = name; + GameObject = gameObject; + BoneNode = boneNode; + BindPoseModelToBoneSpace = Matrix4x4.identity; + CurrentPoseToModelMatrix = Matrix4x4.identity; + } + + public bool IsRoot() + { + return BoneNode.ParentId == -1; + } + + public void SetAnimationTrack(MovBoneAnimationTrack track) + { + AnimationTrack = track; + + FrameTicks = new uint[track.KeyFrames.Length]; + + for (int i = 0; i < track.KeyFrames.Length; i++) + { + FrameTicks[i] = track.KeyFrames[i].Tick; + } + } + + public void DisposeAnimationTrack() + { + AnimationTrack = null; + FrameTicks = null; + CurrentPoseToModelMatrix = Matrix4x4.identity; + } + } + + /// + /// Skeletal animation model renderer + /// MSH(.msh) + MOV(.mov) + /// + public class SkeletalModelRenderer : MonoBehaviour, IDisposable + { + private IMaterialFactory _materialFactory; + private Material[][] _materials; + + private MshFile _mshFile; + private MovFile _movFile; + + private string _textureName; + private Texture2D _texture; + private Color _tintColor; + + private readonly Dictionary _bones = new (); + + private GameObject _rootBoneObject; + private GameObject[] _meshObjects; + private RenderMeshComponent[] _renderMeshComponents; + + private Coroutine _animation; + private CancellationTokenSource _animationCts; + + private readonly Dictionary> _indexBuffer = new(); + private readonly Dictionary _vertexBuffer = new(); + + public void Init(MshFile mshFile, + IMaterialFactory materialFactory, + ITextureResourceProvider textureProvider, + string textureName, + Color? tintColor = default) // TODO: Read texture name from .mtl file + { + Dispose(); + + _mshFile = mshFile; + _materialFactory = materialFactory; + _textureName = textureName; + _texture = textureProvider.GetTexture(_textureName); + _tintColor = tintColor ?? Color.white; + + SetupBone(_mshFile.RootBoneNode, parentBone: null); + RenderMesh(); + } + + public void StartAnimation(MovFile movFile, int loopCount = -1) + { + if (_renderMeshComponents == null) + { + throw new Exception("Animation model not initialized."); + } + + PauseAnimation(); + SetupAnimationTrack(movFile); + + _animationCts = new CancellationTokenSource(); + _animation = StartCoroutine(PlayAnimationInternalAsync(loopCount, + _animationCts.Token)); + } + + public void PauseAnimation() + { + if (_animation != null) + { + _animationCts.Cancel(); + StopCoroutine(_animation); + _animation = null; + } + } + + private void SetupAnimationTrack(MovFile movFile) + { + // Reset all existing bone animation tracks + foreach (Bone bone in _bones.Values) + { + bone.DisposeAnimationTrack(); + } + + _movFile = movFile; + + // Bind animation tracks from mov file + foreach (MovBoneAnimationTrack track in _movFile.BoneAnimationTracks) + { + if (_bones.TryGetValue(track.BoneId, out Bone bonne)) + { + bonne.SetAnimationTrack(track); + } + } + } + + private IEnumerator PlayAnimationInternalAsync(int loopCount, + CancellationToken cancellationToken) + { + if (loopCount == -1) // Infinite loop until cancelled + { + while (!cancellationToken.IsCancellationRequested) + { + yield return PlayOneTimeAnimationInternalAsync(cancellationToken); + } + } + else if (loopCount > 0) + { + while (!cancellationToken.IsCancellationRequested && --loopCount >= 0) + { + yield return PlayOneTimeAnimationInternalAsync(cancellationToken); + } + } + } + + private IEnumerator PlayOneTimeAnimationInternalAsync(CancellationToken cancellationToken) + { + var startTime = Time.timeSinceLevelLoad; + + while (!cancellationToken.IsCancellationRequested) + { + uint tick = GameBoxInterpreter.SecondsToTick(Time.timeSinceLevelLoad - startTime); + + if (tick >= _movFile.Duration) + { + yield break; + } + + UpdateBone(_bones[0], tick); + UpdateSkinning(); + + yield return null; + } + } + + private void UpdateBone(Bone bone, uint tick) + { + GameObject boneGo = bone.GameObject; + MovBoneAnimationTrack? track = bone.AnimationTrack; + BoneNode boneNode = bone.BoneNode; + + if (track != null) + { + int currentFrameIndex = Utility.GetFloorIndex(bone.FrameTicks, tick); + + // TODO: Interpolate between frames + Vector3 localPosition = track.Value.KeyFrames[currentFrameIndex].Translation; + Quaternion localRotation = track.Value.KeyFrames[currentFrameIndex].Rotation; + + boneGo.transform.localPosition = localPosition; + boneGo.transform.localRotation = localRotation; + + Matrix4x4 curPoseToModelMatrix = Matrix4x4.Translate(localPosition) * Matrix4x4.Rotate(localRotation); + + if (!bone.IsRoot()) + { + Bone parentBone = _bones[boneNode.ParentId]; + Matrix4x4 parentCurPoseToModelMatrix = parentBone.CurrentPoseToModelMatrix; + curPoseToModelMatrix = parentCurPoseToModelMatrix * curPoseToModelMatrix; + } + + bone.CurrentPoseToModelMatrix = curPoseToModelMatrix; + } + + // Update children + for (var i = 0; i < boneNode.Children.Length; i++) + { + Bone childBone = _bones[boneNode.Children[i].Id]; + UpdateBone(childBone, tick); + } + } + + private void UpdateSkinning() + { + for (int subMeshIndex = 0; subMeshIndex < _renderMeshComponents.Length; subMeshIndex++) + { + MshMesh subMesh = _mshFile.SubMeshes[subMeshIndex]; + Mesh mesh = _renderMeshComponents[subMeshIndex].Mesh; + mesh.SetVertices(BuildVerticesWithCurrentSkeleton(subMesh, subMeshIndex)); + mesh.RecalculateBounds(); + } + } + + private void SetupBone(BoneNode boneNode, Bone parentBone) + { + GameObject boneGo = new GameObject($"{boneNode.Id}_{boneNode.Name}_{boneNode.Type}"); + + boneGo.transform.SetParent(parentBone == null ? gameObject.transform : parentBone.GameObject.transform); + + boneGo.transform.localPosition = boneNode.Translation; + boneGo.transform.localRotation = boneNode.Rotation; + + Bone bone = new (boneNode.Name, boneGo, boneNode); + + if (parentBone == null) + { + _rootBoneObject = boneGo; + } + else + { + _bones.Add(boneNode.Id, bone); + Matrix4x4 translationMatrix = Matrix4x4.Translate(boneNode.Translation); + Matrix4x4 rotationMatrix = Matrix4x4.Rotate(boneNode.Rotation); + bone.BindPoseModelToBoneSpace = Matrix4x4.Inverse(rotationMatrix) + * Matrix4x4.Inverse(translationMatrix) + * parentBone.BindPoseModelToBoneSpace; + } + + // Render child bones + foreach (BoneNode subBone in boneNode.Children) + { + SetupBone(subBone, bone); + } + } + + private void RenderMesh() + { + if (_mshFile.SubMeshes.Length == 0) return; + + _renderMeshComponents = new RenderMeshComponent[_mshFile.SubMeshes.Length]; + _meshObjects = new GameObject[_mshFile.SubMeshes.Length]; + + for (int i = 0; i < _mshFile.SubMeshes.Length; i++) + { + MshMesh subMesh = _mshFile.SubMeshes[i]; + RenderSubMesh(subMesh, i); + } + } + + private void RenderSubMesh(MshMesh subMesh, int subMeshIndex) + { + _meshObjects[subMeshIndex] = new GameObject($"SubMesh_{subMeshIndex}"); + _meshObjects[subMeshIndex].transform.SetParent(gameObject.transform, false); + + List vertices = new List(); + List triangles = new List(); + Vector3[] normals = null; + List uvs = new List(); + + _indexBuffer[subMeshIndex] = new List(); + _vertexBuffer[subMeshIndex] = new Vector3[subMesh.Vertices.Length]; + + var index = 0; + foreach (PhyFace phyFace in subMesh.Faces) + { + for (var i = 0; i < phyFace.Indices.Length; i++) + { + _indexBuffer[subMeshIndex].Add(phyFace.Indices[i]); + uvs.Add(new Vector2(phyFace.U[i, 0], phyFace.V[i, 0])); + triangles.Add(index++); + } + } + + GameBoxInterpreter.ToUnityTriangles(triangles); + + for (int i = 0; i < _indexBuffer[subMeshIndex].Count; i++) + { + vertices.Add(subMesh.Vertices[_indexBuffer[subMeshIndex][i]].Position); + } + + Material[] materials = _materialFactory.CreateStandardMaterials( + RendererType.Msh, + (_textureName, _texture), + default, + _tintColor, + GameBoxBlendFlag.Opaque); + + Vector3[] verts = vertices.ToArray(); + int[] tris = triangles.ToArray(); + Vector2[] uvs1 = uvs.ToArray(); + Vector2[] uvs2 = null; + + var meshRenderer = _meshObjects[subMeshIndex].AddComponent(); + Mesh renderMesh = meshRenderer.Render( + ref verts, + ref tris, + ref normals, + ref uvs1, + ref uvs2, + ref materials, + true); + + renderMesh.RecalculateNormals(); + renderMesh.RecalculateTangents(); + renderMesh.RecalculateBounds(); + + _renderMeshComponents[subMeshIndex] = new RenderMeshComponent + { + Mesh = renderMesh, + MeshRenderer = meshRenderer, + MeshDataBuffer = new MeshDataBuffer() + { + VertexBuffer = verts + } + }; + } + + private Vector3[] BuildVerticesWithCurrentSkeleton(MshMesh subMesh, int subMeshIndex) + { + for (var i = 0; i < subMesh.Vertices.Length; i++) + { + PhyVertex vert = subMesh.Vertices[i]; + + int boneId = vert.BoneIds[0]; + Bone bone = _bones[boneId]; + + Vector4 originalPosition = new Vector4(vert.Position.x, vert.Position.y, vert.Position.z, 1.0f); + Vector4 currentPosition = bone.CurrentPoseToModelMatrix * bone.BindPoseModelToBoneSpace * originalPosition; + + _vertexBuffer[subMeshIndex][i] = new Vector3( + currentPosition.x / currentPosition.w, + currentPosition.y / currentPosition.w, + currentPosition.z / currentPosition.w); + } + + Vector3[] vertices = _renderMeshComponents[subMeshIndex].Mesh.vertices; + + for (int i = 0; i < _indexBuffer[subMeshIndex].Count; i++) + { + vertices[i] = _vertexBuffer[subMeshIndex][_indexBuffer[subMeshIndex][i]]; + } + + return vertices; + } + + public Bounds GetRendererBounds() + { + if (_renderMeshComponents.Length == 0) + { + return new Bounds(transform.position, Vector3.one); + } + + Bounds bounds = _renderMeshComponents[0].MeshRenderer.GetRendererBounds(); + for (var i = 1; i < _renderMeshComponents.Length; i++) + { + bounds.Encapsulate(_renderMeshComponents[i].MeshRenderer.GetRendererBounds()); + } + return bounds; + } + + public Bounds GetMeshBounds() + { + if (_renderMeshComponents.Length == 0) + { + return new Bounds(Vector3.zero, Vector3.one); + } + Bounds bounds = _renderMeshComponents[0].MeshRenderer.GetMeshBounds(); + for (var i = 1; i < _renderMeshComponents.Length; i++) + { + bounds.Encapsulate(_renderMeshComponents[i].MeshRenderer.GetMeshBounds()); + } + return bounds; + } + + public bool IsVisible() + { + return _meshObjects != null; + } + + private void OnDisable() + { + Dispose(); + } + + public void Dispose() + { + PauseAnimation(); + + if (_renderMeshComponents != null) + { + foreach (RenderMeshComponent renderMeshComponent in _renderMeshComponents) + { + Destroy(renderMeshComponent.Mesh); + Destroy(renderMeshComponent.MeshRenderer); + } + + _renderMeshComponents = null; + } + + if (_meshObjects != null) + { + foreach (GameObject meshObject in _meshObjects) + { + Destroy(meshObject); + } + + _meshObjects = null; + } + + if (_bones != null) + { + foreach (Bone bone in _bones.Values) + { + Destroy(bone.GameObject); + } + + _bones.Clear(); + } + + if (_rootBoneObject != null) + { + Destroy(_rootBoneObject); + _rootBoneObject = null; + } + + _indexBuffer.Clear(); + _vertexBuffer.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Pal3/Renderer/SkeletalModelRenderer.cs.meta b/Assets/Scripts/Pal3/Renderer/SkeletalModelRenderer.cs.meta new file mode 100644 index 000000000..cf8b64af6 --- /dev/null +++ b/Assets/Scripts/Pal3/Renderer/SkeletalModelRenderer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ebe885a4d2b24f37a437c3c479c9291c +timeCreated: 1690401815 \ No newline at end of file diff --git a/Assets/Scripts/ResourceViewer/GameResourceViewer.cs b/Assets/Scripts/ResourceViewer/GameResourceViewer.cs index e485aad48..6753050e4 100644 --- a/Assets/Scripts/ResourceViewer/GameResourceViewer.cs +++ b/Assets/Scripts/ResourceViewer/GameResourceViewer.cs @@ -16,6 +16,8 @@ namespace ResourceViewer using Core.DataReader.Cpk; using Core.DataReader.Cvd; using Core.DataReader.Gdb; + using Core.DataReader.Mov; + using Core.DataReader.Msh; using Core.DataReader.Mv3; using Core.DataReader.Pol; using Core.DataReader.Sce;