diff --git a/Cargo.lock b/Cargo.lock index c6ba81a..6afe16d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4693,6 +4693,15 @@ dependencies = [ [[package]] name = "orthros-client" version = "0.1.0" +dependencies = [ + "bevy", + "bevy_matchbox", + "orthros_network", + "rand 0.9.2", + "rmp-serde", + "serde", + "statrs", +] [[package]] name = "orthros-server" diff --git a/orthros-client/Cargo.toml b/orthros-client/Cargo.toml index 4f6e00d..279b1dd 100644 --- a/orthros-client/Cargo.toml +++ b/orthros-client/Cargo.toml @@ -4,3 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] +bevy = "0.17" +rand = "0.9.1" +# rusqlite = "0.37.0" +statrs = "0.18.0" +orthros_network = {path = "../orthros-network"} +bevy_matchbox = {version = "0.13", features= ["signaling"]} +serde = { version = "1.0", features = ["derive"] } + +rmp-serde = "1.3.0" diff --git a/orthros-server/assets/fonts/quantum/quantfh.ttf b/orthros-client/assets/fonts/quantum/quantfh.ttf similarity index 100% rename from orthros-server/assets/fonts/quantum/quantfh.ttf rename to orthros-client/assets/fonts/quantum/quantfh.ttf diff --git a/orthros-server/assets/fonts/quantum/quantflt.ttf b/orthros-client/assets/fonts/quantum/quantflt.ttf similarity index 100% rename from orthros-server/assets/fonts/quantum/quantflt.ttf rename to orthros-client/assets/fonts/quantum/quantflt.ttf diff --git a/orthros-server/assets/fonts/quantum/quantrh.ttf b/orthros-client/assets/fonts/quantum/quantrh.ttf similarity index 100% rename from orthros-server/assets/fonts/quantum/quantrh.ttf rename to orthros-client/assets/fonts/quantum/quantrh.ttf diff --git a/orthros-server/assets/fonts/quantum/quantrnd.ttf b/orthros-client/assets/fonts/quantum/quantrnd.ttf similarity index 100% rename from orthros-server/assets/fonts/quantum/quantrnd.ttf rename to orthros-client/assets/fonts/quantum/quantrnd.ttf diff --git a/orthros-server/assets/fonts/quantum/quanttap.ttf b/orthros-client/assets/fonts/quantum/quanttap.ttf similarity index 100% rename from orthros-server/assets/fonts/quantum/quanttap.ttf rename to orthros-client/assets/fonts/quantum/quanttap.ttf diff --git a/orthros-server/assets/fonts/quantum/quantum.txt b/orthros-client/assets/fonts/quantum/quantum.txt similarity index 100% rename from orthros-server/assets/fonts/quantum/quantum.txt rename to orthros-client/assets/fonts/quantum/quantum.txt diff --git a/orthros-server/assets/shaders/g-type-star.wgsl b/orthros-client/assets/shaders/g-type-star.wgsl similarity index 100% rename from orthros-server/assets/shaders/g-type-star.wgsl rename to orthros-client/assets/shaders/g-type-star.wgsl diff --git a/orthros-client/src/main.rs b/orthros-client/src/main.rs index e7a11a9..b6ffa1f 100644 --- a/orthros-client/src/main.rs +++ b/orthros-client/src/main.rs @@ -1,3 +1,292 @@ +use bevy::{ + asset::RenderAssetUsages, + camera::RenderTarget, + color::palettes::css::*, + core_pipeline::tonemapping::Tonemapping, + pbr::wireframe::{Wireframe, WireframePlugin}, + post_process::bloom::Bloom, + prelude::*, + render::{render_resource::*, view::Hdr}, +}; +use bevy_matchbox::MatchboxSocket; +use orthros_network::{ + generic::{EntityID, NetworkObject}, + starchart::star::{StarClass, StarInfo}, +}; +use rmp_serde::Deserializer; +use serde::Deserialize; +use std::f32::consts::PI; +const CHANNEL_ID: usize = 0; fn main() { - println!("Hello, world!"); + App::new() + .add_plugins((DefaultPlugins, WireframePlugin::default())) + .add_systems(Startup, (setup_scene, start_socket)) + .add_systems(Update, (receive_messages, update_stars)) + .run(); +} +fn create_star_system_annotation( + commands: &mut Commands, + asset_server: &Res, + images: &mut ResMut>, + meshes: &mut ResMut>, + materials: &mut ResMut>, + star_info: &StarInfo, +) -> (Handle, Handle) { + let size = Extent3d { + width: 1024, + height: 1024, + ..default() + }; + + // This is the texture that will be rendered to. + let mut image = Image::new_fill( + size, + TextureDimension::D2, + &[0, 0, 0, 0], + TextureFormat::Bgra8UnormSrgb, + RenderAssetUsages::default(), + ); + // You need to set these texture usage flags in order to use the image as a render target + image.texture_descriptor.usage = + TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT; + + let image_handle = images.add(image); + + // Light + + let texture_camera = commands + .spawn(( + Camera2d, + Camera { + target: RenderTarget::Image(image_handle.clone().into()), + clear_color: ClearColorConfig::None, + ..default() + }, + )) + .id(); + let border = UiRect::all(Val::Px(40.)); + let non_zero = |x, y| x != Val::Px(0.) && y != Val::Px(0.); + let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. }; + commands + .spawn(( + Node { + // Cover the whole image + width: Val::Percent(100.), + height: Val::Percent(100.), + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + BackgroundColor(Color::Srgba(Srgba { + red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 0.0, + })), + Text::new(format!("{}", star_info.name)), + TextFont { + font: asset_server.load("fonts/quantum/quantflt.ttf"), + // This font is loaded and will be used instead of the default font. + font_size: 60.0, + ..default() + }, + TextShadow::default(), + // Set the justification of the Text + TextLayout::new_with_justify(Justify::Center), + UiTargetCamera(texture_camera), + )) + .with_children(|parent| { + // parent.spawn(( + // // Accepts a `String` or any type that converts into a `String`, such as `&str` + // Text::new("hello\nbevy!"), + // TextFont { + // // This font is loaded and will be used instead of the default font. + // font_size: 60.0, + // ..default() + // }, + // TextShadow::default(), + // // Set the justification of the Text + // TextLayout::new_with_justify(JustifyText::Center), + // // Set the style of the Node itself. + // Node { + // // position_type: PositionType::Relative, + // align_self: AlignSelf::FlexStart, + + // // top: Val::Px(5.0), + // // right: Val::Px(5.0), + // ..default() + // }, + // )); + parent.spawn(( + Node { + width: Val::Px(400.), + height: Val::Px(400.), + border, + margin: UiRect::AUTO, + align_self: AlignSelf::Center, + // align_items: AlignItems::Center, + // justify_content: JustifyContent::Center, + // align_content: AlignContent::Center, + ..default() + }, + BackgroundColor(Color::Srgba(Srgba { + red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 0.0, + })), + BorderColor::all(RED), + BorderRadius::px( + border_size(border.left, border.top), + border_size(border.right, border.top), + border_size(border.right, border.bottom), + border_size(border.left, border.bottom), + ), + // Outline { + // width: Val::Px(6.), + // offset: Val::Px(6.), + // color: Color::WHITE, + // }, + )); + }); + let material_handle = materials.add(StandardMaterial { + // base_color: Color::srgba(1.0, 1.0, 1.0, 0.0), + base_color_texture: Some(image_handle), + unlit: true, + alpha_mode: AlphaMode::Blend, + + ..default() + }); + let aspect = 1.0; + + // create a new quad mesh. this is what we will apply the texture to + let quad_width = 10.0; + let quad_handle = meshes.add(Rectangle::new(quad_width, quad_width * aspect)); + return (quad_handle, material_handle); +} +fn setup_scene(mut commands: Commands) { + commands.spawn(( + Camera3d::default(), + Camera { + clear_color: ClearColorConfig::Custom(Color::from(BLACK)), + ..default() + }, + Hdr, + Tonemapping::Reinhard, // 2. Using a tonemapper that desaturates to white is recommended + Transform::from_xyz(0.0, 10.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), + Bloom::NATURAL, // 3. Enable bloom for the camera + )); +} + +fn star_material( + star_class: &StarClass, + materials: &mut ResMut>, +) -> Handle { + match star_class { + StarClass::MType => materials.add(StandardMaterial { + emissive: LinearRgba::rgb(1000.0, 710.0 / 2.0, 424.0 / 2.0), // 4. Put something bright in a dark environment to see the effect + ..default() + }), + StarClass::BType => materials.add(StandardMaterial { + emissive: LinearRgba::rgb(635.0 / 2.0, 753.0 / 2.0, 1000.0), // 4. Put something bright in a dark environment to see the effect + ..default() + }), + StarClass::GType => materials.add(StandardMaterial { + emissive: LinearRgba::rgb(1000.0 / 2.0, 929.0 / 2.0, 890.0), // 4. Put something bright in a dark environment to see the effect + ..default() + }), + } +} +fn spawn_star( + mut commands: &mut Commands, + star_info: &StarInfo, + mut meshes: &mut ResMut>, + mut materials: &mut ResMut>, + asset_server: &Res, + mut images: &mut ResMut>, +) { + let mesh = meshes.add(Sphere::new(star_info.diameter).mesh().ico(5).unwrap()); + let material = star_material(&star_info.class, &mut materials); + + let (quad_handle, material_handle) = create_star_system_annotation( + &mut commands, + &asset_server, + &mut images, + &mut meshes, + &mut materials, + &star_info, + ); + commands + .spawn(( + star_info.id.clone(), + Mesh3d(mesh.clone()), + MeshMaterial3d(material.clone()), + star_info.transform, + Wireframe, + )) + .with_children(|parent| { + parent.spawn(( + Mesh3d(quad_handle), + MeshMaterial3d(material_handle.clone()), + Transform::from_xyz(0.0, 0.0, 0.0) + .with_scale(Vec3::splat(0.18)) + // .with_scale(Vec3::splat(scale)) + .with_rotation(Quat::from_euler(EulerRot::XYZ, -PI / 2.0, 0.0, PI / 2.0)), + // Wireframe, + )); + }); +} +fn start_socket(mut commands: Commands) { + let socket = MatchboxSocket::new_reliable("ws://localhost:3536/hello"); + info!("Created socket"); + commands.insert_resource(socket); +} +fn update_stars( + mut commands: Commands, + network_stars: Query<(Entity, &StarInfo)>, + spawned_stars: Query<(Entity, &EntityID)>, + mut meshes: ResMut>, + mut materials: ResMut>, + asset_server: Res, + mut images: ResMut>, +) { + for (id, network_star) in network_stars { + if let Some((entity, _)) = spawned_stars + .iter() + .filter(|(_, spawned_id)| **spawned_id == network_star.id) + .last() + { + // let entity_command = commands.get_entity(entity).unwrap(); + } else { + spawn_star( + &mut commands, + network_star, + &mut meshes, + &mut materials, + &asset_server, + &mut images, + ); + } + // + } +} +fn receive_messages(mut socket: ResMut, mut commands: Commands) { + for (peer, state) in socket.update_peers() { + info!("{peer}: {state:?}"); + } + + for (_id, message) in socket.channel_mut(CHANNEL_ID).receive() { + match NetworkObject::deserialize(&mut Deserializer::new(&*message)) { + Ok(message) => { + info!("Received message: {message:?}"); + match message { + NetworkObject::Star(star_info) => { + commands.spawn(star_info); + } + } + } + Err(e) => error!("Failed to convert message to string: {e}"), + } + } } diff --git a/orthros-network/src/generic/mod.rs b/orthros-network/src/generic/mod.rs index 78907c2..ede0b1c 100644 --- a/orthros-network/src/generic/mod.rs +++ b/orthros-network/src/generic/mod.rs @@ -1,9 +1,9 @@ use crate::starchart::star::StarInfo; use bevy::ecs::component::Component; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Component, PartialEq, Clone)] pub struct EntityID(pub u32); #[derive(Serialize, Deserialize, Debug, Component)] -pub enum ObjectUpdate { +pub enum NetworkObject { Star(StarInfo), } diff --git a/orthros-network/src/lib.rs b/orthros-network/src/lib.rs index 23962af..13f315e 100644 --- a/orthros-network/src/lib.rs +++ b/orthros-network/src/lib.rs @@ -1,3 +1,3 @@ pub mod generic; pub mod starchart; -pub const SCALEAU: f32 = 1.0; +pub const SCALEAU: f32 = 1000000.0; diff --git a/orthros-network/src/starchart/star.rs b/orthros-network/src/starchart/star.rs index 7f6f33c..3d2c642 100644 --- a/orthros-network/src/starchart/star.rs +++ b/orthros-network/src/starchart/star.rs @@ -6,13 +6,13 @@ use serde::{Deserialize, Serialize}; fn solar_radius_to_ly_scaled(r: f32) -> f32 { r * SOLARRADIUSTOLY * SCALEAU } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum StarClass { MType, BType, GType, } -#[derive(Serialize, Deserialize, Debug, Component)] +#[derive(Serialize, Deserialize, Debug, Component, Clone)] pub struct StarInfo { pub id: EntityID, pub name: String, diff --git a/orthros-server/src/main.rs b/orthros-server/src/main.rs index 4e2f68b..73edc32 100644 --- a/orthros-server/src/main.rs +++ b/orthros-server/src/main.rs @@ -6,7 +6,7 @@ use bevy::{ }; use bevy_matchbox::{matchbox_signaling::SignalingServer, prelude::*}; use orthros_network::{ - generic::EntityID, + generic::{EntityID, NetworkObject}, starchart::star::{STARS, StarClass, StarInfo, star_diameter_range}, }; use rand::{ @@ -14,15 +14,16 @@ use rand::{ distr::{Distribution, Uniform}, rng, }; +use rmp_serde::{Deserializer, Serializer}; +use serde::{Deserialize, Serialize}; use std::{ collections::HashSet, net::{Ipv4Addr, SocketAddrV4}, time::Duration, }; + const CHANNEL_ID: usize = 0; #[derive(Component)] -struct NetworkObject; -#[derive(Component)] struct Changed; fn main() { App::new() @@ -44,10 +45,7 @@ fn main() { ) .add_systems( Update, - ( - receive_messages, - send_message.run_if(on_timer(Duration::from_secs(5))), - ), + (send_message.run_if(on_timer(Duration::from_secs(5))),), ) .run(); } @@ -74,10 +72,10 @@ fn start_signaling_server(mut commands: Commands) { fn setup_scene(mut commands: Commands, mut socket: ResMut) { let mut spawned_ids: HashSet = HashSet::new(); - for _ in 0..4 { - let x = Uniform::try_from(-2000..2000).unwrap().sample(&mut rng()); - let z = Uniform::try_from(-8000..8000).unwrap().sample(&mut rng()); - let star_class: StarClass = match rng().random_range(0..2) { + for _ in 0..10 { + let x = Uniform::try_from(-200..200).unwrap().sample(&mut rng()); + let z = Uniform::try_from(-800..800).unwrap().sample(&mut rng()); + let star_class: StarClass = match rng().random_range(0..3) { 0 => StarClass::MType, 1 => StarClass::BType, 2 => StarClass::GType, @@ -97,7 +95,7 @@ fn setup_scene(mut commands: Commands, mut socket: ResMut) { transform: Transform::from_xyz(x as f32 / 100.0, z as f32 / 20000.0, z as f32 / 100.0), }; - commands.spawn((StarInfoComp(star_info), NetworkObject, Changed)); + commands.spawn((NetworkObject::Star(star_info), Changed)); let peers: Vec<_> = socket.connected_peers().collect(); for peer in peers { @@ -109,34 +107,27 @@ fn setup_scene(mut commands: Commands, mut socket: ResMut) { } } } -fn on_client_connect(mut socket: ResMut, Query<&StarInfo>) fn start_socket(mut commands: Commands) { let socket = MatchboxSocket::new_reliable("ws://localhost:3536/hello"); info!("Created socket"); commands.insert_resource(socket); } -fn send_message(mut socket: ResMut) { - let peers: Vec<_> = socket.connected_peers().collect(); - - for peer in peers { - let message = "Hello"; - info!("Sending message: {message:?} to {peer}"); - socket - .channel_mut(CHANNEL_ID) - .send(message.as_bytes().into(), peer); - } -} - -fn receive_messages(mut socket: ResMut) { +fn send_message(mut socket: ResMut, network_objects: Query<&NetworkObject>) { for (peer, state) in socket.update_peers() { info!("{peer}: {state:?}"); } - for (_id, message) in socket.channel_mut(CHANNEL_ID).receive() { - match std::str::from_utf8(&message) { - Ok(message) => info!("Received message: {message:?}"), - Err(e) => error!("Failed to convert message to string: {e}"), + let peers: Vec<_> = socket.connected_peers().collect(); + + for peer in peers { + for network_object in network_objects.iter() { + let mut ser_buf: Vec = Vec::new(); + network_object.serialize(&mut Serializer::new(&mut ser_buf)); + info!("Sending message: {ser_buf:?} to {peer}"); + socket + .channel_mut(CHANNEL_ID) + .send(ser_buf.as_slice().into(), peer); } } }