M crates/unified/assets/config/parts/chassis.part.toml => crates/unified/assets/config/parts/chassis.part.toml +4 -4
@@ 8,23 8,23 @@ width = 50
height = 50
mass = 100
-[[joints]]
+[[joint]]
id = "Top"
target = { translation = [ 0.0, 50.0, 0.0 ], rotation = 0.0 }
snap = { translation = [ 0.0, 25.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Right"
target = { translation = [ 50.0, 0.0, 0.0 ], rotation = -90.0 }
snap = { translation = [ 25.0, 0.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Bottom"
target = { translation = [ 0.0, -50.0, 0.0 ], rotation = -180.0 }
snap = { translation = [ 0.0, -25.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Left"
target = { translation = [ -50.0, 0.0, 0.0 ], rotation = -270.0 }
snap = { translation = [ -25.0, 0.0, 0.0 ], rotation = 0.0 }=
\ No newline at end of file
M crates/unified/assets/config/parts/hearty.part.toml => crates/unified/assets/config/parts/hearty.part.toml +8 -7
@@ 8,27 8,28 @@ width = 50
height = 50
mass = 100
-[thruster]
-flow_rate = 0.1
-exhaust_speed = 250
+[[thruster]]
+id = "bottom left"
+apply_force_at_local = [ -50.0, -50.0 ]
+thrust_vector = [ 0.0, 25.0 ]
-[[joints]]
+[[joint]]
id = "Top"
target = { translation = [ 0.0, 55.0, 0.0 ], rotation = 0.0 }
snap = { translation = [ 0.0, 25.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Right"
target = { translation = [ 55.0, 0.0, 0.0 ], rotation = -90.0 }
snap = { translation = [ 25.0, 0.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Bottom"
target = { translation = [ 0.0, -55.0, 0.0 ], rotation = -180.0 }
snap = { translation = [ 0.0, -25.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Left"
target = { translation = [ -55.0, 0.0, 0.0 ], rotation = -270.0 }
snap = { translation = [ -25.0, 0.0, 0.0 ], rotation = 0.0 }=
\ No newline at end of file
M crates/unified/assets/config/parts/housing.part.toml => crates/unified/assets/config/parts/housing.part.toml +4 -4
@@ 8,23 8,23 @@ width = 50
height = 50
mass = 50
-[[joints]]
+[[joint]]
id = "Top"
target = { translation = [ 0.0, 55.0, 0.0 ], rotation = 0.0 }
snap = { translation = [ 0.0, 25.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Right"
target = { translation = [ 55.0, 0.0, 0.0 ], rotation = -90.0 }
snap = { translation = [ 25.0, 0.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Bottom"
target = { translation = [ 0.0, -55.0, 0.0 ], rotation = -180.0 }
snap = { translation = [ 0.0, -25.0, 0.0 ], rotation = 0.0 }
-[[joints]]
+[[joint]]
id = "Left"
target = { translation = [ -55.0, 0.0, 0.0 ], rotation = -270.0 }
snap = { translation = [ -25.0, 0.0, 0.0 ], rotation = 0.0 }
M crates/unified/src/config/part.rs => crates/unified/src/config/part.rs +12 -6
@@ 7,7 7,11 @@ use serde::{Deserialize, Serialize};
pub struct PartConfig {
pub part: PartPartConfig,
pub physics: PartPhysicsConfig,
- pub thruster: Option<PartThrusterConfig>,
+ #[serde(rename = "thruster")]
+ #[serde(default)]
+ pub thrusters: Vec<ThrusterConfig>,
+ #[serde(default)]
+ #[serde(rename = "joint")]
pub joints: Vec<JointConfig>,
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
@@ 23,16 27,18 @@ pub struct PartPhysicsConfig {
pub mass: f32,
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
-pub struct PartThrusterConfig {
- pub flow_rate: f32, // kg/s
- pub exhaust_speed: f32, // m/s
-}
-#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
pub struct JointConfig {
pub id: String,
pub target: JointOffset,
pub snap: JointOffset,
}
+#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
+pub struct ThrusterConfig {
+ pub id: String,
+ #[serde(default)]
+ pub apply_force_at_local: Vec2,
+ pub thrust_vector: Vec2,
+}
#[derive(Deserialize, Serialize, Clone, Debug, TypePath, PartialEq, Copy)]
pub struct JointOffset {
pub translation: Vec3,
M crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +2 -0
@@ 1,3 1,5 @@
+pub mod thruster;
+
use crate::config::part::PartConfig;
use bevy::ecs::entity::MapEntities;
use bevy::math::{Quat, Vec2};
A crates/unified/src/ecs/thruster.rs => crates/unified/src/ecs/thruster.rs +44 -0
@@ 0,0 1,44 @@
+use std::ops::Deref;
+use bevy::ecs::entity::MapEntities;
+use bevy::math::Vec2;
+use bevy::prelude::Bundle;
+use serde::{Deserialize, Serialize};
+use crate::prelude::{ChildOf, Component, Entity, Transform};
+
+#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
+pub struct ThrusterId(pub String);
+impl ThrusterId {
+ #[must_use]
+ pub fn from_part_and_thruster_id(part: impl AsRef<str>, thruster: impl AsRef<str>) -> Self {
+ Self(format!("{}:{}", part.as_ref(), thruster.as_ref()))
+ }
+}
+
+#[derive(Component, Serialize, Deserialize, MapEntities)]
+#[relationship_target(relationship = ThrusterOfPart, linked_spawn)]
+pub struct PartThrusters(#[entities] Vec<Entity>);
+impl Deref for PartThrusters {
+ type Target = Vec<Entity>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+#[derive(Component, Serialize, Deserialize, MapEntities)]
+#[relationship(relationship_target = PartThrusters)]
+pub struct ThrusterOfPart(#[entities] pub Entity);
+
+#[derive(Component, Serialize, Deserialize)]
+pub struct Thruster {
+ pub id: ThrusterId,
+ pub thrust_vector: Vec2
+}
+
+#[derive(Bundle)]
+pub struct ThrusterBundle {
+ pub thruster: Thruster,
+ pub transform: Transform,
+ pub child_of: ChildOf,
+ pub thruster_of_part: ThrusterOfPart,
+}<
\ No newline at end of file
M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +1 -0
@@ 5,6 5,7 @@ pub mod planets;
pub mod player;
mod system_sets;
mod world_config;
+mod thruster;
use crate::server::earth_parts::spawn_parts_plugin;
use crate::server::gravity::newtonian_gravity_plugin;
M crates/unified/src/server/part.rs => crates/unified/src/server/part.rs +25 -1
@@ 3,6 3,7 @@ use crate::config::part::{JointConfig, PartConfig};
use crate::ecs::{Part, PartHandle};
use crate::prelude::*;
use bevy_replicon::prelude::Replicated;
+use crate::ecs::thruster::{PartThrusters, Thruster, ThrusterBundle, ThrusterId, ThrusterOfPart};
pub fn part_management_plugin(app: &mut App) {
app.add_systems(PreUpdate, (handle_ready_parts, handle_part_reloading));
@@ 31,12 32,13 @@ fn handle_ready_parts(
.insert(calculate_bundle(strong_config, &loading_part.0))
.remove::<SpawnPartRequest>();
spawn_joints(strong_config, entity, commands.reborrow());
+ spawn_thrusters(strong_config, entity, commands.reborrow());
}
}
}
fn handle_part_reloading(
- existing_parts: Query<(Entity, &PartHandle, &Joints)>,
+ existing_parts: Query<(Entity, &PartHandle, &Joints, &PartThrusters)>,
joints: Query<(&mut Joint, Option<&Peer>, Entity)>,
snaps: Query<(Entity, &SnapOfJoint)>,
assets: Res<Assets<PartConfig>>,
@@ 51,6 53,13 @@ fn handle_part_reloading(
commands
.entity(existing_part.0)
.insert(calculate_bundle(config, &existing_part.1.0));
+
+ // vaporize all thrusters, then respawn
+ for thruster in &**existing_part.3 {
+ commands.entity(*thruster).despawn();
+ }
+ spawn_thrusters(config, existing_part.0, commands.reborrow());
+
// update all joints
let mut used_joints = vec![];
for joint_id in &**existing_part.2 {
@@ 144,3 153,18 @@ fn spawn_joints(config: &PartConfig, parent: Entity, mut commands: Commands) {
commands.spawn(spawn_snap_bundle(joint, &parent, &joint_id));
}
}
+
+fn spawn_thrusters(config: &PartConfig, part: Entity, mut commands: Commands) {
+ for thruster in &config.thrusters {
+ commands
+ .spawn(ThrusterBundle {
+ thruster: Thruster {
+ id: ThrusterId::from_part_and_thruster_id(&config.part.name, &thruster.id),
+ thrust_vector: thruster.thrust_vector,
+ },
+ transform: Transform::from_translation(Vec3::new(thruster.apply_force_at_local.x, thruster.apply_force_at_local.y, 0.0)),
+ child_of: ChildOf(part),
+ thruster_of_part: ThrusterOfPart(part),
+ });
+ }
+}
A crates/unified/src/server/thruster/mod.rs => crates/unified/src/server/thruster/mod.rs +3 -0
@@ 0,0 1,3 @@
+//! # Thruster solver
+//! Given a ship and the desired target vector, solve for the combination of thrusters
+//! that will produce as close to the desired target as possible.<
\ No newline at end of file