Repository navigation
N bodies model #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
N bodies model #32
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
94a26bb
Implement basic window and setup visualization structure
C-JeanDev 16dbc13
git ignore
C-JeanDev aec3460
test
C-JeanDev 6769480
complete project implementation
C-JeanDev e812a46
gif size reduction
C-JeanDev d0f2965
Update README.md
C-JeanDev 6a99c10
Delete n_bodies/target directory
C-JeanDev 6d483f2
Addressed review comments
C-JeanDev 0b52e85
gif size reduction
C-JeanDev 4265121
format code with rustfmt
C-JeanDev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| /target | ||
| Cargo.lock | ||
| .idea | ||
| /output |
|
C-JeanDev marked this conversation as resolved.
Resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [package] | ||
| name = "n_bodies" | ||
| version = "0.1.0" | ||
| authors = [ | ||
| "Jean Claude Coppola <[email protected]>" | ||
| ] | ||
| edition = "2021" | ||
| license = "MIT" | ||
| readme = "README.md" | ||
|
|
||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
|
||
| [dependencies] | ||
| krabmaga = "0.6.*" | ||
| bevy_egui = { version = "0.39", optional = true } | ||
|
|
||
| [features] | ||
| visualization = ["krabmaga/visualization", "bevy_egui"] | ||
| visualization_wasm = ["krabmaga/visualization_wasm", "bevy_egui"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| [env] | ||
| ENV_DIR = {source="${CARGO_MAKE_PROFILE}", default_value = "debug", mapping = {release = "release"}} | ||
| CARGO_TARGET_DIR = {value = "target", condition = {env_not_set = ["CARGO_TARGET_DIR"]}} | ||
| CARGO_WASM_PATH = "${CARGO_TARGET_DIR}/wasm32-unknown-unknown/${ENV_DIR}/${CARGO_MAKE_CRATE_NAME}.wasm" | ||
| CARGO_PROFILE = "dev" | ||
| TARGET_DIR = "target" | ||
| CARGO_MANIFEST_DIR = "${CARGO_MAKE_WORKING_DIRECTORY}" | ||
|
|
||
| [env.release] | ||
| CARGO_RELEASE_ARGS = "--release" | ||
|
|
||
| [tasks.show-env] | ||
| command = "env" | ||
|
|
||
| [tasks.install-target-wasm32-unknown-unknown] | ||
| args = ["target", "install", "wasm32-unknown-unknown"] | ||
|
|
||
| command = "rustup" | ||
| [tasks.basic-http-server] | ||
| install_crate = {crate_name = "basic-http-server", binary = "basic-http-server", test_arg="--help"} | ||
|
|
||
| [tasks.wasm-bindgen-cli] | ||
| install_crate = {crate_name = "wasm-bindgen-cli", binary = "wasm-bindgen", test_arg="--help"} | ||
|
|
||
| [tasks.cargo-build-web] | ||
| args = ["build", "--target", "wasm32-unknown-unknown", "--features", "visualization_wasm", "@@split(CARGO_RELEASE_ARGS, )"] | ||
| command = "cargo" | ||
| dependencies = ["install-target-wasm32-unknown-unknown"] | ||
|
|
||
| [tasks.build-web] | ||
| args = ["--out-dir", "${TARGET_DIR}", "--out-name", "wasm", "--target", "web", "--no-typescript", "${CARGO_WASM_PATH}"] | ||
| command = "wasm-bindgen" | ||
| dependencies = ["cargo-build-web", "wasm-bindgen-cli"] | ||
|
|
||
| [tasks.build-native] | ||
| args = ["build", "--features", "visualization", "@@split(CARGO_RELEASE_ARGS, )"] | ||
| command = "cargo" | ||
|
|
||
| [tasks.run] | ||
| command = "${CARGO_TARGET_DIR}/${ENV_DIR}/${CARGO_MAKE_BINARY_EXECUTABLE_NAME}" | ||
| dependencies = ["build-native"] | ||
|
|
||
| [tasks.serve] | ||
| command = "basic-http-server" | ||
| args = ["-x"] | ||
| dependencies = ["build-web", "basic-http-server"] | ||
|
|
||
| [tasks.test] | ||
| disabled = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| # N-Bodies | ||
|
|
||
| A 2D gravitational N-body simulation written in Rust with the | ||
| [krABMaga](https://github.com/krABMaga/krABMaga) agent-based modeling framework | ||
| (Bevy-based visualization). | ||
| [N-Bodies](https://www.netlogoweb.org/launch#http://ccl.northwestern.edu/netlogo/models/models/Sample%20Models/Chemistry%20&%20Physics/Mechanics/Unverified/N-Bodies.nlogox) | ||
| model. | ||
|
|
||
|  | ||
|  | ||
|  | ||
|
|
||
| ## Features | ||
|
|
||
| - Every body is a krABMaga **agent** attracted by all the others (Newtonian gravity, O(N²)). | ||
| - **Trails**: the path of the first *N* bodies is drawn as fading polylines | ||
| (Bevy gizmos, no extra sprites), colored like the body they belong to. | ||
| - **Control window** (egui) to change the parameters live or regenerate the system. | ||
| - Four **initial configurations**: star with circular orbits, stable two-planet | ||
| system, random with zero momentum, symmetrical (zero-sum). | ||
| - Optional **keep centered** mode that pins the center of mass to the middle of the view. | ||
| - Negative `G` turns gravity into a repulsive force. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - Rust (stable) with `cargo` | ||
| - The system libraries Bevy needs on your platform | ||
| (see the [Bevy setup guide](https://bevy.org/learn/quick-start/getting-started/setup/)) | ||
|
|
||
| ## Run | ||
|
|
||
| ```bash | ||
| # Visualization (window with the simulation and the menus) | ||
| cargo run --release --features visualization | ||
|
|
||
| # Headless (no window, runs 1000 steps) | ||
| cargo run --release | ||
| ``` | ||
| Press **▶ Start** in the left panel to begin. | ||
|
|
||
| `Cargo.toml` needs the egui dependency used by the menu: | ||
|
|
||
| ```toml | ||
| [dependencies] | ||
| krabmaga = "0.6" | ||
| bevy_egui = { version = "0.39", optional = true } | ||
|
|
||
| [features] | ||
| visualization = ["krabmaga/visualization", "bevy_egui"] | ||
| visualization_wasm = ["krabmaga/visualization_wasm", "bevy_egui"] | ||
| ``` | ||
|
|
||
| Use the same `bevy_egui` version as krABMaga (check with | ||
| `cargo tree --features visualization -i bevy_egui`: only one version must appear). | ||
|
|
||
| ## How it works | ||
|
|
||
| **Physics.** For each body *i* the acceleration is | ||
|
|
||
| ``` | ||
| a_i = Σ_j G · m_j · (r_j − r_i) / (|r_j − r_i|² + ε²)^(3/2) | ||
| ``` | ||
|
|
||
| where `ε` is the *softening* length, which avoids huge forces in close | ||
| encounters. Integration uses the semi-implicit (symplectic) Euler method: | ||
| `v ← v + a·dt`, then `x ← x + v·dt` with the new `v`. | ||
|
|
||
| **Synchronous update.** All the physical data live in `NBodyState` | ||
| (single source of truth). During a step each `Body` agent reads the snapshot | ||
| `state.bodies` and writes its new data in `state.next`; in `after_step` the | ||
| state copies `next` into `bodies`. Every body therefore sees the same instant, | ||
| independently of the agents' execution order. The same hook recenters the | ||
| system (if enabled) and records the trail points. | ||
|
|
||
| **Rendering.** Sprites read their position and size from the state every | ||
| frame. The planets are tinted with a per-body color (`body_color`), and the | ||
| trail of a body uses the same color with a fading alpha. | ||
|
|
||
| ## Menu | ||
|
|
||
| The default krABMaga panel (left) provides Start / Pause / Stop and the steps | ||
| per second. **Stop** regenerates the system using the current parameters. | ||
| The floating **N-Bodies** window adds: | ||
|
|
||
| | Parameter | Effect | Applied | | ||
| |---|---|---| | ||
| | Configuration | Initial layout (see below) | Setup | | ||
| | Number of bodies | Total number of bodies (2–300) | Setup | | ||
| | Central mass | Mass of the star (or mass scale in random setups) | Setup | | ||
| | Max planet mass | Upper bound of planet masses | Setup | | ||
| | Initial speed | Maximum initial speed in random setups | Setup | | ||
| | Bodies with trail | How many bodies get a trail | Setup | | ||
| | G | Gravitational constant (negative = repulsive) | Live | | ||
| | dt | Integration time step | Live | | ||
| | Softening | Smoothing length ε of the force | Live | | ||
| | Keep centered | Keep the center of mass in the middle of the view | Live | | ||
| | Show trails / Trail length | Toggle and length of the trails | Live | | ||
|
|
||
| The window also shows how many bodies are currently **outside the view**. | ||
| After pressing **Setup** the simulation is paused: press ▶ Start again. | ||
|
|
||
| ### Configurations | ||
|
|
||
| | Name | Description | | ||
| |---|---| | ||
| | Star + circular orbits | Heavy star at the center, planets on a spiral (golden angle) with circular-orbit speed | | ||
| | Two planets (stable) | Star and two planets on opposite sides, at different radii | | ||
| | Random (zero momentum) | Random positions, velocities and masses; net momentum removed so the system doesn't drift | | ||
| | Symmetrical (zero-sum) | Half of the bodies are random, the other half are their mirror image (opposite position and velocity) | | ||
|
|
||
| ## Tips | ||
|
|
||
| - Bodies that "escape" are usually the effect of close encounters: increase | ||
| *Softening*, decrease *dt*, or lower *Max planet mass*. | ||
| - If nothing seems to move, check that the simulation is started and try a | ||
| larger `G` or `dt`. | ||
| - The simulation area is `980 × 720`: Bevy's default window is 1280 × 720 and | ||
| krABMaga reserves 300 px on the left for its panel, so this keeps the scale | ||
| 1:1. If you resize the window the scene is stretched accordingly. | ||
| - Sprites: `Emoji("sun")` and `Emoji("planet")` are loaded from | ||
| `assets/emojis/sun.png` and `assets/emojis/planet.png`. If you change the | ||
| image size, update `SPRITE_PX` in `visualization/body_vis.rs`. | ||
| - The default values (`G = 100`, `dt = 0.05`, softening `5`) are defined in | ||
| `Params::default()` in `model/state.rs`. | ||
|
|
||
| ## References | ||
| - http://ccl.northwestern.edu/netlogo/models/N-Bodies |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"/> | ||
| <style> | ||
| body { | ||
| background: linear-gradient( | ||
| 135deg, | ||
| white 0%, | ||
| white 49%, | ||
| black 49%, | ||
| black 51%, | ||
| white 51%, | ||
| white 100% | ||
| ) repeat; | ||
| background-size: 20px 20px; | ||
| } | ||
| canvas { | ||
| background-color: white; | ||
| } | ||
| </style> | ||
| </head> | ||
| <script type="module"> | ||
| import init from './target/wasm.js' | ||
| init() | ||
| </script> | ||
| </html> |
|
C-JeanDev marked this conversation as resolved.
Resolved
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| mod model; | ||
| #[cfg(any(feature = "visualization", feature = "visualization_wasm"))] | ||
| mod visualization; | ||
|
|
||
| // Simulation area. The default window of Bevy is 1280x720 and krABMaga | ||
| // reserves 300 px on the left for its panel, so (1280-300) x 720 gives a | ||
| // 1:1 scale (circles stay circles). | ||
| const DIM: (f32, f32) = (980.0, 720.0); | ||
|
|
||
| #[cfg(not(any(feature = "visualization", feature = "visualization_wasm")))] | ||
| use krabmaga::*; | ||
|
|
||
| #[cfg(not(any(feature = "visualization", feature = "visualization_wasm")))] | ||
| fn main() { | ||
| use crate::model::state::{NBodyState, SetupMode}; | ||
|
|
||
| let mut state = NBodyState::new(DIM); // starts from Params::default() | ||
|
|
||
| // Change whatever you want here, like the menu does in the window... | ||
| state.p.mode = SetupMode::Random; | ||
| state.p.number = 100; | ||
| state.p.g = 100.0; | ||
| state.p.dt = 0.05; | ||
|
|
||
| // ...then regenerate the bodies: setup() reads the parameters above. | ||
| state.setup(); | ||
|
|
||
| // simulate!(state, steps, repetitions) | ||
| simulate!(state, 1000, 1); | ||
| } | ||
|
|
||
| #[cfg(any(feature = "visualization", feature = "visualization_wasm"))] | ||
| fn main() { | ||
| use bevy_egui::EguiPrimaryContextPass; | ||
| use krabmaga::bevy::prelude::{Color, Update}; | ||
| use krabmaga::visualization::visualization::Visualization; | ||
|
|
||
| use crate::model::state::NBodyState; | ||
| use crate::visualization::{menu::menu_system, trails::draw_trails, vis_state::NBodyVis}; | ||
|
|
||
| let state = NBodyState::new(DIM); | ||
|
|
||
| // `setup` (instead of `start`) returns the Bevy App, so we can add our own systems. | ||
| let mut app = Visualization::default() | ||
| .with_window_dimensions(1280., 720.) | ||
| .with_simulation_dimensions(DIM.0, DIM.1) | ||
| .with_background_color(Color::srgb(0.02, 0.02, 0.08)) | ||
| .with_name("N-Bodies") | ||
| .setup::<NBodyVis, NBodyState>(NBodyVis, state); | ||
|
|
||
| app.add_systems(Update, draw_trails) | ||
| .add_systems(EguiPrimaryContextPass, menu_system); | ||
|
|
||
| app.run(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| use krabmaga::engine::agent::Agent; | ||
| use krabmaga::engine::location::Real2D; | ||
| use krabmaga::engine::state::State; | ||
|
|
||
| use crate::model::state::NBodyState; | ||
|
|
||
| //Physical Data of one body -> it lives in the state so that it can be modified and read by the menu and visualization | ||
| #[derive(Clone, Copy)] | ||
| pub struct BodyData { | ||
| pub mass: f32, | ||
| pub radius: f32, | ||
| pub pos: Real2D, | ||
| pub vel: Real2D, | ||
| pub is_star: bool, | ||
| } | ||
|
|
||
| /// The agent only knows its index: it reads the snapshot `state.bodies` | ||
| /// (positions at the beginning of the step) and writes its new data into | ||
| /// `state.next`. `NBodyState::after_step` then publishes `next` -> `bodies`, | ||
| /// so every body sees the same instant (synchronous update). | ||
| #[derive(Clone, Copy)] | ||
| pub struct Body { | ||
| pub id: u32, | ||
| } | ||
|
|
||
| impl Agent for Body { | ||
| fn step(&mut self, state: &mut dyn State) { | ||
| let s = state | ||
| .as_any_mut() | ||
| .downcast_mut::<NBodyState>() | ||
| .expect("state is not NBodyState"); | ||
|
|
||
| let i = self.id as usize; | ||
| let me = s.bodies[i]; | ||
| let eps2 = s.p.softening * s.p.softening; | ||
|
|
||
| // Acceleration = sum_j G * m_j * (r_j - r_i) / (|r|^2 + eps^2)^(3/2) | ||
| // (my own mass cancels out: F = m*a) | ||
| let mut ax = 0.0f32; | ||
| let mut ay = 0.0f32; | ||
|
|
||
| for (j, other) in s.bodies.iter().enumerate() { | ||
| if j == i { | ||
| continue; | ||
| } | ||
| let dx = other.pos.x - me.pos.x; | ||
| let dy = other.pos.y - me.pos.y; | ||
| let d2 = dx * dx + dy * dy + eps2; | ||
| if d2 < 1e-9 { | ||
| continue; | ||
| } | ||
| let k = s.p.g * other.mass / (d2 * d2.sqrt()); | ||
| ax += k * dx; | ||
| ay += k * dy; | ||
| } | ||
|
|
||
| // Semi-implicit Euler: v first, then x with the NEW v. | ||
| let dt = s.p.dt; | ||
| let mut new = me; | ||
| new.vel.x += ax * dt; | ||
| new.vel.y += ay * dt; | ||
| new.pos.x += new.vel.x * dt; | ||
| new.pos.y += new.vel.y * dt; | ||
|
|
||
| s.next[i] = new; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| pub mod body; | ||
| pub mod state; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.