// function is similar to part of CSkelMeshInstance::SetMesh() and Rune mesh loader static void BuildSkeleton(TArray<CCoords> &Coords, const TArray<CSkelMeshBone> &Bones) { guard(BuildSkeleton); int numBones = Bones.Num(); Coords.Empty(numBones); Coords.AddZeroed(numBones); for (int i = 0; i < numBones; i++) { const CSkelMeshBone &B = Bones[i]; CCoords &BC = Coords[i]; // compute reference bone coords CVec3 BP; CQuat BO; // get default pose BP = B.Position; BO = B.Orientation; #if MIRROR_MESH BP[1] *= -1; // y BO.y *= -1; BO.w *= -1; #endif if (!i) BO.Conjugate(); // root bone BC.origin = BP; BO.ToAxis(BC.axis); // move bone position to global coordinate space if (i) // do not rotate root bone { assert(B.ParentIndex < i); Coords[B.ParentIndex].UnTransformCoords(BC, BC); } #if 0 //!! if (i == 32) { appNotify("Bone %d (%8.3f %8.3f %8.3f) - (%8.3f %8.3f %8.3f %8.3f)", i, VECTOR_ARG(BP), QUAT_ARG(BO)); #define C BC appNotify("REF : o=%8.3f %8.3f %8.3f", VECTOR_ARG(C.origin )); appNotify(" 0=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[0])); appNotify(" 1=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[1])); appNotify(" 2=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[2])); #undef C } #endif } unguard; }
void ExportStaticMeshGLTF(const CStaticMesh* Mesh) { guard(ExportStaticMeshGLTF); UObject *OriginalMesh = Mesh->OriginalMesh; if (!Mesh->Lods.Num()) { appNotify("Mesh %s has 0 lods", OriginalMesh->Name); return; } FArchive* Ar = CreateExportArchive(OriginalMesh, FAO_TextFile, "%s.gltf", OriginalMesh->Name); if (Ar) { ExportContext Context; Context.MeshName = OriginalMesh->Name; Context.StatMesh = Mesh; FArchive* Ar2 = CreateExportArchive(OriginalMesh, 0, "%s.bin", OriginalMesh->Name); assert(Ar2); ExportMeshLod(Context, Mesh->Lods[0], Mesh->Lods[0].Verts, *Ar, *Ar2); delete Ar; delete Ar2; } unguard; }
void UMeshAnimation::SerializeSCell(FArchive &Ar) { guard(SerializeSCell); // for logic of track decompression check UMeshAnimation::Moves() function int OldCompression = 0, CompressType = 0; TArray<MotionChunkFixedPoint> T0; // OldCompression!=0, CompressType=0 TArray<MotionChunkCompress<Quat16Track> > T1; // CompressType=1 TArray<MotionChunkCompress<FixPosTrack> > T2; // CompressType=2 TArray<MotionChunkCompress<FixTimeTrack> > T3; // CompressType=3 TArray<MotionChunkCompress<FixPosTimeTrack> > T4; // CompressType=4 if (Version >= 1000) { Ar << OldCompression << T0; // note: this compression type is not supported (absent BoneIndices in MotionChunkFixedPoint) } if (Version >= 2000) { Ar << CompressType << T1 << T2 << T3 << T4; // decompress motion if (CompressType) { int i = 0, Count = 1; while (i < Count) { MotionChunkCompressBase *M = NULL; switch (CompressType) { case 1: Count = T1.Num(); M = &T1[i]; break; case 2: Count = T2.Num(); M = &T2[i]; break; case 3: Count = T3.Num(); M = &T3[i]; break; case 4: Count = T4.Num(); M = &T4[i]; break; default: appError("Unsupported CompressType: %d", CompressType); } if (!Count) { appNotify("CompressType=%d with no tracks", CompressType); break; } if (!i) { // 1st iteration, prepare Moves[] array Moves.Empty(Count); Moves.AddZeroed(Count); } // decompress current track M->Decompress(Moves[i]); // next track i++; } } } // if (OldCompression) appNotify("OldCompression=%d", OldCompression, CompressType); unguard; }
void FStaticLODModel::RestoreLineageMesh() { guard(FStaticLODModel::RestoreLineageMesh); if (Wedges.Num()) return; // nothing to restore appPrintf("Converting Lineage2 LODModel to standard LODModel ...\n"); if (SmoothSections.Num() && RigidSections.Num()) appNotify("have smooth & rigid sections"); int i, j, k; int NumWedges = LineageWedges.Num() + VertexStream.Verts.Num(); // one of them is zero (ensured by assert below) if (!NumWedges) { appNotify("Cannot restore mesh: no wedges"); return; } assert(LineageWedges.Num() == 0 || VertexStream.Verts.Num() == 0); Wedges.Empty(NumWedges); Points.Empty(NumWedges); // really, should be a smaller count VertInfluences.Empty(NumWedges); // min count = NumVerts Faces.Empty((SmoothIndices.Indices.Num() + RigidIndices.Indices.Num()) / 3); TArray<FVector> PointNormals; PointNormals.Empty(NumWedges); // remap bones and build faces TArray<const FSkelMeshSection*> WedgeSection; WedgeSection.Empty(NumWedges); WedgeSection.AddZeroed(NumWedges); // smooth sections guard(SmoothWedges); for (k = 0; k < SmoothSections.Num(); k++) { const FSkelMeshSection &ms = SmoothSections[k]; for (i = 0; i < ms.NumFaces; i++) { FMeshFace *F = new (Faces) FMeshFace; F->MaterialIndex = ms.MaterialIndex; for (j = 0; j < 3; j++) { int WedgeIndex = SmoothIndices.Indices[(ms.FirstFace + i) * 3 + j]; assert(WedgeSection[WedgeIndex] == NULL || WedgeSection[WedgeIndex] == &ms); WedgeSection[WedgeIndex] = &ms; F->iWedge[j] = WedgeIndex; } } } unguard; guard(RigidWedges); // and the same code for rigid sections for (k = 0; k < RigidSections.Num(); k++) { const FSkelMeshSection &ms = RigidSections[k]; for (i = 0; i < ms.NumFaces; i++) { FMeshFace *F = new (Faces) FMeshFace; F->MaterialIndex = ms.MaterialIndex; for (j = 0; j < 3; j++) { int WedgeIndex = RigidIndices.Indices[(ms.FirstFace + i) * 3 + j]; assert(WedgeSection[WedgeIndex] == NULL || WedgeSection[WedgeIndex] == &ms); WedgeSection[WedgeIndex] = &ms; F->iWedge[j] = WedgeIndex; } } } unguard; // process wedges // convert LineageWedges (smooth sections) guard(BuildSmoothWedges); for (i = 0; i < LineageWedges.Num(); i++) { const FLineageWedge &LW = LineageWedges[i]; FVector VPos = LW.Point; // find the same point in previous items int PointIndex = -1; while (true) { PointIndex = Points.FindItem(VPos, PointIndex + 1); if (PointIndex == INDEX_NONE) break; if (PointNormals[PointIndex] == LW.Normal) break; } if (PointIndex == INDEX_NONE) { // point was not found - create it PointIndex = Points.AddUninitialized(); Points[PointIndex] = LW.Point; PointNormals.Add(LW.Normal); // build influences const FSkelMeshSection *ms = WedgeSection[i]; assert(ms); for (j = 0; j < 4; j++) { if (LW.Bones[j] == 255) continue; // no bone assigned float Weight = LW.Weights[j]; if (Weight < 0.000001f) continue; // zero weight FVertInfluence *Inf = new (VertInfluences) FVertInfluence; Inf->Weight = Weight; Inf->BoneIndex = ms->LineageBoneMap[LW.Bones[j]]; Inf->PointIndex = PointIndex; } } // create wedge FMeshWedge *W = new (Wedges) FMeshWedge; W->iVertex = PointIndex; W->TexUV = LW.Tex; } unguard; // similar code for VertexStream (rigid sections) guard(BuildRigidWedges); for (i = 0; i < VertexStream.Verts.Num(); i++) { const FAnimMeshVertex &LW = VertexStream.Verts[i]; FVector VPos = LW.Pos; // find the same point in previous items int PointIndex = -1; while (true) { PointIndex = Points.FindItem(VPos, PointIndex + 1); if (PointIndex == INDEX_NONE) break; if (LW.Norm == PointNormals[PointIndex]) break; } if (PointIndex == INDEX_NONE) { // point was not found - create it PointIndex = Points.AddUninitialized(); Points[PointIndex] = LW.Pos; PointNormals.Add(LW.Norm); // build influences const FSkelMeshSection *ms = WedgeSection[i]; assert(ms); FVertInfluence *Inf = new (VertInfluences) FVertInfluence; Inf->Weight = 1.0f; Inf->BoneIndex = /*VertexStream.Revision; //??*/ ms->BoneIndex; //-- equals 0 in Lineage2 ... Inf->PointIndex = PointIndex; } // create wedge FMeshWedge *W = new (Wedges) FMeshWedge; W->iVertex = PointIndex; W->TexUV = LW.Tex; } unguard; unguard; }
void FArchive::DetectGame() { if (GForcePlatform != PLATFORM_UNKNOWN) Platform = GForcePlatform; if (GForceGame != GAME_UNKNOWN) { Game = GForceGame; return; } // different game platforms autodetection //?? should change this, if will implement command line switch to force mode //?? code moved here, check code of other structs loaded below for ability to use Ar.IsGameName... //?? remove #if ... #endif guards - detect game even when its support is disabled // check for already detected game #if LINEAGE2 || EXTEEL if (Game == GAME_Lineage2) { if (ArLicenseeVer >= 1000) // lineage LicenseeVer < 1000, exteel >= 1000 Game = GAME_Exteel; return; } #endif if (Game != GAME_UNKNOWN) // may be GAME_Ragnarok2 return; // here Game == GAME_UNKNOWN int check = 0; // number of detected games; should be 0 or 1, otherwise autodetect is failed #define SET(game) { Game = game; check++; } /*----------------------------------------------------------------------- * UE2 games *-----------------------------------------------------------------------*/ // Digital Extremes games #if UT2 if ( ((ArVer >= 117 && ArVer <= 119) && (ArLicenseeVer >= 25 && ArLicenseeVer <= 27)) || (ArVer == 120 && (ArLicenseeVer == 27 || ArLicenseeVer == 28)) || ((ArVer >= 121 && ArVer <= 128) && ArLicenseeVer == 29) ) SET(GAME_UT2); #endif #if PARIAH if (ArVer == 119 && ArLicenseeVer == 0x9127) SET(GAME_Pariah); #endif #if UC1 if (ArVer == 119 && (ArLicenseeVer == 28 || ArLicenseeVer == 30)) SET(GAME_UC1); #endif #if UC2 if (ArVer == 151 && (ArLicenseeVer == 0 || ArLicenseeVer == 1)) SET(GAME_UC2); #endif #if LOCO if ((ArVer >= 131 && ArVer <= 134) && ArLicenseeVer == 29) SET(GAME_Loco); #endif #if SPLINTER_CELL if ( (ArVer == 100 && (ArLicenseeVer >= 9 && ArLicenseeVer <= 17)) || // Splinter Cell 1 (ArVer == 102 && (ArLicenseeVer >= 29 && ArLicenseeVer <= 28)) ) // Splinter Cell 2 SET(GAME_SplinterCell); #endif #if SWRC if ( ArLicenseeVer == 1 && ( (ArVer >= 133 && ArVer <= 148) || (ArVer >= 154 && ArVer <= 159) ) ) SET(GAME_RepCommando); #endif #if TRIBES3 if ( ((ArVer == 129 || ArVer == 130) && (ArLicenseeVer >= 0x17 && ArLicenseeVer <= 0x1B)) || ((ArVer == 123) && (ArLicenseeVer >= 3 && ArLicenseeVer <= 0xF )) || ((ArVer == 126) && (ArLicenseeVer >= 0x12 && ArLicenseeVer <= 0x17)) ) SET(GAME_Tribes3); #endif #if BIOSHOCK if ( (ArVer == 141 && (ArLicenseeVer == 56 || ArLicenseeVer == 57)) || //?? Bioshock and Bioshock 2 (ArVer == 143 && ArLicenseeVer == 59) ) // Bioshock 2 multiplayer? SET(GAME_Bioshock); #endif /*----------------------------------------------------------------------- * UE3 games *-----------------------------------------------------------------------*/ // most UE3 games has single version for all packages // here is a list of such games, sorted by version #if R6VEGAS if (ArVer == 241 && ArLicenseeVer == 71) SET(GAME_R6Vegas2); #endif //#if ENDWAR // if (ArVer == 329 && ArLicenseeVer == 0) SET(GAME_EndWar); // LicenseeVer == 0 //#endif #if STRANGLE if (ArVer == 375 && ArLicenseeVer == 25) SET(GAME_Strangle); //!! has extra tag #endif #if A51 if (ArVer == 377 && ArLicenseeVer == 25) SET(GAME_A51); //!! has extra tag #endif #if WHEELMAN if (ArVer == 390 && ArLicenseeVer == 32) SET(GAME_Wheelman); //!! has extra tag #endif #if FURY if (ArVer == 407 && (ArLicenseeVer == 26 || ArLicenseeVer == 36)) SET(GAME_Fury); #endif #if MOHA if (ArVer == 421 && ArLicenseeVer == 11) SET(GAME_MOHA); #endif #if UNDERTOW // if (ArVer == 435 && ArLicenseeVer == 0) SET(GAME_Undertow); // LicenseeVer==0! #endif #if MCARTA if (ArVer == 446 && ArLicenseeVer == 25) SET(GAME_MagnaCarta); #endif #if AVA if (ArVer == 451 && (ArLicenseeVer >= 52 || ArLicenseeVer <= 53)) SET(GAME_AVA); #endif #if DOH if (ArVer == 455 && ArLicenseeVer == 90) SET(GAME_DOH); #endif #if TLR if (ArVer == 507 && ArLicenseeVer == 11) SET(GAME_TLR); #endif #if MEDGE if (ArVer == 536 && ArLicenseeVer == 43) SET(GAME_MirrorEdge); #endif #if BLOODONSAND if (ArVer == 538 && ArLicenseeVer == 73) SET(GAME_50Cent); #endif #if ARGONAUTS if (ArVer == 539 && (ArLicenseeVer == 43 || ArLicenseeVer == 47)) SET(GAME_Argonauts); // Rise of the Argonauts, Thor: God of Thunder #endif #if ALPHA_PR if (ArVer == 539 && ArLicenseeVer == 91) SET(GAME_AlphaProtocol); #endif #if APB if (ArVer == 547 && (ArLicenseeVer == 31 || ArLicenseeVer == 32)) SET(GAME_APB); #endif #if LEGENDARY if (ArVer == 567 && ArLicenseeVer == 39) SET(GAME_Legendary); #endif //#if AA3 // if (ArVer == 568 && ArLicenseeVer == 0) SET(GAME_AA3); //!! LicenseeVer == 0 ! bad! //#endif #if XMEN if (ArVer == 568 && ArLicenseeVer == 101) SET(GAME_XMen); #endif #if CRIMECRAFT if (ArVer == 576 && ArLicenseeVer == 5) SET(GAME_CrimeCraft); #endif #if BATMAN if (ArVer == 576 && ArLicenseeVer == 21) SET(GAME_Batman); #endif #if DARKVOID if (ArVer == 576 && (ArLicenseeVer == 61 || ArLicenseeVer == 66)) SET(GAME_DarkVoid); // demo and release #endif #if MOH2010 if (ArVer == 581 && ArLicenseeVer == 58) SET(GAME_MOH2010); #endif #if SINGULARITY if (ArVer == 584 && ArLicenseeVer == 126) SET(GAME_Singularity); #endif #if TRON if (ArVer == 648 && ArLicenseeVer == 3) SET(GAME_Tron); #endif #if DCU_ONLINE if (ArVer == 648 && ArLicenseeVer == 6405) SET(GAME_DCUniverse); #endif #if ENSLAVED if (ArVer == 673 && ArLicenseeVer == 2) SET(GAME_Enslaved); #endif #if MORTALONLINE if (ArVer == 678 && ArLicenseeVer == 32771) SET(GAME_MortalOnline); #endif #if ALICE if (ArVer == 690 && ArLicenseeVer == 0) SET(GAME_Alice); // only this game has LicenseeVer==0 here! #endif #if SHADOWS_DAMNED if (ArVer == 706 && ArLicenseeVer == 28) SET(GAME_ShadowsDamned); #endif #if DUST514 if (ArVer == 708 && ArLicenseeVer == 35) SET(GAME_Dust514); #endif #if THIEF4 if (ArVer == 721 && ArLicenseeVer == 148) SET(GAME_Thief4); #endif #if BIOSHOCK3 if (ArVer == 727 && ArLicenseeVer == 75) SET(GAME_Bioshock3); #endif #if BULLETSTORM if (ArVer == 742 && ArLicenseeVer == 29) SET(GAME_Bulletstorm); #endif #if ALIENS_CM if (ArVer == 787 && ArLicenseeVer == 47) SET(GAME_AliensCM); #endif #if DISHONORED if (ArVer == 801 && ArLicenseeVer == 30) SET(GAME_Dishonored); #endif #if TRIBES4 if (ArVer == 805 && ArLicenseeVer == 2) SET(GAME_Tribes4); #endif #if BATMAN if (ArVer == 805 && ArLicenseeVer == 101) SET(GAME_Batman2); if ( (ArVer == 806 || ArVer == 807) && (ArLicenseeVer == 103 || ArLicenseeVer == 137 || ArLicenseeVer == 138) ) SET(GAME_Batman3); #endif #if DMC if (ArVer == 845 && ArLicenseeVer == 4) SET(GAME_DmC); #endif #if XCOM_BUREAU if (ArVer == 849 && ArLicenseeVer == 32795) SET(GAME_XcomB); #endif #if FABLE if ( (ArVer == 850 || ArVer == 860) && (ArLicenseeVer == 1017 || ArLicenseeVer == 26985) ) // 850 = Fable: The Journey, 860 = Fable Anniversary SET(GAME_Fable); #endif #if MURDERED if (ArVer == 860 && ArLicenseeVer == 93) SET(GAME_Murdered); #endif #if LOST_PLANET3 if (ArVer == 860 && (ArLicenseeVer == 97 || ArLicenseeVer == 98)) // 97 = Lost Planet 3, 98 = Yaiba: Ninja Gaiden Z SET(GAME_LostPlanet3); #endif #if GUILTY if (ArVer == 868 && ArLicenseeVer == 2) SET(GAME_Guilty); #endif #if SPECIALFORCE2 if (ArVer == 904 && (ArLicenseeVer == 9 || ArLicenseeVer == 14)) SET(GAME_SpecialForce2); #endif // UE3 games with the various versions of files #if TUROK if ( (ArVer == 374 && ArLicenseeVer == 16) || (ArVer == 375 && ArLicenseeVer == 19) || (ArVer == 392 && ArLicenseeVer == 23) || (ArVer == 393 && (ArLicenseeVer >= 27 && ArLicenseeVer <= 61)) ) SET(GAME_Turok); #endif #if TNA_IMPACT if ((ArVer == 380 && ArLicenseeVer == 35) || // TNA Impact (ArVer == 398 && ArLicenseeVer == 37)) // WWE All Stars SET(GAME_TNA); //!! has extra tag #endif #if MASSEFF if ((ArVer == 391 && ArLicenseeVer == 92) || // XBox 360 version (ArVer == 491 && ArLicenseeVer == 1008)) // PC version SET(GAME_MassEffect); if (ArVer == 512 && ArLicenseeVer == 130) SET(GAME_MassEffect2); if (ArVer == 684 && (ArLicenseeVer == 185 || ArLicenseeVer == 194)) // 185 = demo, 194 = release SET(GAME_MassEffect3); #endif #if MKVSDC if ( (ArVer == 402 && ArLicenseeVer == 30) || //!! has extra tag; MK vs DC (ArVer == 472 && ArLicenseeVer == 46) || // Mortal Kombat (ArVer == 573 && ArLicenseeVer == 49) || // Injustice: God Among Us (ArVer == 677 && ArLicenseeVer == 157) ) // Mortal Kombat X SET(GAME_MK); #endif #if HUXLEY if ( (ArVer == 402 && (ArLicenseeVer == 0 || ArLicenseeVer == 10)) || //!! has extra tag (ArVer == 491 && (ArLicenseeVer >= 13 && ArLicenseeVer <= 16)) || (ArVer == 496 && (ArLicenseeVer >= 16 && ArLicenseeVer <= 23)) ) SET(GAME_Huxley); #endif #if FRONTLINES if ( (ArVer == 433 && ArLicenseeVer == 52) || // Frontlines: Fuel of War (ArVer == 576 && ArLicenseeVer == 100) ) // Homefront SET(GAME_Frontlines); #endif #if ARMYOF2 if ( (ArVer == 445 && ArLicenseeVer == 79) || // Army of Two (ArVer == 482 && ArLicenseeVer == 222) || // Army of Two: the 40th Day (ArVer == 483 && ArLicenseeVer == 4317) ) // ... SET(GAME_ArmyOf2); #endif #if TRANSFORMERS if ( (ArVer == 511 && ArLicenseeVer == 39 ) || // The Bourne Conspiracy (ArVer == 511 && ArLicenseeVer == 145) || // Transformers: War for Cybertron (PC version) (ArVer == 511 && ArLicenseeVer == 144) || // Transformers: War for Cybertron (PS3 and XBox 360 version) (ArVer == 537 && ArLicenseeVer == 174) || // Transformers: Dark of the Moon (ArVer == 846 && ArLicenseeVer == 181) ) // Transformers: Fall of Cybertron SET(GAME_Transformers); #endif #if BORDERLANDS if ( (ArVer == 512 && ArLicenseeVer == 35) || // Brothers in Arms: Hell's Highway (ArVer == 584 && (ArLicenseeVer == 57 || ArLicenseeVer == 58)) || // Borderlands: release and update (ArVer == 832 && ArLicenseeVer == 46) ) // Borderlands 2 SET(GAME_Borderlands); #endif #if TERA if ((ArVer == 568 && (ArLicenseeVer >= 9 && ArLicenseeVer <= 10)) || (ArVer == 610 && (ArLicenseeVer >= 13 && ArLicenseeVer <= 14))) SET(GAME_Tera); #endif #if REMEMBER_ME if ((ArVer == 832 || ArVer == 893) && ArLicenseeVer == 21) // Remember Me (832) or Life Is Strange (893) SET(GAME_RememberMe); #endif if (check > 1) appNotify("DetectGame detected a few titles (%d): Ver=%d, LicVer=%d", check, ArVer, ArLicenseeVer); if (Game == GAME_UNKNOWN) { // generic or unknown engine if (ArVer < PACKAGE_V2) Game = GAME_UE1; else if (ArVer < PACKAGE_V3) Game = GAME_UE2; else Game = GAME_UE3; } #undef SET }
void ExportMd5Mesh(const CSkeletalMesh *Mesh) { guard(ExportMd5Mesh); int i; UObject *OriginalMesh = Mesh->OriginalMesh; if (!Mesh->Lods.Num()) { appNotify("Mesh %s has 0 lods", OriginalMesh->Name); return; } FArchive *Ar = CreateExportArchive(OriginalMesh, "%s.md5mesh", OriginalMesh->Name); if (!Ar) return; const CSkelMeshLod &Lod = Mesh->Lods[0]; Ar->Printf( "MD5Version 10\n" "commandline \"Created with UE Viewer\"\n" "\n" "numJoints %d\n" "numMeshes %d\n" "\n", Mesh->RefSkeleton.Num(), Lod.Sections.Num() ); // compute skeleton TArray<CCoords> BoneCoords; BuildSkeleton(BoneCoords, Mesh->RefSkeleton); // write joints Ar->Printf("joints {\n"); for (i = 0; i < Mesh->RefSkeleton.Num(); i++) { const CSkelMeshBone &B = Mesh->RefSkeleton[i]; const CCoords &BC = BoneCoords[i]; CVec3 BP; CQuat BO; BP = BC.origin; BO.FromAxis(BC.axis); if (BO.w < 0) BO.Negate(); // W-component of quaternion will be removed ... Ar->Printf( "\t\"%s\"\t%d ( %f %f %f ) ( %.10f %.10f %.10f )\n", *B.Name, (i == 0) ? -1 : B.ParentIndex, VECTOR_ARG(BP), BO.x, BO.y, BO.z ); #if 0 //!! if (i == 32 || i == 34) { CCoords BC; BC.origin = BP; BO.ToAxis(BC.axis); appNotify("Bone %d (%8.3f %8.3f %8.3f) - (%8.3f %8.3f %8.3f %8.3f)", i, VECTOR_ARG(BP), QUAT_ARG(BO)); #define C BC appNotify("INV : o=%8.3f %8.3f %8.3f", VECTOR_ARG(C.origin )); appNotify(" 0=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[0])); appNotify(" 1=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[1])); appNotify(" 2=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[2])); #undef C // BO.Negate(); BO.w *= -1; BO.ToAxis(BC.axis); #define C BC appNotify("INV2 : o=%8.3f %8.3f %8.3f", VECTOR_ARG(C.origin )); appNotify(" 0=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[0])); appNotify(" 1=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[1])); appNotify(" 2=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[2])); #undef C } #endif } Ar->Printf("}\n\n"); // collect weights information TArray<VertInfluences> Weights; // Point -> Influences Weights.AddZeroed(Lod.NumVerts); for (i = 0; i < Lod.NumVerts; i++) { const CSkelMeshVertex &V = Lod.Verts[i]; CVec4 UnpackedWeights; V.UnpackWeights(UnpackedWeights); for (int j = 0; j < NUM_INFLUENCES; j++) { if (V.Bone[j] < 0) break; VertInfluence *I = new (Weights[i].Inf) VertInfluence; I->Bone = V.Bone[j]; I->Weight = UnpackedWeights[j]; } } CIndexBuffer::IndexAccessor_t Index = Lod.Indices.GetAccessor(); // write meshes // we are using some terms here: // - "mesh vertex" is a vertex in Lod.Verts[] array, global for whole mesh // - "surcace vertex" is a vertex from the mesh stripped to only one (current) section for (int m = 0; m < Lod.Sections.Num(); m++) { const CMeshSection &Sec = Lod.Sections[m]; TArray<int> MeshVerts; // surface vertex -> mesh vertex TArray<int> BackWedge; // mesh vertex -> surface vertex TArray<bool> UsedVerts; // mesh vertex -> surface: used of not TArray<int> MeshWeights; // mesh vertex -> weight index MeshVerts.Empty(Lod.NumVerts); UsedVerts.AddZeroed(Lod.NumVerts); BackWedge.AddZeroed(Lod.NumVerts); MeshWeights.AddZeroed(Lod.NumVerts); // find verts and triangles for current material for (i = 0; i < Sec.NumFaces * 3; i++) { int idx = Index(i + Sec.FirstIndex); if (UsedVerts[idx]) continue; // vertex is already used in previous triangle UsedVerts[idx] = true; int locWedge = MeshVerts.Add(idx); BackWedge[idx] = locWedge; } // find influences int WeightIndex = 0; for (i = 0; i < Lod.NumVerts; i++) { if (!UsedVerts[i]) continue; MeshWeights[i] = WeightIndex; WeightIndex += Weights[i].Inf.Num(); } // mesh header const UUnrealMaterial *Tex = Sec.Material; if (Tex) { Ar->Printf( "mesh {\n" "\tshader \"%s\"\n\n", Tex->Name ); ExportObject(Tex); } else { Ar->Printf( "mesh {\n" "\tshader \"material_%d\"\n\n", m ); } // verts Ar->Printf("\tnumverts %d\n", MeshVerts.Num()); for (i = 0; i < MeshVerts.Num(); i++) { int iPoint = MeshVerts[i]; const CSkelMeshVertex &V = Lod.Verts[iPoint]; Ar->Printf("\tvert %d ( %f %f ) %d %d\n", i, V.UV.U, V.UV.V, MeshWeights[iPoint], Weights[iPoint].Inf.Num()); } // triangles Ar->Printf("\n\tnumtris %d\n", Sec.NumFaces); for (i = 0; i < Sec.NumFaces; i++) { Ar->Printf("\ttri %d", i); #if MIRROR_MESH for (int j = 2; j >= 0; j--) #else for (int j = 0; j < 3; j++) #endif Ar->Printf(" %d", BackWedge[Index(Sec.FirstIndex + i * 3 + j)]); Ar->Printf("\n"); } // weights Ar->Printf("\n\tnumweights %d\n", WeightIndex); int saveWeightIndex = WeightIndex; WeightIndex = 0; for (i = 0; i < Lod.NumVerts; i++) { if (!UsedVerts[i]) continue; for (int j = 0; j < Weights[i].Inf.Num(); j++) { const VertInfluence &I = Weights[i].Inf[j]; CVec3 v; v = Lod.Verts[i].Position; #if MIRROR_MESH v[1] *= -1; // y #endif BoneCoords[I.Bone].TransformPoint(v, v); Ar->Printf( "\tweight %d %d %f ( %f %f %f )\n", WeightIndex, I.Bone, I.Weight, VECTOR_ARG(v) ); WeightIndex++; } } assert(saveWeightIndex == WeightIndex); // mesh footer Ar->Printf("}\n"); } delete Ar; unguard; }
bool UTexture::GetTextureData(CTextureData &TexData) const { guard(UTexture::GetTextureData); TexData.Platform = PLATFORM_PC; TexData.OriginalFormatEnum = Format; TexData.OriginalFormatName = EnumToName("ETextureFormat", Format); TexData.Obj = this; TexData.Palette = Palette; const FArchive* PackageAr = GetPackageArchive(); // process external sources for some games #if BIOSHOCK if (PackageAr && PackageAr->Game == GAME_Bioshock && CachedBulkDataSize) //?? check bStripped or Baked ? { byte* CompressedData = FindBioTexture(this); // may be NULL if (CompressedData) { CMipMap* DstMip = new (TexData.Mips) CMipMap; DstMip->CompressedData = CompressedData; DstMip->ShouldFreeData = true; DstMip->USize = USize; DstMip->VSize = VSize; DstMip->DataSize = (int)CachedBulkDataSize; } TexData.Platform = PackageAr->Platform; } #endif // BIOSHOCK #if UC2 if (PackageAr && PackageAr->Engine() == GAME_UE2X) { // try to find texture inside XBox xpr files int DataSize; byte* CompressedData = FindXprData(Name, &DataSize); // may be NULL if (CompressedData) { CMipMap* DstMip = new (TexData.Mips) CMipMap; DstMip->CompressedData = CompressedData; DstMip->ShouldFreeData = true; DstMip->USize = USize; DstMip->VSize = VSize; DstMip->DataSize = DataSize; } } #endif // UC2 if (TexData.Mips.Num() == 0) { // texture was not taken from external source for (int n = 0; n < Mips.Num(); n++) { // find 1st mipmap with non-null data array // reference: DemoPlayerSkins.utx/DemoSkeleton have null-sized 1st 2 mips const FMipmap &Mip = Mips[n]; if (!Mip.DataArray.Num()) continue; CMipMap* DstMip = new (TexData.Mips) CMipMap; DstMip->CompressedData = &Mip.DataArray[0]; DstMip->ShouldFreeData = false; DstMip->USize = Mip.USize; DstMip->VSize = Mip.VSize; DstMip->DataSize = Mip.DataArray.Num(); break; } } ETexturePixelFormat intFormat; //?? return old code back - UE1 and UE2 differs in codes 6 and 7 only if (Package && (PackageAr->Engine() == GAME_UE1)) { // UE1 has different ETextureFormat layout switch (Format) { case 0: intFormat = TPF_P8; break; // case 1: // intFormat = TPF_RGB32; // in script source code: TEXF_RGB32, but TEXF_RGBA7 in .h // break; // case 2: // intFormat = TPF_RGB64; // in script source code: TEXF_RGB64, but TEXF_RGB16 in .h // break; case 3: intFormat = TPF_DXT1; break; case 4: intFormat = TPF_RGB8; break; case 5: intFormat = TPF_BGRA8; break; // newer UE1 versions has DXT3 and DXT5 case 6: intFormat = TPF_DXT3; break; case 7: intFormat = TPF_DXT5; break; default: appNotify("Unknown UE1 texture format: %d", Format); return false; } } else { // UE2 switch (Format) { case TEXF_P8: intFormat = TPF_P8; break; case TEXF_DXT1: intFormat = TPF_DXT1; break; case TEXF_RGB8: intFormat = TPF_RGB8; break; case TEXF_RGBA8: intFormat = TPF_BGRA8; break; case TEXF_DXT3: intFormat = TPF_DXT3; break; case TEXF_DXT5: intFormat = TPF_DXT5; break; case TEXF_L8: intFormat = TPF_G8; break; case TEXF_CxV8U8: intFormat = TPF_V8U8_2; break; case TEXF_DXT5N: intFormat = TPF_DXT5N; break; case TEXF_3DC: intFormat = TPF_BC5; break; default: appNotify("Unknown UE2 texture format: %s (%d)", TexData.OriginalFormatName, Format); return false; } } TexData.Format = intFormat; #if BIOSHOCK && SUPPORT_XBOX360 if (TexData.Mips.Num() && TexData.Platform == PLATFORM_XBOX360) { for (int MipLevel = 0; MipLevel < TexData.Mips.Num(); MipLevel++) TexData.DecodeXBox360(MipLevel); } #endif #if BIOSHOCK if (Package && PackageAr->Game == GAME_Bioshock) { // This game has DataSize stored for all mipmaps, we should compute side of 1st mipmap // in order to accept this value when uploading texture to video card (some vendors rejects // large values) //?? Place code to CTextureData method? const CPixelFormatInfo &Info = PixelFormatInfo[intFormat]; int numBlocks = TexData.Mips[0].USize * TexData.Mips[0].VSize / (Info.BlockSizeX * Info.BlockSizeY); // used for validation only int requiredDataSize = numBlocks * Info.BytesPerBlock; if (requiredDataSize > TexData.Mips[0].DataSize) { appNotify("Bioshock texture %s: data too small; %dx%d, requires %X bytes, got %X\n", Name, TexData.Mips[0].USize, TexData.Mips[0].VSize, requiredDataSize, TexData.Mips[0].DataSize); } else if (requiredDataSize < TexData.Mips[0].DataSize) { // appPrintf("Bioshock texture %s: stripping data size from %X to %X\n", Name, TexData.Mips[0].DataSize, requiredDataSize); TexData.Mips[0].DataSize = requiredDataSize; } } #endif // BIOSHOCK return (TexData.Mips.Num() > 0); unguardf("%s", Name); }
void CSkelMeshViewer::Dump() { CMeshViewer::Dump(); appPrintf("\n"); Mesh->GetTypeinfo()->DumpProps(Mesh); #if 0 if (Mesh->OriginalMesh->IsA("SkeletalMesh")) // UE2 class { const USkeletalMesh *OriginalMesh = static_cast<USkeletalMesh*>(Mesh->OriginalMesh); appPrintf( "\nSkelMesh info:\n==============\n" "Bones # %4d Points # %4d Points2 # %4d\n" "Wedges # %4d Triangles # %4d\n" "CollapseWedge # %4d f1C8 # %4d\n" "BoneDepth %d\n" "WeightIds # %d BoneInfs # %d VertInfs # %d\n" "Attachments # %d\n" "LODModels # %d\n" "Animations: %s\n", OriginalMesh->RefSkeleton.Num(), OriginalMesh->Points.Num(), OriginalMesh->Points2.Num(), OriginalMesh->Wedges.Num(), OriginalMesh->Triangles.Num(), OriginalMesh->CollapseWedge.Num(), OriginalMesh->f1C8.Num(), OriginalMesh->SkeletalDepth, OriginalMesh->WeightIndices.Num(), OriginalMesh->BoneInfluences.Num(), OriginalMesh->VertInfluences.Num(), OriginalMesh->AttachBoneNames.Num(), OriginalMesh->LODModels.Num(), OriginalMesh->Animation ? OriginalMesh->Animation->Name : "None" ); int i; // check bone sort order (assumed, that child go after parent) for (i = 0; i < OriginalMesh->RefSkeleton.Num(); i++) { const FMeshBone &B = OriginalMesh->RefSkeleton[i]; if (B.ParentIndex >= i + 1) appNotify("bone[%d] has parent %d", i+1, B.ParentIndex); } for (i = 0; i < OriginalMesh->LODModels.Num(); i++) { appPrintf("model # %d\n", i); const FStaticLODModel &lod = OriginalMesh->LODModels[i]; appPrintf( " SkinningData=%d SkinPoints=%d inf=%d wedg=%d dynWedges=%d faces=%d points=%d\n" " DistanceFactor=%g Hysteresis=%g SharedVerts=%d MaxInfluences=%d 114=%d 118=%d\n" " smoothInds=%d rigidInds=%d vertStream.Size=%d\n", lod.SkinningData.Num(), lod.SkinPoints.Num(), lod.VertInfluences.Num(), lod.Wedges.Num(), lod.NumDynWedges, lod.Faces.Num(), lod.Points.Num(), lod.LODDistanceFactor, lod.LODHysteresis, lod.NumSharedVerts, lod.LODMaxInfluences, lod.f114, lod.f118, lod.SmoothIndices.Indices.Num(), lod.RigidIndices.Indices.Num(), lod.VertexStream.Verts.Num()); int i0 = 99999999, i1 = -99999999; int j; for (j = 0; j < lod.SmoothIndices.Indices.Num(); j++) { int x = lod.SmoothIndices.Indices[j]; if (x < i0) i0 = x; if (x > i1) i1 = x; } appPrintf(" smoothIndices: [%d .. %d]\n", i0, i1); i0 = 99999999; i1 = -99999999; for (j = 0; j < lod.RigidIndices.Indices.Num(); j++) { int x = lod.RigidIndices.Indices[j]; if (x < i0) i0 = x; if (x > i1) i1 = x; } appPrintf(" rigidIndices: [%d .. %d]\n", i0, i1); const TArray<FSkelMeshSection> *sec[2]; sec[0] = &lod.SmoothSections; sec[1] = &lod.RigidSections; static const char *secNames[] = { "smooth", "rigid" }; for (int k = 0; k < 2; k++) { appPrintf(" %s sections: %d\n", secNames[k], sec[k]->Num()); for (int j = 0; j < sec[k]->Num(); j++) { const FSkelMeshSection &S = (*sec[k])[j]; appPrintf(" %d: mat=%d %d [w=%d .. %d] %d b=%d %d [f=%d + %d]\n", j, S.MaterialIndex, S.MinStreamIndex, S.MinWedgeIndex, S.MaxWedgeIndex, S.NumStreamIndices, S.BoneIndex, S.fE, S.FirstFace, S.NumFaces); } } } } #endif }
void USkeleton::ConvertAnims(UAnimSequence4* Seq) { guard(USkeleton::ConvertAnims); CAnimSet* AnimSet = ConvertedAnim; if (!AnimSet) { AnimSet = new CAnimSet(this); ConvertedAnim = AnimSet; // Copy bone names AnimSet->TrackBoneNames.Empty(ReferenceSkeleton.RefBoneInfo.Num()); for (int i = 0; i < ReferenceSkeleton.RefBoneInfo.Num(); i++) { AnimSet->TrackBoneNames.Add(ReferenceSkeleton.RefBoneInfo[i].Name); } //TODO: verify if UE4 has AnimRotationOnly stuff AnimSet->AnimRotationOnly = false; } if (!Seq) return; // allow calling ConvertAnims(NULL) to create empty AnimSet // DBG("----------- Skeleton %s: %d seq, %d bones -----------\n", Name, Anims.Num(), ReferenceSkeleton.RefBoneInfo.Num()); int NumTracks = Seq->GetNumTracks(); #if DEBUG_DECOMPRESS appPrintf("Sequence %s: %d bones, %d offsets (%g per bone), %d frames, %d compressed data\n" " trans %s, rot %s, scale %s, key %s\n", Seq->Name, NumTracks, Seq->CompressedTrackOffsets.Num(), Seq->CompressedTrackOffsets.Num() / (float)NumTracks, Seq->NumFrames, Seq->CompressedByteStream.Num(), EnumToName(Seq->TranslationCompressionFormat), EnumToName(Seq->RotationCompressionFormat), EnumToName(Seq->ScaleCompressionFormat), EnumToName(Seq->KeyEncodingFormat) ); for (int i2 = 0; i2 < Seq->CompressedTrackOffsets.Num(); /*empty*/) { if (Seq->KeyEncodingFormat != AKF_PerTrackCompression) { FName BoneName = ReferenceSkeleton.RefBoneInfo[Seq->GetTrackBoneIndex(i2/4)].Name; int TransOffset = Seq->CompressedTrackOffsets[i2 ]; int TransKeys = Seq->CompressedTrackOffsets[i2+1]; int RotOffset = Seq->CompressedTrackOffsets[i2+2]; int RotKeys = Seq->CompressedTrackOffsets[i2+3]; appPrintf(" [%d] = trans %d[%d] rot %d[%d] - %s\n", i2/4, TransOffset, TransKeys, RotOffset, RotKeys, *BoneName); i2 += 4; } else { FName BoneName = ReferenceSkeleton.RefBoneInfo[Seq->GetTrackBoneIndex(i2/2)].Name; int TransOffset = Seq->CompressedTrackOffsets[i2 ]; int RotOffset = Seq->CompressedTrackOffsets[i2+1]; appPrintf(" [%d] = trans %d rot %d - %s\n", i2/2, TransOffset, RotOffset, *BoneName); i2 += 2; } } #endif // DEBUG_DECOMPRESS // some checks int offsetsPerBone = 4; if (Seq->KeyEncodingFormat == AKF_PerTrackCompression) offsetsPerBone = 2; if (Seq->CompressedTrackOffsets.Num() != NumTracks * offsetsPerBone && !Seq->RawAnimationData.Num()) { appNotify("AnimSequence %s has wrong CompressedTrackOffsets size (has %d, expected %d), removing track", Seq->Name, Seq->CompressedTrackOffsets.Num(), NumTracks * offsetsPerBone); return; } // create CAnimSequence CAnimSequence *Dst = new CAnimSequence; AnimSet->Sequences.Add(Dst); Dst->Name = Seq->Name; Dst->NumFrames = Seq->NumFrames; Dst->Rate = Seq->NumFrames / Seq->SequenceLength * Seq->RateScale; // bone tracks ... Dst->Tracks.Empty(NumTracks); FMemReader Reader(Seq->CompressedByteStream.GetData(), Seq->CompressedByteStream.Num()); Reader.SetupFrom(*Package); bool HasTimeTracks = (Seq->KeyEncodingFormat == AKF_VariableKeyLerp); for (int BoneIndex = 0; BoneIndex < ReferenceSkeleton.RefBoneInfo.Num(); BoneIndex++) { CAnimTrack *A = new (Dst->Tracks) CAnimTrack; int TrackIndex = Seq->FindTrackForBoneIndex(BoneIndex); if (TrackIndex < 0) { // this track has no animation, use static pose from ReferenceSkeleton const FTransform& RefPose = ReferenceSkeleton.RefBonePose[BoneIndex]; A->KeyPos.Add(CVT(RefPose.Translation)); A->KeyQuat.Add(CVT(RefPose.Rotation)); //!! RefPose.Scale3D continue; } int k; if (!Seq->CompressedTrackOffsets.Num()) //?? or if RawAnimData.Num() != 0 { // using RawAnimData array assert(Seq->RawAnimationData.Num() == NumTracks); CopyArray(A->KeyPos, CVT(Seq->RawAnimationData[TrackIndex].PosKeys)); CopyArray(A->KeyQuat, CVT(Seq->RawAnimationData[TrackIndex].RotKeys)); CopyArray(A->KeyTime, Seq->RawAnimationData[TrackIndex].KeyTimes); // may be empty for (int k = 0; k < A->KeyTime.Num(); k++) A->KeyTime[k] *= Dst->Rate; continue; } FVector Mins, Ranges; // common ... static const CVec3 nullVec = { 0, 0, 0 }; static const CQuat nullQuat = { 0, 0, 0, 1 }; int offsetIndex = TrackIndex * offsetsPerBone; // PARAGON has invalid data inside some animation tracks. Not sure if game engine ignores them // or trying to process (this game has holes in data due to wrong pointers in CompressedTrackOffsets). // This causes garbage data to appear instead of real animation track header, with wrong compression // method etc. We're going to skip such tracks with displaying a warning message. if (0) // this is just a placeholder for error handler - it should be located somewhere { track_error: AnimSet->Sequences.RemoveSingle(Dst); delete Dst; return; } //---------------------------------------------- // decode AKF_PerTrackCompression data //---------------------------------------------- if (Seq->KeyEncodingFormat == AKF_PerTrackCompression) { // this format uses different key storage guard(PerTrackCompression); assert(Seq->TranslationCompressionFormat == ACF_Identity); assert(Seq->RotationCompressionFormat == ACF_Identity); int TransOffset = Seq->CompressedTrackOffsets[offsetIndex ]; int RotOffset = Seq->CompressedTrackOffsets[offsetIndex+1]; uint32 PackedInfo; AnimationCompressionFormat KeyFormat; int ComponentMask; int NumKeys; #define DECODE_PER_TRACK_INFO(info) \ KeyFormat = (AnimationCompressionFormat)(info >> 28); \ ComponentMask = (info >> 24) & 0xF; \ NumKeys = info & 0xFFFFFF; \ HasTimeTracks = (ComponentMask & 8) != 0; guard(TransKeys); // read translation keys if (TransOffset == -1) { A->KeyPos.Add(nullVec); DBG(" [%d] no translation data\n", TrackIndex); } else { Reader.Seek(TransOffset); Reader << PackedInfo; DECODE_PER_TRACK_INFO(PackedInfo); A->KeyPos.Empty(NumKeys); DBG(" [%d] trans: fmt=%d (%s), %d keys, mask %d\n", TrackIndex, KeyFormat, EnumToName(KeyFormat), NumKeys, ComponentMask ); if (KeyFormat == ACF_IntervalFixed32NoW) { // read mins/maxs Mins.Set(0, 0, 0); Ranges.Set(0, 0, 0); if (ComponentMask & 1) Reader << Mins.X << Ranges.X; if (ComponentMask & 2) Reader << Mins.Y << Ranges.Y; if (ComponentMask & 4) Reader << Mins.Z << Ranges.Z; } for (k = 0; k < NumKeys; k++) { switch (KeyFormat) { // case ACF_None: case ACF_Float96NoW: { FVector v; if (ComponentMask & 7) { v.Set(0, 0, 0); if (ComponentMask & 1) Reader << v.X; if (ComponentMask & 2) Reader << v.Y; if (ComponentMask & 4) Reader << v.Z; } else { // ACF_Float96NoW has a special case for ((ComponentMask & 7) == 0) Reader << v; } A->KeyPos.Add(CVT(v)); } break; TPR(ACF_IntervalFixed32NoW, FVectorIntervalFixed32) case ACF_Fixed48NoW: { uint16 X, Y, Z; CVec3 v; v.Set(0, 0, 0); if (ComponentMask & 1) { Reader << X; v[0] = DecodeFixed48_PerTrackComponent<7>(X); } if (ComponentMask & 2) { Reader << Y; v[1] = DecodeFixed48_PerTrackComponent<7>(Y); } if (ComponentMask & 4) { Reader << Z; v[2] = DecodeFixed48_PerTrackComponent<7>(Z); } A->KeyPos.Add(v); } break; case ACF_Identity: A->KeyPos.Add(nullVec); break; default: { char buf[1024]; Seq->GetFullName(buf, 1024); appNotify("%s: unknown translation compression method: %d (%s) - dropping track", buf, KeyFormat, EnumToName(KeyFormat)); goto track_error; } } } // align to 4 bytes Reader.Seek(Align(Reader.Tell(), 4)); if (HasTimeTracks) ReadTimeArray(Reader, NumKeys, A->KeyPosTime, Seq->NumFrames); } unguard; guard(RotKeys); // read rotation keys if (RotOffset == -1) { A->KeyQuat.Add(nullQuat); DBG(" [%d] no rotation data\n", TrackIndex); } else { Reader.Seek(RotOffset); Reader << PackedInfo; DECODE_PER_TRACK_INFO(PackedInfo); A->KeyQuat.Empty(NumKeys); DBG(" [%d] rot : fmt=%d (%s), %d keys, mask %d\n", TrackIndex, KeyFormat, EnumToName(KeyFormat), NumKeys, ComponentMask ); if (KeyFormat == ACF_IntervalFixed32NoW) { // read mins/maxs Mins.Set(0, 0, 0); Ranges.Set(0, 0, 0); if (ComponentMask & 1) Reader << Mins.X << Ranges.X; if (ComponentMask & 2) Reader << Mins.Y << Ranges.Y; if (ComponentMask & 4) Reader << Mins.Z << Ranges.Z; } for (k = 0; k < NumKeys; k++) { switch (KeyFormat) { // TR (ACF_None, FQuat) case ACF_Float96NoW: { FQuatFloat96NoW q; Reader << q; FQuat q2 = q; // convert A->KeyQuat.Add(CVT(q2)); } break; case ACF_Fixed48NoW: { FQuatFixed48NoW q; q.X = q.Y = q.Z = 32767; // corresponds to 0 if (ComponentMask & 1) Reader << q.X; if (ComponentMask & 2) Reader << q.Y; if (ComponentMask & 4) Reader << q.Z; FQuat q2 = q; // convert A->KeyQuat.Add(CVT(q2)); } break; TR (ACF_Fixed32NoW, FQuatFixed32NoW) TRR(ACF_IntervalFixed32NoW, FQuatIntervalFixed32NoW) TR (ACF_Float32NoW, FQuatFloat32NoW) case ACF_Identity: A->KeyQuat.Add(nullQuat); break; default: { char buf[1024]; Seq->GetFullName(buf, 1024); appNotify("%s: unknown rotation compression method: %d (%s) - dropping track", buf, KeyFormat, EnumToName(KeyFormat)); goto track_error; } } } // align to 4 bytes Reader.Seek(Align(Reader.Tell(), 4)); if (HasTimeTracks) ReadTimeArray(Reader, NumKeys, A->KeyQuatTime, Seq->NumFrames); } unguard; unguard; continue; // end of AKF_PerTrackCompression block ... } //---------------------------------------------- // end of AKF_PerTrackCompression decoder //---------------------------------------------- // read animations int TransOffset = Seq->CompressedTrackOffsets[offsetIndex ]; int TransKeys = Seq->CompressedTrackOffsets[offsetIndex+1]; int RotOffset = Seq->CompressedTrackOffsets[offsetIndex+2]; int RotKeys = Seq->CompressedTrackOffsets[offsetIndex+3]; // appPrintf("[%d:%d:%d] : %d[%d] %d[%d] %d[%d]\n", j, Seq->RotationCompressionFormat, Seq->TranslationCompressionFormat, TransOffset, TransKeys, RotOffset, RotKeys, ScaleOffset, ScaleKeys); A->KeyPos.Empty(TransKeys); A->KeyQuat.Empty(RotKeys); // read translation keys if (TransKeys) { Reader.Seek(TransOffset); AnimationCompressionFormat TranslationCompressionFormat = Seq->TranslationCompressionFormat; if (TransKeys == 1) TranslationCompressionFormat = ACF_None; // single key is stored without compression // read mins/ranges if (TranslationCompressionFormat == ACF_IntervalFixed32NoW) { Reader << Mins << Ranges; } for (k = 0; k < TransKeys; k++) { switch (TranslationCompressionFormat) { TP (ACF_None, FVector) TP (ACF_Float96NoW, FVector) TPR(ACF_IntervalFixed32NoW, FVectorIntervalFixed32) TP (ACF_Fixed48NoW, FVectorFixed48) case ACF_Identity: A->KeyPos.Add(nullVec); break; default: appError("Unknown translation compression method: %d (%s)", TranslationCompressionFormat, EnumToName(TranslationCompressionFormat)); } } // align to 4 bytes Reader.Seek(Align(Reader.Tell(), 4)); if (HasTimeTracks) ReadTimeArray(Reader, TransKeys, A->KeyPosTime, Seq->NumFrames); } else { // A->KeyPos.Add(nullVec); // appNotify("No translation keys!"); }