diff --git a/src/app/app.cpp b/src/app/app.cpp index f0804ef..783e532 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -530,7 +530,7 @@ int App::run() { } std::string status = browser_.status(); if (browser_.onNetwork() && !browser_.isBusy()) { - status = "Point + Trigger: Stream A: Download"; + status = "Trigger: Open/Stream Play button: Download"; } renderer_.setBrowser(true, browser_.title(), rows, browser_.selected(), status); @@ -689,9 +689,11 @@ int App::run() { } } } - if (in.togglePlay) { - browser_.downloadFocused(); - } + } + // Download works even while the folder is still scanning; only an + // already-running download blocks a new one (checked internally). + if (browser_.isOpen() && in.togglePlay) { + browser_.downloadFocused(); } if (browser_.isOpen() && in.recenter && in.headValid) { renderer_.recenterScreen(in.headPos, in.headForward); diff --git a/src/gfx/gltf_mesh.cpp b/src/gfx/gltf_mesh.cpp index 276517d..f13763b 100644 --- a/src/gfx/gltf_mesh.cpp +++ b/src/gfx/gltf_mesh.cpp @@ -84,6 +84,7 @@ GltfMesh loadGlb(const std::vector& glb) { } GltfSubmesh sm; sm.nodeName = (node->name != nullptr) ? node->name : ""; + sm.nodeIndex = static_cast(n); sm.firstIndex = static_cast(out.indices.size()); for (cgltf_size i = 0; i < prim->indices->count; ++i) { out.indices.push_back( diff --git a/src/gfx/gltf_mesh.hpp b/src/gfx/gltf_mesh.hpp index 79528ca..dde2c14 100644 --- a/src/gfx/gltf_mesh.hpp +++ b/src/gfx/gltf_mesh.hpp @@ -20,6 +20,7 @@ struct GltfSubmesh { int image = -1; // index into GltfMesh::images, or -1 for untextured float baseColor[4] = {1.0f, 1.0f, 1.0f, 1.0f}; std::string nodeName; // source glTF node, matched to render-model node state + int nodeIndex = -1; // source glTF node index (node states are parallel) }; // A mesh extracted from a glTF/GLB asset: positions + normals + UVs interleaved, diff --git a/src/gfx/vulkan_renderer.cpp b/src/gfx/vulkan_renderer.cpp index 826bcff..f847c5e 100644 --- a/src/gfx/vulkan_renderer.cpp +++ b/src/gfx/vulkan_renderer.cpp @@ -1873,16 +1873,13 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex, Quat qNode{0.0f, 0.0f, 0.0f, 1.0f}; Vec3 pNode{0.0f, 0.0f, 0.0f}; bool visible = true; - if (!sm.nodeName.empty()) { - for (const NodeStateVis& ns : modelNodeStates_) { - if (ns.name == sm.nodeName) { - qNode = Quat{ns.quat[0], ns.quat[1], ns.quat[2], - ns.quat[3]}; - pNode = Vec3{ns.pos[0], ns.pos[1], ns.pos[2]}; - visible = ns.visible; - break; - } - } + if (sm.nodeIndex >= 0 && + sm.nodeIndex < static_cast(modelNodeStates_.size())) { + const NodeStateVis& ns = + modelNodeStates_[static_cast(sm.nodeIndex)]; + qNode = Quat{ns.quat[0], ns.quat[1], ns.quat[2], ns.quat[3]}; + pNode = Vec3{ns.pos[0], ns.pos[1], ns.pos[2]}; + visible = ns.visible; } if (!visible) continue; const Vec3 worldPos = oPlace + rotate(qPlace, pNode); @@ -3211,10 +3208,14 @@ bool VulkanRenderer::loadControllerModel(const std::vector& glb) { } modelSubmeshes_.reserve(mesh.submeshes.size()); for (const GltfSubmesh& s : mesh.submeshes) { + // The 'status' node maps the whole texture atlas onto a small LCD panel, + // which reads as a stray textured square; skip it. + if (s.nodeName == "status") continue; ModelSubmesh ms; ms.firstIndex = s.firstIndex; ms.indexCount = s.indexCount; ms.nodeName = s.nodeName; + ms.nodeIndex = s.nodeIndex; ms.set = (s.image >= 0 && static_cast(s.image) < imageSets.size()) ? imageSets[s.image] diff --git a/src/gfx/vulkan_renderer.hpp b/src/gfx/vulkan_renderer.hpp index 2e00ad1..d31d511 100644 --- a/src/gfx/vulkan_renderer.hpp +++ b/src/gfx/vulkan_renderer.hpp @@ -449,6 +449,7 @@ private: VkDescriptorSet set = VK_NULL_HANDLE; // base-color texture descriptor float color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; std::string nodeName; // matched to an animated render-model node state + int nodeIndex = -1; // glTF node index (node states are parallel to it) }; VkDescriptorSetLayout modelSetLayout_ = VK_NULL_HANDLE; VkDescriptorPool modelDescPool_ = VK_NULL_HANDLE; diff --git a/src/ui/browser.cpp b/src/ui/browser.cpp index ece5ead..58c1acf 100644 --- a/src/ui/browser.cpp +++ b/src/ui/browser.cpp @@ -318,10 +318,15 @@ bool Browser::downloadFocused() { return false; } const Entry e = entries_[static_cast(selected_)]; - if (!e.isVideo || !inSmb_) { - return false; // Only network videos can be downloaded to local storage. + if (!e.isVideo) { + status_ = "Highlight a video file, then press download"; + return false; } - startDownload(e.path, e.label); // async copy to ~/media, then Play via poll() + if (!inSmb_) { + status_ = "Only network (SMB) videos can be downloaded"; + return false; + } + startDownload(e.path, e.label); // async copy to the download folder, then play return true; } diff --git a/src/xr/render_model.cpp b/src/xr/render_model.cpp index df3afca..a9bec9f 100644 --- a/src/xr/render_model.cpp +++ b/src/xr/render_model.cpp @@ -192,6 +192,10 @@ bool RenderModelSystem::nodeStates(XrTime time, std::vector& out) { } } out.resize(st.nodeStateCount); + if (!stateLogged_) { + stateLogged_ = true; + PIXELVR_LOG_INFO("Render model node states: %u nodes", st.nodeStateCount); + } for (uint32_t i = 0; i < st.nodeStateCount; ++i) { const XrRenderModelNodeStateEXT& n = nodeStateBuf_[i]; NodeState& o = out[i]; diff --git a/src/xr/render_model.hpp b/src/xr/render_model.hpp index 67eb080..5eab16d 100644 --- a/src/xr/render_model.hpp +++ b/src/xr/render_model.hpp @@ -64,6 +64,7 @@ private: uint32_t animatableNodeCount_ = 0; std::vector nodeNames_; std::vector nodeStateBuf_; // reused per frame + bool stateLogged_ = false; // one-shot diagnostic PFN_xrEnumerateInteractionRenderModelIdsEXT pfnEnumerateIds_ = nullptr; PFN_xrCreateRenderModelEXT pfnCreateModel_ = nullptr;