From d86243a82e2399b9dbe628c294a67ad43c8a9207 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 21 Dec 2021 23:54:25 +0300 Subject: [PATCH 1/2] [UE1][Animation] Support HP2 Anims (Still some issues with root bone rotation, but works okay) --- Unreal/GameDatabase.cpp | 3 + Unreal/GameDefines.h | 1 + Unreal/UnCore.h | 67 +++++++++++- Unreal/UnCoreSerialize.cpp | 3 + Unreal/UnrealMesh/UnAnim2.cpp | 4 +- Unreal/UnrealMesh/UnMesh1.cpp | 196 ++++++++++++++++++++++++++++++++++ Unreal/UnrealMesh/UnMesh2.h | 14 ++- 7 files changed, 284 insertions(+), 4 deletions(-) diff --git a/Unreal/GameDatabase.cpp b/Unreal/GameDatabase.cpp index 80d4a349..b5bb2ffe 100644 --- a/Unreal/GameDatabase.cpp +++ b/Unreal/GameDatabase.cpp @@ -30,6 +30,9 @@ const GameInfo GListOfGames[] = { #if UNDYING G("Undying", undying, GAME_Undying), #endif + #if HP2 + G("Harry Potter and the Chamber of Secrets", hp2, GAME_HarryPotter2), + #endif #endif // UNREAL1 // Unreal Engine 2 diff --git a/Unreal/GameDefines.h b/Unreal/GameDefines.h index 3c5fe091..c008d4a0 100644 --- a/Unreal/GameDefines.h +++ b/Unreal/GameDefines.h @@ -33,6 +33,7 @@ #define DEUS_EX 1 #define RUNE 1 #define UNDYING 1 +#define HP2 1 #endif // requires UNREAL25 diff --git a/Unreal/UnCore.h b/Unreal/UnCore.h index c713b689..c5c595a1 100644 --- a/Unreal/UnCore.h +++ b/Unreal/UnCore.h @@ -347,6 +347,7 @@ enum EGame GAME_UE1 = 0x0100000, GAME_Undying, + GAME_HarryPotter2, GAME_UE2 = 0x0200000, GAME_UT2, @@ -1116,6 +1117,18 @@ struct FVector { float X, Y, Z; + // Constructors. + FVector() + {} + + FVector( float InX, float InY, float InZ ) + : X(InX), Y(InY), Z(InZ) + {} + + explicit FVector( float In ) + : X(In), Y(In), Z(In) + {} + void Set(float _X, float _Y, float _Z) { X = _X; Y = _Y; Z = _Z; @@ -1147,6 +1160,17 @@ struct FVector X *= value; Y *= value; Z *= value; } + friend FVector Interp( const FVector& A, const FVector& B, float Param ) + { + float Marap = 1.0f - Param; + return FVector + ( + A.X * Marap + B.X * Param, + A.Y * Marap + B.Y * Param, + A.Z * Marap + B.Z * Param + ); + } + friend FArchive& operator<<(FArchive &Ar, FVector &V) { Ar << V.X << V.Y << V.Z; @@ -1213,6 +1237,10 @@ struct FRotator }; +// Magic numbers for numerical precision. +#define DELTA (0.00001f) +#define SLERP_DELTA (0.0001f) + struct FQuat { float X, Y, Z, W; @@ -1226,6 +1254,42 @@ struct FQuat { return Ar << F.X << F.Y << F.Z << F.W; } + + float operator |( const FQuat& Q ) const + { + return X*Q.X + Y*Q.Y + Z*Q.Z + W*Q.W; + } + + bool Normalize() + { + // Test whether already approximately normalised. + float SquareSum = (float)(X*X+Y*Y+Z*Z+W*W); + if( fabs(SquareSum-1.f) > DELTA ) + { + if( SquareSum >= DELTA ) + { + // + // The approximate inverse square root of a number L near 1 is: + // + // L^(-.5) ~= 1 -.5 (L-1) = 1.5 - .5 L + // + float Scale = 1.5f - SquareSum * 0.5f; + X *= Scale; + Y *= Scale; + Z *= Scale; + W *= Scale; + } + else + { + X = 0.0f; + Y = 0.0f; + Z = 0.1f; + W = 0.0f; + return false; + } + } + return true; + } }; @@ -2045,7 +2109,8 @@ class TStaticArray : public TArray using TArray::DataPtr; using TArray::MaxCount; public: - FORCEINLINE TStaticArray() + FORCEINLINE + TStaticArray() { DataPtr = (void*)&StaticData[0]; MaxCount = N; diff --git a/Unreal/UnCoreSerialize.cpp b/Unreal/UnCoreSerialize.cpp index 2846ea10..85fe2107 100644 --- a/Unreal/UnCoreSerialize.cpp +++ b/Unreal/UnCoreSerialize.cpp @@ -219,6 +219,8 @@ FArchive& FArray::SerializeSimple(FArchive &Ar, int NumFields, int FieldSize) //?? be done using generic serializer, or SerializeSimple should be //?? extended for this + const bool bIsStatic = IsStatic(); + // serialize data count int Count = DataCount; if (GameUsesFCompactIndex(Ar)) @@ -234,6 +236,7 @@ FArchive& FArray::SerializeSimple(FArchive &Ar, int NumFields, int FieldSize) DataCount = Count; } if (!Count) return Ar; + if (bIsStatic) return Ar; // perform serialization itself Ar.Serialize(DataPtr, elementSize * Count); diff --git a/Unreal/UnrealMesh/UnAnim2.cpp b/Unreal/UnrealMesh/UnAnim2.cpp index 8faf72fa..821f45d8 100644 --- a/Unreal/UnrealMesh/UnAnim2.cpp +++ b/Unreal/UnrealMesh/UnAnim2.cpp @@ -18,7 +18,7 @@ UMeshAnimation::~UMeshAnimation() } -void UMeshAnimation::ConvertAnims() +void UMeshAnimation::ConvertAnims(bool bShouldAdjustTime /* = true */) { guard(UMeshAnimation::ConvertAnims); @@ -62,7 +62,7 @@ void UMeshAnimation::ConvertAnims() CopyArray(T->KeyTime, A.KeyTime); // usually MotionChunk.TrackTime is identical to NumFrames, but some packages exists where // time channel should be adjusted - if (M.TrackTime > 0) + if (bShouldAdjustTime && M.TrackTime > 0) { float TimeScale = Src.NumFrames / M.TrackTime; for (int k = 0; k < T->KeyTime.Num(); k++) diff --git a/Unreal/UnrealMesh/UnMesh1.cpp b/Unreal/UnrealMesh/UnMesh1.cpp index 8192d31c..f404d796 100644 --- a/Unreal/UnrealMesh/UnMesh1.cpp +++ b/Unreal/UnrealMesh/UnMesh1.cpp @@ -412,4 +412,200 @@ void USkeletalMesh::SerializeSkelMesh1(FArchive &Ar) unguard; } +#if HP2 + +struct FAnimVec +{ + int16 X = 0, Y = 0, Z = 0; + + // Convert back to Vector, with additional scale. + FVector Vector( float Scale ) const + { + FVector OutVector; + Scale /= BASE_SCALE; + OutVector.Set(X * Scale, Y * Scale, Z * Scale); + return OutVector; + } + + enum { BASE_SCALE = (1<<15)-1 }; + + FQuat Quat() const + { + static float Scale = 1.57079633 / BASE_SCALE; + + const float _X = sin(X * Scale); + const float _Y = sin(Y * Scale); + const float _Z = sin(Z * Scale); + + const float W = sqrt(max(0.0f, 1.f - _X * _X - _Y * _Y - _Z * _Z)); + + FQuat OutQuat; + OutQuat.Set(_X, _Y, _Z, W); + return OutQuat; + } + + friend FArchive& operator<<(FArchive &Ar, FAnimVec &A) + { + return Ar << A.X << A.Y << A.Z; + } +}; + +SIMPLE_TYPE(FAnimVec, int16) + +struct AnalogTrack_HP2 +{ + unsigned Flags = 0; // reserved + TStaticArray KeyQuat; // Orientation key track (count = 1 or KeyTime.Count) + TStaticArray KeyPos; // Position key track (count = 1 or KeyTime.Count) + TStaticArray KeyDelta; // For each key, time when next key takes effect (measured from start of track) + float PosScale = 0.0f; // Scale for position track. + float TimeScale = 0.0f; // Scale for time track. + + inline FVector GetKeyPos(int i) const + { + return KeyPos[i].Vector( PosScale ); + } + + friend FArchive& operator<<(FArchive &Ar, AnalogTrack_HP2 &A) + { + guard(AnalogTrack_HP2<<); + return Ar << A.Flags << A.KeyQuat << A.KeyPos << A.KeyDelta << A.PosScale << A.TimeScale; + unguard; + } +}; + +struct MotionChunk_HP2 +{ + FVector RootSpeed3D; // Net 3d speed. + float TrackTime; // Total time (Same for each track.) + int StartBone; // If we're a partial-hierarchy-movement, this is the lowest bone. + unsigned Flags; // Reserved; equals to UMeshAnimation.Version in UE2.5 + + TArray BoneIndices; // Refbones number of Bone indices (-1 or valid one) to fast-find tracks for a particular bone. + // Frame-less, compressed animation tracks. NumBones times NumAnims tracks in total + TArray AnimTracks; // Compressed key tracks (one for each bone) + // AnalogTrack RootTrack; // May or may not be used; actual traverse-a-scene root tracks for use + // with cutscenes / special physics modes, in addition to the regular skeletal root track. + + friend FArchive& operator<<(FArchive &Ar, MotionChunk_HP2 &M) + { + guard(MotionChunk<<); + return Ar << M.RootSpeed3D << M.TrackTime << M.StartBone << M.Flags << M.BoneIndices << M.AnimTracks; + unguard; + } +}; + +struct FMasterTrack +{ + TArray KeyQuat; + TArray KeyPos; + TArray KeyDelta; + + friend FArchive& operator<<(FArchive &Ar, FMasterTrack &M) + { + guard(FMasterTrack<<); + return Ar << M.KeyQuat << M.KeyPos << M.KeyDelta; + unguard; + } +}; + +#include "Mesh/SkeletalMesh.h" + +void UMeshAnimation::SerializeHP2Moves(FArchive &Ar) +{ + guard(SerializeHP2Moves); + + TArray HPMoves; + FMasterTrack MasterTrack; + Ar << HPMoves << AnimSeqs << MasterTrack; + + int Q = 0, P = 0, T = 0; + + for (MotionChunk_HP2& Move : HPMoves) + { + for (AnalogTrack_HP2& Track : Move.AnimTracks) + { + const int CurrentKeyQuatNum = Track.KeyQuat.Num(); + const int CurrentKeyPosNum = Track.KeyPos.Num(); + const int CurrentKeyDeltaNum = Track.KeyDelta.Num(); + + for (int QuatIndex = 0; QuatIndex < CurrentKeyQuatNum; ++QuatIndex) + { + const int TargetIndex = (Q + QuatIndex); + Track.KeyQuat[QuatIndex] = MasterTrack.KeyQuat[TargetIndex]; + } + for (int PosIndex = 0; PosIndex < CurrentKeyPosNum; ++PosIndex) + { + const int TargetIndex = (P + PosIndex); + Track.KeyPos[PosIndex] = MasterTrack.KeyPos[TargetIndex]; + } + for (int DeltaIndex = 0; DeltaIndex < CurrentKeyDeltaNum; ++DeltaIndex) + { + const int TargetIndex = (T + DeltaIndex); + Track.KeyDelta[DeltaIndex] = MasterTrack.KeyDelta[TargetIndex]; + } + + Q += CurrentKeyQuatNum; + P += CurrentKeyPosNum; + T += CurrentKeyDeltaNum; + } + } + + Moves.SetNum(HPMoves.Num()); + + for (int MoveIndex = 0; MoveIndex < HPMoves.Num(); MoveIndex++) + { + FMeshAnimSeq& AnimSeq = AnimSeqs[MoveIndex]; + + MotionChunk_HP2& SourceMove = HPMoves[MoveIndex]; + MotionChunk& TargetMove = Moves[MoveIndex]; + + TargetMove.RootSpeed3D = SourceMove.RootSpeed3D; + TargetMove.TrackTime = SourceMove.TrackTime; + TargetMove.StartBone = SourceMove.StartBone; + TargetMove.Flags = SourceMove.Flags; + + CopyArray(TargetMove.BoneIndices, SourceMove.BoneIndices); + + // For every bone + TargetMove.AnimTracks.SetNum(SourceMove.AnimTracks.Num()); + + for (int BoneIndex = 0; BoneIndex < TargetMove.AnimTracks.Num(); BoneIndex++) + { + AnalogTrack& TargetAnalogTrack = TargetMove.AnimTracks[BoneIndex]; + AnalogTrack_HP2& SourceAnalogTrack = SourceMove.AnimTracks[BoneIndex]; + + TargetAnalogTrack.Flags = SourceAnalogTrack.Flags; + TargetAnalogTrack.KeyQuat.SetNum(SourceAnalogTrack.KeyQuat.Num()); + TargetAnalogTrack.KeyPos.SetNum(SourceAnalogTrack.KeyPos.Num()); + TargetAnalogTrack.KeyTime.SetNum(SourceAnalogTrack.KeyDelta.Num()); + + for (int KeyQuatIndex = 0; KeyQuatIndex < TargetAnalogTrack.KeyQuat.Num(); KeyQuatIndex++) + { + TargetAnalogTrack.KeyQuat[KeyQuatIndex] = SourceAnalogTrack.KeyQuat[KeyQuatIndex].Quat(); + + TargetAnalogTrack.KeyQuat[KeyQuatIndex].X *= -1.0f; + TargetAnalogTrack.KeyQuat[KeyQuatIndex].W *= -1.0f; + } + for (int KeyPosIndex = 0; KeyPosIndex < TargetAnalogTrack.KeyPos.Num(); KeyPosIndex++) + { + TargetAnalogTrack.KeyPos[KeyPosIndex] = SourceAnalogTrack.GetKeyPos(KeyPosIndex); + TargetAnalogTrack.KeyPos[KeyPosIndex].X *= -1.0f; + } + + int CurrentTime = 0; + for (int KeyTimeIndex = 0; KeyTimeIndex < TargetAnalogTrack.KeyTime.Num(); KeyTimeIndex++) + { + TargetAnalogTrack.KeyTime[KeyTimeIndex] = CurrentTime + SourceAnalogTrack.KeyDelta[KeyTimeIndex]; + CurrentTime += SourceAnalogTrack.KeyDelta[KeyTimeIndex]; + } + } + } + + ConvertAnims(false); + + unguard; +} +#endif // HP2 + #endif // UNREAL1 diff --git a/Unreal/UnrealMesh/UnMesh2.h b/Unreal/UnrealMesh/UnMesh2.h index ea41c350..efcb8816 100644 --- a/Unreal/UnrealMesh/UnMesh2.h +++ b/Unreal/UnrealMesh/UnMesh2.h @@ -1393,6 +1393,9 @@ class UMeshAnimation : public UObject void SerializeSCell(FArchive &Ar); #endif #if UNREAL1 +#if HP2 + void SerializeHP2Moves(FArchive &Ar); +#endif void Upgrade(); #endif @@ -1422,6 +1425,15 @@ class UMeshAnimation : public UObject return; } #endif // SWRC +#if UNREAL1 +#if HP2 + if (Ar.Game == GAME_HarryPotter2) + { + SerializeHP2Moves(Ar); + return; + } +#endif // HP2 +#endif // UNREAL1 #if UC2 if (Ar.Engine() == GAME_UE2X) { @@ -1454,7 +1466,7 @@ class UMeshAnimation : public UObject unguard; } - void ConvertAnims(); + void ConvertAnims(bool bShouldAdjustTime = true); }; From 994f3aa1320d306655761e2a3c749b53e7a8b589 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 24 Dec 2021 21:06:37 +0300 Subject: [PATCH 2/2] [UE1][Mesh][Anim] Full support export skeletal mesh and anims from HP2 --- Unreal/UnCore.h | 61 +++-------------------------------- Unreal/UnrealMesh/UnMesh1.cpp | 37 +++++++++++++++++---- Unreal/UnrealMesh/UnMesh2.h | 1 + 3 files changed, 36 insertions(+), 63 deletions(-) diff --git a/Unreal/UnCore.h b/Unreal/UnCore.h index c5c595a1..4f4c38e2 100644 --- a/Unreal/UnCore.h +++ b/Unreal/UnCore.h @@ -1118,14 +1118,13 @@ struct FVector float X, Y, Z; // Constructors. - FVector() - {} + FVector() {} - FVector( float InX, float InY, float InZ ) + FVector(float InX, float InY, float InZ) : X(InX), Y(InY), Z(InZ) {} - explicit FVector( float In ) + explicit FVector(float In) : X(In), Y(In), Z(In) {} @@ -1160,16 +1159,6 @@ struct FVector X *= value; Y *= value; Z *= value; } - friend FVector Interp( const FVector& A, const FVector& B, float Param ) - { - float Marap = 1.0f - Param; - return FVector - ( - A.X * Marap + B.X * Param, - A.Y * Marap + B.Y * Param, - A.Z * Marap + B.Z * Param - ); - } friend FArchive& operator<<(FArchive &Ar, FVector &V) { @@ -1236,11 +1225,6 @@ struct FRotator } }; - -// Magic numbers for numerical precision. -#define DELTA (0.00001f) -#define SLERP_DELTA (0.0001f) - struct FQuat { float X, Y, Z, W; @@ -1254,42 +1238,6 @@ struct FQuat { return Ar << F.X << F.Y << F.Z << F.W; } - - float operator |( const FQuat& Q ) const - { - return X*Q.X + Y*Q.Y + Z*Q.Z + W*Q.W; - } - - bool Normalize() - { - // Test whether already approximately normalised. - float SquareSum = (float)(X*X+Y*Y+Z*Z+W*W); - if( fabs(SquareSum-1.f) > DELTA ) - { - if( SquareSum >= DELTA ) - { - // - // The approximate inverse square root of a number L near 1 is: - // - // L^(-.5) ~= 1 -.5 (L-1) = 1.5 - .5 L - // - float Scale = 1.5f - SquareSum * 0.5f; - X *= Scale; - Y *= Scale; - Z *= Scale; - W *= Scale; - } - else - { - X = 0.0f; - Y = 0.0f; - Z = 0.1f; - W = 0.0f; - return false; - } - } - return true; - } }; @@ -2109,8 +2057,7 @@ class TStaticArray : public TArray using TArray::DataPtr; using TArray::MaxCount; public: - FORCEINLINE - TStaticArray() + FORCEINLINE TStaticArray() { DataPtr = (void*)&StaticData[0]; MaxCount = N; diff --git a/Unreal/UnrealMesh/UnMesh1.cpp b/Unreal/UnrealMesh/UnMesh1.cpp index f404d796..ea911422 100644 --- a/Unreal/UnrealMesh/UnMesh1.cpp +++ b/Unreal/UnrealMesh/UnMesh1.cpp @@ -393,6 +393,27 @@ void USkeletalMesh::SerializeSkelMesh1(FArchive &Ar) } unguard; +#if HP2 + if (Ar.Game == GAME_HarryPotter2) + { + int i; + for (i = 0; i < Points.Num(); i++) + Points[i].Y *= -1; + for (i = 0; i < Triangles.Num(); i++) + Exchange(Triangles[i].WedgeIndex[0], Triangles[i].WedgeIndex[1]); + for (i = 0; i < RefSkeleton.Num(); i++) + { + FMeshBone &S = RefSkeleton[i]; + S.BonePos.Position.Y *= -1; + S.BonePos.Orientation.Y *= -1; + S.BonePos.Orientation.W *= -1; + } + + ConvertMesh(); + return; + } +#endif // HP2 + // mirror model: points, faces and skeleton int i; for (i = 0; i < Points.Num(); i++) @@ -582,15 +603,21 @@ void UMeshAnimation::SerializeHP2Moves(FArchive &Ar) for (int KeyQuatIndex = 0; KeyQuatIndex < TargetAnalogTrack.KeyQuat.Num(); KeyQuatIndex++) { - TargetAnalogTrack.KeyQuat[KeyQuatIndex] = SourceAnalogTrack.KeyQuat[KeyQuatIndex].Quat(); + FQuat TargetQuat = SourceAnalogTrack.KeyQuat[KeyQuatIndex].Quat(); + TargetQuat.Y *= -1.0f; - TargetAnalogTrack.KeyQuat[KeyQuatIndex].X *= -1.0f; - TargetAnalogTrack.KeyQuat[KeyQuatIndex].W *= -1.0f; + // "Due to historical idiosyncrasy, root orientation is negated. !" From UE1 + if (BoneIndex != 0) + { + TargetQuat.W *= -1.0f; + } + + TargetAnalogTrack.KeyQuat[KeyQuatIndex] = TargetQuat; } for (int KeyPosIndex = 0; KeyPosIndex < TargetAnalogTrack.KeyPos.Num(); KeyPosIndex++) { TargetAnalogTrack.KeyPos[KeyPosIndex] = SourceAnalogTrack.GetKeyPos(KeyPosIndex); - TargetAnalogTrack.KeyPos[KeyPosIndex].X *= -1.0f; + TargetAnalogTrack.KeyPos[KeyPosIndex].Y *= -1.0f; } int CurrentTime = 0; @@ -602,8 +629,6 @@ void UMeshAnimation::SerializeHP2Moves(FArchive &Ar) } } - ConvertAnims(false); - unguard; } #endif // HP2 diff --git a/Unreal/UnrealMesh/UnMesh2.h b/Unreal/UnrealMesh/UnMesh2.h index efcb8816..15ac4c5d 100644 --- a/Unreal/UnrealMesh/UnMesh2.h +++ b/Unreal/UnrealMesh/UnMesh2.h @@ -1430,6 +1430,7 @@ class UMeshAnimation : public UObject if (Ar.Game == GAME_HarryPotter2) { SerializeHP2Moves(Ar); + ConvertAnims(false); return; } #endif // HP2