diff --git a/bridge/Cargo.lock b/bridge/Cargo.lock index 2b68118..cb028d8 100644 --- a/bridge/Cargo.lock +++ b/bridge/Cargo.lock @@ -2823,7 +2823,7 @@ dependencies = [ [[package]] name = "stardust-xr-molecules" version = "0.45.0" -source = "git+https://github.com/StardustXR/molecules.git?branch=dev#53cfb2eecb066faf60a1b0da0b70f84231bae2be" +source = "git+https://github.com/StardustXR/molecules.git?branch=dev#26e004af199ccccb2ff4d8662f82f4d65311d8d3" dependencies = [ "ashpd", "futures-util", diff --git a/bridge/src/lib.rs b/bridge/src/lib.rs index 5181fa4..accc662 100644 --- a/bridge/src/lib.rs +++ b/bridge/src/lib.rs @@ -195,6 +195,7 @@ impl Reify for BridgeState { } else { format!("primitive from {}", model_path.display()) }; +<<<<<<< HEAD eprintln!("[bridge/reify] Loading {} for node {} {}", entity_type_name, id, model_source); match node.entity_type { 4 => { @@ -204,6 +205,26 @@ impl Reify for BridgeState { node.color[0], node.color[1], node.color[2], node.color[3] )); Some(text.build()) +======= + + eprintln!("[bridge/reify] Loading {} for node {} {}", + entity_type_name, id, model_source); + + match Model::direct(&model_path) { + Ok(model) => { + // TODO: Color tinting is not currently supported due to missing public API in asteroids. + // When Model/MaterialParameter API is available, apply color here. + if node.color != [1.0, 1.0, 1.0, 1.0] { + eprintln!("[bridge/reify] Node {} requested color tint RGBA({:.2}, {:.2}, {:.2}, {:.2}) -- NOT SUPPORTED YET", + id, node.color[0], node.color[1], node.color[2], node.color[3]); + } + // TODO: Apply texture from texture_url (future) + if !node.texture_url.is_empty() { + eprintln!("[bridge/reify] Node {} has texture URL: {} - NOT YET APPLIED", + id, node.texture_url); + } + Some(model.build()) +>>>>>>> origin/main } 5 => { eprintln!("[bridge/reify] Image entity type detected for node {}. Using PanelUI as placeholder.", id); @@ -263,108 +284,67 @@ impl Reify for BridgeState { // Recursively build children let children: Vec<_> = nodes.iter() .filter_map(|(child_id, child_node)| { - if child_node.parent == Some(id) { - build_node(*child_id, nodes, downloader) - } else { - None + eprintln!("[bridge/reify] Loading {} for node {} {}", entity_type_name, id, model_source); + match node.entity_type { + 4 => { + let text = ast::elements::Text::new(&node.name) + .character_height(node.dimensions[1].max(0.01)) + .color(ast::elements::RgbaLinear::new( + node.color[0], node.color[1], node.color[2], node.color[3] + )); + Some(text.build()) + } + 5 => { + eprintln!("[bridge/reify] Image entity type detected for node {}. Using PanelUI as placeholder.", id); + let panel = ast::elements::PanelUI::default(); + Some(panel.build()) + } + 6 => { + eprintln!("[bridge/reify] Light entity type detected for node {}.", id); + let color = ast::elements::RgbaLinear::new( + node.color[0], node.color[1], node.color[2], node.color[3] + ); + let intensity = node.dimensions.get(1).copied().unwrap_or(1.0).max(0.01); + let light = ast::elements::Light::new() + .color(color) + .intensity(intensity); + Some(light.build()) + } + 7 => { + eprintln!("[bridge/reify] Zone entity type detected for node {}.", id); + let color = ast::elements::RgbaLinear::new( + node.color[0], node.color[1], node.color[2], node.color[3] + ); + let size = glam::Vec3::from(node.dimensions); + let zone = ast::elements::Zone::new() + .color(color) + .size([size.x, size.y, size.z]); + Some(zone.build()) + } + _ => { + match Model::direct(&model_path) { + Ok(mut model) => { + if node.color != [1.0, 1.0, 1.0, 1.0] { + let color = ast::elements::RgbaLinear::new( + node.color[0], node.color[1], node.color[2], node.color[3] + ); + model = model.color_tint(color); + eprintln!("[bridge/reify] Node {}: applied color tint RGBA({:.2}, {:.2}, {:.2}, {:.2})", + id, node.color[0], node.color[1], node.color[2], node.color[3]); + } + if !node.texture_url.is_empty() { + eprintln!("[bridge/reify] Node {} has texture URL: {} - NOT YET APPLIED (API limitation)", + id, node.texture_url); + } + Some(model.build()) + } + Err(e) => { + eprintln!("[bridge/reify] Failed to load model for node {}: {}", id, e); + None + } + } + } } - }) - .collect(); - let mut spatial = Spatial::default() - .transform(transform) - .build() - .maybe_child(model_child); - for (_cid, child_spatial) in children { - spatial = spatial.child(child_spatial); - } - Some((id, spatial)) - } - // Only attach root nodes (no parent) to PlaySpace - let root_nodes: Vec<_> = self.nodes.iter() - .filter_map(|(id, node)| if node.parent.is_none() { build_node(*id, &self.nodes, downloader) } else { None }) - .collect(); - PlaySpace.build().stable_children(root_nodes) - } -} - -static STARTED: AtomicBool = AtomicBool::new(false); -static STOP_REQUESTED: AtomicBool = AtomicBool::new(false); -lazy_static::lazy_static! { - static ref CTRL: Mutex = Mutex::new(Ctrl::default()); -} - -#[derive(Clone, serde::Serialize, serde::Deserialize)] -struct Node { - id: u64, - name: String, - parent: Option, - #[serde(skip)] - transform: Mat4, - entity_type: u8, // 0=Unknown, 1=Box, 2=Sphere, 3=Model, etc. - model_url: String, - texture_url: String, - #[serde(skip)] - color: [f32; 4], // RGBA - #[serde(skip)] - dimensions: [f32; 3], // xyz dimensions in meters -} - -struct Ctrl { - rt: Option, - handle: Option>, // client running thread - tx: Option>, - next_id: u64, - nodes: HashMap, - shared_state: Option>>, -} - -impl Default for Ctrl { - fn default() -> Self { - Self { - rt: None, - handle: None, - tx: None, - next_id: 1, - nodes: HashMap::new(), - shared_state: None, - } - } -} - -#[no_mangle] -pub extern "C" fn sdxr_start(app_id: *const std::os::raw::c_char) -> i32 { - if STARTED.swap(true, Ordering::SeqCst) { return 0; } - let _name = unsafe { CStr::from_ptr(app_id) }.to_string_lossy().to_string(); - - // Reset connection status flags - CONNECTION_SUCCESS.store(false, Ordering::SeqCst); - CONNECTION_FAILED.store(false, Ordering::SeqCst); - - let mut ctrl = CTRL.lock().unwrap(); - ctrl.next_id = 1; - let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); - ctrl.tx = Some(tx.clone()); - - // Shared state that both the command handler and the client state will access - let shared_state = Arc::new(Mutex::new(BridgeState::default())); - let shared_for_commands = Arc::clone(&shared_state); - let shared_for_event_loop = Arc::clone(&shared_state); - - // Build a multi-threaded Tokio runtime for the client - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .expect("tokio runtime"); - let handle = std::thread::spawn(move || { - let res = rt.block_on(async move { - // Spawn command processor task that updates shared state - let cmd_task = tokio::spawn(async move { - while let Some(cmd) = rx.recv().await { - match cmd { - Command::Create { c_id, name, parent, transform } => { - if let Ok(mut state) = shared_for_commands.lock() { - let node = Node { - id: c_id, name: name.clone(), parent, transform,