Compare commits

...

35 Commits

Author SHA1 Message Date
MayaTheShy
4e8f584949 fix: replace model rendering with wireframe representation for all entity types in BridgeState 2025-11-08 21:11:30 -05:00
MayaTheShy
887a1be3d8 fix: update model part names for consistency in BridgeState reification 2025-11-08 21:08:36 -05:00
MayaTheShy
083e36c4e3 fix: add debug logging for shared state synchronization in ClientState 2025-11-08 21:06:34 -05:00
MayaTheShy
e1aeaa59a1 fix: add debug logging for frame processing in event loop 2025-11-08 21:05:58 -05:00
MayaTheShy
fa94eae719 fix: add debug logging for frame processing in event loop 2025-11-08 21:05:48 -05:00
MayaTheShy
7650ef3cd9 fix: add debug logging for node reification process in BridgeState 2025-11-08 21:05:10 -05:00
MayaTheShy
958ddd5981 fix: simplify node rendering by replacing wireframe with solid models for Box and Sphere entities 2025-11-08 21:02:42 -05:00
MayaTheShy
15cc6d9476 fix: update stardust-xr-fusion and stardust-xr-molecules dependencies to use git sources 2025-11-08 21:01:38 -05:00
MayaTheShy
07c2391a50 fix: add missing imports for MaterialParameter and ResourceID from stardust_xr_fusion 2025-11-08 21:01:31 -05:00
MayaTheShy
8d82f51596 fix: update imports and use correct types from stardust_xr_fusion for rendering 2025-11-08 21:01:26 -05:00
MayaTheShy
d42e15e9f6 fix: add warning for null function pointer in setNodeColor 2025-11-08 20:50:22 -05:00
MayaTheShy
16f7a83d4d fix: correct tuple wrapping in Reify implementation for node rendering 2025-11-08 20:47:46 -05:00
MayaTheShy
9e39c9cfc9 fix: correct tuple wrapping in Reify implementation for line rendering 2025-11-08 20:47:34 -05:00
MayaTheShy
d5b89171ee fix: correct tuple wrapping in Reify implementation for line rendering 2025-11-08 20:47:27 -05:00
MayaTheShy
38a561e581 feat: filter out nodes with zero dimensions in reify implementation to improve rendering efficiency 2025-11-08 20:47:17 -05:00
MayaTheShy
863a8bc162 feat: initialize root node with type 0 and zero dimensions to prevent rendering 2025-11-08 20:47:00 -05:00
MayaTheShy
f065e1cdeb feat: add entity rendering enhancements with visual properties and improved packet parsing 2025-11-08 20:44:42 -05:00
MayaTheShy
a43ba28d20 feat: re-export connect_client from the correct version to avoid version conflicts 2025-11-08 20:43:07 -05:00
MayaTheShy
e4e0238640 feat: refactor color handling in Reify implementation to use rgba_linear macro from asteroids 2025-11-08 20:42:52 -05:00
MayaTheShy
15855db838 feat: refactor frame info handling to avoid version conflicts with drawable types 2025-11-08 20:42:00 -05:00
MayaTheShy
d070ad384a feat: update dependencies for stardust-xr packages and add new versions 2025-11-08 20:41:55 -05:00
MayaTheShy
1f5607aaa1 feat: update visual properties for newly created and existing nodes in SceneSync 2025-11-08 20:41:05 -05:00
MayaTheShy
8f8e9208e4 feat: add function pointers for setting node model, texture, color, dimensions, and entity type 2025-11-08 20:40:30 -05:00
MayaTheShy
103fbd32d8 feat: implement functions to set node model, texture, color, dimensions, and entity type 2025-11-08 20:40:13 -05:00
MayaTheShy
70c2c97f9a feat: add function pointers for setting node model, texture, color, dimensions, and entity type 2025-11-08 20:39:57 -05:00
MayaTheShy
0b1c381071 feat: add functions to set visual properties for nodes including model, texture, color, dimensions, and entity type 2025-11-08 20:39:52 -05:00
MayaTheShy
a5fcf3e90f feat: enhance entity visualization with dynamic rendering based on entity type and dimensions 2025-11-08 20:39:19 -05:00
MayaTheShy
eea426e55c feat: add functions to set node model, texture, color, dimensions, and entity type 2025-11-08 20:38:37 -05:00
MayaTheShy
57c1239675 feat: enhance node creation and manipulation with additional properties and commands 2025-11-08 20:38:20 -05:00
MayaTheShy
f47961a486 feat: extend Node structure with entity properties for enhanced manipulation 2025-11-08 20:37:55 -05:00
MayaTheShy
be51027c26 feat: add additional commands for entity manipulation in the bridge 2025-11-08 20:37:48 -05:00
MayaTheShy
419bd90049 feat: enhance simulation mode with diverse demo entities and improved entity parsing 2025-11-08 20:37:25 -05:00
MayaTheShy
e1474170a5 feat: add visual properties to OverteEntity structure 2025-11-08 20:36:34 -05:00
MayaTheShy
752f1d95a1 feat: enhance entity packet parsing with full property support 2025-11-08 20:16:32 -05:00
MayaTheShy
f0a611c48d feat: add enhanced entity injection script with full property support 2025-11-08 20:14:06 -05:00
10 changed files with 1036 additions and 50 deletions

View 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
View File

@@ -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",

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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
View 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()