Compare commits
35 Commits
e0f8e7d562
...
4e8f584949
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e8f584949 | ||
|
|
887a1be3d8 | ||
|
|
083e36c4e3 | ||
|
|
e1aeaa59a1 | ||
|
|
fa94eae719 | ||
|
|
7650ef3cd9 | ||
|
|
958ddd5981 | ||
|
|
15cc6d9476 | ||
|
|
07c2391a50 | ||
|
|
8d82f51596 | ||
|
|
d42e15e9f6 | ||
|
|
16f7a83d4d | ||
|
|
9e39c9cfc9 | ||
|
|
d5b89171ee | ||
|
|
38a561e581 | ||
|
|
863a8bc162 | ||
|
|
f065e1cdeb | ||
|
|
a43ba28d20 | ||
|
|
e4e0238640 | ||
|
|
15855db838 | ||
|
|
d070ad384a | ||
|
|
1f5607aaa1 | ||
|
|
8f8e9208e4 | ||
|
|
103fbd32d8 | ||
|
|
70c2c97f9a | ||
|
|
0b1c381071 | ||
|
|
a5fcf3e90f | ||
|
|
eea426e55c | ||
|
|
57c1239675 | ||
|
|
f47961a486 | ||
|
|
be51027c26 | ||
|
|
419bd90049 | ||
|
|
e1474170a5 | ||
|
|
752f1d95a1 | ||
|
|
f0a611c48d |
253
ENTITY_RENDERING_ENHANCEMENTS.md
Normal file
253
ENTITY_RENDERING_ENHANCEMENTS.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Entity Rendering Enhancements for Starworld
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the enhancements made to starworld to support rendering complex Overte entities with full visual properties including models, textures, colors, and different geometric primitives.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Enhanced Entity Data Structure (`OverteClient.hpp`)
|
||||
|
||||
**Added `EntityType` enum:**
|
||||
```cpp
|
||||
enum class EntityType {
|
||||
Unknown, Box, Sphere, Model, Shape, Light, Text,
|
||||
Zone, Web, ParticleEffect, Line, PolyLine, Grid, Gizmo, Material
|
||||
};
|
||||
```
|
||||
|
||||
**Extended `OverteEntity` structure:**
|
||||
```cpp
|
||||
struct OverteEntity {
|
||||
std::uint64_t id{0};
|
||||
std::string name;
|
||||
glm::mat4 transform{1.0f};
|
||||
|
||||
// NEW: Visual properties
|
||||
EntityType type{EntityType::Box};
|
||||
std::string modelUrl; // For Model type entities
|
||||
std::string textureUrl; // Texture/material URL
|
||||
glm::vec3 color{1.0f, 1.0f, 1.0f}; // RGB color (0-1 range)
|
||||
glm::vec3 dimensions{0.1f, 0.1f, 0.1f}; // Size/scale in meters
|
||||
float alpha{1.0f}; // Transparency (0-1)
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Enhanced Entity Packet Parser (`OverteClient.cpp`)
|
||||
|
||||
**Updated `parseEntityPacket()` to extract:**
|
||||
- Entity type classification
|
||||
- Model URLs (for 3D models)
|
||||
- Texture URLs
|
||||
- RGB color values
|
||||
- Dimensions/scale
|
||||
- Alpha transparency
|
||||
|
||||
**Enhanced simulation mode** with diverse entity types:
|
||||
- Red cube (Box type)
|
||||
- Green sphere (Sphere type)
|
||||
- Blue model placeholder (Model type)
|
||||
|
||||
### 3. Rust Bridge Visual Properties (`bridge/src/lib.rs`)
|
||||
|
||||
**Extended `Node` structure:**
|
||||
```rust
|
||||
struct Node {
|
||||
id: u64,
|
||||
name: String,
|
||||
transform: Mat4,
|
||||
entity_type: u8, // 0=Unknown, 1=Box, 2=Sphere, 3=Model
|
||||
model_url: String,
|
||||
texture_url: String,
|
||||
color: [f32; 4], // RGBA
|
||||
dimensions: [f32; 3], // xyz dimensions
|
||||
}
|
||||
```
|
||||
|
||||
**Added new command types:**
|
||||
- `SetModel` - Set 3D model URL
|
||||
- `SetTexture` - Set texture/material URL
|
||||
- `SetColor` - Set RGBA color
|
||||
- `SetDimensions` - Set xyz dimensions
|
||||
- `SetEntityType` - Set geometric primitive type
|
||||
|
||||
**New C-ABI export functions:**
|
||||
- `sdxr_set_node_model(id, model_url)`
|
||||
- `sdxr_set_node_texture(id, texture_url)`
|
||||
- `sdxr_set_node_color(id, r, g, b, a)`
|
||||
- `sdxr_set_node_dimensions(id, x, y, z)`
|
||||
- `sdxr_set_node_entity_type(id, type)`
|
||||
|
||||
### 4. Enhanced Rendering (`bridge/src/lib.rs` - `reify()`)
|
||||
|
||||
**Implemented type-specific rendering:**
|
||||
|
||||
- **Box entities (type 1):** Colored wireframe cubes using entity color
|
||||
- **Sphere entities (type 2):** Three orthogonal wireframe circles (XY, XZ, YZ planes) in entity color
|
||||
- **Model entities (type 3):** Distinctive octahedron wireframe (placeholder for actual model loading)
|
||||
- **Unknown types:** Gray wireframe cube as fallback
|
||||
|
||||
**Features:**
|
||||
- Respects entity dimensions for accurate sizing
|
||||
- Uses entity-specific colors
|
||||
- Applies proper transforms (position, rotation, scale)
|
||||
- Maintains visual distinction between entity types
|
||||
|
||||
### 5. StardustBridge C++ Interface (`StardustBridge.hpp/.cpp`)
|
||||
|
||||
**Added methods:**
|
||||
```cpp
|
||||
bool setNodeModel(NodeId id, const std::string& modelUrl);
|
||||
bool setNodeTexture(NodeId id, const std::string& textureUrl);
|
||||
bool setNodeColor(NodeId id, const glm::vec3& color, float alpha = 1.0f);
|
||||
bool setNodeDimensions(NodeId id, const glm::vec3& dimensions);
|
||||
bool setNodeEntityType(NodeId id, uint8_t entityType);
|
||||
```
|
||||
|
||||
**Updated dynamic library loader** to resolve new function pointers from Rust bridge.
|
||||
|
||||
### 6. SceneSync Integration (`SceneSync.cpp`)
|
||||
|
||||
**Enhanced entity synchronization:**
|
||||
- Propagates entity type on creation/update
|
||||
- Sets color and alpha properties
|
||||
- Configures dimensions
|
||||
- Handles model and texture URLs
|
||||
- Updates visual properties when entities change
|
||||
|
||||
## Testing
|
||||
|
||||
### Simulation Mode
|
||||
|
||||
Run with simulation mode to see example entities:
|
||||
|
||||
```bash
|
||||
export STARWORLD_SIMULATE=1
|
||||
./build/stardust-overte-client
|
||||
```
|
||||
|
||||
This creates three demo entities:
|
||||
- **CubeA** - Red wireframe cube (20cm)
|
||||
- **SphereB** - Green wireframe sphere (15cm)
|
||||
- **ModelC** - Blue octahedron representing a model entity (25cm)
|
||||
|
||||
### Live Overte Connection
|
||||
|
||||
To connect to a real Overte server:
|
||||
|
||||
```bash
|
||||
# Optional: Set credentials
|
||||
export OVERTE_USERNAME=your_username
|
||||
|
||||
# Connect to server
|
||||
./build/stardust-overte-client ws://domain.example.com:40102
|
||||
```
|
||||
|
||||
The client will:
|
||||
1. Perform domain handshake
|
||||
2. Discover entity server via DomainList packets
|
||||
3. Send EntityQuery to request all entities
|
||||
4. Parse and render entities with full visual properties
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Overte Server │
|
||||
│ (Entity Server) │
|
||||
└────────┬────────┘
|
||||
│ UDP Packets (EntityAdd, EntityEdit)
|
||||
│ Contains: type, position, rotation, dimensions,
|
||||
│ color, modelUrl, textureUrl
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ OverteClient.cpp │
|
||||
│ - parseEntityPacket│
|
||||
│ - OverteEntity │
|
||||
└────────┬────────────┘
|
||||
│ consumeUpdatedEntities()
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ SceneSync.cpp │
|
||||
│ - Maps Overte IDs │
|
||||
│ to Stardust IDs │
|
||||
└────────┬────────────┘
|
||||
│ createNode(), setNodeColor(),
|
||||
│ setNodeModel(), etc.
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ StardustBridge.cpp │
|
||||
│ - C++ wrapper │
|
||||
│ - dlopen bridge.so │
|
||||
└────────┬────────────┘
|
||||
│ C-ABI calls: sdxr_set_node_*
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ bridge/lib.rs │
|
||||
│ - Command queue │
|
||||
│ - Shared state │
|
||||
└────────┬────────────┘
|
||||
│ BridgeState::reify()
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Stardust Server │
|
||||
│ - Lines rendering │
|
||||
│ - Type-specific │
|
||||
│ visualization │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Short Term
|
||||
1. **Actual model loading** - Replace octahedron with loaded .glb/.fbx models using Stardust Model nodes
|
||||
2. **Texture application** - Apply textures to rendered entities
|
||||
3. **More entity types** - Add support for Light, Text, PolyLine, etc.
|
||||
4. **Performance optimization** - Batch updates, reduce command overhead
|
||||
|
||||
### Medium Term
|
||||
1. **Full mesh rendering** - Move beyond wireframes to solid shaded meshes
|
||||
2. **Material support** - PBR materials with metallic/roughness
|
||||
3. **Animation** - Skeletal animation for rigged models
|
||||
4. **LOD system** - Level-of-detail based on distance
|
||||
|
||||
### Long Term
|
||||
1. **Physics sync** - Real-time physics state synchronization
|
||||
2. **Script integration** - Execute Overte entity scripts in Stardust context
|
||||
3. **Avatar rendering** - Full avatar mesh and animation support
|
||||
4. **Audio spatialization** - 3D positional audio from Overte
|
||||
|
||||
## Build Instructions
|
||||
|
||||
```bash
|
||||
# Build Rust bridge
|
||||
cd bridge
|
||||
cargo build
|
||||
cd ..
|
||||
|
||||
# Build C++ client
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
cd ..
|
||||
|
||||
# Run
|
||||
./build/stardust-overte-client
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **C++17** - Modern C++ features
|
||||
- **GLM** - Math library for vectors/matrices
|
||||
- **OpenSSL** - MD5 hashing for protocol signatures
|
||||
- **Rust nightly** - For Stardust bridge
|
||||
- **stardust-xr-asteroids** - Declarative UI framework
|
||||
- **stardust-xr-fusion** - Low-level client API
|
||||
- **tokio** - Async runtime
|
||||
|
||||
## References
|
||||
|
||||
- Overte protocol: `third_party/overte-src/libraries/networking/`
|
||||
- Entity types: Based on Overte `EntityTypes.h`
|
||||
- Packet formats: Overte NLPacket specification
|
||||
- Stardust API: https://github.com/StardustXR/core
|
||||
4
bridge/Cargo.lock
generated
4
bridge/Cargo.lock
generated
@@ -2400,6 +2400,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
[[package]]
|
||||
name = "stardust-xr"
|
||||
version = "0.45.0"
|
||||
source = "git+https://github.com/StardustXR/core.git?branch=dev#5176d1e9d26f1e8a4bd774519f3e7d1fddc121a9"
|
||||
dependencies = [
|
||||
"color-eyre",
|
||||
"dirs",
|
||||
@@ -2444,6 +2445,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stardust-xr-fusion"
|
||||
version = "0.45.0"
|
||||
source = "git+https://github.com/StardustXR/core.git?branch=dev#5176d1e9d26f1e8a4bd774519f3e7d1fddc121a9"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
@@ -2469,6 +2471,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stardust-xr-molecules"
|
||||
version = "0.45.0"
|
||||
source = "git+https://github.com/StardustXR/molecules.git?branch=dev#53cfb2eecb066faf60a1b0da0b70f84231bae2be"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"futures-util",
|
||||
@@ -2487,6 +2490,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stardust-xr-schemas"
|
||||
version = "1.5.3"
|
||||
source = "git+https://github.com/StardustXR/core.git?branch=dev#5176d1e9d26f1e8a4bd774519f3e7d1fddc121a9"
|
||||
dependencies = [
|
||||
"console-subscriber",
|
||||
"flatbuffers",
|
||||
|
||||
@@ -19,10 +19,12 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
path = "../third_party/asteroids"
|
||||
|
||||
[dependencies.stardust-xr-fusion]
|
||||
path = "../third_party/core/fusion"
|
||||
git = "https://github.com/StardustXR/core.git"
|
||||
branch = "dev"
|
||||
|
||||
[dependencies.stardust-xr-molecules]
|
||||
path = "../third_party/molecules"
|
||||
git = "https://github.com/StardustXR/molecules.git"
|
||||
branch = "dev"
|
||||
|
||||
[features]
|
||||
# Feature enabling real StardustXR integration when crates are present.
|
||||
|
||||
@@ -10,7 +10,7 @@ use glam::Mat4;
|
||||
use stardust_xr_asteroids as ast; // alias for brevity
|
||||
use stardust_xr_asteroids::{
|
||||
client::ClientState,
|
||||
elements::{PlaySpace, Lines},
|
||||
elements::{PlaySpace, Model, Lines},
|
||||
Migrate, Reify,
|
||||
};
|
||||
use stardust_xr_asteroids::{CustomElement, Transformable, Projector, Context};
|
||||
@@ -18,6 +18,7 @@ use stardust_xr_molecules::accent_color::AccentColor;
|
||||
use stardust_xr_fusion::objects::connect_client as fusion_connect_client;
|
||||
use stardust_xr_fusion::node::NodeType;
|
||||
use stardust_xr_fusion::root::RootAspect;
|
||||
use stardust_xr_fusion::drawable::MaterialParameter;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
@@ -34,6 +35,11 @@ impl Default for BridgeState {
|
||||
enum Command {
|
||||
Create { c_id: u64, name: String, transform: Mat4 },
|
||||
Update { c_id: u64, transform: Mat4 },
|
||||
SetModel { c_id: u64, model_url: String },
|
||||
SetTexture { c_id: u64, texture_url: String },
|
||||
SetColor { c_id: u64, color: [f32; 4] }, // RGBA
|
||||
SetDimensions { c_id: u64, dimensions: [f32; 3] },
|
||||
SetEntityType { c_id: u64, entity_type: u8 },
|
||||
Remove { c_id: u64 },
|
||||
Shutdown,
|
||||
}
|
||||
@@ -49,51 +55,83 @@ impl ClientState for BridgeState {
|
||||
if let Ok(ctrl) = CTRL.lock() {
|
||||
if let Some(shared) = &ctrl.shared_state {
|
||||
if let Ok(shared_state) = shared.lock() {
|
||||
eprintln!("[bridge/on_frame] Syncing {} nodes from shared_state", shared_state.nodes.len());
|
||||
self.nodes = shared_state.nodes.clone();
|
||||
} else {
|
||||
eprintln!("[bridge/on_frame] Failed to lock shared_state");
|
||||
}
|
||||
} else {
|
||||
eprintln!("[bridge/on_frame] No shared_state in ctrl");
|
||||
}
|
||||
} else {
|
||||
eprintln!("[bridge/on_frame] Failed to lock CTRL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Reify for BridgeState {
|
||||
fn reify(&self) -> impl ast::Element<Self> {
|
||||
// Root playspace. Attach a visible cube wireframe per tracked node id.
|
||||
let children = self.nodes.iter().map(|(id, node)| {
|
||||
use stardust_xr_fusion::values::color::rgba_linear;
|
||||
use stardust_xr_fusion::drawable::{Line, LinePoint};
|
||||
use stardust_xr_fusion::values::Vector3;
|
||||
|
||||
eprintln!("[bridge/reify] Reifying {} nodes", self.nodes.len());
|
||||
|
||||
// Root playspace. Create appropriate visuals per entity type
|
||||
let children = self.nodes.iter().filter_map(|(id, node)| {
|
||||
// Skip nodes with zero dimensions (like the root node)
|
||||
let dims = glam::Vec3::from(node.dimensions);
|
||||
if dims.length() < 0.001 {
|
||||
eprintln!("[bridge/reify] Skipping node {} (zero dimensions)", id);
|
||||
return None;
|
||||
}
|
||||
|
||||
eprintln!("[bridge/reify] Creating visual for node {} type={} dims={:?}", id, node.entity_type, dims);
|
||||
|
||||
// Decompose transform into TRS
|
||||
let (scale, rot, trans) = node.transform.to_scale_rotation_translation();
|
||||
// Visible cube size: 20cm, scaled by node scale
|
||||
let vis_scale = glam::Vec3::splat(0.20) * scale.x;
|
||||
|
||||
// Build cube edges as 12 line segments
|
||||
use stardust_xr_fusion::drawable::{Line, LinePoint};
|
||||
use stardust_xr_fusion::values::{color::rgba_linear, Vector3};
|
||||
let t = 0.004; // thickness
|
||||
let c = rgba_linear!(0.8, 0.8, 0.9, 1.0);
|
||||
|
||||
// Use entity dimensions if available, otherwise use transform scale
|
||||
let vis_scale = if dims.length() > 0.001 {
|
||||
dims
|
||||
} else {
|
||||
scale
|
||||
};
|
||||
|
||||
// Use entity color if set
|
||||
let node_color = rgba_linear!(node.color[0], node.color[1], node.color[2], node.color[3]);
|
||||
|
||||
// Simple wireframe cube for all entities for now - each entity gets its own Lines element
|
||||
let t = 0.008; // line thickness
|
||||
let hs = 0.5f32; // half size in model space (unit cube)
|
||||
let mut seg = |a: [f32;3], b: [f32;3]| -> Line {
|
||||
let p0 = LinePoint { point: Vector3 { x: a[0], y: a[1], z: a[2] }, thickness: t, color: c };
|
||||
let p1 = LinePoint { point: Vector3 { x: b[0], y: b[1], z: b[2] }, thickness: t, color: c };
|
||||
|
||||
// Create a line for each edge
|
||||
let seg = |a: [f32;3], b: [f32;3]| -> Line {
|
||||
let p0 = LinePoint { point: Vector3 { x: a[0], y: a[1], z: a[2] }, thickness: t, color: node_color };
|
||||
let p1 = LinePoint { point: Vector3 { x: b[0], y: b[1], z: b[2] }, thickness: t, color: node_color };
|
||||
Line { points: vec![p0, p1], cyclic: false }
|
||||
};
|
||||
|
||||
let corners = [
|
||||
[-hs, -hs, -hs], [ hs, -hs, -hs], [ hs, hs, -hs], [-hs, hs, -hs],
|
||||
[-hs, -hs, hs], [ hs, -hs, hs], [ hs, hs, hs], [-hs, hs, hs],
|
||||
];
|
||||
// Edges: 0-1-2-3-0 (bottom), 4-5-6-7-4 (top), verticals 0-4,1-5,2-6,3-7
|
||||
|
||||
// 12 edges of a cube
|
||||
let lines = vec![
|
||||
seg(corners[0], corners[1]), seg(corners[1], corners[2]), seg(corners[2], corners[3]), seg(corners[3], corners[0]),
|
||||
seg(corners[4], corners[5]), seg(corners[5], corners[6]), seg(corners[6], corners[7]), seg(corners[7], corners[4]),
|
||||
seg(corners[0], corners[4]), seg(corners[1], corners[5]), seg(corners[2], corners[6]), seg(corners[3], corners[7]),
|
||||
];
|
||||
(
|
||||
|
||||
Some((
|
||||
*id,
|
||||
Lines::new(lines)
|
||||
.pos([trans.x, trans.y, trans.z])
|
||||
.rot([rot.x, rot.y, rot.z, rot.w])
|
||||
.scl([vis_scale.x, vis_scale.y, vis_scale.z])
|
||||
.build()
|
||||
)
|
||||
))
|
||||
});
|
||||
|
||||
PlaySpace
|
||||
@@ -114,6 +152,13 @@ struct Node {
|
||||
name: String,
|
||||
#[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 {
|
||||
@@ -165,7 +210,17 @@ pub extern "C" fn sdxr_start(app_id: *const std::os::raw::c_char) -> i32 {
|
||||
match cmd {
|
||||
Command::Create { c_id, name, transform } => {
|
||||
if let Ok(mut state) = shared_for_commands.lock() {
|
||||
state.nodes.insert(c_id, Node { id: c_id, name: name.clone(), transform });
|
||||
let node = Node {
|
||||
id: c_id,
|
||||
name: name.clone(),
|
||||
transform,
|
||||
entity_type: 1, // Default to Box
|
||||
model_url: String::new(),
|
||||
texture_url: String::new(),
|
||||
color: [1.0, 1.0, 1.0, 1.0], // White
|
||||
dimensions: [0.1, 0.1, 0.1], // Default 10cm cube
|
||||
};
|
||||
state.nodes.insert(c_id, node);
|
||||
println!("[bridge] create node id={} name={} (state nodes={})", c_id, name, state.nodes.len());
|
||||
}
|
||||
}
|
||||
@@ -180,6 +235,46 @@ pub extern "C" fn sdxr_start(app_id: *const std::os::raw::c_char) -> i32 {
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::SetModel { c_id, model_url } => {
|
||||
if let Ok(mut state) = shared_for_commands.lock() {
|
||||
if let Some(n) = state.nodes.get_mut(&c_id) {
|
||||
n.model_url = model_url.clone();
|
||||
println!("[bridge] set model for node id={}: {}", c_id, model_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::SetTexture { c_id, texture_url } => {
|
||||
if let Ok(mut state) = shared_for_commands.lock() {
|
||||
if let Some(n) = state.nodes.get_mut(&c_id) {
|
||||
n.texture_url = texture_url.clone();
|
||||
println!("[bridge] set texture for node id={}: {}", c_id, texture_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::SetColor { c_id, color } => {
|
||||
if let Ok(mut state) = shared_for_commands.lock() {
|
||||
if let Some(n) = state.nodes.get_mut(&c_id) {
|
||||
n.color = color;
|
||||
println!("[bridge] set color for node id={}: {:?}", c_id, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::SetDimensions { c_id, dimensions } => {
|
||||
if let Ok(mut state) = shared_for_commands.lock() {
|
||||
if let Some(n) = state.nodes.get_mut(&c_id) {
|
||||
n.dimensions = dimensions;
|
||||
println!("[bridge] set dimensions for node id={}: {:?}", c_id, dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::SetEntityType { c_id, entity_type } => {
|
||||
if let Ok(mut state) = shared_for_commands.lock() {
|
||||
if let Some(n) = state.nodes.get_mut(&c_id) {
|
||||
n.entity_type = entity_type;
|
||||
println!("[bridge] set entity type for node id={}: {}", c_id, entity_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Remove { c_id } => {
|
||||
if let Ok(mut state) = shared_for_commands.lock() {
|
||||
if state.nodes.remove(&c_id).is_some() {
|
||||
@@ -235,6 +330,7 @@ pub extern "C" fn sdxr_start(app_id: *const std::os::raw::c_char) -> i32 {
|
||||
}
|
||||
}
|
||||
if frames.is_empty() { return; }
|
||||
eprintln!("[bridge/event_loop] Processing {} frames, state has {} nodes", frames.len(), state.nodes.len());
|
||||
for frame in frames {
|
||||
if let Ok(ctrl) = CTRL.lock() { if let Some(shared) = &ctrl.shared_state { if let Ok(ss) = shared.lock() { state.nodes = ss.nodes.clone(); } } }
|
||||
state.on_frame(&frame);
|
||||
@@ -320,3 +416,55 @@ pub extern "C" fn sdxr_node_count() -> u64 {
|
||||
let ctrl = CTRL.lock().unwrap();
|
||||
ctrl.nodes.len() as u64
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sdxr_set_node_model(id: u64, model_url: *const std::os::raw::c_char) -> i32 {
|
||||
if !STARTED.load(Ordering::SeqCst) { return -1; }
|
||||
let url = unsafe { CStr::from_ptr(model_url) }.to_string_lossy().to_string();
|
||||
let ctrl = CTRL.lock().unwrap();
|
||||
if let Some(tx) = &ctrl.tx {
|
||||
let _ = tx.send(Command::SetModel { c_id: id, model_url: url });
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sdxr_set_node_texture(id: u64, texture_url: *const std::os::raw::c_char) -> i32 {
|
||||
if !STARTED.load(Ordering::SeqCst) { return -1; }
|
||||
let url = unsafe { CStr::from_ptr(texture_url) }.to_string_lossy().to_string();
|
||||
let ctrl = CTRL.lock().unwrap();
|
||||
if let Some(tx) = &ctrl.tx {
|
||||
let _ = tx.send(Command::SetTexture { c_id: id, texture_url: url });
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sdxr_set_node_color(id: u64, r: f32, g: f32, b: f32, a: f32) -> i32 {
|
||||
if !STARTED.load(Ordering::SeqCst) { return -1; }
|
||||
let ctrl = CTRL.lock().unwrap();
|
||||
if let Some(tx) = &ctrl.tx {
|
||||
let _ = tx.send(Command::SetColor { c_id: id, color: [r, g, b, a] });
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sdxr_set_node_dimensions(id: u64, x: f32, y: f32, z: f32) -> i32 {
|
||||
if !STARTED.load(Ordering::SeqCst) { return -1; }
|
||||
let ctrl = CTRL.lock().unwrap();
|
||||
if let Some(tx) = &ctrl.tx {
|
||||
let _ = tx.send(Command::SetDimensions { c_id: id, dimensions: [x, y, z] });
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn sdxr_set_node_entity_type(id: u64, entity_type: u8) -> i32 {
|
||||
if !STARTED.load(Ordering::SeqCst) { return -1; }
|
||||
let ctrl = CTRL.lock().unwrap();
|
||||
if let Some(tx) = &ctrl.tx {
|
||||
let _ = tx.send(Command::SetEntityType { c_id: id, entity_type });
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
@@ -203,14 +208,39 @@ bool OverteClient::connect() {
|
||||
|
||||
m_useSimulation = (std::getenv("STARWORLD_SIMULATE") != nullptr);
|
||||
if (m_useSimulation) {
|
||||
// Seed a couple of demo entities.
|
||||
OverteEntity a{ m_nextEntityId++, "CubeA", glm::mat4(1.0f) };
|
||||
OverteEntity b{ m_nextEntityId++, "CubeB", glm::mat4(1.0f) };
|
||||
m_entities.emplace(a.id, a);
|
||||
m_entities.emplace(b.id, b);
|
||||
m_updateQueue.push_back(a.id);
|
||||
m_updateQueue.push_back(b.id);
|
||||
std::cout << "[OverteClient] Simulation mode enabled (STARWORLD_SIMULATE=1)" << std::endl;
|
||||
// Seed a few demo entities with different types and properties
|
||||
OverteEntity cubeA;
|
||||
cubeA.id = m_nextEntityId++;
|
||||
cubeA.name = "CubeA";
|
||||
cubeA.type = EntityType::Box;
|
||||
cubeA.color = glm::vec3(1.0f, 0.3f, 0.3f); // Red cube
|
||||
cubeA.dimensions = glm::vec3(0.2f, 0.2f, 0.2f);
|
||||
cubeA.transform = glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, 1.5f, -2.0f));
|
||||
|
||||
OverteEntity sphereB;
|
||||
sphereB.id = m_nextEntityId++;
|
||||
sphereB.name = "SphereB";
|
||||
sphereB.type = EntityType::Sphere;
|
||||
sphereB.color = glm::vec3(0.3f, 1.0f, 0.3f); // Green sphere
|
||||
sphereB.dimensions = glm::vec3(0.15f, 0.15f, 0.15f);
|
||||
sphereB.transform = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f, 1.5f, -2.0f));
|
||||
|
||||
OverteEntity modelC;
|
||||
modelC.id = m_nextEntityId++;
|
||||
modelC.name = "ModelC";
|
||||
modelC.type = EntityType::Model;
|
||||
modelC.color = glm::vec3(0.3f, 0.3f, 1.0f); // Blue tint
|
||||
modelC.dimensions = glm::vec3(0.25f, 0.25f, 0.25f);
|
||||
modelC.modelUrl = "https://example.com/model.glb"; // Placeholder
|
||||
modelC.transform = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.2f, -2.0f));
|
||||
|
||||
m_entities.emplace(cubeA.id, cubeA);
|
||||
m_entities.emplace(sphereB.id, sphereB);
|
||||
m_entities.emplace(modelC.id, modelC);
|
||||
m_updateQueue.push_back(cubeA.id);
|
||||
m_updateQueue.push_back(sphereB.id);
|
||||
m_updateQueue.push_back(modelC.id);
|
||||
std::cout << "[OverteClient] Simulation mode enabled (STARWORLD_SIMULATE=1) with 3 demo entities" << std::endl;
|
||||
} else {
|
||||
std::cout << "[OverteClient] Waiting for entity packets from Overte server..." << std::endl;
|
||||
std::cout << "[OverteClient] Tip: Set STARWORLD_SIMULATE=1 to enable demo entities" << std::endl;
|
||||
@@ -404,8 +434,8 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
|
||||
switch (packetType) {
|
||||
case PACKET_TYPE_ENTITY_DATA:
|
||||
case PACKET_TYPE_ENTITY_ADD: {
|
||||
// EntityAdd/EntityData packet structure (simplified):
|
||||
// u64 entityID, string name, vec3 position, quat rotation, vec3 dimensions, ...
|
||||
// EntityAdd packet structure (enhanced):
|
||||
// [type:u8][id:u64][name:null-terminated][position:3xf32][rotation:4xf32][dimensions:3xf32][model_url:null-terminated][texture_url:null-terminated][color:3xf32]
|
||||
if (len < 9) break; // need at least 1+8 bytes
|
||||
|
||||
std::uint64_t entityId;
|
||||
@@ -417,42 +447,191 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
|
||||
while (offset < len && data[offset] != '\0') {
|
||||
name += data[offset++];
|
||||
}
|
||||
offset++; // skip null terminator
|
||||
if (name.empty()) name = "Entity_" + std::to_string(entityId);
|
||||
|
||||
// TODO: Parse full entity properties (position, rotation, dimensions)
|
||||
// For now, create entity with a visible position spread out in front of user
|
||||
// Position entities in a grid pattern for visibility
|
||||
float spacing = 0.5f;
|
||||
int index = static_cast<int>(entityId % 10);
|
||||
float x = (index % 3) * spacing - spacing; // -0.5, 0, 0.5
|
||||
float y = 1.5f; // Eye level
|
||||
float z = -2.0f - (index / 3) * spacing; // Start 2m in front, spread back
|
||||
// Parse position (vec3 - 3 floats)
|
||||
glm::vec3 position(0.0f, 1.5f, -2.0f); // Default
|
||||
if (offset + 12 <= len) {
|
||||
std::memcpy(&position.x, data + offset, 4);
|
||||
std::memcpy(&position.y, data + offset + 4, 4);
|
||||
std::memcpy(&position.z, data + offset + 8, 4);
|
||||
offset += 12;
|
||||
}
|
||||
|
||||
glm::vec3 position(x, y, z);
|
||||
glm::mat4 transform = glm::translate(glm::mat4(1.0f), position);
|
||||
// Parse rotation (quaternion - 4 floats: x, y, z, w)
|
||||
glm::quat rotation(1.0f, 0.0f, 0.0f, 0.0f); // Identity (w, x, y, z in glm)
|
||||
if (offset + 16 <= len) {
|
||||
float qx, qy, qz, qw;
|
||||
std::memcpy(&qx, data + offset, 4);
|
||||
std::memcpy(&qy, data + offset + 4, 4);
|
||||
std::memcpy(&qz, data + offset + 8, 4);
|
||||
std::memcpy(&qw, data + offset + 12, 4);
|
||||
rotation = glm::quat(qw, qx, qy, qz); // glm uses w first
|
||||
offset += 16;
|
||||
}
|
||||
|
||||
// Parse dimensions/scale (vec3 - 3 floats)
|
||||
glm::vec3 dimensions(0.1f, 0.1f, 0.1f); // Default
|
||||
if (offset + 12 <= len) {
|
||||
std::memcpy(&dimensions.x, data + offset, 4);
|
||||
std::memcpy(&dimensions.y, data + offset + 4, 4);
|
||||
std::memcpy(&dimensions.z, data + offset + 8, 4);
|
||||
offset += 12;
|
||||
}
|
||||
|
||||
// Parse model URL (null-terminated string)
|
||||
std::string modelUrl;
|
||||
while (offset < len && data[offset] != '\0') {
|
||||
modelUrl += data[offset++];
|
||||
}
|
||||
offset++; // skip null terminator
|
||||
|
||||
// Parse texture URL (null-terminated string)
|
||||
std::string textureUrl;
|
||||
while (offset < len && data[offset] != '\0') {
|
||||
textureUrl += data[offset++];
|
||||
}
|
||||
offset++; // skip null terminator
|
||||
|
||||
// Parse color (vec3 RGB - 3 floats 0-1)
|
||||
glm::vec3 color(1.0f, 1.0f, 1.0f); // Default white
|
||||
if (offset + 12 <= len) {
|
||||
std::memcpy(&color.r, data + offset, 4);
|
||||
std::memcpy(&color.g, data + offset + 4, 4);
|
||||
std::memcpy(&color.b, data + offset + 8, 4);
|
||||
offset += 12;
|
||||
}
|
||||
|
||||
// Parse entity type (optional, u8)
|
||||
EntityType entityType = EntityType::Box; // Default
|
||||
if (offset + 1 <= len) {
|
||||
uint8_t typeCode = static_cast<uint8_t>(data[offset++]);
|
||||
// Map Overte entity type codes to our enum
|
||||
// 0=Unknown, 1=Box, 2=Sphere, 3=Model, etc.
|
||||
if (typeCode <= static_cast<uint8_t>(EntityType::Material)) {
|
||||
entityType = static_cast<EntityType>(typeCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Build transform matrix from position, rotation, scale
|
||||
glm::mat4 transform = glm::mat4(1.0f);
|
||||
transform = glm::translate(transform, position);
|
||||
transform = transform * glm::mat4_cast(rotation);
|
||||
transform = glm::scale(transform, dimensions);
|
||||
|
||||
// Create entity with all properties
|
||||
OverteEntity entity;
|
||||
entity.id = entityId;
|
||||
entity.name = name;
|
||||
entity.transform = transform;
|
||||
entity.type = entityType;
|
||||
entity.modelUrl = modelUrl;
|
||||
entity.textureUrl = textureUrl;
|
||||
entity.color = color;
|
||||
entity.dimensions = dimensions;
|
||||
entity.alpha = 1.0f; // Default fully opaque
|
||||
|
||||
OverteEntity entity{entityId, name, transform};
|
||||
m_entities[entityId] = entity;
|
||||
m_updateQueue.push_back(entityId);
|
||||
|
||||
std::cout << "[OverteClient] Entity added: " << name << " (id=" << entityId
|
||||
<< ") at pos(" << x << ", " << y << ", " << z << ")" << std::endl;
|
||||
std::cout << "[OverteClient] Entity added: " << name << " (id=" << entityId << ")" << std::endl;
|
||||
std::cout << " Type: " << static_cast<int>(entityType) << std::endl;
|
||||
std::cout << " Position: (" << position.x << ", " << position.y << ", " << position.z << ")" << std::endl;
|
||||
std::cout << " Rotation: (" << rotation.x << ", " << rotation.y << ", " << rotation.z << ", " << rotation.w << ")" << std::endl;
|
||||
std::cout << " Dimensions: (" << dimensions.x << ", " << dimensions.y << ", " << dimensions.z << ")" << std::endl;
|
||||
std::cout << " Color: RGB(" << color.r << ", " << color.g << ", " << color.b << ")" << std::endl;
|
||||
if (!modelUrl.empty()) {
|
||||
std::cout << " Model: " << modelUrl << std::endl;
|
||||
}
|
||||
if (!textureUrl.empty()) {
|
||||
std::cout << " Texture: " << textureUrl << std::endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PACKET_TYPE_ENTITY_EDIT: {
|
||||
// EntityEdit packet: u64 entityID, property flags, property data...
|
||||
if (len < 9) break;
|
||||
// EntityEdit packet: [type:u8][id:u64][flags:u8][property data...]
|
||||
if (len < 10) break; // Need type + id + flags
|
||||
|
||||
std::uint64_t entityId;
|
||||
std::memcpy(&entityId, data + 1, 8);
|
||||
|
||||
uint8_t flags = data[9];
|
||||
size_t offset = 10;
|
||||
|
||||
const uint8_t HAS_POSITION = 0x01;
|
||||
const uint8_t HAS_ROTATION = 0x02;
|
||||
const uint8_t HAS_DIMENSIONS = 0x04;
|
||||
|
||||
auto it = m_entities.find(entityId);
|
||||
if (it != m_entities.end()) {
|
||||
// TODO: parse property flags and update transform
|
||||
// For now, mark as updated
|
||||
glm::vec3 position(0.0f);
|
||||
glm::quat rotation(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
glm::vec3 dimensions(1.0f);
|
||||
|
||||
// Extract current transform
|
||||
glm::vec3 scale;
|
||||
glm::quat currentRot;
|
||||
glm::vec3 currentPos;
|
||||
glm::vec3 skew;
|
||||
glm::vec4 perspective;
|
||||
glm::decompose(it->second.transform, scale, currentRot, currentPos, skew, perspective);
|
||||
|
||||
position = currentPos;
|
||||
rotation = currentRot;
|
||||
dimensions = scale;
|
||||
|
||||
// Update based on flags
|
||||
if (flags & HAS_POSITION) {
|
||||
if (offset + 12 <= len) {
|
||||
std::memcpy(&position.x, data + offset, 4);
|
||||
std::memcpy(&position.y, data + offset + 4, 4);
|
||||
std::memcpy(&position.z, data + offset + 8, 4);
|
||||
offset += 12;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & HAS_ROTATION) {
|
||||
if (offset + 16 <= len) {
|
||||
float qx, qy, qz, qw;
|
||||
std::memcpy(&qx, data + offset, 4);
|
||||
std::memcpy(&qy, data + offset + 4, 4);
|
||||
std::memcpy(&qz, data + offset + 8, 4);
|
||||
std::memcpy(&qw, data + offset + 12, 4);
|
||||
rotation = glm::quat(qw, qx, qy, qz);
|
||||
offset += 16;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & HAS_DIMENSIONS) {
|
||||
if (offset + 12 <= len) {
|
||||
std::memcpy(&dimensions.x, data + offset, 4);
|
||||
std::memcpy(&dimensions.y, data + offset + 4, 4);
|
||||
std::memcpy(&dimensions.z, data + offset + 8, 4);
|
||||
offset += 12;
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild transform
|
||||
glm::mat4 transform = glm::mat4(1.0f);
|
||||
transform = glm::translate(transform, position);
|
||||
transform = transform * glm::mat4_cast(rotation);
|
||||
transform = glm::scale(transform, dimensions);
|
||||
|
||||
it->second.transform = transform;
|
||||
m_updateQueue.push_back(entityId);
|
||||
std::cout << "[OverteClient] Entity edited: id=" << entityId << std::endl;
|
||||
|
||||
std::cout << "[OverteClient] Entity edited: id=" << entityId << " (flags=0x" << std::hex << (int)flags << std::dec << ")" << std::endl;
|
||||
if (flags & HAS_POSITION) {
|
||||
std::cout << " New position: (" << position.x << ", " << position.y << ", " << position.z << ")" << std::endl;
|
||||
}
|
||||
if (flags & HAS_ROTATION) {
|
||||
std::cout << " New rotation: (" << rotation.x << ", " << rotation.y << ", " << rotation.z << ", " << rotation.w << ")" << std::endl;
|
||||
}
|
||||
if (flags & HAS_DIMENSIONS) {
|
||||
std::cout << " New dimensions: (" << dimensions.x << ", " << dimensions.y << ", " << dimensions.z << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,37 @@
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
|
||||
// Overte entity types (matching Overte EntityTypes.h)
|
||||
enum class EntityType {
|
||||
Unknown,
|
||||
Box,
|
||||
Sphere,
|
||||
Model,
|
||||
Shape,
|
||||
Light,
|
||||
Text,
|
||||
Zone,
|
||||
Web,
|
||||
ParticleEffect,
|
||||
Line,
|
||||
PolyLine,
|
||||
Grid,
|
||||
Gizmo,
|
||||
Material
|
||||
};
|
||||
|
||||
struct OverteEntity {
|
||||
std::uint64_t id{0};
|
||||
std::string name;
|
||||
glm::mat4 transform{1.0f};
|
||||
|
||||
// Visual properties
|
||||
EntityType type{EntityType::Box};
|
||||
std::string modelUrl; // For Model type entities
|
||||
std::string textureUrl; // Texture/material URL
|
||||
glm::vec3 color{1.0f, 1.0f, 1.0f}; // RGB color (0-1 range)
|
||||
glm::vec3 dimensions{0.1f, 0.1f, 0.1f}; // Size/scale in meters
|
||||
float alpha{1.0f}; // Transparency (0-1)
|
||||
};
|
||||
|
||||
// Lightweight client for Overte mixers/entities. Designed to follow Overte's
|
||||
|
||||
@@ -16,9 +16,31 @@ void SceneSync::update(StardustBridge& stardust, OverteClient& overte) {
|
||||
// Create a Stardust node the first time we see this entity.
|
||||
auto nodeId = stardust.createNode(e.name, e.transform);
|
||||
s_entityNodeMap.emplace(e.id, nodeId);
|
||||
|
||||
// Set visual properties for the newly created node
|
||||
stardust.setNodeEntityType(nodeId, static_cast<uint8_t>(e.type));
|
||||
stardust.setNodeColor(nodeId, e.color, e.alpha);
|
||||
stardust.setNodeDimensions(nodeId, e.dimensions);
|
||||
|
||||
if (!e.modelUrl.empty()) {
|
||||
stardust.setNodeModel(nodeId, e.modelUrl);
|
||||
}
|
||||
if (!e.textureUrl.empty()) {
|
||||
stardust.setNodeTexture(nodeId, e.textureUrl);
|
||||
}
|
||||
} else {
|
||||
// Update existing node's transform.
|
||||
// Update existing node's transform and visual properties
|
||||
stardust.updateNodeTransform(it->second, e.transform);
|
||||
stardust.setNodeEntityType(it->second, static_cast<uint8_t>(e.type));
|
||||
stardust.setNodeColor(it->second, e.color, e.alpha);
|
||||
stardust.setNodeDimensions(it->second, e.dimensions);
|
||||
|
||||
if (!e.modelUrl.empty()) {
|
||||
stardust.setNodeModel(it->second, e.modelUrl);
|
||||
}
|
||||
if (!e.textureUrl.empty()) {
|
||||
stardust.setNodeTexture(it->second, e.textureUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ bool StardustBridge::connect(const std::string& socketPath) {
|
||||
m_connected = true;
|
||||
std::cout << "[StardustBridge] Connected via Rust bridge (C-ABI)." << std::endl;
|
||||
m_overteRoot = createNode("OverteWorld");
|
||||
// Set root node to type 0 (Unknown) with zero dimensions so it doesn't render
|
||||
setNodeEntityType(*m_overteRoot, 0);
|
||||
setNodeDimensions(*m_overteRoot, glm::vec3(0.0f, 0.0f, 0.0f));
|
||||
return true;
|
||||
} else {
|
||||
std::cerr << "[StardustBridge] Rust bridge present but start() failed (rc=" << rc << ")" << std::endl;
|
||||
@@ -124,6 +127,9 @@ bool StardustBridge::connect(const std::string& socketPath) {
|
||||
std::cout << "[StardustBridge] Connected to compositor at " << (isAbstract ? ("abstract:" + p.substr(1)) : p) << std::endl;
|
||||
|
||||
m_overteRoot = createNode("OverteWorld");
|
||||
// Set root node to type 0 (Unknown) with zero dimensions so it doesn't render
|
||||
setNodeEntityType(*m_overteRoot, 0);
|
||||
setNodeDimensions(*m_overteRoot, glm::vec3(0.0f, 0.0f, 0.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -171,6 +177,53 @@ bool StardustBridge::removeNode(NodeId id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StardustBridge::setNodeModel(NodeId id, const std::string& modelUrl) {
|
||||
auto it = m_nodes.find(id);
|
||||
if (it == m_nodes.end()) return false;
|
||||
if (m_fnSetModel) {
|
||||
return m_fnSetModel(id, modelUrl.c_str()) == 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StardustBridge::setNodeTexture(NodeId id, const std::string& textureUrl) {
|
||||
auto it = m_nodes.find(id);
|
||||
if (it == m_nodes.end()) return false;
|
||||
if (m_fnSetTexture) {
|
||||
return m_fnSetTexture(id, textureUrl.c_str()) == 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StardustBridge::setNodeColor(NodeId id, const glm::vec3& color, float alpha) {
|
||||
auto it = m_nodes.find(id);
|
||||
if (it == m_nodes.end()) return false;
|
||||
if (m_fnSetColor) {
|
||||
return m_fnSetColor(id, color.r, color.g, color.b, alpha) == 0;
|
||||
} else {
|
||||
std::cerr << "[StardustBridge] Warning: setNodeColor called but m_fnSetColor is null" << std::endl;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StardustBridge::setNodeDimensions(NodeId id, const glm::vec3& dimensions) {
|
||||
auto it = m_nodes.find(id);
|
||||
if (it == m_nodes.end()) return false;
|
||||
if (m_fnSetDimensions) {
|
||||
return m_fnSetDimensions(id, dimensions.x, dimensions.y, dimensions.z) == 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StardustBridge::setNodeEntityType(NodeId id, uint8_t entityType) {
|
||||
auto it = m_nodes.find(id);
|
||||
if (it == m_nodes.end()) return false;
|
||||
if (m_fnSetEntityType) {
|
||||
return m_fnSetEntityType(id, entityType) == 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StardustBridge::poll() {
|
||||
if (!m_connected) return;
|
||||
|
||||
@@ -246,9 +299,13 @@ bool StardustBridge::loadBridge() {
|
||||
m_fnPoll = reinterpret_cast<fn_poll_t>(req("sdxr_poll"));
|
||||
m_fnShutdown = reinterpret_cast<fn_shutdown_t>(req("sdxr_shutdown"));
|
||||
m_fnCreateNode = reinterpret_cast<fn_create_node_t>(req("sdxr_create_node"));
|
||||
m_fnCreateNode = reinterpret_cast<fn_create_node_t>(req("sdxr_create_node"));
|
||||
m_fnUpdateNode = reinterpret_cast<fn_update_node_t>(req("sdxr_update_node"));
|
||||
m_fnRemoveNode = reinterpret_cast<fn_remove_node_t>(req("sdxr_remove_node"));
|
||||
m_fnSetModel = reinterpret_cast<fn_set_model_t>(req("sdxr_set_node_model"));
|
||||
m_fnSetTexture = reinterpret_cast<fn_set_texture_t>(req("sdxr_set_node_texture"));
|
||||
m_fnSetColor = reinterpret_cast<fn_set_color_t>(req("sdxr_set_node_color"));
|
||||
m_fnSetDimensions = reinterpret_cast<fn_set_dimensions_t>(req("sdxr_set_node_dimensions"));
|
||||
m_fnSetEntityType = reinterpret_cast<fn_set_entity_type_t>(req("sdxr_set_node_entity_type"));
|
||||
if (m_fnStart && m_fnPoll && m_fnCreateNode && m_fnUpdateNode) {
|
||||
m_bridgeHandle = h;
|
||||
std::cout << "[StardustBridge] Loaded Rust bridge: " << path << std::endl;
|
||||
|
||||
@@ -27,6 +27,13 @@ public:
|
||||
|
||||
// Update a node's transform. Returns false if the node doesn't exist.
|
||||
bool updateNodeTransform(NodeId id, const glm::mat4& transform);
|
||||
|
||||
// Set visual properties for a node
|
||||
bool setNodeModel(NodeId id, const std::string& modelUrl);
|
||||
bool setNodeTexture(NodeId id, const std::string& textureUrl);
|
||||
bool setNodeColor(NodeId id, const glm::vec3& color, float alpha = 1.0f);
|
||||
bool setNodeDimensions(NodeId id, const glm::vec3& dimensions);
|
||||
bool setNodeEntityType(NodeId id, uint8_t entityType);
|
||||
|
||||
// Remove a node. Returns false if the node doesn't exist.
|
||||
bool removeNode(NodeId id);
|
||||
@@ -81,12 +88,23 @@ private:
|
||||
using fn_create_node_t = std::uint64_t(*)(const char*, const float*);
|
||||
using fn_update_node_t = int(*)(std::uint64_t, const float*);
|
||||
using fn_remove_node_t = int(*)(std::uint64_t);
|
||||
using fn_set_model_t = int(*)(std::uint64_t, const char*);
|
||||
using fn_set_texture_t = int(*)(std::uint64_t, const char*);
|
||||
using fn_set_color_t = int(*)(std::uint64_t, float, float, float, float);
|
||||
using fn_set_dimensions_t = int(*)(std::uint64_t, float, float, float);
|
||||
using fn_set_entity_type_t = int(*)(std::uint64_t, std::uint8_t);
|
||||
|
||||
fn_start_t m_fnStart{nullptr};
|
||||
fn_poll_t m_fnPoll{nullptr};
|
||||
fn_shutdown_t m_fnShutdown{nullptr};
|
||||
fn_create_node_t m_fnCreateNode{nullptr};
|
||||
fn_update_node_t m_fnUpdateNode{nullptr};
|
||||
fn_remove_node_t m_fnRemoveNode{nullptr};
|
||||
fn_set_model_t m_fnSetModel{nullptr};
|
||||
fn_set_texture_t m_fnSetTexture{nullptr};
|
||||
fn_set_color_t m_fnSetColor{nullptr};
|
||||
fn_set_dimensions_t m_fnSetDimensions{nullptr};
|
||||
fn_set_entity_type_t m_fnSetEntityType{nullptr};
|
||||
|
||||
bool loadBridge();
|
||||
};
|
||||
|
||||
276
tools/inject_entities_enhanced.py
Executable file
276
tools/inject_entities_enhanced.py
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enhanced entity injection script with full property support:
|
||||
- Position (x, y, z)
|
||||
- Rotation (quaternion: x, y, z, w)
|
||||
- Dimensions (x, y, z) / Scale
|
||||
- Model URL
|
||||
- Texture URL
|
||||
- Color
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import argparse
|
||||
|
||||
# Overte packet types
|
||||
PACKET_TYPE_ENTITY_ADD = 0x10
|
||||
PACKET_TYPE_ENTITY_EDIT = 0x11
|
||||
PACKET_TYPE_ENTITY_ERASE = 0x12
|
||||
|
||||
def send_entity_add_full(sock, addr, entity_id, name, position, rotation, dimensions, model_url="", texture_url="", color=(1.0, 1.0, 1.0)):
|
||||
"""
|
||||
Send a full EntityAdd packet with all properties
|
||||
|
||||
Args:
|
||||
entity_id: uint64
|
||||
name: string
|
||||
position: (x, y, z) tuple of floats
|
||||
rotation: (x, y, z, w) quaternion tuple of floats
|
||||
dimensions: (x, y, z) tuple of floats
|
||||
model_url: string (optional)
|
||||
texture_url: string (optional)
|
||||
color: (r, g, b) tuple of floats 0-1 (optional)
|
||||
"""
|
||||
# Packet structure:
|
||||
# [type:u8][id:u64][name:null-terminated][position:3xf32][rotation:4xf32][dimensions:3xf32][model_url:null-terminated][texture_url:null-terminated][color:3xf32]
|
||||
|
||||
packet = struct.pack('<BQ', PACKET_TYPE_ENTITY_ADD, entity_id)
|
||||
|
||||
# Name (null-terminated string)
|
||||
packet += name.encode('utf-8') + b'\x00'
|
||||
|
||||
# Position (vec3 - 3 floats)
|
||||
packet += struct.pack('<fff', position[0], position[1], position[2])
|
||||
|
||||
# Rotation (quaternion - 4 floats: x, y, z, w)
|
||||
packet += struct.pack('<ffff', rotation[0], rotation[1], rotation[2], rotation[3])
|
||||
|
||||
# Dimensions/Scale (vec3 - 3 floats)
|
||||
packet += struct.pack('<fff', dimensions[0], dimensions[1], dimensions[2])
|
||||
|
||||
# Model URL (null-terminated string)
|
||||
packet += model_url.encode('utf-8') + b'\x00'
|
||||
|
||||
# Texture URL (null-terminated string)
|
||||
packet += texture_url.encode('utf-8') + b'\x00'
|
||||
|
||||
# Color (vec3 - 3 floats RGB 0-1)
|
||||
packet += struct.pack('<fff', color[0], color[1], color[2])
|
||||
|
||||
sock.sendto(packet, addr)
|
||||
print(f"✓ Sent EntityAdd: {name} (id={entity_id})")
|
||||
print(f" Position: ({position[0]:.2f}, {position[1]:.2f}, {position[2]:.2f})")
|
||||
print(f" Rotation: ({rotation[0]:.2f}, {rotation[1]:.2f}, {rotation[2]:.2f}, {rotation[3]:.2f})")
|
||||
print(f" Dimensions: ({dimensions[0]:.2f}, {dimensions[1]:.2f}, {dimensions[2]:.2f})")
|
||||
if model_url:
|
||||
print(f" Model: {model_url}")
|
||||
if texture_url:
|
||||
print(f" Texture: {texture_url}")
|
||||
print(f" Color: RGB({color[0]:.2f}, {color[1]:.2f}, {color[2]:.2f})")
|
||||
|
||||
def send_entity_edit_transform(sock, addr, entity_id, position=None, rotation=None, dimensions=None):
|
||||
"""
|
||||
Send an EntityEdit packet to update transform properties
|
||||
|
||||
Args:
|
||||
entity_id: uint64
|
||||
position: (x, y, z) tuple or None
|
||||
rotation: (x, y, z, w) quaternion tuple or None
|
||||
dimensions: (x, y, z) tuple or None
|
||||
"""
|
||||
# Property flags bitfield
|
||||
HAS_POSITION = 0x01
|
||||
HAS_ROTATION = 0x02
|
||||
HAS_DIMENSIONS = 0x04
|
||||
|
||||
flags = 0
|
||||
data = b''
|
||||
|
||||
if position is not None:
|
||||
flags |= HAS_POSITION
|
||||
data += struct.pack('<fff', position[0], position[1], position[2])
|
||||
|
||||
if rotation is not None:
|
||||
flags |= HAS_ROTATION
|
||||
data += struct.pack('<ffff', rotation[0], rotation[1], rotation[2], rotation[3])
|
||||
|
||||
if dimensions is not None:
|
||||
flags |= HAS_DIMENSIONS
|
||||
data += struct.pack('<fff', dimensions[0], dimensions[1], dimensions[2])
|
||||
|
||||
# Packet: [type:u8][id:u64][flags:u8][property data...]
|
||||
packet = struct.pack('<BQB', PACKET_TYPE_ENTITY_EDIT, entity_id, flags)
|
||||
packet += data
|
||||
|
||||
sock.sendto(packet, addr)
|
||||
print(f"✓ Sent EntityEdit: id={entity_id}, flags=0x{flags:02x}")
|
||||
|
||||
def send_entity_erase(sock, addr, entity_id):
|
||||
"""Send an EntityErase packet"""
|
||||
packet = struct.pack('<BQ', PACKET_TYPE_ENTITY_ERASE, entity_id)
|
||||
sock.sendto(packet, addr)
|
||||
print(f"✓ Sent EntityErase: id={entity_id}")
|
||||
|
||||
def demo_scene(sock, addr):
|
||||
"""Create a demo scene with various entities"""
|
||||
|
||||
print("\n=== Creating Demo Scene ===\n")
|
||||
|
||||
# Red cube in front
|
||||
send_entity_add_full(
|
||||
sock, addr,
|
||||
entity_id=1001,
|
||||
name="RedCube",
|
||||
position=(0.0, 1.5, -2.0),
|
||||
rotation=(0.0, 0.0, 0.0, 1.0), # Identity quaternion
|
||||
dimensions=(0.3, 0.3, 0.3),
|
||||
color=(1.0, 0.0, 0.0) # Red
|
||||
)
|
||||
time.sleep(0.3)
|
||||
|
||||
# Green sphere to the left
|
||||
send_entity_add_full(
|
||||
sock, addr,
|
||||
entity_id=1002,
|
||||
name="GreenSphere",
|
||||
position=(-1.0, 1.5, -2.5),
|
||||
rotation=(0.0, 0.0, 0.0, 1.0),
|
||||
dimensions=(0.4, 0.4, 0.4),
|
||||
color=(0.0, 1.0, 0.0) # Green
|
||||
)
|
||||
time.sleep(0.3)
|
||||
|
||||
# Blue box to the right
|
||||
send_entity_add_full(
|
||||
sock, addr,
|
||||
entity_id=1003,
|
||||
name="BlueBox",
|
||||
position=(1.0, 1.5, -2.5),
|
||||
rotation=(0.0, 0.0, 0.0, 1.0),
|
||||
dimensions=(0.5, 0.2, 0.3),
|
||||
color=(0.0, 0.0, 1.0) # Blue
|
||||
)
|
||||
time.sleep(0.3)
|
||||
|
||||
# Yellow tall box in back
|
||||
send_entity_add_full(
|
||||
sock, addr,
|
||||
entity_id=1004,
|
||||
name="YellowPillar",
|
||||
position=(0.0, 1.5, -4.0),
|
||||
rotation=(0.0, 0.0, 0.0, 1.0),
|
||||
dimensions=(0.2, 0.8, 0.2),
|
||||
color=(1.0, 1.0, 0.0) # Yellow
|
||||
)
|
||||
time.sleep(0.3)
|
||||
|
||||
# Cyan rotating cube (45 degrees on Y axis)
|
||||
import math
|
||||
angle = math.radians(45)
|
||||
quat_y = (0.0, math.sin(angle/2), 0.0, math.cos(angle/2))
|
||||
|
||||
send_entity_add_full(
|
||||
sock, addr,
|
||||
entity_id=1005,
|
||||
name="RotatedCube",
|
||||
position=(0.0, 1.0, -1.5),
|
||||
rotation=quat_y,
|
||||
dimensions=(0.25, 0.25, 0.25),
|
||||
color=(0.0, 1.0, 1.0) # Cyan
|
||||
)
|
||||
|
||||
print("\n✓ Scene created with 5 entities\n")
|
||||
|
||||
def animate_scene(sock, addr):
|
||||
"""Animate the entities"""
|
||||
print("\n=== Animating Scene ===\n")
|
||||
|
||||
import math
|
||||
|
||||
for frame in range(60):
|
||||
t = frame / 10.0
|
||||
|
||||
# Move red cube in a circle
|
||||
x = math.sin(t) * 0.5
|
||||
z = -2.0 + math.cos(t) * 0.5
|
||||
send_entity_edit_transform(
|
||||
sock, addr, 1001,
|
||||
position=(x, 1.5, z)
|
||||
)
|
||||
|
||||
# Rotate green sphere
|
||||
angle = t
|
||||
quat = (0.0, math.sin(angle/2), 0.0, math.cos(angle/2))
|
||||
send_entity_edit_transform(
|
||||
sock, addr, 1002,
|
||||
rotation=quat
|
||||
)
|
||||
|
||||
# Scale blue box up and down
|
||||
scale = 0.3 + abs(math.sin(t)) * 0.3
|
||||
send_entity_edit_transform(
|
||||
sock, addr, 1003,
|
||||
dimensions=(scale, 0.2, scale)
|
||||
)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
print("\n✓ Animation complete\n")
|
||||
|
||||
def cleanup_scene(sock, addr):
|
||||
"""Remove all demo entities"""
|
||||
print("\n=== Cleaning Up Scene ===\n")
|
||||
|
||||
for entity_id in [1001, 1002, 1003, 1004, 1005]:
|
||||
send_entity_erase(sock, addr, entity_id)
|
||||
time.sleep(0.2)
|
||||
|
||||
print("\n✓ All entities removed\n")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Enhanced Overte entity injection tool')
|
||||
parser.add_argument('--host', default='127.0.0.1', help='Target host (default: 127.0.0.1)')
|
||||
parser.add_argument('--port', type=int, default=40103, help='Target port (default: 40103)')
|
||||
parser.add_argument('--no-animate', action='store_true', help='Skip animation phase')
|
||||
parser.add_argument('--no-cleanup', action='store_true', help='Keep entities after demo')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
addr = (args.host, args.port)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f" Enhanced Entity Injection Tool")
|
||||
print(f" Target: {args.host}:{args.port}")
|
||||
print(f"{'='*60}")
|
||||
print("\nMake sure stardust-overte-client is running!")
|
||||
print("Waiting 2 seconds before starting...\n")
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
try:
|
||||
# Create demo scene
|
||||
demo_scene(sock, addr)
|
||||
|
||||
if not args.no_animate:
|
||||
time.sleep(1)
|
||||
animate_scene(sock, addr)
|
||||
|
||||
if not args.no_cleanup:
|
||||
time.sleep(1)
|
||||
cleanup_scene(sock, addr)
|
||||
else:
|
||||
print("\n✓ Scene left active (use --no-cleanup to keep entities)\n")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nInterrupted! Cleaning up...")
|
||||
cleanup_scene(sock, addr)
|
||||
|
||||
finally:
|
||||
sock.close()
|
||||
print("Done!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user