diff --git a/code/nel/tools/3d/shapes_exporter/main.cpp b/code/nel/tools/3d/shapes_exporter/main.cpp index 4e3a81888..6532d26d4 100644 --- a/code/nel/tools/3d/shapes_exporter/main.cpp +++ b/code/nel/tools/3d/shapes_exporter/main.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "shapes_exporter.h" using namespace NLMISC; @@ -40,6 +41,44 @@ void split(const std::string &str, std::vector &tokens, const std:: } } +static CHashKeyMD5 getNewMD5(const std::string &filename) +{ + CMD5Context md5ctx; + CHashKeyMD5 Message_Digest; + Message_Digest.clear(); + + CIFile ifile; + if (!ifile.open(filename)) + { + nlwarning ("MD5: Can't open the file '%s'", filename.c_str()); + return Message_Digest; + } + + md5ctx.init(); + + uint8 buffer[1024]; + int bufferSize = 1024; + sint fs = ifile.getFileSize(); + sint n, read = 0; + do + { + //bs = (int)fread (buffer, 1, bufferSize, fp); + n = std::min (bufferSize, fs-read); + //nlinfo ("read %d bytes", n); + ifile.serialBuffer((uint8 *)buffer, n); + + md5ctx.update(buffer, n); + + read += n; + } + while (!ifile.eof()); + + ifile.close (); + + md5ctx.final(Message_Digest); + + return Message_Digest; +} #if defined(NL_OS_WINDOWS) && !defined(_CONSOLE) sint WINAPI WinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, LPSTR cmdline, int /* nCmdShow */) @@ -68,7 +107,6 @@ sint main(int argc, char **argv) exporter.parseConfigFile("shapes_exporter.cfg"); exporter.init(); - exporter.setupLight(); if (argc > 1) { @@ -84,8 +122,26 @@ sint main(int argc, char **argv) std::vector filenames; // search all .max files + CPath::getPathContent(exporter.settings.input_path, true, false, true, filenames); // CPath::getFileList("max", filenames); // search .max files corresponding to a filter + // fix bad textures + CPath::remapFile("ma_hof_armor_00_tibia_c1.tga", "ma_hof_armor00_tibia_c1.png"); + CPath::remapFile("ma_hof_armor_00_foot_c1.tga", "ma_hof_armor00_foot_c1.png"); + CPath::remapFile("ma_hof_armor_01_botte_c1.tga", "ma_hof_armor01_botte_c1.png"); + CPath::remapFile("ma_hof_armor_01_pied_c1.tga", "ma_hof_armor01_pied_c1.png"); + + CPath::remapFile("ma_hom_armor_01_botte_c1.tga", "ma_hom_armor01_botte_c1.png"); + CPath::remapFile("ma_hom_armor_01_pied_c1.tga", "ma_hom_armor01_pied_c1.png"); + + CPath::remapFile("hair_spec.tga", "spec_hair.png"); + + CPath::remapFile("zo_hof_armor_00_mollet_c1.tga", "zo_hof_armor00_mollet_c1.png"); + CPath::remapFile("zo_hof_armor_00_pied_c1.tga", "zo_hof_armor00_pied_c1.png"); + + CPath::remapFile("zo_hom_armor_00_mollet_c1.tga", "zo_hom_armor00_mollet_c1.png"); + CPath::remapFile("zo_hom_armor_00_pied_c1.tga", "zo_hom_armor00_pied_c1.png"); + // CPath::getFileListByName("ps", "braziera", filenames); // CPath::getFileListByName("ps", "fireworka", filenames); // CPath::getFileListByName("ps", "fireworkf", filenames); @@ -100,38 +156,106 @@ sint main(int argc, char **argv) CPath::getFileList("ps", shapes); for(size_t i = 0; i < filenames.size(); ++i) { + if (filenames[i].find(".max") == std::string::npos) + continue; + + std::string baseFilename = toLower(CFile::getFilenameWithoutExtension(filenames[i])); + // compute the md5 of .max file - std::string md5 = getMD5(filenames[i]).toString(); + std::string md5 = getNewMD5(filenames[i]).toString(); nlinfo("processing %s with md5 = %s", filenames[i].c_str(), md5.c_str()); // the final directory with images std::string output_path = exporter.settings.output_path + md5.substr(0, 2) + "/" + md5; + // file is an animation + std::string animation = ""; // CPath::lookup(baseFilename + ".anim", false, false, false); + // file is a skeleton + std::string skeleton = ""; // CPath::lookup(baseFilename + ".skel", false, false, false); + // file is a shape + std::string shape = CPath::lookup(baseFilename + ".shape", false, false, false); + + // copy .shape file + if (!shape.empty() && false) + { + CIFile in; + COFile out; + + // create output directory if it doesn't already exists + if (!CFile::isExists(output_path) && !CFile::createDirectoryTree(output_path)) + { + nlwarning("can't create %s", output_path.c_str()); + continue; + } + + if (in.open(shape) && out.open(output_path + "/" + baseFilename + ".shape")) + { + uint32 size = in.getFileSize(); + + uint8 *buffer = new uint8[size]; + + in.serialBuffer(buffer, size); + out.serialBuffer(buffer, size); + + delete [] buffer; + } + } + + // try with several shapes binded on a skeleton std::vector filtered_shapes; - if(CFile::getExtension(filenames[i]) == "ps") + if (!animation.empty()) { + // render animation + skeleton = ShapesExporter::findSkeleton(filenames[i]); + + // TODO: take from cfg +// filtered_shapes.push_back(); + continue; + // no filter if it's a PS filtered_shapes.push_back(filenames[i]); } + else if (!shape.empty()) + { +// skeleton = ShapesExporter::findSkeleton(shape); + // render shape + } + else if (!skeleton.empty()) + { + // don't render anything + continue; + } else { + continue; +/* // create a temporary list with shapes which could correspond to .max file for(size_t j = 0; j < shapes.size(); ++j) { // only add files with the same beginning - if (shapes[j].find(CFile::getFilenameWithoutExtension(filenames[i])) == 0) + if (shapes[j].find(baseFilename) == 0) { filtered_shapes.push_back(shapes[j]); } } - } +*/ + // if there is no corresponding file, we can't render it + if (filtered_shapes.empty()) + { + nlwarning("didn't find type of %s", filenames[i].c_str()); + continue; + } - // if there is no corresponding file, we can't render it - if (filtered_shapes.empty()) - { - nlinfo("no shape found"); - continue; + // if we found only one shape, we don't need a skeleton + if (filtered_shapes.size() == 1) + { + shape = filtered_shapes[0]; + } + else + { + skeleton = ShapesExporter::findSkeleton(filenames[i]); + } } // create output directory if it doesn't already exists @@ -141,32 +265,29 @@ sint main(int argc, char **argv) continue; } - // if we found only one shape, we don't need a skeleton - if (filtered_shapes.size() == 1) + bool res = false; + + if (!skeleton.empty()) { - if (!exporter.exportShape(filtered_shapes[0], output_path)) + res = exporter.exportSkeleton(skeleton, filtered_shapes, output_path); + } + else + { + res = exporter.exportShape(shape, output_path); + } + + if (res) + { + if (!exporter.createThumbnail(filenames[i], output_path)) { - // an error occured, try to delete directory - nlwarning("can't export shape"); - CFile::deleteDirectory(output_path); - continue; + nlwarning("can't create thumbnail"); } } else { - // TODO: search for the right skeleton - if (!exporter.exportSkeleton("fy_hof_skel.skel", filtered_shapes, output_path)) - { - // an error occured, try to delete directory - nlwarning("can't export shape"); - CFile::deleteDirectory(output_path); - continue; - } - } - - if (!exporter.createThumbnail(filenames[i], output_path)) - { - nlwarning("can't create thumbnail"); + // an error occured, try to delete directory + nlwarning("can't export shape"); + CFile::deleteDirectory(output_path); } } } diff --git a/code/nel/tools/3d/shapes_exporter/shapes_exporter.cfg b/code/nel/tools/3d/shapes_exporter/shapes_exporter.cfg index b5d69fa02..6db4b9a5a 100644 --- a/code/nel/tools/3d/shapes_exporter/shapes_exporter.cfg +++ b/code/nel/tools/3d/shapes_exporter/shapes_exporter.cfg @@ -1,7 +1,8 @@ +input_path = ""; search_pathes = { }; recursive_search_pathes = { }; output_path = "storage"; -extensions_remapping = { "dds", "tga" }; +extensions_remapping = { "png", "tga", "dds", "tga" }; preview_width = 80; preview_height = 80; @@ -17,7 +18,7 @@ output_antialiasing = 2; // 0 or 1 = none, 2 = 2x, etc... output_quality = 90; output_background = { 0, 0, 0 }; -light_ambiant = { 255, 255, 255 }; +light_ambiant = { 0, 0, 0 }; light_diffuse = { 255, 255, 255 }; light_specular = { 255, 255, 255 }; -light_direction = { 0.25, 0.5, 0.25 }; +light_direction = { 0.25, 0.25, 0.25 }; diff --git a/code/nel/tools/3d/shapes_exporter/shapes_exporter.cpp b/code/nel/tools/3d/shapes_exporter/shapes_exporter.cpp index 7caf6ec40..288f581ed 100644 --- a/code/nel/tools/3d/shapes_exporter/shapes_exporter.cpp +++ b/code/nel/tools/3d/shapes_exporter/shapes_exporter.cpp @@ -106,6 +106,15 @@ bool ShapesExporter::parseConfigFile(const string &filename) return false; } + // input path + try + { + settings.input_path = CPath::standardizePath(cf.getVar("input_path").asString()); + } + catch (EUnknownVar &) + { + } + // output path try { @@ -332,7 +341,7 @@ bool ShapesExporter::parseConfigFile(const string &filename) return true; } -bool ShapesExporter::setupLight() +bool ShapesExporter::setupLight(const CVector &position, const CVector &direction) { // create the light ULight *Light = ULight::createLight(); @@ -342,7 +351,8 @@ bool ShapesExporter::setupLight() Light->setMode(ULight::DirectionalLight); // set position of the light - Light->setupDirectional(settings.light_ambiant, settings.light_diffuse, settings.light_specular, settings.light_direction); +// Light->setupDirectional(settings.light_ambiant, settings.light_diffuse, settings.light_specular, settings.light_direction); + Light->setupPointLight(settings.light_ambiant, settings.light_diffuse, settings.light_specular, position, direction + settings.light_direction); // set and enable the light Driver->setLight(0, *Light); @@ -351,21 +361,117 @@ bool ShapesExporter::setupLight() return true; } -void ShapesExporter::setCamera(CAABBox &bbox, UInstance &entity, bool high_z) +void ShapesExporter::setCamera(CAABBox &bbox, UTransform &entity, bool high_z) { + CVector pos(0.f, 0.f, 0.f); + CQuat quat(0.f, 0.f, 0.f, 0.f); + NL3D::UInstance inst; + inst.cast(entity); + if (!inst.empty()) + { + inst.getDefaultPos(pos); + inst.getDefaultRotQuat(quat); +/* + if (quat.getAxis().isNull()) + { + quat.set(0, 0, 0, 0); + inst.setRotQuat(quat); + } +*/ +// quat.set(1.f, 1.f, 0.f, 0.f); + +// inst.setRotQuat(quat); +// inst.getRotQuat(quat); + + // check for presence of all textures from each sets + bool allGood = true; + + for(uint s = 0; s < 5; ++s) + { + inst.selectTextureSet(s); + + uint numMat = inst.getNumMaterials(); + + // by default, all textures are present + allGood = true; + + for(uint i = 0; i < numMat; ++i) + { + UInstanceMaterial mat = inst.getMaterial(i); + + for(sint j = 0; j <= mat.getLastTextureStage(); ++j) + { + // if a texture is missing + if (mat.isTextureFile(j) && mat.getTextureFileName(j) == "CTextureMultiFile:Dummy") + allGood = false; + } + } + + // if all textures have been found for this set, skip other sets + if (allGood) + break; + } + } + + // fix scale (some shapes have a different value) + entity.setScale(1.f, 1.f, 1.f); + UCamera Camera = Scene->getCam(); - CVector center = bbox.getCenter(); CVector max_radius = bbox.getHalfSize(); + CVector center = bbox.getCenter(); entity.setPivot(center); + center += pos; float fov = float(20.0*Pi/180.0); Camera.setPerspective (fov, 1.0f, 0.1f, 1000.0f); float radius = max(max(max_radius.x, max_radius.y), max_radius.z); + if (radius == 0.f) radius = 1.f; float left, right, bottom, top, znear, zfar; Camera.getFrustum(left, right, bottom, top, znear, zfar); float dist = radius / (tan(fov/2)); - Camera.lookAt(CVector(center.x+dist+max_radius.x, center.y, center.z+(high_z?max_radius.z/1.0f:0.0f)), center); + CVector eye(center); +/* if (axis == CVector::I) + eye.y -= dist+radius; + else if (axis == CVector::J) + eye.x += dist+radius; +*/ +// quat.normalize(); + + CVector ax(quat.getAxis()); + +// float angle = quat.getAngle(); +/* + if (ax.isNull()) + { + if (int(angle*100.f) == int(NLMISC::Pi * 200.f)) + { + ax = CVector::J; + } + } + else +*/ + if (ax.isNull() || ax == CVector::I) + { + ax = CVector::I; + } + else if (ax == -CVector::K) + { + ax = -CVector::J; + } +/* else if (ax.x < -0.9f && ax.y == 0.f && ax.z == 0.f) + { + ax = -CVector::J ; + } +*/ +// ax.normalize(); + + eye -= ax * (dist+radius); + if (high_z) + eye.z += max_radius.z/1.0f; + Camera.lookAt(eye, center); + + setupLight(eye, center - eye); } bool ShapesExporter::exportShape(const string &filename, const string &output_path) @@ -397,15 +503,15 @@ bool ShapesExporter::exportShape(const string &filename, const string &output_pa Scene->animate(1.0); Scene->render(); - UParticleSystemInstance *psi = static_cast(&Entity); - if(psi) - { - psi->getSystemBBox(bbox); - setCamera(bbox, Entity); - } - if(CFile::getExtension(filename) == "ps") { + UParticleSystemInstance *psi = static_cast(&Entity); + if(psi) + { + psi->getSystemBBox(bbox); + setCamera(bbox, Entity); + } + // first pass to detect bbox & duration CAABBox bbox2; double duration = 0.0; @@ -418,7 +524,7 @@ bool ShapesExporter::exportShape(const string &filename, const string &output_pa } else { - renderShape(Entity, CVector::I, output_path); + renderShape(Entity, output_path); } // delete entity @@ -470,22 +576,9 @@ bool ShapesExporter::exportSkeleton(const string &skeleton, const vector // get AABox of Entity CAABBox bbox; Skeleton.computeCurrentBBox(bbox, NULL); + setCamera(bbox, Skeleton); - CVector center = bbox.getCenter(); - CVector max_radius = bbox.getMax(); - - Skeleton.setPivot(center); - - float radius = max_radius.x; - - if (max_radius.y > radius) radius = max_radius.y; - if (max_radius.z > radius) radius = max_radius.z; - - // camera will look at entities -// Camera.lookAt(CVector(center.x, center.y - bbox.getRadius() * 1.5f, center.z * 2.f), CVector(center.x, center.y, center.z * 2.f)); - Camera.lookAt(CVector(center.x + bbox.getRadius() * 2.0f, center.y, center.z), CVector(center.x, center.y, center.z)); - - renderShape(Skeleton, CVector::J, output_path); + renderShape(Skeleton, output_path); // delete entities for(size_t i = 0; i < Entities.size(); ++i) @@ -500,6 +593,95 @@ bool ShapesExporter::exportSkeleton(const string &skeleton, const vector return true; } +/* +bool ShapesExporter::exportAnimation(const std::string &animation, const std::string &skeleton, const std::vector &parts, const std::string &output_path) +{ + UPlayListManager *PlayListManager = Scene->createPlayListManager(); + UAnimationSet *AnimSet = Driver->createAnimationSet(); + +// uint anim_id = AnimSet->addAnimation("anim.anim", "anim_name", false); +// uint weight_id = AnimSet->addSkeletonWeight("file.wgt", "skel_name"): + +// UAnimation *anim = AnimSet->getAnimation(anim_id); +// anim->getEndTime(); + +// UPlayList *playlist = playlist_manager->createPlayList(AnimSet); +// playlist->registerTransform(Skeleton); + +// playlist->setAnimation(0, anim_id); +// playlist->setTimeOrigin(newSlot, time); +// playlist->setWeightSmoothness(newSlot, 1.0f); + + // get scene camera + UCamera Camera = Scene->getCam(); + if (Camera.empty()) + { + nlwarning("can't get camera from scene"); + return false; + } + + // add a skeleton to the scene + USkeleton Skeleton = Scene->createSkeleton(skeleton); + + // if we can't create entity, skip it + if (Skeleton.empty()) + { + nlwarning("can't create skeleton from %s", skeleton.c_str()); + return false; + } + + std::vector Entities(parts.size()); + + for(size_t i = 0; i < parts.size(); ++i) + { + Entities[i] = Scene->createInstance(parts[i]); + + // if we can't create entity, skip it + if (Entities[i].empty()) + { + nlwarning("can't create instance from %s", parts[i].c_str()); + return false; + } + + if (!Skeleton.bindSkin(Entities[i])) + { + nlwarning("can't bind %s to skeleton", parts[i].c_str()); + return false; + } + } + + // get AABox of Entity + CAABBox bbox; + Skeleton.computeCurrentBBox(bbox, NULL); + + setCamera(); + + // camera will look at skeleton + Camera.lookAt(CVector(center.x + dist - radius, center.y, center.z), center); + + renderAllImages(Skeleton, CVector::J, output_path); + + // delete entities + for(size_t i = 0; i < Entities.size(); ++i) + { + Skeleton.detachSkeletonSon(Entities[i]); + Scene->deleteInstance(Entities[i]); + } + + // delete skeleton + Scene->deleteSkeleton(Skeleton); + + Scene->deletePlayListManager(PlayListManager); + Driver->deleteAnimationSet(AnimSet); + +// m_playlist->emptyPlayList(); +// m_playlist->resetAllChannels(); +// m_playlistman->deletePlayList(m_playlist); + + return true; +} +*/ + bool ShapesExporter::saveOneImage(const string &filename) { CBitmap btm; @@ -543,21 +725,49 @@ bool ShapesExporter::saveOneImage(const string &filename) nlwarning("can't create %s", filename.c_str()); return false; } + return true; } -bool ShapesExporter::renderShape(UTransform &entity, const CVector &axis, const string &output_path) +bool ShapesExporter::renderShape(UTransform &entity, const string &output_path) { + CQuat quat(0.f, 0.f, 0.f, 0.f); + + CVector axis1 = CVector::J, axis2 = CVector::K; + int orientation1 = -1, orientation2 = 1; + + NL3D::UInstance inst; + inst.cast(entity); + if (!inst.empty()) + { +// inst.getDefaultRotQuat(quat); + inst.getRotQuat(quat); +/* if (!quat.getAxis().isNull()) + { + CVector a = quat.getAxis(); + if (a.z != 0 && a.x == 0.f) + { + axis1 = CVector::J; + orientation1 = -1; + } + if (a.y != 0.f) + { + axis2 = CVector::J; + } + } +*/ + } + // main loop for (uint step_z = 0; step_z < settings.output_steps_z; ++step_z) { - CQuat z(axis, (float)step_z * ((float)NLMISC::Pi*2.f / (float)settings.output_steps_z)); + CQuat z(axis1, orientation1 * (float)step_z * ((float)NLMISC::Pi*2.f / (float)settings.output_steps_z)); for (uint step_x = 0; step_x < settings.output_steps_x; ++step_x) { - CQuat x(CVector::K, (float)step_x * ((float)NLMISC::Pi*2.f / (float)settings.output_steps_x)); + CQuat x(axis2, orientation2 * (float)step_x * ((float)NLMISC::Pi*2.f / (float)settings.output_steps_x)); - entity.setRotQuat(z * x); + entity.setRotQuat(quat * z * x); string filename = CPath::standardizePath(output_path) + toString("%03d_%03d.%s", step_z, step_x, settings.output_format.c_str()); @@ -729,3 +939,65 @@ bool ShapesExporter::createThumbnail(const string &filename, const string &path) return true; } + +std::string ShapesExporter::findSkeleton(const std::string &shape) +{ + std::string baseFilename = CFile::getFilenameWithoutExtension(shape); + + // work in 60% of cases + std::string skeleton = CPath::lookup(baseFilename + ".skel", false, false, false); + + if (!skeleton.empty()) + return skeleton; + + // remove last part + size_t pos = baseFilename.rfind("_"); + + if (pos != std::string::npos) + { + skeleton = CPath::lookup(baseFilename.substr(0, pos) + ".skel", false, false, false); + + if (!skeleton.empty()) + return skeleton; + + pos = baseFilename.find("_"); + + std::vector filenames; + + CPath::getFileListByName("skel", baseFilename.substr(pos), filenames); + + if (filenames.size() == 1) + { + skeleton = filenames[0]; + return skeleton; + } + + } + + int gender = 0; + + if (baseFilename.find("_hom_") != std::string::npos) + { + gender = 1; + } + else if (baseFilename.find("_hof_") != std::string::npos) + { + gender = 2; + } + + // bipeds + if (gender > 0) + { + // karavan + if (baseFilename.find("ca_") == 0) + return gender == 1 ? "ca_hom_armor01.skel":"ca_hof_armor01.skel"; + + return gender == 1 ? "fy_hom_skel.skel":"fy_hof_skel.skel"; + } + + nlwarning("can't find skeleton for %s", shape.c_str()); + // goo mobs +// CPath::getFileListByName("max", "_hof_", filenames); + + return ""; +} diff --git a/code/nel/tools/3d/shapes_exporter/shapes_exporter.h b/code/nel/tools/3d/shapes_exporter/shapes_exporter.h index d8a989c71..233682e55 100644 --- a/code/nel/tools/3d/shapes_exporter/shapes_exporter.h +++ b/code/nel/tools/3d/shapes_exporter/shapes_exporter.h @@ -26,6 +26,7 @@ struct Settings { Settings(); + std::string input_path; std::string output_path; std::string preview_format; @@ -56,18 +57,19 @@ public: bool init(); bool parseConfigFile(const std::string &filename); - bool setupLight(); + bool setupLight(const NLMISC::CVector &position, const NLMISC::CVector &direction); bool exportShape(const std::string &filename, const std::string &output_path); bool exportSkeleton(const std::string &skeleton, const std::vector &parts, const std::string &output_path); - - void setCamera(NLMISC::CAABBox &bbox, NL3D::UInstance &entity, bool high_z=false); + void setCamera(NLMISC::CAABBox &bbox, NL3D::UTransform &entity, bool high_z=false); bool saveOneImage(const std::string &output_path); - bool renderShape(NL3D::UTransform &entity, const NLMISC::CVector &axis, const std::string &output_path); + bool renderShape(NL3D::UTransform &entity, const std::string &output_path); bool renderPS(NL3D::UInstance &entity, const std::string &output_path, double &duration, NLMISC::CAABBox &bbox); bool createThumbnail(const std::string &filename, const std::string &path); + static std::string findSkeleton(const std::string &shape); + Settings settings; NL3D::UDriver* Driver; NL3D::UScene* Scene; diff --git a/code/nel/tools/all.sln b/code/nel/tools/all.sln index bbb9e335d..d5f8411ef 100644 --- a/code/nel/tools/all.sln +++ b/code/nel/tools/all.sln @@ -395,6 +395,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xml_packer", "misc\xml_pack {44B21233-EFCC-4825-B5E5-3A3BD6CC5516} = {44B21233-EFCC-4825-B5E5-3A3BD6CC5516} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_exporter", "3d\shapes_exporter\shapes_exporter.vcproj", "{F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}" + ProjectSection(ProjectDependencies) = postProject + {2B48BE83-108B-4E8E-8A55-6627CF09AC5A} = {2B48BE83-108B-4E8E-8A55-6627CF09AC5A} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -931,6 +936,14 @@ Global {222663AE-5BAD-49B3-837F-26A518A3373A}.Release|Win32.Build.0 = Release|Win32 {222663AE-5BAD-49B3-837F-26A518A3373A}.Release|x64.ActiveCfg = Release|x64 {222663AE-5BAD-49B3-837F-26A518A3373A}.Release|x64.Build.0 = Release|x64 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Debug|Win32.ActiveCfg = Debug|Win32 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Debug|Win32.Build.0 = Debug|Win32 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Debug|x64.ActiveCfg = Debug|x64 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Debug|x64.Build.0 = Debug|x64 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Release|Win32.ActiveCfg = Release|Win32 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Release|Win32.Build.0 = Release|Win32 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Release|x64.ActiveCfg = Release|x64 + {F8607267-5BA2-4BE8-A674-26C3F7D0A9BB}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE