diff --git a/code/nel/tools/CMakeLists.txt b/code/nel/tools/CMakeLists.txt index 8734bf103..5297f34a4 100644 --- a/code/nel/tools/CMakeLists.txt +++ b/code/nel/tools/CMakeLists.txt @@ -17,6 +17,10 @@ IF(WITH_NEL_TOOLS) ADD_SUBDIRECTORY(pacs) ENDIF() + IF(WITH_LIGO) + ADD_SUBDIRECTORY(ligo) + ENDIF() + IF(WITH_LOGIC) ADD_SUBDIRECTORY(logic) ENDIF() diff --git a/code/nel/tools/ligo/CMakeLists.txt b/code/nel/tools/ligo/CMakeLists.txt new file mode 100644 index 000000000..9a7f24ebe --- /dev/null +++ b/code/nel/tools/ligo/CMakeLists.txt @@ -0,0 +1,6 @@ + +IF(WITH_LIGO) + IF(WITH_3D) + ADD_SUBDIRECTORY(unbuild_land) + ENDIF() +ENDIF() diff --git a/code/nel/tools/ligo/unbuild_land/CMakeLists.txt b/code/nel/tools/ligo/unbuild_land/CMakeLists.txt new file mode 100644 index 000000000..3e8c8fbdb --- /dev/null +++ b/code/nel/tools/ligo/unbuild_land/CMakeLists.txt @@ -0,0 +1,11 @@ +FILE(GLOB SRC *.cpp *.h *.rc) + +SOURCE_GROUP("" FILES ${SRC}) + +ADD_EXECUTABLE(unbuild_land ${SRC}) + +TARGET_LINK_LIBRARIES(unbuild_land nel3d nelmisc nelligo) +NL_DEFAULT_PROPS(unbuild_land "NeL, Tools, Ligo: Unbuild Land") +NL_ADD_RUNTIME_FLAGS(unbuild_land) + +INSTALL(TARGETS unbuild_land RUNTIME DESTINATION ${NL_BIN_PREFIX} COMPONENT toolsligo) diff --git a/code/nel/tools/ligo/unbuild_land/gold_pill.ico b/code/nel/tools/ligo/unbuild_land/gold_pill.ico new file mode 100644 index 000000000..618b67a5d Binary files /dev/null and b/code/nel/tools/ligo/unbuild_land/gold_pill.ico differ diff --git a/code/nel/tools/ligo/unbuild_land/main.rc b/code/nel/tools/ligo/unbuild_land/main.rc new file mode 100644 index 000000000..20feb7ec8 --- /dev/null +++ b/code/nel/tools/ligo/unbuild_land/main.rc @@ -0,0 +1,42 @@ +#include +#include "config.h" + +IDI_MAIN_ICON ICON DISCARDABLE "gold_pill.ico" + +#ifdef _DEBUG +#define NL_FILEEXT "_d" +#else +#define NL_FILEEXT "" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION NL_VERSION_RC + PRODUCTVERSION NL_VERSION_RC + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", AUTHOR + VALUE "FileDescription", "NeL Unbuild Land" + VALUE "FileVersion", NL_VERSION + VALUE "LegalCopyright", COPYRIGHT + VALUE "OriginalFilename", "unbuild_land" NL_FILEEXT ".exe" + VALUE "ProductName", "NeL Tools" + VALUE "ProductVersion", NL_PRODUCT_VERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x9, 1200 + END +END diff --git a/code/nel/tools/ligo/unbuild_land/unbuild_land.cpp b/code/nel/tools/ligo/unbuild_land/unbuild_land.cpp new file mode 100644 index 000000000..4f9ed0d1f --- /dev/null +++ b/code/nel/tools/ligo/unbuild_land/unbuild_land.cpp @@ -0,0 +1,495 @@ +// NeL - MMORPG Framework +// Copyright (C) 2019 Jan BOON (Kaetemi) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// This utility is intended to rescue a lost .land file from existing +// runtime zones. It does not recover the heightmap. +// +// Author: Jan BOON (Kaetemi) + +// #include "../../3d/zone_lib/zone_utility.h" + +#include +#include +#include +#include +#include +#include +#include +//#include +#include +//#include +//#include +//#include +//#include +#include +#include +#include + +using namespace NL3D; +using namespace NLMISC; +using namespace NLLIGO; +using namespace std; + +namespace /* anonymous */ +{ + +sint32 s_ZoneMinX, s_ZoneMinY, s_ZoneMaxX, s_ZoneMaxY; +float s_CellSize = 160.0f; + +std::string s_ZoneBricksDirIn; // UTF-8 path +std::string s_ZoneRuntimeDirIn; // UTF-8 path +std::string s_LandFileOut; // UTF-8 path + +CZoneRegion s_Land; + +bool saveLand() +{ + try + { + COFile fileOut; + if (fileOut.open(s_LandFileOut, false, true, false)) + { + COXml xml; + nlverify(xml.init(&fileOut)); + s_Land.serial(xml); + } + else + { + nlwarning("Can't open the land file for writing: %s", s_LandFileOut.c_str()); + return false; + } + } + catch (const Exception &e) + { + nlwarning("Error in writing land file: %s", e.what()); + return true; + } + return true; +} + +bool getXYFromZoneName(sint32 &x, sint32 &y, const string &zoneName) +{ + string xStr, yStr; + uint32 i = 0; + while (zoneName[i] != '_') + { + yStr += zoneName[i]; + ++i; + if (i == zoneName.size()) + goto Fail; + } + if (!NLMISC::fromString(yStr, y)) + goto Fail; + y = -y; + ++i; + while (i < zoneName.size()) + { + xStr += zoneName[i]; + ++i; + } + if (xStr.size() != 2) + goto Fail; + xStr = NLMISC::toUpper(xStr); + x = ((xStr[0] - 'A') * 26 + (xStr[1] - 'A')); + return true; +Fail: + x = -1; + y = -1; + return false; +} + +void centerVertices(std::vector &vertices) +{ + CVector2f avg = CVector2f(0, 0); + for (ptrdiff_t i = 0; i < vertices.size(); ++i) + avg += vertices[i]; + avg /= (float)vertices.size(); + nldebug("Average: %f, %f", avg.x, avg.y); + for (ptrdiff_t i = 0; i < vertices.size(); ++i) + vertices[i] -= avg; +} + +void offsetVertices(std::vector &vertices, int x, int y) +{ + CVector2f off = CVector2f((float)x * s_CellSize, (float)y * s_CellSize); + for (ptrdiff_t i = 0; i < vertices.size(); ++i) + vertices[i] -= off; +} + +float ratePoints(const std::vector &zone, const std::vector &ref, float xx, float xy, float yx, float yy) +{ + // Rudimentary distance rating of vertices (not very reliable, but good enough!) + + float md = 0.f; + // std::vector refcpy = ref; + std::vector usedref; + usedref.resize(ref.size(), false); + for (ptrdiff_t i = 0; i < zone.size(); ++i) + { + if (ref.size()) + { + int lowj = 0; + float lowv = (CVector2f(ref[0].x * xx + ref[0].y * xy, ref[0].x * yx + ref[0].y * yy) - zone[i]).sqrnorm(); + for (ptrdiff_t j = 1; j < ref.size(); ++j) + { + float v = (CVector2f(ref[j].x * xx + ref[j].y * xy, ref[j].x * yx + ref[j].y * yy) - zone[i]).sqrnorm(); + if (v < lowv) + { + lowj = j; + lowv = v; + } + } + md += sqrtf(lowv); + usedref[lowj] = true; + // keep it! - refcpy.erase(refcpy.begin() + lowj); + } + else + { + md += zone[i].norm(); + } + } +#if 0 + md = 0.f; + std::vector usedzone; + usedzone.resize(zone.size(), false); + for (ptrdiff_t j = 0; j < ref.size(); ++j) + { + if (usedref[j]) + { + if (zone.size()) + { + int lowi = 0; + float lowv = (CVector2f(ref[j].x * xx + ref[j].y * xy, ref[j].x * yx + ref[j].y * yy) - zone[0]).sqrnorm(); + for (ptrdiff_t i = 1; i < zone.size(); ++i) + { + float v = (CVector2f(ref[j].x * xx + ref[j].y * xy, ref[j].x * yx + ref[j].y * yy) - zone[i]).sqrnorm(); + if (v < lowv) + { + lowi = i; + lowv = v; + } + } + md += lowv; + usedzone[lowi] = true; + } + else + { + md += ref[j].norm(); + } + } + } + md = 0.f; + int nc = 0; + for (ptrdiff_t i = 0; i < zone.size(); ++i) + { + if (usedzone[i]) + { + if (ref.size()) + { + int lowj = 0; + float lowv = (CVector2f(ref[0].x * xx + ref[0].y * xy, ref[0].x * yx + ref[0].y * yy) - zone[i]).sqrnorm(); + for (ptrdiff_t j = 1; j < ref.size(); ++j) + { + float v = (CVector2f(ref[j].x * xx + ref[j].y * xy, ref[j].x * yx + ref[j].y * yy) - zone[i]).sqrnorm(); + if (v < lowv) + { + lowj = j; + lowv = v; + } + } + md += sqrtf(lowv); + usedref[lowj] = true; + // keep it! - refcpy.erase(refcpy.begin() + lowj); + } + else + { + md += zone[i].norm(); + } + } + else + { + ++nc; + // md += 1.0f; + // md += 0.01; + } + } + for (ptrdiff_t j = 0; j < ref.size(); ++j) + { + if (!usedref[j]) + { + // md += 1.0f; + // md += 0.01; + } + } + if (nc * 8 > zone.size()) + return md + 10000; +#endif + return md; +} + +void findBestBrick(std::string &brick, int &rotate, int &flip, float &es, std::vector &zoneVertices, const std::map> &brickVertices) +{ + float bestPoints = (float)(uint32)~0; + for (std::map>::const_iterator it = brickVertices.begin(), end = brickVertices.end(); it != end; ++it) + { + float rating; + rating = ratePoints(zoneVertices, it->second, 1.0, 0.0, 0.0, 1.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 0; + flip = 0; + bestPoints = rating; + } + rating = ratePoints(zoneVertices, it->second, 0.0, -1.0, 1.0, 0.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 1; + flip = 0; + bestPoints = rating; + } + rating = ratePoints(zoneVertices, it->second, -1.0, 0.0, 0.0, -1.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 2; + flip = 0; + bestPoints = rating; + } + rating = ratePoints(zoneVertices, it->second, 0.0, 1.0, -1.0, 0.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 3; + flip = 0; + } + rating = ratePoints(zoneVertices, it->second, -1.0, 0.0, 0.0, 1.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 0; + flip = 1; + bestPoints = rating; + } + rating = ratePoints(zoneVertices, it->second, 0.0, -1.0, -1.0, 0.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 1; + flip = 1; + bestPoints = rating; + } + rating = ratePoints(zoneVertices, it->second, 1.0, 0.0, 0.0, -1.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 2; + flip = 1; + bestPoints = rating; + } + rating = ratePoints(zoneVertices, it->second, 0.0, 1.0, 1.0, 0.0); + if (rating < bestPoints) + { + brick = it->first; + rotate = 3; + flip = 1; + bestPoints = rating; + } + } + es = bestPoints; +} + +bool unbuildLand() +{ + s_Land.resize(s_ZoneMinX, s_ZoneMaxX, s_ZoneMinY, s_ZoneMaxY); + + // float th = s_CellSize * 0.5f; + float th = (s_CellSize * 0.5f) - (s_CellSize * 0.2f * 0.5f); + + // Read in all the bricks + std::vector brickFiles; + CPath::getPathContent(s_ZoneBricksDirIn, false, false, true, brickFiles); + std::map> brickVertices; + for (std::vector::const_iterator it = brickFiles.begin(), end = brickFiles.end(); it != end; ++it) + { + if (CFile::getExtension(*it) != "zone") + continue; + + if (NLMISC::startsWith(CFile::getFilename(*it), "converted-")) // Not a real ligo + continue; + + // if (NLMISC::startsWith(CFile::getFilename(*it), "foret-moor")) // Not useful for r2_forest + // continue; + // if (NLMISC::startsWith(CFile::getFilename(*it), "goo-")) // Not useful for r2_forest + // continue; + // if (NLMISC::endsWith(CFile::getFilenameWithoutExtension(*it), "_zc")) // Not useful for r2_forest + // continue; + + nldebug("Load %s", CFile::getFilenameWithoutExtension(*it).c_str()); + + CIFile zoneFile(*it); + CZone zone; + zone.serial(zoneFile); + zoneFile.close(); + + // Retrieve patches and vertices + uint16 zoneId = zone.getZoneId(); + std::vector zonePatches; + std::vector zoneBorderVertices; + zone.retrieve(zonePatches, zoneBorderVertices); + + brickVertices[CFile::getFilenameWithoutExtension(*it)] = std::vector(); + std::vector &vec = brickVertices[CFile::getFilenameWithoutExtension(*it)]; + CVector2f off = CVector2f(s_CellSize * 0.5f, s_CellSize * 0.5f); + for (ptrdiff_t i = 0; i < zonePatches.size(); ++i) + { + for (ptrdiff_t j = 0; j < 4; ++j) + { + float rx = zonePatches[i].Patch.Vertices[j].x - off.x; + float ry = zonePatches[i].Patch.Vertices[j].y - off.y; + if (rx <= -th || rx >= th + || ry <= -th || ry >= th) + goto SkipA; + } + for (ptrdiff_t j = 0; j < 4; ++j) + { + float rx = zonePatches[i].Patch.Vertices[j].x - off.x; + float ry = zonePatches[i].Patch.Vertices[j].y - off.y; + vec.push_back(CVector2f(rx, ry)); + } + SkipA:; + } + + // centerVertices(vec); + } + + std::vector runtimeFiles; + CPath::getPathContent(s_ZoneRuntimeDirIn, false, false, true, runtimeFiles); + for (std::vector::const_iterator it = runtimeFiles.begin(), end = runtimeFiles.end(); it != end; ++it) + { + if (CFile::getExtension(*it) != "zonel") + continue; + + int x, y; + std::string zoneName = CFile::getFilenameWithoutExtension(*it); + if (!getXYFromZoneName(x, y, zoneName)) + { + nlerrornoex("Bad zone name: %s", zoneName.c_str()); + continue; + } + + CIFile zoneFile(*it); + CZone zone; + zone.serial(zoneFile); + zoneFile.close(); + + // Retrieve patches and vertices + uint16 zoneId = zone.getZoneId(); + std::vector zonePatches; + std::vector zoneBorderVertices; + zone.retrieve(zonePatches, zoneBorderVertices); + + std::vector vec; + CVector2f off = CVector2f((float)x * s_CellSize, (float)y * s_CellSize) + CVector2f(s_CellSize * 0.5f, s_CellSize * 0.5f); + for (ptrdiff_t i = 0; i < zonePatches.size(); ++i) + { + for (ptrdiff_t j = 0; j < 4; ++j) + { + float rx = zonePatches[i].Patch.Vertices[j].x - off.x; + float ry = zonePatches[i].Patch.Vertices[j].y - off.y; + if (rx <= -th || rx >= th + || ry <= -th || ry >= th) + goto SkipB; + } + for (ptrdiff_t j = 0; j < 4; ++j) + { + float rx = zonePatches[i].Patch.Vertices[j].x - off.x; + float ry = zonePatches[i].Patch.Vertices[j].y - off.y; + vec.push_back(CVector2f(rx, ry)); + } + SkipB:; + } + + // offsetVertices(vec, x, y); + // centerVertices(vec); + + std::string brick; + int rotate; + int flip; + float es; + findBestBrick(brick, rotate, flip, es, vec, brickVertices); + + nlinfo("Zone: %s, brick: %s, rotate: %i; flip: %i, error score: %f", zoneName.c_str(), brick.c_str(), rotate, flip, es); + + s_Land.setName(x, y, brick); + s_Land.setRot(x, y, rotate); + s_Land.setFlip(x, y, flip); + } + + return saveLand(); +} + +} /* anonymous namespace */ + +int main(int argc, char **argv) +{ + NLMISC::CApplicationContext myApplicationContext; + + NLMISC::CCmdArgs args; + + args.addAdditionalArg("land", "Output ligo land file"); + args.addAdditionalArg("zonebricks", "Input zone bricks directory"); + args.addAdditionalArg("zoneruntime", "Input runtime zone directory"); + args.addAdditionalArg("zonemin", "Zone boundary"); + args.addAdditionalArg("zonemax", "Zone boundary"); + args.addArg("", "cellsize", "meters", "Zone cell size"); + + if (!args.parse(argc, argv)) + { + return EXIT_FAILURE; + } + + s_LandFileOut = args.getAdditionalArg("land")[0]; + s_ZoneBricksDirIn = args.getAdditionalArg("zonebricks")[0]; + s_ZoneRuntimeDirIn = args.getAdditionalArg("zoneruntime")[0]; + + sint32 zoneMinX, zoneMinY; + sint32 zoneMaxX, zoneMaxY; + if (!getXYFromZoneName(zoneMinX, zoneMinY, args.getAdditionalArg("zonemin")[0]) + || !getXYFromZoneName(zoneMaxX, zoneMaxY, args.getAdditionalArg("zonemax")[0])) + { + goto Fail; + } + s_ZoneMinX = min(zoneMinX, zoneMaxX); + s_ZoneMaxX = max(zoneMinX, zoneMaxX); + s_ZoneMinY = min(zoneMinY, zoneMaxY); + s_ZoneMaxY = max(zoneMinY, zoneMaxY); + + if (args.haveLongArg("cellsize")) + { + if (!NLMISC::fromString(args.getLongArg("cellsize")[0], s_CellSize)) + goto Fail; + } + + if (!unbuildLand()) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +Fail: + args.displayHelp(); + return EXIT_FAILURE; +}