From a1f1bd888c44d2aa273099ff72dd63483a2689d9 Mon Sep 17 00:00:00 2001 From: kaetemi Date: Mon, 4 Jan 2021 09:58:35 +0800 Subject: [PATCH 01/12] Fix rbank water mapping --- .../build_gamedata/processes/rbank/2_build.py | 26 +++++++++++---- nel/tools/pacs/build_rbank/prim_checker.cpp | 32 +++++++++++++------ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/nel/tools/build_gamedata/processes/rbank/2_build.py b/nel/tools/build_gamedata/processes/rbank/2_build.py index b38aea94b..b4c0ca674 100755 --- a/nel/tools/build_gamedata/processes/rbank/2_build.py +++ b/nel/tools/build_gamedata/processes/rbank/2_build.py @@ -55,6 +55,7 @@ printLog(log, "") # Build rbank bbox printLog(log, ">>> Build rbank bbox <<<") tempBbox = ExportBuildDirectory + "/" + RbankBboxBuildDirectory + "/temp.bbox" +rebuiltBbox = False if BuildIgBoxes == "": toolLogFail(log, BuildIgBoxesTool, ToolSuffix) else: @@ -70,6 +71,7 @@ else: else: printLog(log, "DETECT SKIP Shape->Bbox") if needUpdateIg or needUpdateShape: + rebuiltBbox = True printLog(log, "DETECT DECIDE UPDATE") cf = open("build_ig_boxes.cfg", "w") cf.write("\n") @@ -204,23 +206,35 @@ elif ExecTimeout == "": toolLogFail(log, ExecTimeoutTool, ToolSuffix) else: zonefiles = findFiles(log, ExportBuildDirectory + "/" + ZoneWeldBuildDirectory, "", ".zonew") + zonesToBuild = [] for zonefile in zonefiles: zone = os.path.basename(zonefile)[0:-len(".zonew")] lr1 = ExportBuildDirectory + "/" + RbankSmoothBuildDirectory + "/" + zone + ".lr" nearzones = subprocess.Popen([ GetNeighbors, zone ], stdout = subprocess.PIPE).communicate()[0].strip().split(" ") printLog(log, "ZONE " + zone + ": " + str(nearzones)) - zone_to_build = 0 + zoneToBuild = 0 for nearzone in nearzones: sourcePath = ExportBuildDirectory + "/" + ZoneWeldBuildDirectory + "/" + nearzone + ".zonew" if (os.path.isfile(sourcePath)): - if (needUpdate(log, sourcePath, lr1)): - zone_to_build = 1 + if (rebuiltBbox or needUpdate(log, sourcePath, lr1)): + zoneToBuild = 1 + zonesToBuild.append(os.path.basename(zonefile)) sourcePath = ExportBuildDirectory + "/" + ZoneWeldBuildDirectory + "/" + zone + ".zonew" - if zone_to_build: + if zoneToBuild: printLog(log, sourcePath + " -> " + lr1) - subprocess.call([ ExecTimeout, str(RbankBuildTesselTimeout), BuildRbank, "-c", "-P", "-g", os.path.basename(zonefile) ]) + # subprocess.call([ ExecTimeout, str(RbankBuildTesselTimeout), BuildRbank, "-c", "-P", "-g", os.path.basename(zonefile) ]) else: printLog(log, "SKIP " + lr1) + while len(zonesToBuild) > 0: + processCommand = [ ExecTimeout, str(RbankBuildTesselTimeout), BuildRbank, "-c", "-P", "-g" ] + processCommand.extend(zonesToBuild[:min(len(zonesToBuild), 64)]) + if len(zonesToBuild) > 64: + zonesToBuild = zonesToBuild[64:] + else: + zonesToBuild = [] + print processCommand + print len(processCommand) + subprocess.call(processCommand) printLog(log, "") printLog(log, ">>> Detect modifications to rebuild lr <<<") @@ -245,7 +259,7 @@ if needUpdateBboxRbank: else: printLog(log, "DETECT SKIP Lr->Rbank") -if needUpdateCmbLr or needUpdateCmbRbank or needUpdateLrRbank or needUpdateBboxRbank: +if rebuiltBbox or needUpdateCmbLr or needUpdateCmbRbank or needUpdateLrRbank or needUpdateBboxRbank: printLog(log, "DETECT DECIDE UPDATE") printLog(log, ">>> Build rbank process global <<<") # This generates temp lr files. TODO: Check if the LR changed? if BuildRbank == "": diff --git a/nel/tools/pacs/build_rbank/prim_checker.cpp b/nel/tools/pacs/build_rbank/prim_checker.cpp index e4c2b0b91..effd07bd7 100644 --- a/nel/tools/pacs/build_rbank/prim_checker.cpp +++ b/nel/tools/pacs/build_rbank/prim_checker.cpp @@ -336,13 +336,19 @@ void CPrimChecker::render(CPrimZone *zone, uint8 bits) */ void CPrimChecker::render(const CPolygon &poly, uint16 value) { + static const sint centerOffset = 20480; // zones are max 40960 + CPolygon polyOffset = poly; + for (ptrdiff_t i = 0; i < (ptrdiff_t)polyOffset.Vertices.size(); ++i) + // center to computeBorders range (-32k to 32k) + polyOffset.Vertices[i] += CVector(-centerOffset, centerOffset, 0); + list convex; // divide poly in convex polys - if (!poly.toConvexPolygons(convex, CMatrix::Identity)) + if (!polyOffset.toConvexPolygons(convex, CMatrix::Identity)) { convex.clear(); - CPolygon reverse = poly; + CPolygon reverse = polyOffset; std::reverse(reverse.Vertices.begin(), reverse.Vertices.end()); if (!reverse.toConvexPolygons(convex, CMatrix::Identity)) return; @@ -357,6 +363,7 @@ void CPrimChecker::render(const CPolygon &poly, uint16 value) sint ymin; convex2d.computeBorders(rasterized, ymin); + ymin -= centerOffset; // uncenter sint dy; for (dy=0; dy<(sint)rasterized.size(); ++dy) @@ -365,19 +372,19 @@ void CPrimChecker::render(const CPolygon &poly, uint16 value) for (x=rasterized[dy].first; x<=rasterized[dy].second; ++x) { - uint8 prevBits = _Grid.get((uint)x, (uint)(ymin+dy)); + uint8 prevBits = _Grid.get((uint)x + centerOffset, (uint)(ymin + dy)); // only set if there was not a water shape there or if previous was lower if ((prevBits & Water) != 0) { - uint16 prevWS = _Grid.index((uint)x, (uint)(ymin+dy)); + uint16 prevWS = _Grid.index((uint)x + centerOffset, (uint)(ymin + dy)); if (_WaterHeight[value] < _WaterHeight[prevWS]) continue; } - _Grid.index((uint)x, (uint)(ymin+dy), value); - _Grid.set((uint)x, (uint)(ymin+dy), Water); + _Grid.index((uint)x + centerOffset, (uint)(ymin + dy), value); + _Grid.set((uint)x + centerOffset, (uint)(ymin + dy), Water); } } } @@ -389,13 +396,19 @@ void CPrimChecker::render(const CPolygon &poly, uint16 value) */ void CPrimChecker::renderBits(const CPolygon &poly, uint8 bits) { + static const sint centerOffset = 20480; // zones are max 40960 + CPolygon polyOffset = poly; + for (ptrdiff_t i = 0; i < (ptrdiff_t)polyOffset.Vertices.size(); ++i) + // center to computeBorders range (-32k to 32k) + polyOffset.Vertices[i] += CVector(-centerOffset, centerOffset, 0); + list convex; // divide poly in convex polys - if (!poly.toConvexPolygons(convex, CMatrix::Identity)) + if (!polyOffset.toConvexPolygons(convex, CMatrix::Identity)) { convex.clear(); - CPolygon reverse = poly; + CPolygon reverse = polyOffset; std::reverse(reverse.Vertices.begin(), reverse.Vertices.end()); if (!reverse.toConvexPolygons(convex, CMatrix::Identity)) return; @@ -410,6 +423,7 @@ void CPrimChecker::renderBits(const CPolygon &poly, uint8 bits) sint ymin; convex2d.computeBorders(rasterized, ymin); + ymin -= centerOffset; // uncenter sint dy; for (dy=0; dy<(sint)rasterized.size(); ++dy) @@ -418,7 +432,7 @@ void CPrimChecker::renderBits(const CPolygon &poly, uint8 bits) for (x=rasterized[dy].first; x<=rasterized[dy].second; ++x) { - _Grid.set((uint)x, (uint)(ymin+dy), bits); + _Grid.set((uint)x + centerOffset, (uint)(ymin + dy), bits); } } } From 76d29b2eda54ff250a66580478fb8fdca221ca8c Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 4 Jan 2021 12:38:39 +0200 Subject: [PATCH 02/12] Fixed: Only call curl cleanup when needed. --- nel/src/web/http_client_curl.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nel/src/web/http_client_curl.cpp b/nel/src/web/http_client_curl.cpp index 10bb0776c..b62300089 100644 --- a/nel/src/web/http_client_curl.cpp +++ b/nel/src/web/http_client_curl.cpp @@ -55,7 +55,10 @@ bool CCurlHttpClient::connect(const std::string &/* server */) curl_global_init(CURL_GLOBAL_ALL); _CurlStruct = curl_easy_init(); if(_Curl == NULL) + { + curl_global_cleanup(); return false; + } return true; } @@ -195,8 +198,8 @@ void CCurlHttpClient::disconnect() { curl_easy_cleanup(_Curl); _CurlStruct = NULL; + curl_global_cleanup(); } - curl_global_cleanup(); } CCurlHttpClient CurlHttpClient; From 00520d6c1198c107e11a69403502d9e9968472cf Mon Sep 17 00:00:00 2001 From: kaetemi Date: Mon, 4 Jan 2021 09:58:44 +0800 Subject: [PATCH 03/12] Process rbank in parallel --- .../build_gamedata/processes/rbank/2_build.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/nel/tools/build_gamedata/processes/rbank/2_build.py b/nel/tools/build_gamedata/processes/rbank/2_build.py index b4c0ca674..ee41ede7f 100755 --- a/nel/tools/build_gamedata/processes/rbank/2_build.py +++ b/nel/tools/build_gamedata/processes/rbank/2_build.py @@ -24,7 +24,7 @@ # along with this program. If not, see . # -import time, sys, os, shutil, subprocess, distutils.dir_util +import time, sys, os, shutil, subprocess, distutils.dir_util, multiprocessing sys.path.append("../../configuration") if os.path.isfile("log.log"): @@ -226,15 +226,20 @@ else: else: printLog(log, "SKIP " + lr1) while len(zonesToBuild) > 0: - processCommand = [ ExecTimeout, str(RbankBuildTesselTimeout), BuildRbank, "-c", "-P", "-g" ] - processCommand.extend(zonesToBuild[:min(len(zonesToBuild), 64)]) - if len(zonesToBuild) > 64: - zonesToBuild = zonesToBuild[64:] - else: - zonesToBuild = [] - print processCommand - print len(processCommand) - subprocess.call(processCommand) + procs = [] + for i in range(0, multiprocessing.cpu_count()): + processCommand = [ ExecTimeout, str(RbankBuildTesselTimeout), BuildRbank, "-c", "-P", "-g" ] + processCommand.extend(zonesToBuild[:min(len(zonesToBuild), 64)]) + if len(zonesToBuild) > 64: + zonesToBuild = zonesToBuild[64:] + else: + zonesToBuild = [] + print processCommand + print len(processCommand) + proc = subprocess.Popen(processCommand) + procs.append(proc) + for proc in procs: + proc.wait() printLog(log, "") printLog(log, ">>> Detect modifications to rebuild lr <<<") From 8fff5b875428ec36851814729d729ec66dc9d6f1 Mon Sep 17 00:00:00 2001 From: kaetemi Date: Mon, 4 Jan 2021 10:42:52 +0800 Subject: [PATCH 04/12] Disable CPU mask in zone lighter --- nel/src/3d/zone_lighter.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nel/src/3d/zone_lighter.cpp b/nel/src/3d/zone_lighter.cpp index 570ba1075..0ba8c2b95 100644 --- a/nel/src/3d/zone_lighter.cpp +++ b/nel/src/3d/zone_lighter.cpp @@ -350,7 +350,7 @@ void setCPUMask (IThread *thread, uint process) } // Set the CPU mask - thread->setCPUMask (1<setCPUMask (1<processCalc (_Process, *_Description); _ZoneLighter->_ProcessExitedMutex.enter(); @@ -624,7 +624,7 @@ void RenderTriangle (const CZoneLighter::CTriangle &triangle, const CZoneLighter void NL3D::CRenderZBuffer::run() { // Set the CPU mask - setCPUMask (Thread, _Process); + // setCPUMask (Thread, _Process); // Span array CPolygon2D::TRasterVect borders; @@ -927,7 +927,7 @@ void CZoneLighter::light (CLandscape &landscape, CZone& output, uint zoneToLight // Backup thread mask IThread *currentThread = IThread::getCurrentThread (); uint64 threadMask = currentThread->getCPUMask(); - currentThread->setCPUMask (1); + // currentThread->setCPUMask (1); // Calc the ray basis _SunDirection=description.SunDirection; @@ -1219,7 +1219,7 @@ void CZoneLighter::light (CLandscape &landscape, CZone& output, uint zoneToLight } // Reset old thread mask - currentThread->setCPUMask (threadMask); + // currentThread->setCPUMask (threadMask); // overflow ? if (_ZBufferOverflow) From 06006cffd3cc05cf35a90d3b82c833dfa03e623f Mon Sep 17 00:00:00 2001 From: kaetemi Date: Mon, 4 Jan 2021 10:44:42 +0800 Subject: [PATCH 05/12] Parallel processing of rbank, zone heightmap, and zone lighter --- .../build_gamedata/configuration/scripts.py | 23 ++++++++++++++++- .../build_gamedata/processes/rbank/2_build.py | 25 ++++++++----------- .../build_gamedata/processes/zone/2_build.py | 3 ++- .../processes/zone_light/2_build.py | 3 ++- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/nel/tools/build_gamedata/configuration/scripts.py b/nel/tools/build_gamedata/configuration/scripts.py index 5bbed497f..6e885bdd3 100755 --- a/nel/tools/build_gamedata/configuration/scripts.py +++ b/nel/tools/build_gamedata/configuration/scripts.py @@ -23,7 +23,7 @@ # along with this program. If not, see . # -import time, sys, os, shutil, subprocess, distutils.dir_util +import time, sys, os, shutil, subprocess, distutils.dir_util, multiprocessing, math ActiveProjectDirectory = os.getenv("NELBUILDACTIVEPROJECT", "configuration/project") sys.path.append(ActiveProjectDirectory) @@ -32,6 +32,27 @@ def printLog(log, text): log.write(text + "\n") print text +pendingProcesses = [] +processLimit = math.ceil(multiprocessing.cpu_count() * 0.75) + +def callParallelProcess(command): + res = 0 + if len(pendingProcesses) >= processLimit: + waitingProc = pendingProcesses.pop(0) + res = waitingProc.wait() + proc = subprocess.Popen(command) + pendingProcesses.append(proc) + return res + +def flushParallelProcesses(): + res = 0 + while (len(pendingProcesses) > 0): + waitingProc = pendingProcesses.pop(0) + procRes = waitingProc.wait() + if procRes != 0: + res = procRes + return res + def mkPath(log, path): printLog(log, "DIR " + path) distutils.dir_util.mkpath(path) diff --git a/nel/tools/build_gamedata/processes/rbank/2_build.py b/nel/tools/build_gamedata/processes/rbank/2_build.py index ee41ede7f..6a55e94fc 100755 --- a/nel/tools/build_gamedata/processes/rbank/2_build.py +++ b/nel/tools/build_gamedata/processes/rbank/2_build.py @@ -24,7 +24,7 @@ # along with this program. If not, see . # -import time, sys, os, shutil, subprocess, distutils.dir_util, multiprocessing +import time, sys, os, shutil, subprocess, distutils.dir_util sys.path.append("../../configuration") if os.path.isfile("log.log"): @@ -226,20 +226,15 @@ else: else: printLog(log, "SKIP " + lr1) while len(zonesToBuild) > 0: - procs = [] - for i in range(0, multiprocessing.cpu_count()): - processCommand = [ ExecTimeout, str(RbankBuildTesselTimeout), BuildRbank, "-c", "-P", "-g" ] - processCommand.extend(zonesToBuild[:min(len(zonesToBuild), 64)]) - if len(zonesToBuild) > 64: - zonesToBuild = zonesToBuild[64:] - else: - zonesToBuild = [] - print processCommand - print len(processCommand) - proc = subprocess.Popen(processCommand) - procs.append(proc) - for proc in procs: - proc.wait() + processCommand = [ ExecTimeout, str(RbankBuildTesselTimeout), BuildRbank, "-c", "-P", "-g" ] + processCommand.extend(zonesToBuild[:min(len(zonesToBuild), 64)]) + if len(zonesToBuild) > 64: + zonesToBuild = zonesToBuild[64:] + else: + zonesToBuild = [] + print processCommand + callParallelProcess(processCommand) + flushParallelProcesses() printLog(log, "") printLog(log, ">>> Detect modifications to rebuild lr <<<") diff --git a/nel/tools/build_gamedata/processes/zone/2_build.py b/nel/tools/build_gamedata/processes/zone/2_build.py index f9a31e3c4..cb9287c36 100755 --- a/nel/tools/build_gamedata/processes/zone/2_build.py +++ b/nel/tools/build_gamedata/processes/zone/2_build.py @@ -147,7 +147,8 @@ else: sourceFile = ExportBuildDirectory + "/" + ZoneWeldBuildDirectory + "/" + file destFile = ExportBuildDirectory + "/" + ZoneWeldBuildDirectory + "/" + os.path.basename(file)[0:-len(".zonenhw")] + ".zonew" if needUpdateLogRemoveDest(log, sourceFile, destFile): - subprocess.call([ ZoneElevation, sourceFile, destFile, "--land", land, "--heightmap", heightMap1, "--zfactor", LigoExportZFactor1, "--heightmap2", heightMap2, "--zfactor2", LigoExportZFactor2 ]) + callParallelProcess([ ZoneElevation, sourceFile, destFile, "--land", land, "--heightmap", heightMap1, "--zfactor", LigoExportZFactor1, "--heightmap2", heightMap2, "--zfactor2", LigoExportZFactor2 ]) + flushParallelProcesses() printLog(log, "") log.close() diff --git a/nel/tools/build_gamedata/processes/zone_light/2_build.py b/nel/tools/build_gamedata/processes/zone_light/2_build.py index 84d2b26a4..53a926c29 100755 --- a/nel/tools/build_gamedata/processes/zone_light/2_build.py +++ b/nel/tools/build_gamedata/processes/zone_light/2_build.py @@ -68,7 +68,8 @@ else: destFile = destDir + "/" + file[0:-len(".zonew")] + ".zonel" if (needUpdateLogRemoveDest(log, srcFile, destFile)): dependFile = dependDir + "/" + file[0:-len(".zonew")] + ".depend" - subprocess.call([ ExecTimeout, str(ZoneLightBuildTimeout), ZoneLighter, srcFile, destFile, ActiveProjectDirectory + "/generated/properties.cfg", dependFile ]) + callParallelProcess([ ExecTimeout, str(ZoneLightBuildTimeout), ZoneLighter, srcFile, destFile, ActiveProjectDirectory + "/generated/properties.cfg", dependFile ]) + flushParallelProcesses() printLog(log, "") # For each zone_light ig From 06be2d003cd908f46ab36d7869c7787462aaa794 Mon Sep 17 00:00:00 2001 From: kaetemi Date: Mon, 4 Jan 2021 11:13:20 +0800 Subject: [PATCH 06/12] Parallel wmap sizes --- .../build_gamedata/processes/ai_wmap/2_build.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nel/tools/build_gamedata/processes/ai_wmap/2_build.py b/nel/tools/build_gamedata/processes/ai_wmap/2_build.py index 8aa63f62f..8dd43be16 100755 --- a/nel/tools/build_gamedata/processes/ai_wmap/2_build.py +++ b/nel/tools/build_gamedata/processes/ai_wmap/2_build.py @@ -68,13 +68,15 @@ else: printLog(log, ">>> Generate sized wmap <<<") subprocess.call([ AiBuildWmap, "pacsBuildGabarit " + AiWmapContinentName ]) printLog(log, ">>> Generate cwmaps for each size <<<") - subprocess.call([ AiBuildWmap, "pacsBuildWmap " + AiWmapContinentName + "_0" ]) - subprocess.call([ AiBuildWmap, "pacsBuildWmap " + AiWmapContinentName + "_1" ]) - subprocess.call([ AiBuildWmap, "pacsBuildWmap " + AiWmapContinentName + "_2" ]) + callParallelProcess([ AiBuildWmap, "pacsBuildWmap " + AiWmapContinentName + "_0" ]) + callParallelProcess([ AiBuildWmap, "pacsBuildWmap " + AiWmapContinentName + "_1" ]) + callParallelProcess([ AiBuildWmap, "pacsBuildWmap " + AiWmapContinentName + "_2" ]) + flushParallelProcesses() printLog(log, ">>> Generate bitmap for each size <<<") - subprocess.call([ AiBuildWmap, "pacsBuildBitmap " + AiWmapContinentName + "_0" ]) - subprocess.call([ AiBuildWmap, "pacsBuildBitmap " + AiWmapContinentName + "_1" ]) - subprocess.call([ AiBuildWmap, "pacsBuildBitmap " + AiWmapContinentName + "_2" ]) + callParallelProcess([ AiBuildWmap, "pacsBuildBitmap " + AiWmapContinentName + "_0" ]) + callParallelProcess([ AiBuildWmap, "pacsBuildBitmap " + AiWmapContinentName + "_1" ]) + callParallelProcess([ AiBuildWmap, "pacsBuildBitmap " + AiWmapContinentName + "_2" ]) + flushParallelProcesses() printLog(log, ">>> Clear height maps for size 1 and 2 <<<") subprocess.call([ AiBuildWmap, "pacsClearHeightmap " + AiWmapContinentName ]) printLog(log, ">>> Cut tga for world editor <<<") From 3164e5fe3e884cf96f4b3d7523dce67ca30dae4c Mon Sep 17 00:00:00 2001 From: kaetemi Date: Mon, 4 Jan 2021 20:25:28 +0800 Subject: [PATCH 07/12] Parallel ig light --- nel/tools/build_gamedata/processes/zone_light/2_build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nel/tools/build_gamedata/processes/zone_light/2_build.py b/nel/tools/build_gamedata/processes/zone_light/2_build.py index 53a926c29..1e8e0c925 100755 --- a/nel/tools/build_gamedata/processes/zone_light/2_build.py +++ b/nel/tools/build_gamedata/processes/zone_light/2_build.py @@ -95,7 +95,8 @@ else: if (needUpdateLogRemoveDest(log, igsrcFile, destFile)): srcFile = srcDir + "/" + file dependFile = dependDir + "/" + file[0:-len(".zonel")] + ".depend" - subprocess.call([ ExecTimeout, str(ZoneIgLightBuildTimeout), ZoneIgLighter, srcFile, destFile, ActiveProjectDirectory + "/generated/properties.cfg", dependFile ]) + callParallelProcess([ ExecTimeout, str(ZoneIgLightBuildTimeout), ZoneIgLighter, srcFile, destFile, ActiveProjectDirectory + "/generated/properties.cfg", dependFile ]) + flushParallelProcesses() printLog(log, "") log.close() From 30ece314c58cd81e584d65a99c2c7d7ef9220895 Mon Sep 17 00:00:00 2001 From: kaetemi Date: Tue, 5 Jan 2021 12:10:32 +0800 Subject: [PATCH 08/12] Add tool to unbuild heightmaps --- nel/tools/3d/CMakeLists.txt | 1 + nel/tools/3d/unbuild_elevation/CMakeLists.txt | 11 + nel/tools/3d/unbuild_elevation/gold_pill.ico | Bin 0 -> 3638 bytes nel/tools/3d/unbuild_elevation/main.rc | 42 ++ .../unbuild_elevation/unbuild_elevation.cpp | 477 ++++++++++++++++++ 5 files changed, 531 insertions(+) create mode 100644 nel/tools/3d/unbuild_elevation/CMakeLists.txt create mode 100644 nel/tools/3d/unbuild_elevation/gold_pill.ico create mode 100644 nel/tools/3d/unbuild_elevation/main.rc create mode 100644 nel/tools/3d/unbuild_elevation/unbuild_elevation.cpp diff --git a/nel/tools/3d/CMakeLists.txt b/nel/tools/3d/CMakeLists.txt index e6ba62e3b..8bf56b6c8 100644 --- a/nel/tools/3d/CMakeLists.txt +++ b/nel/tools/3d/CMakeLists.txt @@ -32,6 +32,7 @@ IF(WITH_NEL_TOOLS) zone_ig_lighter zone_lighter zone_welder + unbuild_elevation zone_elevation shapes_exporter shape2obj diff --git a/nel/tools/3d/unbuild_elevation/CMakeLists.txt b/nel/tools/3d/unbuild_elevation/CMakeLists.txt new file mode 100644 index 000000000..e6b7636d3 --- /dev/null +++ b/nel/tools/3d/unbuild_elevation/CMakeLists.txt @@ -0,0 +1,11 @@ +FILE(GLOB SRC *.cpp *.h *.rc) + +SOURCE_GROUP("" FILES ${SRC}) + +ADD_EXECUTABLE(unbuild_elevation ${SRC}) + +TARGET_LINK_LIBRARIES(unbuild_elevation nel3d nelmisc nelligo) +NL_DEFAULT_PROPS(unbuild_elevation "NeL, Tools, 3D: Unbuild Elevation") +NL_ADD_RUNTIME_FLAGS(unbuild_elevation) + +INSTALL(TARGETS unbuild_elevation RUNTIME DESTINATION ${NL_BIN_PREFIX} COMPONENT tools3d) diff --git a/nel/tools/3d/unbuild_elevation/gold_pill.ico b/nel/tools/3d/unbuild_elevation/gold_pill.ico new file mode 100644 index 0000000000000000000000000000000000000000..618b67a5d196bdbdc4d497a3b7ca998b79039677 GIT binary patch literal 3638 zcmeH|zityj5XQf`vv2R8lRF0=!64zvLOcRZ@(47%1EO$G7suZ;o}Ow9KWIF7uW|24qu=jCYmGObHC}wun9t{!&1Sg$ zP6MyLV>})M&yFz~jWC%^fQS2+=cprf1?md?hZP`8`Oj#}K*|8hER?sITv4;NA<$;R zq>3WH(P#z%rJ$%(X!S5zlUP+N zzr|;JJa&1AAFqXdR(y5mKwA%eZ9P`}w)9@{Sn*u(Tk&M^-mm(n@m=v)}3W?H=*(Tyb(EO1J?UWK}aqZWd!Zofe&kK&zR)e`x(xG +#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 Zone Elevation" + VALUE "FileVersion", NL_VERSION + VALUE "LegalCopyright", COPYRIGHT + VALUE "OriginalFilename", "zone_elevation" 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/nel/tools/3d/unbuild_elevation/unbuild_elevation.cpp b/nel/tools/3d/unbuild_elevation/unbuild_elevation.cpp new file mode 100644 index 000000000..f3c109a25 --- /dev/null +++ b/nel/tools/3d/unbuild_elevation/unbuild_elevation.cpp @@ -0,0 +1,477 @@ +// NeL - MMORPG Framework +// Copyright (C) 2021 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 . + +#include "../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_SourceDir; /* R:\reference\2008_july\data\r2_desert */ +std::string s_ReferenceDir; /* R:\pipeline\export\continents\r2_desert\zone_weld */ + +std::string s_OutputPy; + +std::string s_SourceExt = "zonel"; +std::string s_ReferenceExt = "zonenhw"; + +CZoneRegion s_Land; /* "R:\graphics\landscape\ligo\desert\r2_desert.land" */ + +int s_Warnings; + +// unbuild_elevation --land "R:\graphics\landscape\ligo\desert\r2_desert.land" "Y:\temp\r2_desert_elevation.Py" "R:\reference\2008_july\data\r2_desert" "R:\pipeline\export\continents\r2_desert\zone_weld" +// --land "R:\graphics\landscape\ligo\jungle\zorai.land" --referenceext zonenhw "X:\wsl\big_zorai.py" "R:\reference\2008_july\data\zorai_zones" "R:\pipeline\export\continents\zorai\zone_weld" +// --land "R:\graphics\landscape\ligo\desert\r2_desert.land" "X:\wsl\big_r2_desert.py" "R:\reference\2008_july\data\r2_desert" "R:\pipeline\export\continents\r2_desert\zone_weld" +// --land "R:\graphics\landscape\ligo\jungle\r2_jungle.land" "X:\wsl\big_r2_jungle.py" "R:\reference\2008_july\data\r2_jungle" "R:\pipeline\export\continents\r2_jungle\zone_weld" +// --land "R:\graphics\landscape\ligo\jungle\r2_forest.land" "X:\wsl\big_r2_forest.py" "R:\reference\2008_july\data\r2_forest" "R:\pipeline\export\continents\r2_forest\zone_weld" +// --land "R:\graphics\landscape\ligo\lacustre\r2_lakes.land" "X:\wsl\big_r2_lakes.py" "R:\reference\2008_july\data\r2_lakes" "R:\pipeline\export\continents\r2_lakes\zone_weld" +// --land "R:\graphics\landscape\ligo\primes_racines\r2_roots.land" "X:\wsl\big_r2_roots.py" "R:\reference\2008_july\data\r2_roots" "R:\pipeline\export\continents\r2_roots\zone_weld" + +bool loadLand(const string &filename) +{ + try + { + CIFile fileIn; + if (fileIn.open (filename)) + { + CIXml xml(true); + nlverify(xml.init(fileIn)); + s_Land.serial(xml); + } + else + { + nlwarning("Can't open the land file: %s", filename.c_str()); + return false; + } + } + catch (const Exception& e) + { + nlwarning("Error in 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::toUpperAscii(xStr); + x = ((xStr[0] - 'A') * 26 + (xStr[1] - 'A')); + return true; +Fail: + x = -1; + y = -1; + return false; +} + +void getBitmapSize(int &w, int &h) +{ + sint32 sizeX = s_ZoneMaxX - s_ZoneMinX + 1; + sint32 sizeY = s_ZoneMaxY - s_ZoneMinY + 1; + w = sizeX * 20; + h = sizeY * 20; +} + +void getBitmapCoord(float &xc, float &yc, float x, float y) +{ + float deltaZ = 0.0f, deltaZ2 = 0.0f; + CRGBAF color; + sint32 sizeX = s_ZoneMaxX - s_ZoneMinX + 1; + sint32 sizeY = s_ZoneMaxY - s_ZoneMinY + 1; + + clamp(x, s_CellSize * s_ZoneMinX, s_CellSize * (s_ZoneMaxX + 1)); + clamp(y, s_CellSize * s_ZoneMinY, s_CellSize * (s_ZoneMaxY + 1)); + + xc = (x - s_CellSize * s_ZoneMinX) / (s_CellSize * sizeX); + yc = 1.0f - ((y - s_CellSize * s_ZoneMinY) / (s_CellSize * sizeY)); +} + +bool processZone(std::vector &output, const std::string &sourceFile, const std::string &referenceFile) +{ + std::string zone = CFile::getFilenameWithoutExtension(referenceFile); + + std::vector sourceVertices; + std::vector referenceVertices; + { + CZoneInfo zoneInfo; + { + CZone zone; + { + CIFile f(sourceFile); + zone.serial(f); + f.close(); + } + zone.retrieve(zoneInfo); + } + for (ptrdiff_t i = 0; i < (ptrdiff_t)zoneInfo.Patchs.size(); ++i) + { + sourceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[0]); + sourceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[1]); + sourceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[2]); + sourceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[3]); + } + } + { + CZoneInfo zoneInfo; + { + CZone zone; + { + CIFile f(referenceFile); + zone.serial(f); + f.close(); + } + zone.retrieve(zoneInfo); + } + for (ptrdiff_t i = 0; i < (ptrdiff_t)zoneInfo.Patchs.size(); ++i) + { + referenceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[0]); + referenceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[1]); + referenceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[2]); + referenceVertices.push_back(zoneInfo.Patchs[i].Patch.Vertices[3]); + } + } + printf("Source vertices: %i, reference vertices: %i\n", (int)sourceVertices.size(), (int)referenceVertices.size()); + if (sourceVertices.size() != referenceVertices.size()) + { + nlwarning("Mismatching vertex count in zone %s, source vertices: %i, reference vertices: %i", zone.c_str(), (int)sourceVertices.size(), (int)referenceVertices.size()); + ++s_Warnings; + } + + std::vector processedSourceIndices; + std::vector processedReferenceIndices; + processedSourceIndices.resize(sourceVertices.size()); + processedReferenceIndices.resize(referenceVertices.size()); + + int referenceProcessedCount = 0; + int sourceProcessedCount = 0; + + for (ptrdiff_t i = 0; i < (ptrdiff_t)referenceVertices.size(); ++i) + { + if (processedReferenceIndices[i]) + continue; + + float referenceHeight = referenceVertices[i].z; + int referenceHeightDiv = 1; + ++referenceProcessedCount; + + for (ptrdiff_t j = i + 1; j < (ptrdiff_t)referenceVertices.size(); ++j) + { + if (processedReferenceIndices[j]) + continue; + + if (abs(referenceVertices[i].x - referenceVertices[j].x) < 0.1f + && abs(referenceVertices[i].y - referenceVertices[j].y) < 0.1f) + { + processedReferenceIndices[j] = true; + referenceHeight += referenceVertices[j].z; + ++referenceHeightDiv; + ++referenceProcessedCount; + } + } + + float sourceHeight = 0.f; + int sourceHeightDiv = 0; + + for (ptrdiff_t j = 0; j < (ptrdiff_t)sourceVertices.size(); ++j) + { + if (processedSourceIndices[j]) + continue; + + if (abs(referenceVertices[i].x - sourceVertices[j].x) < 0.1f + && abs(referenceVertices[i].y - sourceVertices[j].y) < 0.1f) + { + processedSourceIndices[j] = true; + sourceHeight += sourceVertices[j].z; + ++sourceHeightDiv; + ++sourceProcessedCount; + } + } + + if (!sourceHeightDiv) + { + nlwarning("No matching vertices in source for zone %s, x: %f, y: %f", zone.c_str(), referenceVertices[i].x, referenceVertices[i].y); + ++s_Warnings; + continue; + } + + if (referenceHeightDiv != sourceHeightDiv) + { + nlwarning("Mismatching vertex count for zone %s, x: %f, y: %f (reference: %i, source: %i)", zone.c_str(), referenceVertices[i].x, referenceVertices[i].y, referenceHeightDiv, sourceHeightDiv); + ++s_Warnings; + continue; + } + + referenceHeight /= (float)referenceHeightDiv; + sourceHeight /= (float)sourceHeightDiv; + + output.push_back(NLMISC::CVector(referenceVertices[i].x, referenceVertices[i].y, sourceHeight - referenceHeight)); + } + + return true; +} + +bool unbuildElevation() +{ + std::vector referenceZones; + CPath::getPathContent(s_ReferenceDir, true, false, true, referenceZones); + int totalZones = 0; + + std::vector output; + + for (std::vector::iterator it(referenceZones.begin()), end(referenceZones.end()); it != end; ++it) + { + if (CFile::getExtension(*it) != s_ReferenceExt) + continue; + + std::string zone = CFile::getFilenameWithoutExtension(*it); + std::string sourceZone = s_SourceDir + "/" + CFile::getFilenameWithoutExtension(*it) + "." + s_SourceExt; + + if (!CFile::fileExists(sourceZone)) + continue; + + printf("%s\n", nlUtf8ToMbcs(zone)); + ++totalZones; + + if (!processZone(output, sourceZone, *it)) + return false; + + printf("\n"); + } + + printf("Total zones: %i\n", totalZones); + +#if 1 + + std::vector processedOutput; + processedOutput.resize(output.size()); + std::vector reducedOutput; + for (ptrdiff_t i = 0; i < (ptrdiff_t)output.size(); ++i) + { + if (processedOutput[i]) + continue; + + CVector v = output[i]; + int div = 1; + + for (ptrdiff_t j = i + 1; j < (ptrdiff_t)output.size(); ++j) + { + if (processedOutput[j]) + continue; + + if (abs(output[i].x - output[j].x) < 0.1f + && abs(output[i].y - output[j].y) < 0.1f) + { + processedOutput[j] = true; + v.z += output[j].z; + ++div; + } + } + + v.z /= (float)div; + reducedOutput.push_back(v); + } + + printf("Reduced vertex count from %i to %i\n", (int)output.size(), (int)reducedOutput.size()); + +#else + + std::vector reducedOutput = output; + +#endif + + { + int w, h; + getBitmapSize(w, h); + + FILE *fo = fopen(nlUtf8ToMbcs(s_OutputPy), "w"); + if (!fo) + return false; + + fprintf(fo, "import naturalneighbor # https://github.com/innolitics/natural-neighbor-interpolation - pip3 install naturalneighbor\n"); + fprintf(fo, "import numpy as np\n"); + fprintf(fo, "import png # pip3 install pypng\n"); + fprintf(fo, "grid_ranges = [[0, 1, %i%s], [0, 1, %i%s], [-1, 1, 1j]]\n", w, "j", h, "j"); + + fprintf(fo, "points = np.array([\n"); + + for (ptrdiff_t i = 0; i < (ptrdiff_t)reducedOutput.size(); ++i) + { + float x, y; + getBitmapCoord(x, y, reducedOutput[i].x, reducedOutput[i].y); + fprintf(fo, "\t[ %f, %f, 0 ], # %f, %f\n", x, y, reducedOutput[i].x, reducedOutput[i].y); + } + + fprintf(fo, "])\n"); + fprintf(fo, "\n"); + fprintf(fo, "values = np.array([\n"); + + for (ptrdiff_t i = 0; i < (ptrdiff_t)reducedOutput.size(); ++i) + { + fprintf(fo, "\t%f,\n", reducedOutput[i].z); + } + + fprintf(fo, "])\n"); + + fprintf(fo, "nn_interpolated_values = naturalneighbor.griddata(points, values, grid_ranges)\n"); + fprintf(fo, "img = []\n"); + fprintf(fo, "for y in range(0, %i):\n", h); + fprintf(fo, "\tline = []\n"); + fprintf(fo, "\tfor x in range(0, %i):\n", w); + fprintf(fo, "\t\tline.append(np.round(nn_interpolated_values[x][y][0]).astype(int) + 127)\n"); + fprintf(fo, "\timg.append(line)\n"); + fprintf(fo, "with open('%s.png', 'wb') as f:\n", CFile::getFilenameWithoutExtension(s_OutputPy).c_str()); + fprintf(fo, "\tw = png.Writer(%i, %i, greyscale=True)\n", w, h); + fprintf(fo, "\tw.write(f, img)\n"); + fflush(fo); + fclose(fo); + } + + printf("Warnings: %i\n", s_Warnings); + return true; +} + +bool unbuildElevation(NLMISC::CCmdArgs &args) +{ + s_OutputPy = args.getAdditionalArg("output")[0]; + s_SourceDir = args.getAdditionalArg("source")[0]; + s_ReferenceDir = args.getAdditionalArg("reference")[0]; + + if (args.haveLongArg("zonemin") && args.haveLongArg("zonemax")) + { + sint32 zoneMinX, zoneMinY; + sint32 zoneMaxX, zoneMaxY; + if (!getXYFromZoneName(zoneMinX, zoneMinY, args.getLongArg("zonemin")[0]) + || !getXYFromZoneName(zoneMaxX, zoneMaxY, args.getLongArg("zonemax")[0])) + { + return false; + } + s_ZoneMinX = min(zoneMinX, zoneMaxX); + s_ZoneMaxX = max(zoneMinX, zoneMaxX); + s_ZoneMinY = min(zoneMinY, zoneMaxY); + s_ZoneMaxY = max(zoneMinY, zoneMaxY); + } + else if (args.haveLongArg("land")) + { + if (!loadLand(args.getLongArg("land")[0])) + return false; + s_ZoneMinX = s_Land.getMinX(); + s_ZoneMaxX = s_Land.getMaxX(); + s_ZoneMinY = s_Land.getMinY(); + s_ZoneMaxY = s_Land.getMaxY(); + } + else + { + nlwarning("Must have either both 'zonemin' and 'zonemax', or 'land' specified"); + return false; + } + + if (args.haveLongArg("cellsize")) + { + if (!NLMISC::fromString(args.getLongArg("cellsize")[0], s_CellSize)) + return false; + } + + if (args.haveLongArg("sourceext")) + { + s_SourceExt = args.getLongArg("sourceext")[0]; + } + + if (args.haveLongArg("referenceext")) + { + s_ReferenceExt = args.getLongArg("referenceext")[0]; + } + + return unbuildElevation(); +} + +} /* anonymous namespace */ + +int main(int argc, char **argv) +{ + NLMISC::CApplicationContext myApplicationContext; + + NLMISC::CCmdArgs args; + + args.addAdditionalArg("output", "Output Python file path"); + args.addAdditionalArg("source", "Input folder with zones at the right height"); + args.addAdditionalArg("reference", "Input folder with zones at the wrong height"); + + args.addArg("", "sourceext", "extension", "Source zone extension (default: zonel)"); + args.addArg("", "referenceext", "extension", "Reference zone extension (default: zonew)"); + + args.addArg("", "land", "land", "Ligo land file (either specify this or the boundaries)"); + args.addArg("", "zonemin", "zone", "Zone boundary"); + args.addArg("", "zonemax", "zone", "Zone boundary"); + args.addArg("", "cellsize", "meters", "Zone cell size (default: 160)"); + + if (!args.parse(argc, argv)) + { + return EXIT_FAILURE; + } + + if (!unbuildElevation(args)) + { + args.displayHelp(); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/* end of file */ \ No newline at end of file From 87b5a77d4fb29c00301d153bb322f58f9e3b9b79 Mon Sep 17 00:00:00 2001 From: kaetemi Date: Tue, 5 Jan 2021 22:41:55 +0800 Subject: [PATCH 09/12] Add elevation snippets to weld bound vertices --- .../3d/zone_elevation/zone_elevation.cpp | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/nel/tools/3d/zone_elevation/zone_elevation.cpp b/nel/tools/3d/zone_elevation/zone_elevation.cpp index 598cfda31..abe9b511c 100644 --- a/nel/tools/3d/zone_elevation/zone_elevation.cpp +++ b/nel/tools/3d/zone_elevation/zone_elevation.cpp @@ -186,6 +186,111 @@ NLMISC::CVector getHeightNormal(float x, float y) // compute the heightmap normal with the tangents return (ds ^ dt).normed(); } +// *************************************************************************** +// void CExport::computeSubdividedTangents(uint numBinds, const NL3D::CBezierPatch &patch, uint edge, NLMISC::CVector subTangents[8]) +void computeSubdividedTangents(uint numBinds, const NL3D::CBezierPatch &patch, uint edge, NLMISC::CVector subTangents[8]) +{ + // Subdivide the Bezier patch to get the correct tangents to apply to neighbors + CBezierPatch subPatchs1_2[2]; + CBezierPatch subPatchs1_4[4]; + + // subdivide on s if edge is horizontal + bool subDivideOnS= (edge&1)==1; + + // Subdivide one time. + if(subDivideOnS) patch.subdivideS(subPatchs1_2[0], subPatchs1_2[1]); + else patch.subdivideT(subPatchs1_2[0], subPatchs1_2[1]); + + // Subdivide again for bind 1/4. + if(numBinds==4) + { + if(subDivideOnS) + { + subPatchs1_2[0].subdivideS(subPatchs1_4[0], subPatchs1_4[1]); + subPatchs1_2[1].subdivideS(subPatchs1_4[2], subPatchs1_4[3]); + } + else + { + subPatchs1_2[0].subdivideT(subPatchs1_4[0], subPatchs1_4[1]); + subPatchs1_2[1].subdivideT(subPatchs1_4[2], subPatchs1_4[3]); + } + } + + // Now, fill the tangents according to edge. + bool invertPaSrc= edge>=2; + // Bind 1/2 case. + if(numBinds==2) + { + // 4 tangents to fill. + for(uint i=0;i<4;i++) + { + // get patch id from 0 to 1. + uint paSrcId= i/2; + // invert if edge is 2 or 3 + if(invertPaSrc) paSrcId= 1-paSrcId; + // get tg id in this patch. + uint tgSrcId= (i&1) + edge*2; + // fill result. + subTangents[i]= subPatchs1_2[paSrcId].Tangents[tgSrcId]; + } + } + // Bind 1/4 case. + else + { + // 8 tangents to fill. + for(uint i=0;i<8;i++) + { + // get patch id from 0 to 3. + uint paSrcId= i/2; + // invert if edge is 2 or 3 + if(invertPaSrc) paSrcId= 3-paSrcId; + // get tg id in this patch. + uint tgSrcId= (i&1) + edge*2; + // fill result. + subTangents[i]= subPatchs1_4[paSrcId].Tangents[tgSrcId]; + } + } +} + +// *************************************************************************** +// bool CExport::applyVertexBind(NL3D::CPatchInfo &pa, NL3D::CPatchInfo &oldPa, uint edgeToModify, bool startEdge, ... +bool applyVertexBind(NL3D::CPatchInfo &pa, NL3D::CPatchInfo &oldPa, uint edgeToModify, bool startEdge, + const NLMISC::CMatrix &oldTgSpace, const NLMISC::CMatrix &newTgSpace, + const NLMISC::CVector &bindedPos, const NLMISC::CVector &bindedTangent ) +{ + // Get the vertex to modify according to edge/startEdge + uint vertexToModify= edgeToModify + (startEdge?0:1); + vertexToModify&=3; + + // If already moved, no-op + if(pa.Patch.Vertices[vertexToModify]==bindedPos) + return false; + else + { + // Change the vertex + pa.Patch.Vertices[vertexToModify]= bindedPos; + + // change the tangent, according to startEdge + pa.Patch.Tangents[edgeToModify*2 + (startEdge?0:1) ]= bindedTangent; + + // Must change the tangent which is on the other side of the vertex: + uint tgToModify= 8 + edgeToModify*2 + (startEdge?-1:+2); + tgToModify&=7; + /* To keep the same continuity aspect around the vertex, we compute the original tangent in a + special space: the Binded Patch Tangent Space. Once we have the original tangent in the original patch TgSpace, + we reapply it in the transformed patch TgSpace, to get the transformed tangent + */ + pa.Patch.Tangents[tgToModify]= newTgSpace * ( oldTgSpace.inverted() * oldPa.Patch.Tangents[tgToModify] ); + + + // Do the same to the associated interior. + pa.Patch.Interiors[vertexToModify]= newTgSpace * ( oldTgSpace.inverted() * oldPa.Patch.Interiors[vertexToModify] ); + + + // modified + return true; + } +} void applyZoneHeightmap() { @@ -201,6 +306,9 @@ void applyZoneHeightmap() std::vector zoneBorderVertices; zone.retrieve(zonePatches, zoneBorderVertices); + // Bkup the original patchs. + vector oldPatchInfos = zonePatches; + // Apply the Heighmap to all vertices/tangents/interiors (see Land Export tool.) for (size_t i = 0; i < zonePatches.size(); ++i) { @@ -254,6 +362,94 @@ void applyZoneHeightmap() } } + // See `void CExport::transformZone (CZone &zeZone, sint32 nPosX, sint32 nPosY, uint8 nRot, uint8 nFlip, bool computeHeightmap)` + // 3. For all binds, reset the position of near vertices/tangents/interiors. Must do it at last + // -------- + bool bindVertexModified = true; + // Since this is a recursive problem (binded patchs may bind other patchs), do it unitl all vertices no more move :) + while (bindVertexModified) + { + bindVertexModified = false; + for (size_t i = 0; i < zonePatches.size(); ++i) + { + CPatchInfo &rPI = zonePatches[i]; + + // For all edges + for (size_t j = 0; j < 4; ++j) + { + uint numBinds = rPI.BindEdges[j].NPatchs; + // If this edge is binded on 2 or 4 patches. + if (numBinds == 2 || numBinds == 4) + { + // compute the 4 or 8 tangents along the edge (in CCW) + CVector subTangents[8]; + computeSubdividedTangents(numBinds, rPI.Patch, j, subTangents); + + // For all vertex to bind: 1 or 3. + for (uint vb = 0; vb < numBinds - 1; vb++) + { + // compute the s/t coordinate + float bindS, bindT; + // 0.5, or 0.25, 0.5, 0.75 + float ec = (float)(vb + 1) / (float)numBinds; + switch (j) + { + case 0: bindS= 0; bindT= ec; break; + case 1: bindS= ec; bindT= 1; break; + case 2: bindS= 1; bindT= 1-ec; break; + case 3: bindS= 1-ec; bindT= 0; break; + } + + // compute the vertex position from big patch. + CVector bindedPos; + bindedPos = rPI.Patch.eval(bindS, bindT); + + // Compute a TgSpace matrix around this position. + CMatrix oldTgSpace; + CMatrix newTgSpace; + + // Build the original tgtSpace (patch before deformation) + oldTgSpace.setRot(oldPatchInfos[i].Patch.evalTangentS(bindS, bindT), + oldPatchInfos[i].Patch.evalTangentT(bindS, bindT), + oldPatchInfos[i].Patch.evalNormal(bindS, bindT)); + oldTgSpace.normalize(CMatrix::ZYX); + oldTgSpace.setPos(oldPatchInfos[i].Patch.eval(bindS, bindT)); + + // Build the new tgtSpace + newTgSpace.setRot(rPI.Patch.evalTangentS(bindS, bindT), + rPI.Patch.evalTangentT(bindS, bindT), + rPI.Patch.evalNormal(bindS, bindT)); + newTgSpace.normalize(CMatrix::ZYX); + newTgSpace.setPos(bindedPos); + + // apply to the 2 smaller binded patchs which share this vertex. + uint edgeToModify; + sint ngbId; + CVector bindedTangent; + + // The first patch (CCW) must change the vertex which starts on the edge (CCW) + edgeToModify = rPI.BindEdges[j].Edge[vb]; + // get the tangent to set + bindedTangent = subTangents[vb * 2 + 1]; + // get the patch id. + ngbId = rPI.BindEdges[j].Next[vb]; + bindVertexModified |= applyVertexBind(zonePatches[ngbId], oldPatchInfos[ngbId], edgeToModify, + true, oldTgSpace, newTgSpace, bindedPos, bindedTangent); + + // The second patch (CCW) must change the vertex which ends on the edge (CCW) + edgeToModify = rPI.BindEdges[j].Edge[vb + 1]; + // get the tangent to set + bindedTangent = subTangents[vb * 2 + 2]; + // get the patch id. + ngbId = rPI.BindEdges[j].Next[vb + 1]; + bindVertexModified |= applyVertexBind(zonePatches[ngbId], oldPatchInfos[ngbId], edgeToModify, + false, oldTgSpace, newTgSpace, bindedPos, bindedTangent); + } + } + } + } + } + // Save zone zone.build(zoneId, zonePatches, zoneBorderVertices); COFile centerSave(s_OutputZone); From 661a2bdaf7827cac9c863ab1eb4c69edafd244da Mon Sep 17 00:00:00 2001 From: Nimetu Date: Mon, 4 Jan 2021 20:34:56 +0200 Subject: [PATCH 10/12] Added small program to measure font atlas performance, ryzom/ryzomcore#626 --- nel/samples/3d/CMakeLists.txt | 1 + nel/samples/3d/font/main.cpp | 32 +++++++- nel/samples/3d/font_perf/CMakeLists.txt | 14 ++++ nel/samples/3d/font_perf/beteckna.ttf | Bin 0 -> 16488 bytes nel/samples/3d/font_perf/main.cpp | 99 ++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 nel/samples/3d/font_perf/CMakeLists.txt create mode 100644 nel/samples/3d/font_perf/beteckna.ttf create mode 100644 nel/samples/3d/font_perf/main.cpp diff --git a/nel/samples/3d/CMakeLists.txt b/nel/samples/3d/CMakeLists.txt index 772ab0056..1062579cd 100644 --- a/nel/samples/3d/CMakeLists.txt +++ b/nel/samples/3d/CMakeLists.txt @@ -1,4 +1,5 @@ ADD_SUBDIRECTORY(font) +ADD_SUBDIRECTORY(font_perf) ADD_SUBDIRECTORY(cluster_viewer) ADD_SUBDIRECTORY(shape_viewer) diff --git a/nel/samples/3d/font/main.cpp b/nel/samples/3d/font/main.cpp index 5a656be49..1d5eaa114 100644 --- a/nel/samples/3d/font/main.cpp +++ b/nel/samples/3d/font/main.cpp @@ -22,6 +22,7 @@ #include "nel/misc/event_emitter.h" #include "nel/misc/event_listener.h" #include "nel/misc/path.h" +#include "nel/misc/random.h" // look at 3dinit example #include "nel/3d/nelu.h" @@ -97,10 +98,12 @@ int main(int argc, char **argv) CNELU::EventServer.addEmitter(CNELU::Driver->getEventEmitter()); CNELU::AsyncListener.addToServer(CNELU::EventServer); + NLMISC::CValueSmoother smoothFPS; + NLMISC::CRandom rnd; do { // look at 3dinit example - CNELU::clearBuffers(CRGBA(0,0,0)); + CNELU::clearBuffers(CRGBA(120,120,0)); // now, every frame, we have to render the computer string. @@ -168,12 +171,39 @@ int main(int argc, char **argv) tc.setHotSpot (CComputedString::BottomRight); tc.printAt (0.99f, 0.01f, string("Press to quit")); + /*for(uint i = 0; i < 1000; ++i) + { + uint fontSize = rnd.rand(40) + 10; + tc.setColor(CRGBA(rnd.rand(255), rnd.rand(255), rnd.rand(255))); + tc.setFontSize(fontSize); + tc.setHotSpot(CComputedString::MiddleMiddle); + tc.printAt(rnd.frand(1.f), rnd.frand(1.f), toString("%d", fontSize)); + }*/ + + { + static TTicks oldTick = CTime::getPerformanceTime(); + TTicks newTick = CTime::getPerformanceTime(); + double deltaTime = CTime::ticksToSecond (newTick-oldTick); + oldTick = newTick; + smoothFPS.addValue((float)deltaTime); + deltaTime = smoothFPS.getSmoothValue (); + if (deltaTime > 0.0) + { + //printf("FPS: %.5f\n", 1.f/deltaTime); + tc.setFontSize(16); + tc.setColor(CRGBA::Yellow); + tc.setHotSpot(CComputedString::TopLeft); + tc.printAt(0.01f, 0.99f, toString("FPS:%.f", 1.0f/deltaTime)); + } + } + // look 3dinit example CNELU::swapBuffers(); CNELU::screenshot(); // look at event example CNELU::EventServer.pump(true); + } while(!CNELU::AsyncListener.isKeyPushed(KeyESCAPE)); diff --git a/nel/samples/3d/font_perf/CMakeLists.txt b/nel/samples/3d/font_perf/CMakeLists.txt new file mode 100644 index 000000000..2f794c983 --- /dev/null +++ b/nel/samples/3d/font_perf/CMakeLists.txt @@ -0,0 +1,14 @@ +FILE(GLOB SRC *.cpp) + +ADD_EXECUTABLE(nl_sample_font_perf ${SRC}) + +ADD_DEFINITIONS(-DFONT_DIR="\\"${NL_SHARE_ABSOLUTE_PREFIX}/nl_sample_font_perf/\\"") + +TARGET_LINK_LIBRARIES(nl_sample_font_perf nelmisc nel3d) +NL_DEFAULT_PROPS(nl_sample_font_perf "NeL, Samples, 3D: Font Performance Test") +NL_ADD_RUNTIME_FLAGS(nl_sample_font_perf) + +INSTALL(TARGETS nl_sample_font_perf RUNTIME DESTINATION ${NL_BIN_PREFIX} COMPONENT samples3d) +INSTALL(FILES beteckna.ttf DESTINATION ${NL_SHARE_PREFIX}/nl_sample_font_perf COMPONENT samples3d) + + diff --git a/nel/samples/3d/font_perf/beteckna.ttf b/nel/samples/3d/font_perf/beteckna.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e9a117f4d4906362913e2eebe10d06c0119ce957 GIT binary patch literal 16488 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 +// Copyright (C) 2020 Winch Gate Property Limited +// +// 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 . + +#include "nel/misc/types_nl.h" + +#include "nel/misc/event_emitter.h" +#include "nel/misc/event_listener.h" +#include "nel/misc/path.h" +#include "nel/misc/random.h" + +// look at 3dinit example +#include "nel/3d/nelu.h" + +// used for font management +#include "nel/3d/font_manager.h" +#include "nel/3d/computed_string.h" +#include "nel/3d/text_context.h" +#include "nel/3d/driver_user.h" + +#ifdef NL_OS_WINDOWS + #ifndef NL_COMP_MINGW + #define NOMINMAX + #endif + #include +#endif // NL_OS_WINDOWS + +#ifndef FONT_DIR +# define FONT_DIR "." +#endif + +using namespace std; +using namespace NL3D; +using namespace NLMISC; + +int main(int argc, char **argv) +{ + // look at 3dinit example + CNELU::init (800, 600, CViewport(), 32, true, 0, false, false); + + NLMISC::CPath::addSearchPath(FONT_DIR); + + // create a font manager + CFontManager fontManager; + + // set the font cache to 2 megabytes (default is 1mb) + fontManager.setMaxMemory(2000000); + + CTextContext tc; + + tc.init (CNELU::Driver, &fontManager); + + // The first param is the font name (could be ttf, pfb, fon, etc...). The + // second one is optional, it's the font kerning file + tc.setFontGenerator (NLMISC::CPath::lookup("beteckna.ttf")); + + NLMISC::CRandom rnd; + + uint nbCount = 100000; + TTicks startTick = CTime::getPerformanceTime(); + std::string txt; + for(uint i = 0; i < nbCount; ++i) + { + uint fontSize = rnd.rand(200); + bool embolden = rnd.rand(1) == 1; + bool oblique = rnd.rand(1) == 1; + txt = toString("Lorem ipsum %03d", fontSize); + + CComputedString cs; + fontManager.computeString(txt, tc.getFontGenerator(), CRGBA::White, fontSize, embolden, oblique, CNELU::Driver, cs); + } + + TTicks endTick = CTime::getPerformanceTime(); + + double deltaTime = CTime::ticksToSecond(endTick-startTick); + std::string msg = toString("Generated %d strings in %.2fs\n", nbCount, deltaTime); + + nlinfo("%s", msg.c_str()); + printf("%s", msg.c_str()); + + fontManager.dumpCache ("font_pref_cache_dump.tga"); + + // look at 3dinit example + CNELU::release(); + + return EXIT_SUCCESS; +} From 990c38ca813e8b066e47417d734dd005b1e83910 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 5 Jan 2021 13:48:28 +0200 Subject: [PATCH 11/12] Change letter cache to map lookup, ryzom/ryzomcore#626 --- nel/include/nel/3d/texture_font.h | 59 +++++++++++++++++-------------- nel/src/3d/texture_font.cpp | 18 ++++------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/nel/include/nel/3d/texture_font.h b/nel/include/nel/3d/texture_font.h index b250fecd8..9ca423315 100644 --- a/nel/include/nel/3d/texture_font.h +++ b/nel/include/nel/3d/texture_font.h @@ -38,6 +38,36 @@ class CTextureFont : public ITexture public: + struct SLetterKey + { + u32char Char; + sint Size; + bool Embolden; + bool Oblique; + CFontGenerator *FontGenerator; + + SLetterKey() : Char(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL) + { + } + + // Does not use FontGenerator in return value + inline uint64 getVal() const + { + return Char // 32 bits + | (uint64(Size & 0xFFFF) << 32) // 16 bits + | (uint64(Embolden) << (32+16)) // 1 bit + | (uint64(Oblique) << (32+16+1)); // 1 bit + } + + bool operator<(const SLetterKey &rhs) const + { + uint64 a = getVal(); + uint64 b = rhs.getVal(); + return (a < b) || ((a == b) && (FontGenerator < rhs.FontGenerator)); + } + }; + + // Holds info for glyphs rendered on atlas struct SGlyphInfo { @@ -70,14 +100,8 @@ public: }; // Holds info for glyphs displayed on screen - struct SLetterInfo + struct SLetterInfo : SLetterKey { - u32char Char; - sint Size; - bool Embolden; - bool Oblique; - CFontGenerator *FontGenerator; - uint32 GlyphIndex; uint32 CharWidth; // Displayed glyph height uint32 CharHeight; // Displayed glyph height @@ -88,29 +112,12 @@ public: SGlyphInfo* glyph; SLetterInfo() - : Char(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL), - GlyphIndex(0), CharWidth(0), CharHeight(0), Top(0), Left(0), AdvX(0), + : GlyphIndex(0), CharWidth(0), CharHeight(0), Top(0), Left(0), AdvX(0), glyph(NULL) { } }; - struct SLetterKey - { - u32char Char; - sint Size; - bool Embolden; - bool Oblique; - CFontGenerator *FontGenerator; - - // Does not use FontGenerator in return value - uint32 getVal(); - - SLetterKey():Char(0), FontGenerator(NULL), Size(0), Embolden(false), Oblique(false) - { - } - }; - public: /** @@ -151,7 +158,7 @@ private: // Keep track of available space in main texture std::vector _AtlasNodes; - std::vector _Letters; + std::map _Letters; // lookup letter from letter cache or create new SLetterInfo* findLetter(SLetterKey& k, bool insert); diff --git a/nel/src/3d/texture_font.cpp b/nel/src/3d/texture_font.cpp index b40083516..dd966eb8d 100644 --- a/nel/src/3d/texture_font.cpp +++ b/nel/src/3d/texture_font.cpp @@ -112,9 +112,9 @@ void CTextureFont::clearAtlas() _Data[0].fill(0); // clear glyph cache - for(uint i = 0; i< _Letters.size(); ++i) + for(std::map::iterator it = _Letters.begin(); it != _Letters.end(); ++it) { - _Letters[i].glyph = NULL; + it->second.glyph = NULL; } _GlyphCache.clear(); @@ -473,21 +473,15 @@ CTextureFont::SGlyphInfo* CTextureFont::findLetterGlyph(SLetterInfo *letter, boo // --------------------------------------------------------------------------- CTextureFont::SLetterInfo* CTextureFont::findLetter(SLetterKey &k, bool insert) { - // TODO: use std::map - for(uint i = 0; i < _Letters.size(); ++i) + std::map::iterator it = _Letters.find(k); + if (it != _Letters.end()) { - if (_Letters[i].Char == k.Char && _Letters[i].Size == k.Size && - _Letters[i].Embolden == k.Embolden && _Letters[i].Oblique == k.Oblique && - _Letters[i].FontGenerator == k.FontGenerator) - { - return &_Letters[i]; - } + return &(it->second); } if (insert) { - _Letters.push_back(SLetterInfo()); - SLetterInfo* letter = &_Letters.back(); + SLetterInfo* letter = &_Letters[k]; // get metrics for requested size letter->Char = k.Char; From f39038f98d2a8cb36b2aff4f84b05eb2add753be Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 5 Jan 2021 17:33:19 +0200 Subject: [PATCH 12/12] Change glyph cache to map lookup, ryzom/ryzomcore#626 --- nel/include/nel/3d/texture_font.h | 18 ++++++---------- nel/src/3d/texture_font.cpp | 35 +++++++++++-------------------- 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/nel/include/nel/3d/texture_font.h b/nel/include/nel/3d/texture_font.h index 9ca423315..61e92fa58 100644 --- a/nel/include/nel/3d/texture_font.h +++ b/nel/include/nel/3d/texture_font.h @@ -67,13 +67,14 @@ public: } }; - // Holds info for glyphs rendered on atlas - struct SGlyphInfo + struct SGlyphInfo : SLetterKey { // font atlas info uint32 CacheVersion; + uint32 GlyphIndex; + // atlas region with padding uint32 X, Y, W, H; @@ -84,17 +85,10 @@ public: // UV coords for rendered glyph without padding float U0, V0, U1, V1; - uint32 GlyphIndex; - sint Size; - bool Embolden; - bool Oblique; - CFontGenerator *FontGenerator; - SGlyphInfo() - : CacheVersion(0), + : CacheVersion(0), GlyphIndex(0), U0(0.f), V0(0.f), U1(0.f), V1(0.f), - X(0), Y(0), W(0), H(0), CharWidth(0), CharHeight(0), - GlyphIndex(0), Size(0), Embolden(false), Oblique(false), FontGenerator(NULL) + X(0), Y(0), W(0), H(0), CharWidth(0), CharHeight(0) { } }; @@ -172,7 +166,7 @@ private: uint _GlyphSizeStep; // rendered glyph cache - std::list _GlyphCache; + std::map _GlyphCache; SGlyphInfo* findLetterGlyph(SLetterInfo *letter, bool insert); // render letter glyph into glyph cache diff --git a/nel/src/3d/texture_font.cpp b/nel/src/3d/texture_font.cpp index dd966eb8d..9aa08ca15 100644 --- a/nel/src/3d/texture_font.cpp +++ b/nel/src/3d/texture_font.cpp @@ -161,15 +161,15 @@ void CTextureFont::repackAtlas(uint32 newW, uint32 newH) _AtlasNodes.push_back(CRect(0, 0, _TextureSizeX, _TextureSizeY)); CObjectVector&src = btm.getPixels(); - for(std::list::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it) + for(std::map::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it) { - if (it->CacheVersion != _CacheVersion) + if (it->second.CacheVersion != _CacheVersion) { // TODO: must remove glyph from all letters before removing glyph from cache //continue; } - SGlyphInfo &glyph = *it; + SGlyphInfo &glyph = it->second; glyph.CacheVersion = newCacheVersion; @@ -403,18 +403,10 @@ CTextureFont::SGlyphInfo* CTextureFont::renderLetterGlyph(SLetterInfo *letter, u } copyGlyphBitmap(bitmap, charWidth, charHeight, atlasX, atlasY); - SGlyphInfo* glyphInfo = NULL; - { - // keep cache sorted by height (smaller first) - std::list::iterator it = _GlyphCache.begin(); - while(it != _GlyphCache.end() && it->CharHeight < charHeight) - { - ++it; - } + SLetterKey k = *letter; + k.Size = bitmapFontSize; - it = _GlyphCache.insert(it, SGlyphInfo()); - glyphInfo = &(*it); - } + SGlyphInfo* glyphInfo = &_GlyphCache[k]; glyphInfo->GlyphIndex = glyphIndex; glyphInfo->Size = bitmapFontSize; @@ -450,16 +442,13 @@ CTextureFont::SGlyphInfo* CTextureFont::findLetterGlyph(SLetterInfo *letter, boo } // CacheVersion not checked, all glyphs in cache must be rendered on texture - for(std::list::iterator it = _GlyphCache.begin(); it != _GlyphCache.end(); ++it) + SLetterKey g = *letter; + g.Size = bitmapFontSize; + + std::map::iterator it = _GlyphCache.find(g); + if (it != _GlyphCache.end()) { - if (it->GlyphIndex == letter->GlyphIndex && - it->Size == bitmapFontSize && - it->Embolden == letter->Embolden && - it->Oblique == letter->Oblique && - it->FontGenerator == letter->FontGenerator) - { - return &(*it); - } + return &(it->second); } if (insert)