init
This commit is contained in:
commit
5aa6d2be89
14 changed files with 5093 additions and 0 deletions
16
.cargo/config.toml
Normal file
16
.cargo/config.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
linker = "clang"
|
||||||
|
rustflags = [ # alignment
|
||||||
|
"-C",
|
||||||
|
"link-arg=-fuse-ld=lld",
|
||||||
|
"-Zshare-generics=y",
|
||||||
|
]
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
codegen-backend = true
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
codegen-backend = "cranelift"
|
||||||
|
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
codegen-backend = "llvm"
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
4698
Cargo.lock
generated
Normal file
4698
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
72
Cargo.toml
Normal file
72
Cargo.toml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
[package]
|
||||||
|
name = "noders"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy = { version = "0.16.1", default-features = false, features = [
|
||||||
|
# "android-game-activity",
|
||||||
|
# "android_shared_stdcxx",
|
||||||
|
# "animation",
|
||||||
|
"async_executor",
|
||||||
|
"bevy_asset",
|
||||||
|
# "bevy_audio",
|
||||||
|
"bevy_color",
|
||||||
|
"bevy_core_pipeline",
|
||||||
|
# "bevy_gilrs",
|
||||||
|
"bevy_gizmos",
|
||||||
|
# "bevy_gltf",
|
||||||
|
# "bevy_input_focus",
|
||||||
|
# "bevy_log",
|
||||||
|
# "bevy_mesh_picking_backend",
|
||||||
|
"bevy_pbr",
|
||||||
|
# "bevy_picking",
|
||||||
|
"bevy_render",
|
||||||
|
"bevy_scene",
|
||||||
|
"bevy_sprite",
|
||||||
|
# "bevy_sprite_picking_backend",
|
||||||
|
"bevy_state",
|
||||||
|
"bevy_text",
|
||||||
|
# "bevy_ui",
|
||||||
|
# "bevy_ui_picking_backend",
|
||||||
|
"bevy_window",
|
||||||
|
"bevy_winit",
|
||||||
|
# "custom_cursor",
|
||||||
|
# "default_font",
|
||||||
|
"hdr",
|
||||||
|
"multi_threaded",
|
||||||
|
"png",
|
||||||
|
# "smaa_luts",
|
||||||
|
"std",
|
||||||
|
"sysinfo_plugin",
|
||||||
|
# "tonemapping_luts",
|
||||||
|
# "vorbis",
|
||||||
|
"webgl2",
|
||||||
|
"x11",
|
||||||
|
"dynamic_linking",
|
||||||
|
] }
|
||||||
|
bevy_pancam = "0.18.0"
|
||||||
|
bevy_rapier2d = { version = "0.30.0", default-features = false, features = [
|
||||||
|
"async-collider",
|
||||||
|
# "debug-render-2d",
|
||||||
|
"dim2",
|
||||||
|
# "picking-backend",
|
||||||
|
# "to-bevy-mesh",
|
||||||
|
"parallel",
|
||||||
|
"simd-stable",
|
||||||
|
] }
|
||||||
|
itertools = "0.14.0"
|
||||||
|
log = { version = "*", features = [
|
||||||
|
"max_level_debug",
|
||||||
|
"release_max_level_warn",
|
||||||
|
] }
|
||||||
|
rand = "0.9.2"
|
||||||
|
rand_xorshift = "0.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "noders"
|
||||||
|
path = "src/noders.rs"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
16
examples/simple.rs
Normal file
16
examples/simple.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use noders::{display::display, graph::Graph};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut graph = Graph::empty();
|
||||||
|
|
||||||
|
let a = graph.node("A");
|
||||||
|
let b = graph.node("B");
|
||||||
|
let c = graph.node("C");
|
||||||
|
let d = graph.node("D");
|
||||||
|
|
||||||
|
graph.link(a, b);
|
||||||
|
graph.link(a, c);
|
||||||
|
graph.link(a, d);
|
||||||
|
|
||||||
|
display(graph);
|
||||||
|
}
|
37
examples/tree.rs
Normal file
37
examples/tree.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use noders::{
|
||||||
|
display::display,
|
||||||
|
graph::{Graph, NodeId},
|
||||||
|
};
|
||||||
|
use rand::{SeedableRng, prelude::*};
|
||||||
|
use rand_xorshift::XorShiftRng;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut rng = XorShiftRng::seed_from_u64(111111111112111122);
|
||||||
|
let mut graph = Graph::empty();
|
||||||
|
let root = graph.node("ROOT");
|
||||||
|
branch(&mut graph, &mut rng, root, vec![], 7, 5);
|
||||||
|
dbg!(graph.nodes.len());
|
||||||
|
display(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn branch(
|
||||||
|
graph: &mut Graph,
|
||||||
|
rng: &mut XorShiftRng,
|
||||||
|
parent: NodeId,
|
||||||
|
path: Vec<usize>,
|
||||||
|
max_children: usize,
|
||||||
|
max_depth: usize,
|
||||||
|
) {
|
||||||
|
if max_depth == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let child_count = rng.random_range(0..max_children);
|
||||||
|
for index in 1..child_count {
|
||||||
|
let mut path = path.clone();
|
||||||
|
path.push(index);
|
||||||
|
let label = path.iter().map(ToString::to_string).collect::<Vec<_>>().join(".");
|
||||||
|
let child = graph.node(label);
|
||||||
|
graph.link(child, parent);
|
||||||
|
branch(graph, rng, child, path, max_children, max_depth - 1);
|
||||||
|
}
|
||||||
|
}
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
hard_tabs = true
|
||||||
|
max_width = 120
|
||||||
|
use_small_heuristics = "Max"
|
131
src/core/display.rs
Normal file
131
src/core/display.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::graph::Graph;
|
||||||
|
use bevy::{color::palettes, prelude::*};
|
||||||
|
use bevy_pancam::{PanCam, PanCamPlugin};
|
||||||
|
use bevy_rapier2d::{prelude::*, rapier::prelude::RigidBodyVelocity};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
pub fn display(graph: Graph) {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0))
|
||||||
|
// .add_plugins(RapierDebugRenderPlugin::default())
|
||||||
|
.add_plugins(PanCamPlugin)
|
||||||
|
.add_systems(Startup, disable_gravity)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Startup, move |mut c: Commands| {
|
||||||
|
let mut node_ents = HashMap::new();
|
||||||
|
let node_size = vec2(100., 50.);
|
||||||
|
let placement = placement::circle(graph.nodes.len(), node_size.length() * 1.5);
|
||||||
|
let placement = placement::Grid::square(graph.nodes.len(), node_size * 2.);
|
||||||
|
let placement = placement.into_iter();
|
||||||
|
for (node, pos) in graph.nodes.values().zip(placement) {
|
||||||
|
let entity = spawn_node(&mut c, node.label.clone(), node_size, pos);
|
||||||
|
node_ents.insert(node.id, entity);
|
||||||
|
}
|
||||||
|
for link in graph.links.values() {
|
||||||
|
let a = node_ents.get(&link.from_node_id);
|
||||||
|
let b = node_ents.get(&link.into_node_id);
|
||||||
|
let Some((&a, &b)) = a.zip(b) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let joint = SpringJoint::new(200., 1_000_000., 0.01);
|
||||||
|
let joint = ImpulseJoint::new(a, joint);
|
||||||
|
c.entity(b).with_child(joint);
|
||||||
|
spawn_link(&mut c, a, b);
|
||||||
|
}
|
||||||
|
for (&a, &b) in node_ents.values().tuple_combinations() {
|
||||||
|
let joint = SpringJoint::new(1_000., 1_000., 0.01);
|
||||||
|
let joint = ImpulseJoint::new(a, joint);
|
||||||
|
c.entity(b).with_child(joint);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add_systems(Update, (draw_link, draw_node));
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_node(c: &mut Commands, label: String, size: Vec2, pos: Vec2) -> Entity {
|
||||||
|
c.spawn((
|
||||||
|
Damping { linear_damping: 0.5, angular_damping: 0.5 },
|
||||||
|
Transform::default().with_translation(vec3(pos.x, pos.y, 0.)),
|
||||||
|
RigidBody::Dynamic,
|
||||||
|
Collider::cuboid(size.x / 2., size.y / 2.),
|
||||||
|
Sensor,
|
||||||
|
ExternalImpulse::default(),
|
||||||
|
Velocity::default(),
|
||||||
|
LockedAxes::ROTATION_LOCKED,
|
||||||
|
Sleeping::disabled(),
|
||||||
|
NodeComp { label, size },
|
||||||
|
))
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Component, Reflect)]
|
||||||
|
struct NodeComp {
|
||||||
|
label: String,
|
||||||
|
size: Vec2,
|
||||||
|
}
|
||||||
|
fn spawn_link(c: &mut Commands, a: Entity, b: Entity) {
|
||||||
|
c.spawn(LinkComp { a, b });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Component, Reflect)]
|
||||||
|
struct LinkComp {
|
||||||
|
a: Entity,
|
||||||
|
b: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LINK_DISTANCE: f32 = 150.;
|
||||||
|
const LINK_STIFFNESS: f32 = 100.;
|
||||||
|
|
||||||
|
fn sim_link(links: Query<&LinkComp>, mut nodes: Query<(&Transform, &mut ExternalImpulse), With<NodeComp>>) {
|
||||||
|
for link in links.iter() {
|
||||||
|
let [(transform_a, mut impulse_a), (transform_b, mut impulse_b)] =
|
||||||
|
nodes.get_many_mut([link.a, link.b]).unwrap();
|
||||||
|
let a_to_b = transform_b.translation.xy() - transform_a.translation.xy();
|
||||||
|
// negative when stretched inward.
|
||||||
|
let stretch = a_to_b.length() - LINK_DISTANCE;
|
||||||
|
let strength = stretch * LINK_STIFFNESS;
|
||||||
|
impulse_a.impulse = a_to_b.normalize_or_zero() * strength;
|
||||||
|
impulse_b.impulse = a_to_b.normalize_or_zero() * -strength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repulsion(mut nodes: Query<(&Transform, &mut ExternalImpulse), With<NodeComp>>) {
|
||||||
|
let mut combinations = nodes.iter_combinations_mut::<2>();
|
||||||
|
while let Some([(transform_a, mut impulse_a), (transform_b, mut impulse_b)]) = combinations.fetch_next() {
|
||||||
|
let a_to_b = transform_b.translation.xy() - transform_a.translation.xy();
|
||||||
|
// // negative when stretched inward.
|
||||||
|
// let stretch = a_to_b.length() - LINK_DISTANCE;
|
||||||
|
// let strength = stretch * LINK_STIFFNESS;
|
||||||
|
// impulse_a.impulse = a_to_b.normalize_or_zero() * strength;
|
||||||
|
// impulse_b.impulse = a_to_b.normalize_or_zero() * -strength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_link(mut gizmos: Gizmos, links: Query<&LinkComp>, nodes: Query<&Transform, With<NodeComp>>) {
|
||||||
|
for link in links {
|
||||||
|
let a = nodes.get(link.a).unwrap();
|
||||||
|
let b = nodes.get(link.b).unwrap();
|
||||||
|
gizmos.line_2d(a.translation.xy(), b.translation.xy(), palettes::basic::WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_node(mut gizmos: Gizmos, nodes: Query<(&Transform, &NodeComp)>) {
|
||||||
|
for node in nodes {
|
||||||
|
gizmos.rect_2d(Isometry2d::from_translation(node.0.translation.xy()), node.1.size, palettes::basic::WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut c: Commands) {
|
||||||
|
c.spawn((Camera2d, PanCam::default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable_gravity(mut rapier_config: Query<&mut RapierConfiguration>) {
|
||||||
|
if let Ok(mut config) = rapier_config.single_mut() {
|
||||||
|
config.gravity = Vec2::ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod placement;
|
53
src/core/display/placement.rs
Normal file
53
src/core/display/placement.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use bevy::math::{Vec2, vec2};
|
||||||
|
|
||||||
|
pub struct Grid {
|
||||||
|
column_count: usize,
|
||||||
|
cell_size: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
pub fn square(slot_count: usize, cell_size: Vec2) -> Self {
|
||||||
|
let column_count = ((slot_count as f32).sqrt() + 1.) as usize;
|
||||||
|
Self { column_count, cell_size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'g> IntoIterator for &'g Grid {
|
||||||
|
type IntoIter = GridIter<'g>;
|
||||||
|
type Item = Vec2;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
let grid = self;
|
||||||
|
let i = 0;
|
||||||
|
GridIter { grid, i }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GridIter<'g> {
|
||||||
|
grid: &'g Grid,
|
||||||
|
i: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'g> Iterator for GridIter<'g> {
|
||||||
|
type Item = Vec2;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let x = self.i % self.grid.column_count;
|
||||||
|
let y = self.i / self.grid.column_count;
|
||||||
|
let value = vec2(x as _, y as _) * self.grid.cell_size;
|
||||||
|
self.i += 1;
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn circle(items: usize, item_width: f32) -> impl Iterator<Item = Vec2> {
|
||||||
|
let perimeter = items as f32 * item_width;
|
||||||
|
let radius = perimeter / (2. * PI);
|
||||||
|
(0..items).map(move |i| {
|
||||||
|
let frac = i as f32 / items as f32;
|
||||||
|
let angle = frac * 2. * PI;
|
||||||
|
vec2(angle.cos(), angle.sin()) * radius
|
||||||
|
})
|
||||||
|
}
|
56
src/core/graph.rs
Normal file
56
src/core/graph.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Graph {
|
||||||
|
next_id: usize,
|
||||||
|
pub nodes: HashMap<NodeId, Node>,
|
||||||
|
pub links: HashMap<LinkId, Link>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Graph {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node(&mut self, label: impl ToString) -> NodeId {
|
||||||
|
let id = self.id(NodeId);
|
||||||
|
let label = label.to_string();
|
||||||
|
let node = Node { id, label };
|
||||||
|
self.nodes.insert(id, node);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link(&mut self, from_node_id: NodeId, into_node_id: NodeId) -> Option<LinkId> {
|
||||||
|
self.nodes.get(&from_node_id)?;
|
||||||
|
self.nodes.get(&into_node_id)?;
|
||||||
|
let id = self.id(LinkId);
|
||||||
|
let link = Link { from_node_id, into_node_id, id };
|
||||||
|
self.links.insert(id, link);
|
||||||
|
Some(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id<T>(&mut self, cons: impl Fn(usize) -> T) -> T {
|
||||||
|
let id = cons(self.next_id);
|
||||||
|
self.next_id += 1;
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Node {
|
||||||
|
pub id: NodeId,
|
||||||
|
pub label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Link {
|
||||||
|
pub id: LinkId,
|
||||||
|
pub from_node_id: NodeId,
|
||||||
|
pub into_node_id: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
pub struct NodeId(usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
pub struct LinkId(usize);
|
2
src/core/mod.rs
Normal file
2
src/core/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod display;
|
||||||
|
pub mod graph;
|
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod core;
|
||||||
|
|
||||||
|
pub use core::*;
|
3
src/noders.rs
Normal file
3
src/noders.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Henlo.");
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue