Sync with upstream#3
Conversation
needs to disable textures
CMake: install lib & exports
CMake: only export if system dependencies
There was a problem hiding this comment.
Pull request overview
Syncs repository with upstream changes, adding DeepZoom I/O support, new mesh loader capabilities (scale + TS/colormap), and introducing the Nexus3D web package/build pipelines.
Changes:
- Modernize CMake/Qt discovery and add install/export packaging support.
- Add DeepZoom support across save/load paths (external node/texture files in
_files/). - Add new TS loader + colormap utilities and introduce the
nexus3dJS package with new CI workflows.
Reviewed changes
Copilot reviewed 53 out of 57 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/nxsview/CMakeLists.txt | Raise CMake min version; update Qt OpenGL discovery. |
| src/nxsedit/extractor.cpp | Add DeepZoom extraction mode writing nodes/textures into _files/. |
| src/nxsedit/CMakeLists.txt | Raise CMake min version; remove duplicated cmake/project declarations. |
| src/nxsbuild/tsloader.h | Add TS mesh loader interface. |
| src/nxsbuild/tsloader.cpp | Implement TS loader, colormap parsing, and streaming triangle/vertex reads. |
| src/nxsbuild/stlloader.cpp | Apply scale during STL import. |
| src/nxsbuild/plyloader.cpp | Apply scale and fix texture-flag handling. |
| src/nxsbuild/objloader.h | Change MTL parsing signature to accept the OBJ file handle. |
| src/nxsbuild/objloader.cpp | Auto-discover mtllib and apply scale during OBJ import. |
| src/nxsbuild/nxsbuild.pro | Add TS loader + colormap sources/headers to qmake build. |
| src/nxsbuild/meshstream.h | Add scale and TS colormap options to Stream. |
| src/nxsbuild/meshstream.cpp | Route .ts files to TsLoader; propagate scale. |
| src/nxsbuild/meshloader.h | Add scale property for all loaders. |
| src/nxsbuild/main.cpp | Add CLI options for scale and TS colormap; improve default output naming. |
| src/nxsbuild/colormap.h | Add colormap utility class. |
| src/nxsbuild/colormap.cpp | Implement predefined colormaps + CSV loading + mapping. |
| src/nxsbuild/CMakeLists.txt | Raise CMake min version; remove duplicated cmake/project declarations. |
| src/corto | Update corto submodule SHA. |
| src/common/signature.h | Add Signature::isDeepzoom(). |
| src/common/renderer.cpp | Disable custom log2 definition under WIN32. |
| src/common/qtnexusfile.h | Extend NexusFile Qt implementation with DeepZoom APIs + fileName(). |
| src/common/qtnexusfile.cpp | Implement DeepZoom node/texture file reads. |
| src/common/nexusfile.h | Extend NexusFile interface for DeepZoom node/texture loading. |
| src/common/nexusdata.cpp | Load node/texture data via DeepZoom APIs; adjust drop/unmap logic. |
| src/common/nexus.cpp | Load textures via DeepZoom APIs in loadImageFromData. |
| src/CMakeLists.txt | Raise CMake min version; add install headers + exported targets. |
| nexus3d/webpacklib.config.js | Add webpack config for building Nexus3D library bundle. |
| nexus3d/webpack.config.js | Add webpack config for the example/dev server. |
| nexus3d/src/index.js | Add example app using Nexus3D with three.js. |
| nexus3d/src/Traversal.js | Add traversal/selection logic for streamed rendering. |
| nexus3d/src/PriorityQueue.js | Add heap-based priority queue used by traversal. |
| nexus3d/src/Nexus3D.js | Add main Nexus3D three.js mesh integration + renderer patching. |
| nexus3d/src/NXSRaw.js | Add legacy/raw nexus renderer implementation. |
| nexus3d/src/NXS.js | Add legacy scenegraph-based nexus implementation. |
| nexus3d/src/Monitor.js | Add runtime monitor UI for cache/error/fps/progress. |
| nexus3d/src/Mesh.js | Add NXS file loader (header/index parsing + deepzoom baseurl). |
| nexus3d/src/Cache.js | Add streaming cache (pending requests, eviction, corto worker). |
| nexus3d/src/Binary.js | Add binary parsing + matrix utilities for Nexus3D. |
| nexus3d/rollup.config.js | Add rollup build for library + corto worker bundling. |
| nexus3d/package.json | Define npm package metadata, scripts, exports and dependencies. |
| nexus3d/dist/index.html | Add example HTML shell for webpack output. |
| nexus3d/build/nexus3D.min.js | Add prebuilt/minified Nexus3D bundle artifact. |
| nexus3d/build/nexus3D.js | Add prebuilt/dev Nexus3D bundle artifact (webpack eval). |
| nexus3d/README.md | Add docs for installing/using Nexus3D. |
| html/js/nexus.js | Add IndexedDB caching; fix texture load call + minor indentation. |
| NEXUS_VERSION | Update version string to 2025.05. |
| Config.cmake.in | Add CMake package config template for downstream find_package(nexus). |
| CMakeLists.txt | Raise CMake min version; prefer system vcglib/corto when available; export/install targets. |
| .github/workflows/Windows.yml | Remove legacy Windows CI workflow. |
| .github/workflows/MacOS.yml | Remove legacy macOS CI workflow. |
| .github/workflows/Linux.yml | Remove legacy Linux CI workflow. |
| .github/workflows/CreateRelease.yml | Add manual release workflow (version bump + build + upload + release). |
| .github/workflows/BuildNexus.yml | Add unified build workflow using composite actions. |
| .github/actions/upload/action.yml | Add composite action to archive/upload per-OS artifacts. |
| .github/actions/build_and_deploy/action.yml | Add composite action to build + deploy per OS/arch. |
Comments suppressed due to low confidence (20)
src/nxsbuild/stlloader.cpp:1
- The Z scaling is applied to
d[0]instead ofd[2], which will distort geometry (X scaled twice, Z not scaled). Change line 67 to multiplyd[2]byscale[2].
src/nxsbuild/tsloader.cpp:1 - This condition is always true because a line can't start with both "VRTX" and "PVRTX" at the same time, so all vertices are skipped. Use
&&here (skip only if it's neither VRTX nor PVRTX).
src/nxsbuild/tsloader.cpp:1 line[0]is aQChar, but it's being compared to a string literal (\"}\"), which will not compile (or will behave incorrectly if it compiles via implicit conversions). Compare against a character literal ('}') or useline.startsWith(\"}\").
src/nxsbuild/tsloader.cpp:1- The bounds checks don't match the indexed access:
parts[5 + property_position]requiresparts.size() > 5 + property_position, but the current condition checksparts.size() < property_position(which is also incorrect whenproperty_positionis negative). Update the condition to validate the exact index and consider splitting with skip-empty-parts to handle multiple spaces.
src/nxsbuild/tsloader.cpp:1 - The bounds checks don't match the indexed access:
parts[5 + property_position]requiresparts.size() > 5 + property_position, but the current condition checksparts.size() < property_position(which is also incorrect whenproperty_positionis negative). Update the condition to validate the exact index and consider splitting with skip-empty-parts to handle multiple spaces.
src/nxsbuild/tsloader.cpp:1 - When handling
ATOM, a new vertex is appended butn_verticesis never incremented, so the next write overwrites the same slot and the count becomes inconsistent. Incrementn_verticesafter writing the duplicated vertex (and validateid - 1is within the current cache range).
src/nxsbuild/tsloader.cpp:1 - The error message contains a
%1placeholder but doesn't.arg(...)the missing colormap name, so the thrown message is incomplete. Also,return false;is unreachable afterthrowand can be removed.
src/nxsbuild/tsloader.cpp:1 - Correct spelling of 'Proble' to 'Problem' in the error message.
src/nxsbuild/tsloader.cpp:1 - Correct spelling of 'Proble' to 'Problem' in the error message.
src/common/qtnexusfile.cpp:1 - This returns a pointer into a temporary
QByteArraythat is destroyed at function exit, leaving a dangling pointer. Store the UTF-8 bytes in a member (e.g.,QByteArray cachedName) and returncachedName.constData(), or change the API to return astd::string/QString(orconst char*backed by stable storage).
src/common/nexus.cpp:1 dropDZTextakes achar*buffer (perNexusFileinterface), but this code passes the texture indext. This is a type/logic mismatch and will either fail to compile or free the wrong thing. Callfile->dropDZTex(data.memory)in the DeepZoom case (and keepunmapfor the mmap case).
src/common/nexus.cpp:1dropDZTextakes achar*buffer (perNexusFileinterface), but this code passes the texture indext. This is a type/logic mismatch and will either fail to compile or free the wrong thing. Callfile->dropDZTex(data.memory)in the DeepZoom case (and keepunmapfor the mmap case).
src/common/nexusdata.cpp:1- In DeepZoom mode this loads a texture using
loadDZTex(n)wherenis the node id, but it should load by texture indext. AlsoloadImageFromDatais commented out, so the mapped/allocated texture data is never consumed nor released here, which leaks memory and breaks texture loading.
src/common/nexusdata.cpp:1 - In DeepZoom mode this loads a texture using
loadDZTex(n)wherenis the node id, but it should load by texture indext. AlsoloadImageFromDatais commented out, so the mapped/allocated texture data is never consumed nor released here, which leaks memory and breaks texture loading.
src/nxsbuild/colormap.cpp:1 - The
clampmacro includes a trailing semicolon, which can create surprising extra empty statements at call sites, and it also shadows common names (e.g.,std::clamp). Prefer aninlinefunction (orstd::clampif available). Also remove thecout << \"Returning true\"debug print to avoid noisy stdout in library code.
src/nxsbuild/colormap.cpp:1 - The
clampmacro includes a trailing semicolon, which can create surprising extra empty statements at call sites, and it also shadows common names (e.g.,std::clamp). Prefer aninlinefunction (orstd::clampif available). Also remove thecout << \"Returning true\"debug print to avoid noisy stdout in library code.
src/nxsbuild/main.cpp:1 - If the input filename has no '.',
lastIndexOfreturns-1andleft(-1)yields an empty/invalid output base. Handle thelast == -1case by using the full filename (or only stripping an extension if one exists).
src/nxsbuild/main.cpp:1 scalateis an unclear name for a user-facing CLI option backing variable. Consider renaming it to something likescaleStr/scaleOption(and keepscalefor the parsed numeric vector).
src/nxsbuild/main.cpp:1scalateis an unclear name for a user-facing CLI option backing variable. Consider renaming it to something likescaleStr/scaleOption(and keepscalefor the parsed numeric vector).
src/nxsbuild/objloader.cpp:1readMTLadvances the passed-in OBJQFileread position while scanning formtllib, but it no longer preserves/restores the original position (the oldpos = file.pos()logic was removed). Saving the initial position and seeking back at the end would make this function safer and less coupled to the caller's file-seek behavior.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| p[16] = -m[2] + m[3]; p[17] = -m[6] + m[7]; p[18] = -m[10] + m[11]; p[19] = -m[14] + m[15]; //near | ||
| p[20] = -m[2] + m[3]; p[21] = -m[6] + m[7]; p[22] = -m[10] + m[11]; p[23] = -m[14] + m[15]; //far |
There was a problem hiding this comment.
Near and far planes are computed identically, which makes frustum culling incorrect (far plane never differs from near). Update the far-plane extraction formula to the correct row/column combination (typically m[3] - m[2], etc., depending on the matrix layout used elsewhere in this file).
| if(cache.nodes.has(mesh)) { | ||
| for(let id of cache.nodes.get(mesh)) { | ||
| let error = t.nodeError(id); | ||
| if(t.instance_errors[id] == 0) { | ||
| t.instance_errors[i] = error; | ||
| mesh.errors[id] = Math.max(mesh.errors[id], error); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This writes t.instance_errors[i] = error, but i is not the loop variable here (it refers to an unrelated outer variable), so errors are stored in the wrong slot (or throw in strict mode). This should write to t.instance_errors[id].
| var lo = view.getUint32(view.offset, true); | ||
| var hi = view.getUint32(view.offset + 4, true); | ||
| view.offset += 8; | ||
| return ((hi * (1 << 32)) + lo); |
There was a problem hiding this comment.
In JavaScript, (1 << 32) is 1 due to 32-bit shift semantics, so getUint64 returns incorrect values for any hi != 0. Use hi * 2**32 + lo (still limited by IEEE-754 precision) or switch to BigInt arithmetic to represent full 64-bit values reliably.
| return ((hi * (1 << 32)) + lo); | |
| return (hi * 2 ** 32 + lo); |
| extract : false, //extraction disabled} | ||
| } |
There was a problem hiding this comment.
The braces are unbalanced: line 42 closes the object literal, and line 43 closes the _Cache constructor early, leaving subsequent constructor initialization outside the function (breaking module execution). Remove the stray } on line 42 (or move it to its own line) so the object literal closes correctly and the constructor continues.
| extract : false, //extraction disabled} | |
| } | |
| extract : false //extraction disabled | |
| }; |
| if(mesh.reqAttempt[id] > maxReqAttempt) { | ||
| if(this.debug.verbose) console.log("Max request limit for " + m.url + " node: " + n); | ||
| t.removeNode(mesh, id); | ||
| return; | ||
| } |
There was a problem hiding this comment.
m and n are undefined in this scope (should be mesh and id), which will throw when these log lines execute and can break recovery logic. Update the log statements to reference the actual parameters.
| switch (mode){ | ||
| case 0: | ||
| t.requestNodeGeometry(mesh, id); | ||
| if(this.debug.verbose) console.log("Recovering geometry for " + m.url + " node: " + n); |
There was a problem hiding this comment.
m and n are undefined in this scope (should be mesh and id), which will throw when these log lines execute and can break recovery logic. Update the log statements to reference the actual parameters.
| break; | ||
| case 1: | ||
| t.requestNodeTexture(mesh, id); | ||
| if(this.debug.verbose) console.log("Recovering texture for " + m.url + " node: " + n); |
There was a problem hiding this comment.
m and n are undefined in this scope (should be mesh and id), which will throw when these log lines execute and can break recovery logic. Update the log statements to reference the actual parameters.
| e.cacheStyle.style.background_color = greenToRed(cacheFraction); | ||
| e.cacheStyle.style.width = cacheFraction; | ||
|
|
||
| e.errorLabel.setAttribute('data-label', `Real: ${realError.toFixed(1)} px Target: ${currentError.toFixed(1)} px`); | ||
| e.errorStyle.style.background_color = greenToRed(errorFraction); | ||
| e.errorStyle.style.width = errorFraction; | ||
|
|
||
| e.fpsLabel.setAttribute('data-label', `${parseInt(Math.round(currentFps))} fps`); | ||
| e.fpsStyle.style.background_color = greenToRed(fpsFraction); | ||
| e.fpsStyle.style.width = fpsFraction; | ||
|
|
||
| e.progressLabel.setAttribute('data-label', `${parseInt(Math.round(progressNodes))} nodes`); | ||
| e.progressStyle.style.background_color = greenToRed(progressFraction); | ||
| e.progressStyle.style.width = progressFraction; |
There was a problem hiding this comment.
DOM style properties should use camelCase (backgroundColor), and style.width needs a unit (e.g., '42%'). Also progressFraction is computed as a 0..1 fraction but is passed to greenToRed as if it were a percent; convert to 0..100 consistently.
| e.cacheStyle.style.background_color = greenToRed(cacheFraction); | |
| e.cacheStyle.style.width = cacheFraction; | |
| e.errorLabel.setAttribute('data-label', `Real: ${realError.toFixed(1)} px Target: ${currentError.toFixed(1)} px`); | |
| e.errorStyle.style.background_color = greenToRed(errorFraction); | |
| e.errorStyle.style.width = errorFraction; | |
| e.fpsLabel.setAttribute('data-label', `${parseInt(Math.round(currentFps))} fps`); | |
| e.fpsStyle.style.background_color = greenToRed(fpsFraction); | |
| e.fpsStyle.style.width = fpsFraction; | |
| e.progressLabel.setAttribute('data-label', `${parseInt(Math.round(progressNodes))} nodes`); | |
| e.progressStyle.style.background_color = greenToRed(progressFraction); | |
| e.progressStyle.style.width = progressFraction; | |
| e.cacheStyle.style.backgroundColor = greenToRed(cacheFraction); | |
| e.cacheStyle.style.width = cacheFraction + 'px'; | |
| e.errorLabel.setAttribute('data-label', `Real: ${realError.toFixed(1)} px Target: ${currentError.toFixed(1)} px`); | |
| e.errorStyle.style.backgroundColor = greenToRed(errorFraction); | |
| e.errorStyle.style.width = errorFraction + 'px'; | |
| e.fpsLabel.setAttribute('data-label', `${parseInt(Math.round(currentFps))} fps`); | |
| e.fpsStyle.style.backgroundColor = greenToRed(fpsFraction); | |
| e.fpsStyle.style.width = fpsFraction + 'px'; | |
| e.progressLabel.setAttribute('data-label', `${parseInt(Math.round(progressNodes))} nodes`); | |
| let progressPercent = progressFraction * 100; | |
| e.progressStyle.style.backgroundColor = greenToRed(progressPercent); | |
| e.progressStyle.style.width = progressPercent + 'px'; |
|
|
||
| let rendered = context.rendered; | ||
|
|
||
| let e = this.elements; | ||
|
|
||
| e.cacheLabel.setAttribute('data-label', `${toHuman(cacheSize, 'B')}/${toHuman(maxCacheSize, 'B')}`); | ||
| e.cacheStyle.style.background_color = greenToRed(cacheFraction); | ||
| e.cacheStyle.style.width = cacheFraction; | ||
|
|
||
| e.errorLabel.setAttribute('data-label', `Real: ${realError.toFixed(1)} px Target: ${currentError.toFixed(1)} px`); | ||
| e.errorStyle.style.background_color = greenToRed(errorFraction); | ||
| e.errorStyle.style.width = errorFraction; | ||
|
|
||
| e.fpsLabel.setAttribute('data-label', `${parseInt(Math.round(currentFps))} fps`); | ||
| e.fpsStyle.style.background_color = greenToRed(fpsFraction); | ||
| e.fpsStyle.style.width = fpsFraction; | ||
|
|
||
| e.progressLabel.setAttribute('data-label', `${parseInt(Math.round(progressNodes))} nodes`); | ||
| e.progressStyle.style.background_color = greenToRed(progressFraction); | ||
| e.progressStyle.style.width = progressFraction; |
There was a problem hiding this comment.
DOM style properties should use camelCase (backgroundColor), and style.width needs a unit (e.g., '42%'). Also progressFraction is computed as a 0..1 fraction but is passed to greenToRed as if it were a percent; convert to 0..100 consistently.
| let rendered = context.rendered; | |
| let e = this.elements; | |
| e.cacheLabel.setAttribute('data-label', `${toHuman(cacheSize, 'B')}/${toHuman(maxCacheSize, 'B')}`); | |
| e.cacheStyle.style.background_color = greenToRed(cacheFraction); | |
| e.cacheStyle.style.width = cacheFraction; | |
| e.errorLabel.setAttribute('data-label', `Real: ${realError.toFixed(1)} px Target: ${currentError.toFixed(1)} px`); | |
| e.errorStyle.style.background_color = greenToRed(errorFraction); | |
| e.errorStyle.style.width = errorFraction; | |
| e.fpsLabel.setAttribute('data-label', `${parseInt(Math.round(currentFps))} fps`); | |
| e.fpsStyle.style.background_color = greenToRed(fpsFraction); | |
| e.fpsStyle.style.width = fpsFraction; | |
| e.progressLabel.setAttribute('data-label', `${parseInt(Math.round(progressNodes))} nodes`); | |
| e.progressStyle.style.background_color = greenToRed(progressFraction); | |
| e.progressStyle.style.width = progressFraction; | |
| let progressPercent = progressFraction * 100; | |
| let rendered = context.rendered; | |
| let e = this.elements; | |
| e.cacheLabel.setAttribute('data-label', `${toHuman(cacheSize, 'B')}/${toHuman(maxCacheSize, 'B')}`); | |
| e.cacheStyle.style.backgroundColor = greenToRed(cacheFraction); | |
| e.cacheStyle.style.width = `${cacheFraction}%`; | |
| e.errorLabel.setAttribute('data-label', `Real: ${realError.toFixed(1)} px Target: ${currentError.toFixed(1)} px`); | |
| e.errorStyle.style.backgroundColor = greenToRed(errorFraction); | |
| e.errorStyle.style.width = `${errorFraction}%`; | |
| e.fpsLabel.setAttribute('data-label', `${parseInt(Math.round(currentFps))} fps`); | |
| e.fpsStyle.style.backgroundColor = greenToRed(fpsFraction); | |
| e.fpsStyle.style.width = `${fpsFraction}%`; | |
| e.progressLabel.setAttribute('data-label', `${parseInt(Math.round(progressNodes))} nodes`); | |
| e.progressStyle.style.backgroundColor = greenToRed(progressPercent); | |
| e.progressStyle.style.width = `${progressPercent}%`; |
| /* | ||
| * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). | ||
| * This devtool is neither made for production nor for readable output files. | ||
| * It uses "eval()" calls to create a separate source file in the browser devtools. | ||
| * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) | ||
| * or disable the default devtool with "devtool: false". | ||
| * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). | ||
| */ |
There was a problem hiding this comment.
This committed build artifact is a development bundle using webpack eval devtool, which is not suitable for production or distribution and makes diffs noisy. Prefer generating build outputs in CI/releases (or via npm run build) and exclude them from the repo, or ensure committed artifacts are production builds with stable source maps.
| /* | |
| * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). | |
| * This devtool is neither made for production nor for readable output files. | |
| * It uses "eval()" calls to create a separate source file in the browser devtools. | |
| * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) | |
| * or disable the default devtool with "devtool: false". | |
| * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). | |
| */ |
No description provided.