From 6ea05cd8b9d5d93878b9072dcc258707fa0aab91 Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Sun, 9 Nov 2025 16:49:23 -0500 Subject: [PATCH] feat: enhance model loading with URL support and downloader initialization --- bridge/src/lib.rs | 81 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/bridge/src/lib.rs b/bridge/src/lib.rs index fffce35..c67a4c3 100644 --- a/bridge/src/lib.rs +++ b/bridge/src/lib.rs @@ -5,7 +5,7 @@ mod model_downloader; use std::collections::HashMap; use std::ffi::CStr; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, OnceLock}; use std::thread::JoinHandle; use glam::Mat4; @@ -23,6 +23,9 @@ use tokio::runtime::Runtime; use std::path::PathBuf; use model_downloader::ModelDownloader; +// Global model downloader instance +static MODEL_DOWNLOADER: OnceLock = OnceLock::new(); + #[derive(Clone, serde::Serialize, serde::Deserialize)] struct BridgeState { nodes: HashMap, @@ -77,7 +80,42 @@ impl Reify for BridgeState { eprintln!("[bridge/reify] Reifying {} nodes", self.nodes.len()); - fn get_model_path(entity_type: u8) -> Option { + // Initialize model downloader if not already done + let downloader = MODEL_DOWNLOADER.get_or_init(|| { + let cache_dir = dirs::cache_dir() + .unwrap_or_else(|| PathBuf::from("/tmp")) + .join("starworld/models"); + ModelDownloader::new(cache_dir).expect("Failed to create model downloader") + }); + + fn get_model_path(entity_type: u8, model_url: &str, downloader: &ModelDownloader) -> Option { + // First check if there's a model URL provided + if !model_url.is_empty() { + // Handle HTTP/HTTPS URLs + if model_url.starts_with("http://") || model_url.starts_with("https://") { + eprintln!("[bridge/reify] Attempting to download model from URL: {}", model_url); + if let Some(path) = downloader.get_model(model_url) { + eprintln!("[bridge/reify] Using downloaded model: {}", path.display()); + return Some(path); + } else { + eprintln!("[bridge/reify] Model download failed or in progress, falling back to primitive"); + // Fall through to use primitive based on entity type + } + } else if model_url.starts_with("file://") { + // Local file path + let path = PathBuf::from(&model_url[7..]); + if path.exists() { + eprintln!("[bridge/reify] Using local file: {}", path.display()); + return Some(path); + } + } else if PathBuf::from(model_url).exists() { + // Direct path + eprintln!("[bridge/reify] Using direct path: {}", model_url); + return Some(PathBuf::from(model_url)); + } + } + + // Fall back to primitive models based on entity type let cache_dir = dirs::cache_dir()?.join("starworld/primitives"); let filename = match entity_type { 1 => "cube.glb", // Box @@ -89,7 +127,7 @@ impl Reify for BridgeState { if path.exists() { Some(path) } else { - eprintln!("[bridge/reify] Model file not found: {}", path.display()); + eprintln!("[bridge/reify] Primitive model file not found: {}", path.display()); None } } @@ -109,18 +147,35 @@ impl Reify for BridgeState { let scale_array = [vis_scale.x, vis_scale.y, vis_scale.z]; let transform = stardust_xr_fusion::spatial::Transform::from_translation_rotation_scale(trans_array, rot_array, scale_array); - // Try to load the appropriate model based on entity type - let model_child = if let Some(model_path) = get_model_path(node.entity_type) { - eprintln!("[bridge/reify] Loading {} model for node {} from {}", - match node.entity_type { - 1 => "cube", - 2 => "sphere", - 3 => "3D model", - _ => "unknown" - }, id, model_path.display()); + // Try to load the appropriate model based on entity type and model URL + let model_child = if let Some(model_path) = get_model_path(node.entity_type, &node.model_url, downloader) { + let entity_type_name = match node.entity_type { + 1 => "cube", + 2 => "sphere", + 3 => "3D model", + _ => "unknown" + }; + + let model_source = if !node.model_url.is_empty() { + format!("from URL: {}", node.model_url) + } else { + format!("primitive from {}", model_path.display()) + }; + + eprintln!("[bridge/reify] Loading {} for node {} {}", + entity_type_name, id, model_source); match Model::direct(&model_path) { - Ok(model) => Some(model.build()), + Ok(model) => { + // TODO: Apply color tint to the model + // This would require material manipulation which is not yet exposed + // in the asteroids API. For now, just log it. + if node.color != [1.0, 1.0, 1.0, 1.0] { + eprintln!("[bridge/reify] Node {} has color tint: {:?} (not yet applied)", + id, node.color); + } + Some(model.build()) + } Err(e) => { eprintln!("[bridge/reify] Failed to load model for node {}: {}", id, e); None