/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "vmapexport.h" #include "Errors.h" #include "model.h" #include "StringFormat.h" #include "wmo.h" #include "adtfile.h" #include "cascfile.h" #include "VMapDefinitions.h" #include #include #include #include extern std::shared_ptr CascStorage; Model::Model(std::string &filename) : filename(filename), header(), vertices(nullptr), indices(nullptr) { } bool Model::open() { CASCFile f(CascStorage, filename.c_str()); if (f.isEof()) { f.close(); // Do not show this error on console to avoid confusion, the extractor can continue working even if some models fail to load //printf("Error loading model %s\n", filename.c_str()); return false; } _unload(); uint32 m2start = 0; char const* ptr = f.getBuffer(); while (m2start + 4 < f.getSize() && memcmp(ptr, "MD20", 4) != 0) { ++m2start; ++ptr; if (m2start + sizeof(ModelHeader) > f.getSize()) return false; } memcpy(&header, f.getBuffer() + m2start, sizeof(ModelHeader)); bounds = header.collisionBox; if (header.nBoundingTriangles > 0) { f.seek(m2start); f.seekRelative(header.ofsBoundingVertices); vertices = new Vec3D[header.nBoundingVertices]; f.read(vertices,header.nBoundingVertices*12); for (uint32 i=0; i tempindices = std::make_unique(header.nBoundingTriangles); f.read(tempindices.get(), header.nBoundingTriangles * 2); std::copy_n(tempindices.get(), header.nBoundingTriangles, indices); f.close(); } else { //printf("not included %s\n", filename.c_str()); f.close(); return false; } return true; } bool Model::ConvertToVMAPModel(const char * outfilename) { int N[12] = { }; FILE* output = fopen(outfilename, "wb"); if (!output) { printf("Can't create the output file '%s'\n", outfilename); return false; } fwrite(VMAP::RAW_VMAP_MAGIC, 8, 1, output); uint32 nVertices = header.nBoundingVertices; fwrite(&nVertices, sizeof(int), 1, output); uint32 nofgroups = 1; fwrite(&nofgroups, sizeof(uint32), 1, output); fwrite(N, 4, 1, output);// RootWMOID ModelFlags tcFlags = ModelFlags::IsM2; fwrite(&tcFlags, sizeof(ModelFlags), 1, output); fwrite(N, 4 * 2, 1, output);// mogpFlags, groupWMOID fwrite(&bounds, sizeof(AaBox3D), 1, output);//bbox, only needed for WMO currently fwrite(N, 4, 1, output);// liquidflags fwrite("GRP ", 4, 1, output); uint32 branches = 1; int wsize; wsize = sizeof(branches) + sizeof(uint32) * branches; fwrite(&wsize, sizeof(int), 1, output); fwrite(&branches, sizeof(branches), 1, output); uint32 nIndexes = header.nBoundingTriangles; fwrite(&nIndexes, sizeof(uint32), 1, output); fwrite("INDX", 4, 1, output); wsize = sizeof(uint32) + sizeof(unsigned short) * nIndexes; fwrite(&wsize, sizeof(int), 1, output); fwrite(&nIndexes, sizeof(uint32), 1, output); if (nIndexes > 0) { for (uint32 i = 0; i < nIndexes; ++i) { if ((i % 3) - 1 == 0 && i + 1 < nIndexes) { uint32 tmp = indices[i]; indices[i] = indices[i + 1]; indices[i + 1] = tmp; } } fwrite(indices, sizeof(uint32), nIndexes, output); } fwrite("VERT", 4, 1, output); wsize = sizeof(int) + sizeof(float) * 3 * nVertices; fwrite(&wsize, sizeof(int), 1, output); fwrite(&nVertices, sizeof(int), 1, output); if (nVertices > 0) { for (uint32 vpos = 0; vpos < nVertices; ++vpos) { float tmp = vertices[vpos].y; vertices[vpos].y = -vertices[vpos].z; vertices[vpos].z = tmp; } fwrite(vertices, sizeof(float) * 3, nVertices, output); } fclose(output); return true; } Vec3D fixCoordSystem(Vec3D const& v) { return Vec3D(v.x, v.z, -v.y); } void Doodad::Extract(ADT::MDDF const& doodadDef, char const* ModelInstName, uint32 mapID, uint32 originalMapId, FILE* pDirfile, std::vector* dirfileCache) { std::string tempname = Trinity::StringFormat("{}/{}", szWorkDirWmo, ModelInstName); FILE* input = fopen(tempname.c_str(), "r+b"); if (!input) return; fseek(input, 8, SEEK_SET); // get the correct no of vertices int nVertices; int count = fread(&nVertices, sizeof(int), 1, input); fclose(input); if (count != 1 || nVertices == 0) return; // scale factor - divide by 1024. blizzard devs must be on crack, why not just use a float? float sc = doodadDef.Scale / 1024.0f; Vec3D position = fixCoords(doodadDef.Position); uint8 nameSet = 0;// not used for models uint32 uniqueId = GenerateUniqueObjectId(doodadDef.UniqueId, 0, false); uint8 tcflags = 0; if (mapID != originalMapId) tcflags |= MOD_PARENT_SPAWN; //write Flags, NameSet, UniqueId, Pos, Rot, Scale, name fwrite(&tcflags, sizeof(uint8), 1, pDirfile); fwrite(&nameSet, sizeof(uint8), 1, pDirfile); fwrite(&uniqueId, sizeof(uint32), 1, pDirfile); fwrite(&position, sizeof(Vec3D), 1, pDirfile); fwrite(&doodadDef.Rotation, sizeof(Vec3D), 1, pDirfile); fwrite(&sc, sizeof(float), 1, pDirfile); uint32 nlen = strlen(ModelInstName); fwrite(&nlen, sizeof(uint32), 1, pDirfile); fwrite(ModelInstName, sizeof(char), nlen, pDirfile); if (dirfileCache) { dirfileCache->emplace_back(); ADTOutputCache& cacheModelData = dirfileCache->back(); cacheModelData.Flags = tcflags & ~MOD_PARENT_SPAWN; cacheModelData.Data.resize( sizeof(uint8) + // nameSet sizeof(uint32) + // uniqueId sizeof(Vec3D) + // position sizeof(Vec3D) + // doodadDef.Rotation sizeof(float) + // sc sizeof(uint32) + // nlen nlen); // ModelInstName uint8* cacheData = cacheModelData.Data.data(); #define CACHE_WRITE(value, size, cnt, dest) memcpy(dest, value, size * cnt); dest += size * cnt; CACHE_WRITE(&nameSet, sizeof(uint8), 1, cacheData); CACHE_WRITE(&uniqueId, sizeof(uint32), 1, cacheData); CACHE_WRITE(&position, sizeof(Vec3D), 1, cacheData); CACHE_WRITE(&doodadDef.Rotation, sizeof(Vec3D), 1, cacheData); CACHE_WRITE(&sc, sizeof(float), 1, cacheData); CACHE_WRITE(&nlen, sizeof(uint32), 1, cacheData); CACHE_WRITE(ModelInstName, sizeof(char), nlen, cacheData); } } void Doodad::ExtractSet(WMODoodadData const& doodadData, ADT::MODF const& wmo, bool isGlobalWmo, uint32 mapID, uint32 originalMapId, FILE* pDirfile, std::vector* dirfileCache) { if (doodadData.Sets.empty()) return; G3D::Vector3 wmoPosition(wmo.Position.z, wmo.Position.x, wmo.Position.y); G3D::Matrix3 wmoRotation = G3D::Matrix3::fromEulerAnglesZYX(G3D::toRadians(wmo.Rotation.y), G3D::toRadians(wmo.Rotation.x), G3D::toRadians(wmo.Rotation.z)); if (isGlobalWmo) wmoPosition += G3D::Vector3(533.33333f * 32, 533.33333f * 32, 0.0f); uint16 doodadId = 0; auto extractSingleSet = [&](WMO::MODS const& doodadSetData) { for (uint16 doodadIndex : doodadData.References) { if (doodadIndex < doodadSetData.StartIndex || doodadIndex >= doodadSetData.StartIndex + doodadSetData.Count) continue; WMO::MODD const& doodad = doodadData.Spawns[doodadIndex]; std::string ModelInstName; if (doodadData.Paths) ModelInstName = GetPlainName(&doodadData.Paths[doodad.NameIndex]); else if (doodadData.FileDataIds) ModelInstName = Trinity::StringFormat("FILE{:08X}.xxx", doodadData.FileDataIds[doodad.NameIndex]); else ASSERT(false); uint32 nlen = ModelInstName.length(); NormalizeFileName(ModelInstName.data(), nlen); if (ModelInstName.ends_with(".mdx") || ModelInstName.ends_with(".mdl")) { ModelInstName.replace(ModelInstName.length() - 2, 2, "2"); nlen = ModelInstName.length(); } std::string tempname = Trinity::StringFormat("{}/{}", szWorkDirWmo, ModelInstName); FILE* input = fopen(tempname.c_str(), "r+b"); if (!input) continue; fseek(input, 8, SEEK_SET); // get the correct no of vertices int nVertices; int count = fread(&nVertices, sizeof(int), 1, input); fclose(input); if (count != 1 || nVertices == 0) continue; ASSERT(doodadId < std::numeric_limits::max()); ++doodadId; G3D::Vector3 position = wmoPosition + (wmoRotation * G3D::Vector3(doodad.Position.x, doodad.Position.y, doodad.Position.z)); Vec3D rotation; (G3D::Quat(doodad.Rotation.X, doodad.Rotation.Y, doodad.Rotation.Z, doodad.Rotation.W) .toRotationMatrix() * wmoRotation) .toEulerAnglesXYZ(rotation.z, rotation.x, rotation.y); rotation.z = G3D::toDegrees(rotation.z); rotation.x = G3D::toDegrees(rotation.x); rotation.y = G3D::toDegrees(rotation.y); uint8 nameSet = 0; // not used for models uint32 uniqueId = GenerateUniqueObjectId(wmo.UniqueId, doodadId, false); uint8 tcflags = 0; if (mapID != originalMapId) tcflags |= MOD_PARENT_SPAWN; //write Flags, NameSet, UniqueId, Pos, Rot, Scale, name fwrite(&tcflags, sizeof(uint8), 1, pDirfile); fwrite(&nameSet, sizeof(uint8), 1, pDirfile); fwrite(&uniqueId, sizeof(uint32), 1, pDirfile); fwrite(&position, sizeof(Vec3D), 1, pDirfile); fwrite(&rotation, sizeof(Vec3D), 1, pDirfile); fwrite(&doodad.Scale, sizeof(float), 1, pDirfile); fwrite(&nlen, sizeof(uint32), 1, pDirfile); fwrite(ModelInstName.c_str(), sizeof(char), nlen, pDirfile); if (dirfileCache) { dirfileCache->emplace_back(); ADTOutputCache& cacheModelData = dirfileCache->back(); cacheModelData.Flags = tcflags & ~MOD_PARENT_SPAWN; cacheModelData.Data.resize( sizeof(uint8) + // nameSet sizeof(uint32) + // uniqueId sizeof(Vec3D) + // position sizeof(Vec3D) + // rotation sizeof(float) + // doodad.Scale sizeof(uint32) + // nlen nlen); // ModelInstName uint8* cacheData = cacheModelData.Data.data(); CACHE_WRITE(&nameSet, sizeof(uint8), 1, cacheData); CACHE_WRITE(&uniqueId, sizeof(uint32), 1, cacheData); CACHE_WRITE(&position, sizeof(Vec3D), 1, cacheData); CACHE_WRITE(&rotation, sizeof(Vec3D), 1, cacheData); CACHE_WRITE(&doodad.Scale, sizeof(float), 1, cacheData); CACHE_WRITE(&nlen, sizeof(uint32), 1, cacheData); CACHE_WRITE(ModelInstName.c_str(), sizeof(char), nlen, cacheData); } } }; // first doodad set is always active extractSingleSet(doodadData.Sets[0]); if (wmo.DoodadSet != 0 && wmo.DoodadSet < doodadData.Sets.size()) extractSingleSet(doodadData.Sets[wmo.DoodadSet]); } #undef CACHE_WRITE