private void SerializeCompressedData(FAssetArchive Ar) { // These fields were serialized as properties in pre-UE4.12 engine version KeyEncodingFormat = Ar.Read <AnimationKeyFormat>(); TranslationCompressionFormat = Ar.Read <AnimationCompressionFormat>(); RotationCompressionFormat = Ar.Read <AnimationCompressionFormat>(); ScaleCompressionFormat = Ar.Read <AnimationCompressionFormat>(); CompressedTrackOffsets = Ar.ReadArray <int>(); CompressedScaleOffsets = new FCompressedOffsetData(Ar); if (Ar.Game >= EGame.GAME_UE4_21) { // UE4.21+ - added compressed segments; disappeared in 4.23 CompressedSegments = Ar.ReadArray <FCompressedSegment>(); if (CompressedSegments.Length > 0) { Log.Information("animation has CompressedSegments!"); } } CompressedTrackToSkeletonMapTable = Ar.ReadArray <FTrackToSkeletonMap>(); if (Ar.Game < EGame.GAME_UE4_22) { CompressedCurveData = new FStructFallback(Ar, "RawCurveTracks"); } else { var compressedCurveNames = Ar.ReadArray(() => new FSmartName(Ar)); } if (Ar.Game >= EGame.GAME_UE4_17) { // UE4.17+ var compressedRawDataSize = Ar.Read <int>(); } if (Ar.Game >= EGame.GAME_UE4_22) { var compressedNumFrames = Ar.Read <int>(); } // compressed data var numBytes = Ar.Read <int>(); CompressedByteStream = Ar.ReadBytes(numBytes); if (Ar.Game >= EGame.GAME_UE4_22) { var curveCodecPath = Ar.ReadFString(); var compressedCurveByteStream = Ar.ReadArray <byte>(); } // Fix layout of "byte swapped" data (workaround for UE4 bug) if (KeyEncodingFormat == AnimationKeyFormat.AKF_PerTrackCompression && CompressedScaleOffsets.OffsetData.Length > 0 && Ar.Game < EGame.GAME_UE4_23) { throw new NotImplementedException(); } }
// UE4.23-4.24 has changed compressed data layout for streaming, so it's worth making a separate // serializer function for it. private void SerializeCompressedData2(FAssetArchive Ar) { var compressedRawDataSize = Ar.Read <int>(); CompressedTrackToSkeletonMapTable = Ar.ReadArray <FTrackToSkeletonMap>(); var compressedCurveNames = Ar.ReadArray(() => new FSmartName(Ar)); // Since 4.23, this is FUECompressedAnimData::SerializeCompressedData KeyEncodingFormat = Ar.Read <AnimationKeyFormat>(); TranslationCompressionFormat = Ar.Read <AnimationCompressionFormat>(); RotationCompressionFormat = Ar.Read <AnimationCompressionFormat>(); ScaleCompressionFormat = Ar.Read <AnimationCompressionFormat>(); var compressedNumFrames = Ar.Read <int>(); // SerializeView() just serializes array size var compressedTrackOffsetsNum = Ar.Read <int>(); var compressedScaleOffsetsNum = Ar.Read <int>(); CompressedScaleOffsets = new FCompressedOffsetData(Ar.Read <int>()); var compressedByteStreamNum = Ar.Read <int>(); // ... end of FUECompressedAnimData::SerializeCompressedData var numBytes = Ar.Read <int>(); var bUseBulkDataForLoad = Ar.ReadBoolean(); // In UE4.23 CompressedByteStream field exists in FUECompressedAnimData (as TArrayView) and in // FCompressedAnimSequence (as byte array). Serialization is done in FCompressedAnimSequence, // either as TArray or as bulk, and then array is separated onto multiple "views" for // FUECompressedAnimData. We'll use a different name for "joined" serialized array here to // avoid confuse. byte[] serializedByteStream; if (bUseBulkDataForLoad) { throw new NotImplementedException("Anim: bUseBulkDataForLoad not implemented"); //todo: read from bulk to serializedByteStream } else { serializedByteStream = Ar.ReadBytes(numBytes); } // Setup all array views from single array. In UE4 this is done in FUECompressedAnimData::InitViewsFromBuffer. // We'll simply copy array data away from SerializedByteStream, and then SerializedByteStream // will be released from memory as it is a local variable here. // Note: copying is not byte-order wise, so if there will be any problems in the future, // should use byte swap functions. using (var tempAr = new FByteArchive("SerializedByteStream", serializedByteStream, Ar.Versions)) { CompressedTrackOffsets = tempAr.ReadArray <int>(compressedTrackOffsetsNum); CompressedScaleOffsets.OffsetData = tempAr.ReadArray <int>(compressedScaleOffsetsNum); CompressedByteStream = tempAr.ReadBytes(compressedByteStreamNum); } var curveCodecPath = Ar.ReadFString(); var compressedCurveByteStream = Ar.ReadArray <byte>(); }
public FAnimKeyHeader(BinaryReader reader) { var packed = reader.ReadUInt32(); key_format = (AnimationCompressionFormat)(packed >> 28); component_mask = (packed >> 24) & 0xF; num_keys = packed & 0xFFFFFF; has_time_tracks = (component_mask & 8) != 0; }
public void UpdateProps(PropertyCollection props, MEGame newGame, AnimationCompressionFormat newRotationCompression = AnimationCompressionFormat.ACF_Float96NoW) { if (compressedDataSource == MEGame.Unknown || (newGame != compressedDataSource && !(newGame <= MEGame.ME3 && compressedDataSource <= MEGame.ME3))) { CompressAnimationData(newGame, newRotationCompression); props.RemoveNamedProperty("KeyEncodingFormat"); props.RemoveNamedProperty("TranslationCompressionFormat"); props.AddOrReplaceProp(new EnumProperty(rotCompression.ToString(), nameof(AnimationCompressionFormat), newGame, "RotationCompressionFormat")); props.AddOrReplaceProp(new ArrayProperty <IntProperty>(TrackOffsets.Select(i => new IntProperty(i)), "CompressedTrackOffsets")); props.AddOrReplaceProp(new IntProperty(NumFrames, "NumFrames")); props.AddOrReplaceProp(new FloatProperty(RateScale, "RateScale")); props.AddOrReplaceProp(new FloatProperty(SequenceLength, "SequenceLength")); props.AddOrReplaceProp(new NameProperty(Name, "SequenceName")); } }
public void DecompressAnimationData() { var ms = new MemoryStream(CompressedAnimationData); RawAnimationData = new List <AnimTrack>(); for (int i = 0; i < Bones.Count; i++) { int posOff = TrackOffsets[i * 4]; int numPosKeys = TrackOffsets[i * 4 + 1]; int rotOff = TrackOffsets[i * 4 + 2]; int numRotKeys = TrackOffsets[i * 4 + 3]; var track = new AnimTrack { Positions = new List <Vector3>(numPosKeys), Rotations = new List <Quaternion>(numRotKeys) }; if (numPosKeys > 0) { ms.JumpTo(posOff); AnimationCompressionFormat compressionFormat = posCompression; if (numPosKeys == 1) { compressionFormat = AnimationCompressionFormat.ACF_None; } for (int j = 0; j < numPosKeys; j++) { switch (compressionFormat) { case AnimationCompressionFormat.ACF_None: case AnimationCompressionFormat.ACF_Float96NoW: track.Positions.Add(new Vector3(ms.ReadFloat(), ms.ReadFloat(), ms.ReadFloat())); break; case AnimationCompressionFormat.ACF_IntervalFixed32NoW: case AnimationCompressionFormat.ACF_Fixed48NoW: case AnimationCompressionFormat.ACF_Fixed32NoW: case AnimationCompressionFormat.ACF_Float32NoW: case AnimationCompressionFormat.ACF_BioFixed48: throw new NotImplementedException($"Translation keys in format {compressionFormat} cannot be read yet!"); } } if (keyEncoding == AnimationKeyFormat.AKF_VariableKeyLerp && numPosKeys > 1) { ms.JumpTo(ms.Position.Align(4)); var keyTimes = new List <int>(numPosKeys); for (int j = 0; j < numPosKeys; j++) { keyTimes.Add(NumFrames > 0xFF ? ms.ReadUInt16() : ms.ReadByte()); } //RawAnimationData should have either 1 key, or the same number of keys as frames. //Lerp any missing keys List <Vector3> tempPositions = track.Positions; track.Positions = new List <Vector3>(NumFrames) { tempPositions[0] }; for (int frameIdx = 1, keyIdx = 1; frameIdx < NumFrames; keyIdx++, frameIdx++) { if (keyIdx >= keyTimes.Count) { track.Positions.Add(track.Positions[frameIdx - 1]); } else if (keyTimes[keyIdx] == frameIdx) { track.Positions.Add(tempPositions[keyIdx]); } else { int nextFrame = keyTimes[keyIdx]; int prevFrame = frameIdx - 1; for (int j = frameIdx; j < nextFrame; j++) { float amount = (float)(j - prevFrame) / (nextFrame - prevFrame); track.Positions.Add(Vector3.Lerp(track.Positions[prevFrame], tempPositions[keyIdx], amount)); } track.Positions.Add(tempPositions[keyIdx]); frameIdx = nextFrame; } } } } if (numRotKeys > 0) { ms.JumpTo(rotOff); AnimationCompressionFormat compressionFormat = rotCompression; if (numRotKeys == 1) { compressionFormat = AnimationCompressionFormat.ACF_Float96NoW; } else if (compressedDataSource != MEGame.UDK) { ms.Skip(12 * 2); //skip mins and ranges } for (int j = 0; j < numRotKeys; j++) { switch (compressionFormat) { case AnimationCompressionFormat.ACF_None: track.Rotations.Add(new Quaternion(ms.ReadFloat(), ms.ReadFloat(), ms.ReadFloat(), ms.ReadFloat())); break; case AnimationCompressionFormat.ACF_Float96NoW: { float x = ms.ReadFloat(); float y = ms.ReadFloat(); float z = ms.ReadFloat(); track.Rotations.Add(new Quaternion(x, y, z, getW(x, y, z))); break; } case AnimationCompressionFormat.ACF_BioFixed48: { const float shift = 0.70710678118f; const float scale = 1.41421356237f; const float precisionMult = 32767.0f; ushort a = ms.ReadUInt16(); ushort b = ms.ReadUInt16(); ushort c = ms.ReadUInt16(); float x = (a & 0x7FFF) / precisionMult * scale - shift; float y = (b & 0x7FFF) / precisionMult * scale - shift; float z = (c & 0x7FFF) / precisionMult * scale - shift; float w = getW(x, y, z); int wPos = ((a >> 14) & 2) | ((b >> 15) & 1); track.Rotations.Add(wPos switch { 0 => new Quaternion(w, x, y, z), 1 => new Quaternion(x, w, y, z), 2 => new Quaternion(x, y, w, z), _ => new Quaternion(x, y, z, w) }); break; }