mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Add force directed flow chart
This commit is contained in:
Generated
+1
@@ -2688,6 +2688,7 @@ dependencies = [
|
||||
"egui_extras",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"unshell-lib",
|
||||
"wasm-bindgen-futures",
|
||||
|
||||
@@ -28,6 +28,7 @@ log = "0.4.27"
|
||||
# You only need serde if you want app persistence:
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
egui_extras = "0.33.2"
|
||||
rand = "0.9.2"
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
||||
@@ -31,17 +31,17 @@ impl Default for TemplateApp {
|
||||
|
||||
impl TemplateApp {
|
||||
/// Called once before the first frame.
|
||||
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
// This is also where you can customize the look and feel of egui using
|
||||
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
|
||||
|
||||
// Load previous app state (if any).
|
||||
// Note that you must enable the `persistence` feature for this to work.
|
||||
if let Some(storage) = cc.storage {
|
||||
eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
// if let Some(storage) = cc.storage {
|
||||
// eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default()
|
||||
// } else {
|
||||
Default::default()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ impl Config {
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::auto().resizable(false))
|
||||
.min_scrolled_height(0.0)
|
||||
.sense(egui::Sense::click());
|
||||
|
||||
@@ -117,6 +118,7 @@ impl Config {
|
||||
header.col(|ui| {
|
||||
ui.strong("Runtimes");
|
||||
});
|
||||
header.col(|_| {});
|
||||
})
|
||||
.body(|mut body| {
|
||||
for i in 0..self.current_payloads.len() {
|
||||
@@ -138,6 +140,20 @@ impl Config {
|
||||
row.col(|ui| {
|
||||
ui.label("A");
|
||||
});
|
||||
row.col(|ui| {
|
||||
if ui.button("Edit").clicked() {
|
||||
self.state = ConfigState::EditConfig(
|
||||
i,
|
||||
self.current_payloads[i].clone(),
|
||||
);
|
||||
}
|
||||
// if ui.button("Delete").clicked() {
|
||||
// self.state = ConfigState::EditConfig(
|
||||
// i,
|
||||
// self.current_payloads[i].clone(),
|
||||
// );
|
||||
// }
|
||||
});
|
||||
|
||||
if row.response().clicked() {
|
||||
self.state = ConfigState::EditConfig(
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
use egui::Ui;
|
||||
use egui::{Color32, Painter, Pos2, Rect, Stroke, UiBuilder, Vec2};
|
||||
|
||||
const TARGET_LINE_GAP: f32 = 80.;
|
||||
|
||||
const BG_STROKE: Stroke = Stroke {
|
||||
width: 0.3,
|
||||
color: Color32::GRAY,
|
||||
};
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
|
||||
pub struct FlowChart {
|
||||
// frame: Frame,
|
||||
container: DraggableContainer,
|
||||
}
|
||||
|
||||
impl FlowChart {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
container: DraggableContainer::new(
|
||||
Pos2 { x: 100., y: 100. },
|
||||
Vec2 { x: 100., y: 100. },
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_bg(&self, rect: &Rect, painter: &Painter) {
|
||||
let h_count = (rect.width() / TARGET_LINE_GAP).round() as usize;
|
||||
let h_spacing = rect.width() / h_count as f32;
|
||||
for n in 0..h_count {
|
||||
painter.vline(rect.min.x + n as f32 * h_spacing, rect.y_range(), BG_STROKE);
|
||||
}
|
||||
|
||||
let v_count = (rect.height() / TARGET_LINE_GAP).round() as usize;
|
||||
let v_spacing = rect.height() / v_count as f32;
|
||||
for n in 0..v_count {
|
||||
painter.hline(rect.x_range(), rect.min.y + n as f32 * v_spacing, BG_STROKE);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, ui: &mut Ui) {
|
||||
self.paint_bg(&ui.clip_rect(), ui.painter());
|
||||
self.container.show(ui, |ui, rect| {
|
||||
ui.painter().rect(
|
||||
// ui.top
|
||||
*rect,
|
||||
0.,
|
||||
Color32::PURPLE,
|
||||
BG_STROKE,
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
ui.label("Tests");
|
||||
let _ = ui.button("Test");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct DraggableContainer {
|
||||
pub pos: egui::Pos2,
|
||||
pub size: egui::Vec2,
|
||||
is_dragging: bool,
|
||||
drag_offset: egui::Vec2,
|
||||
}
|
||||
|
||||
impl DraggableContainer {
|
||||
pub fn new(pos: egui::Pos2, size: egui::Vec2) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
size,
|
||||
is_dragging: false,
|
||||
drag_offset: egui::Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show<R>(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
add_contents: impl FnOnce(&mut egui::Ui, &Rect) -> R,
|
||||
) -> R {
|
||||
let rect = egui::Rect::from_min_size(self.pos, self.size);
|
||||
|
||||
// Handle dragging logic
|
||||
let response = ui.interact(rect, ui.id().with("drag_area"), egui::Sense::drag());
|
||||
|
||||
if response.drag_started() {
|
||||
self.is_dragging = true;
|
||||
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
self.drag_offset = self.pos - pointer_pos;
|
||||
}
|
||||
}
|
||||
|
||||
if response.dragged() && self.is_dragging {
|
||||
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
self.pos = pointer_pos + self.drag_offset;
|
||||
}
|
||||
}
|
||||
|
||||
if response.drag_stopped() {
|
||||
self.is_dragging = false;
|
||||
}
|
||||
|
||||
// Create a child UI at the specified position
|
||||
// let mut child_ui = ui.child_ui(rect, egui::Layout::top_down(egui::Align::LEFT), None);
|
||||
|
||||
let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect));
|
||||
|
||||
// Add contents
|
||||
add_contents(&mut child_ui, &rect)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
use egui::{Pos2, Rect, UiBuilder, Vec2};
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct DraggableContainer {
|
||||
pub pos: egui::Vec2, // Offset from center of clip_rect
|
||||
pub size: egui::Vec2,
|
||||
is_dragging: bool,
|
||||
drag_offset: egui::Vec2,
|
||||
drag_id: String,
|
||||
|
||||
pub vel: egui::Vec2,
|
||||
}
|
||||
|
||||
impl DraggableContainer {
|
||||
// pub fn new(center_offset: egui::Vec2, size: egui::Vec2, id: usize) -> Self {
|
||||
// Self {
|
||||
// pos: center_offset,
|
||||
// size,
|
||||
// is_dragging: false,
|
||||
// drag_offset: egui::Vec2::ZERO,
|
||||
// drag_id: format!("flowchart_drag_area{}", id),
|
||||
// vel: Vec2::ZERO,
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn new_zero(id: usize) -> Self {
|
||||
Self {
|
||||
pos: Vec2::ZERO,
|
||||
size: Vec2 { x: 100., y: 100. },
|
||||
is_dragging: false,
|
||||
drag_offset: egui::Vec2::ZERO,
|
||||
drag_id: format!("flowchart_drag_area{}", id),
|
||||
vel: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn show<R>(
|
||||
// &mut self,
|
||||
// ui: &mut egui::Ui,
|
||||
// add_contents: impl FnOnce(&mut egui::Ui, &Rect) -> R,
|
||||
// ) -> R {
|
||||
// let rect = egui::Rect::from_min_size(self.pos, self.size);
|
||||
|
||||
// // Handle dragging logic
|
||||
// let response = ui.interact(rect, ui.id().with(&self.drag_id), egui::Sense::drag());
|
||||
|
||||
// if response.drag_started() {
|
||||
// self.is_dragging = true;
|
||||
// if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
// self.drag_offset = self.pos - pointer_pos;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if response.dragged() && self.is_dragging {
|
||||
// if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
// self.pos = pointer_pos + self.drag_offset;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if response.drag_stopped() {
|
||||
// self.is_dragging = false;
|
||||
// }
|
||||
|
||||
// // Create a child UI at the specified position
|
||||
// // let mut child_ui = ui.child_ui(rect, egui::Layout::top_down(egui::Align::LEFT), None);
|
||||
|
||||
// let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect));
|
||||
|
||||
// // Add contents
|
||||
// add_contents(&mut child_ui, &rect)
|
||||
// }
|
||||
|
||||
pub fn get_pos(&self, center: &Pos2) -> Pos2 {
|
||||
center.clone() + self.pos
|
||||
}
|
||||
|
||||
pub fn show<R>(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
add_contents: impl FnOnce(&mut egui::Ui, &Rect) -> R,
|
||||
) -> R {
|
||||
// Calculate center of the clip rect
|
||||
let clip_center = ui.clip_rect().center();
|
||||
|
||||
// Calculate actual position from center offset
|
||||
let center_pos = clip_center + self.pos;
|
||||
let pos = center_pos - self.size / 2.0; // Top-left corner from center
|
||||
let rect = egui::Rect::from_min_size(pos, self.size);
|
||||
|
||||
// Handle dragging logic
|
||||
let response = ui.interact(rect, ui.id().with(&self.drag_id), egui::Sense::drag());
|
||||
|
||||
if response.drag_started() {
|
||||
self.is_dragging = true;
|
||||
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
self.drag_offset = center_pos - pointer_pos;
|
||||
}
|
||||
}
|
||||
|
||||
if response.dragged() && self.is_dragging {
|
||||
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
|
||||
let new_center = pointer_pos + self.drag_offset;
|
||||
self.pos = new_center - clip_center;
|
||||
}
|
||||
}
|
||||
|
||||
if response.drag_stopped() {
|
||||
self.is_dragging = false;
|
||||
}
|
||||
|
||||
// Create a child UI at the specified position
|
||||
let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect));
|
||||
|
||||
// Add contents
|
||||
add_contents(&mut child_ui, &rect)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
use egui::Shape;
|
||||
use egui::Ui;
|
||||
use egui::{Color32, Painter, Pos2, Rect};
|
||||
|
||||
use crate::flowchart::CONNECTION_STROKE;
|
||||
use crate::flowchart::GROUP_BORDER_MARGIN;
|
||||
use crate::flowchart::container::DraggableContainer;
|
||||
use crate::flowchart::group::convex_hull;
|
||||
use crate::flowchart::{BG_STROKE, TARGET_LINE_GAP};
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
|
||||
pub struct FlowChart {
|
||||
pub containers: Vec<DraggableContainer>,
|
||||
pub connections: Vec<(usize, usize)>,
|
||||
pub groups: Vec<Vec<usize>>,
|
||||
}
|
||||
|
||||
impl FlowChart {
|
||||
pub fn new() -> Self {
|
||||
let mut this = Self {
|
||||
containers: vec![
|
||||
DraggableContainer::new_zero(0),
|
||||
DraggableContainer::new_zero(1),
|
||||
DraggableContainer::new_zero(2),
|
||||
DraggableContainer::new_zero(3),
|
||||
DraggableContainer::new_zero(4),
|
||||
DraggableContainer::new_zero(5),
|
||||
DraggableContainer::new_zero(6),
|
||||
DraggableContainer::new_zero(7),
|
||||
],
|
||||
connections: vec![(0, 1), (1, 2), (1, 3), (1, 4), (3, 5), (3, 6), (3, 7)],
|
||||
groups: vec![],
|
||||
};
|
||||
|
||||
this.arrange_circle();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn paint_bg(&self, rect: &Rect, painter: &Painter) {
|
||||
let h_count = (rect.width() / TARGET_LINE_GAP).round() as usize;
|
||||
let h_spacing = rect.width() / h_count as f32;
|
||||
for n in 0..h_count {
|
||||
painter.vline(rect.min.x + n as f32 * h_spacing, rect.y_range(), BG_STROKE);
|
||||
}
|
||||
|
||||
let v_count = (rect.height() / TARGET_LINE_GAP).round() as usize;
|
||||
let v_spacing = rect.height() / v_count as f32;
|
||||
for n in 0..v_count {
|
||||
painter.hline(rect.x_range(), rect.min.y + n as f32 * v_spacing, BG_STROKE);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_groups(&self, ui: &mut Ui) {
|
||||
let center = ui.clip_rect().center();
|
||||
for group in &self.groups {
|
||||
let mut points = Vec::new();
|
||||
|
||||
for n in group {
|
||||
let container = &self.containers[*n];
|
||||
let pos = container.get_pos(¢er);
|
||||
let size = container.size;
|
||||
points.append(&mut vec![
|
||||
Pos2 {
|
||||
x: pos.x - size.x / 2. - GROUP_BORDER_MARGIN,
|
||||
y: pos.y - size.x / 2. - GROUP_BORDER_MARGIN,
|
||||
},
|
||||
Pos2 {
|
||||
x: pos.x + size.x / 2. + GROUP_BORDER_MARGIN,
|
||||
y: pos.y - size.y / 2. - GROUP_BORDER_MARGIN,
|
||||
},
|
||||
Pos2 {
|
||||
x: pos.x - size.x / 2. - GROUP_BORDER_MARGIN,
|
||||
y: pos.y + size.y / 2. + GROUP_BORDER_MARGIN,
|
||||
},
|
||||
Pos2 {
|
||||
x: pos.x + size.x / 2. + GROUP_BORDER_MARGIN,
|
||||
y: pos.y + size.y / 2. + GROUP_BORDER_MARGIN,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
let points = convex_hull(&points);
|
||||
|
||||
ui.painter().add(Shape::convex_polygon(
|
||||
points,
|
||||
Color32::DEBUG_COLOR,
|
||||
BG_STROKE,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, ui: &mut Ui) {
|
||||
self.paint_bg(&ui.clip_rect(), ui.painter());
|
||||
self.paint_groups(ui);
|
||||
|
||||
let center = ui.clip_rect().center();
|
||||
|
||||
for (a, b) in &self.connections {
|
||||
ui.painter().line_segment(
|
||||
[
|
||||
self.containers[*a].get_pos(¢er),
|
||||
self.containers[*b].get_pos(¢er),
|
||||
],
|
||||
CONNECTION_STROKE,
|
||||
);
|
||||
|
||||
// let start = self.containers[m.clone()];
|
||||
// let end = self.containers[n.clone()];
|
||||
}
|
||||
|
||||
for container in &mut self.containers {
|
||||
container.show(ui, |ui, rect| {
|
||||
ui.painter().rect(
|
||||
// ui.top
|
||||
*rect,
|
||||
0.,
|
||||
Color32::PURPLE,
|
||||
BG_STROKE,
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
// ui.label("Tests");
|
||||
// let _ = ui.button("Test");
|
||||
});
|
||||
}
|
||||
|
||||
if ui.button("Arrange").clicked() {
|
||||
// let positions: Vec<Vec2> = (0..num_nodes)
|
||||
// .map(|i| {
|
||||
// let angle = (i as f32) * 2.0 * std::f32::consts::PI / (num_nodes as f32);
|
||||
// Vec2::new(angle.cos() * 100.0, angle.sin() * 100.0)
|
||||
// })
|
||||
// .collect();
|
||||
|
||||
// let node_count = self.containers.len() as f32;
|
||||
|
||||
// for (i, m) in self.containers.iter_mut().enumerate() {
|
||||
// let ang = -(i as f32 / node_count) * PI * 2.;
|
||||
// m.pos = Vec2 {
|
||||
// x: 1000. * ang.sin(),
|
||||
// y: 1000. * ang.cos(),
|
||||
// };
|
||||
// m.vel = Vec2::ZERO;
|
||||
// }
|
||||
|
||||
for _ in 0..1_000 {
|
||||
self.force(0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
use std::f32::consts::TAU;
|
||||
|
||||
use egui::Vec2;
|
||||
|
||||
use crate::flowchart::{
|
||||
ATTRACTION_STRENGTH, CENTER_ATTRACTION_STRENGTH, DAMPING, FlowChart, GROUP_ATTRACTION_STRENGTH,
|
||||
REPULSION_STRENGTH, REST_LENGTH,
|
||||
};
|
||||
|
||||
pub fn normalize(v: &Vec2) -> Vec2 {
|
||||
let len = v.length();
|
||||
if len > 0.0 {
|
||||
Vec2 {
|
||||
x: v.x / len,
|
||||
y: v.y / len,
|
||||
}
|
||||
} else {
|
||||
Vec2 { x: 0.0, y: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowChart {
|
||||
pub fn force(&mut self, delta_time: f32) {
|
||||
let num_nodes = self.containers.len();
|
||||
let mut forces = vec![Vec2::new(0.0, 0.0); num_nodes];
|
||||
|
||||
// Calculate repulsive forces between all nodes
|
||||
for i in 0..num_nodes {
|
||||
for j in (i + 1)..num_nodes {
|
||||
let diff = self.containers[i].pos - self.containers[j].pos;
|
||||
let dist = diff.length().max(0.1); // Prevent division by zero
|
||||
let force = normalize(&diff) * (REPULSION_STRENGTH / (dist * dist));
|
||||
|
||||
forces[i] = forces[i] + force;
|
||||
forces[j] = forces[j] + (force * -1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate attractive forces along connections
|
||||
for &(i, j) in &self.connections {
|
||||
let diff = self.containers[j].pos - self.containers[i].pos;
|
||||
let dist = diff.length();
|
||||
let displacement = dist - REST_LENGTH;
|
||||
let force = normalize(&diff) * (displacement * ATTRACTION_STRENGTH);
|
||||
|
||||
forces[i] = forces[i] + force;
|
||||
forces[j] = forces[j] + (force * -1.0);
|
||||
}
|
||||
|
||||
// Apply force to center
|
||||
for i in 0..num_nodes {
|
||||
let diff = self.containers[i].pos;
|
||||
let dist = diff.length();
|
||||
let displacement = dist - REST_LENGTH;
|
||||
let force = normalize(&diff) * (displacement * CENTER_ATTRACTION_STRENGTH);
|
||||
|
||||
forces[i] = forces[i] + force * -1.;
|
||||
}
|
||||
|
||||
let group_avg = &self
|
||||
.groups
|
||||
.iter()
|
||||
.map(|group| {
|
||||
let mut sum = Vec2::ZERO;
|
||||
for n in group {
|
||||
sum += self.containers[*n].pos;
|
||||
}
|
||||
sum / group.len() as f32
|
||||
})
|
||||
.collect::<Vec<Vec2>>();
|
||||
|
||||
for (group, group_avg) in self.groups.iter().zip(group_avg) {
|
||||
for i in 0..num_nodes {
|
||||
let diff = self.containers[i].pos - *group_avg;
|
||||
let dist = diff.length();
|
||||
let displacement = dist - REST_LENGTH;
|
||||
let force = normalize(&diff) * (displacement * GROUP_ATTRACTION_STRENGTH);
|
||||
|
||||
if group.contains(&i) {
|
||||
forces[i] = forces[i] + force * -1.;
|
||||
} else {
|
||||
forces[i] = forces[i] + force;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update velocities and positions
|
||||
for i in 0..num_nodes {
|
||||
let c = &mut self.containers[i];
|
||||
c.vel = (c.vel + forces[i] * delta_time) * DAMPING;
|
||||
c.pos += c.vel * delta_time;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arrange_circle(&mut self) {
|
||||
let node_count = self.containers.len() as f32;
|
||||
for (i, m) in self.containers.iter_mut().enumerate() {
|
||||
let ang = -(i as f32 / node_count) * TAU;
|
||||
m.pos = Vec2 {
|
||||
x: 300. * ang.sin(),
|
||||
y: 300. * ang.cos(),
|
||||
};
|
||||
m.vel = Vec2::ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn get_positions(&self) -> &[Vec2] {
|
||||
// &self.positions
|
||||
// }
|
||||
|
||||
// pub fn get_connections(&self) -> &[(usize, usize)] {
|
||||
// &self.connections
|
||||
// }
|
||||
}
|
||||
|
||||
// fn main() {
|
||||
// // Example usage: Create a simple triangle graph
|
||||
// let connections = vec![(0, 1), (1, 2), (2, 0)];
|
||||
// let mut graph = ForceDirectedGraph::new(
|
||||
// 3,
|
||||
// connections,
|
||||
// 1000.0, // repulsion_strength
|
||||
// 0.1, // attraction_strength
|
||||
// 50.0, // rest_length
|
||||
// 0.9, // damping
|
||||
// );
|
||||
|
||||
// // Simulate 100 frames at 60 FPS
|
||||
// let delta_time = 1.0 / 60.0;
|
||||
// for frame in 0..100 {
|
||||
// graph.update(delta_time);
|
||||
|
||||
// if frame % 20 == 0 {
|
||||
// println!("Frame {}: ", frame);
|
||||
// for (i, pos) in graph.get_positions().iter().enumerate() {
|
||||
// println!(" Node {}: ({:.2}, {:.2})", i, pos.x, pos.y);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,59 @@
|
||||
use egui::Pos2;
|
||||
|
||||
/// Calculate the convex hull of a set of points using Graham scan
|
||||
pub fn convex_hull(points: &[Pos2]) -> Vec<Pos2> {
|
||||
if points.len() < 3 {
|
||||
return points.to_vec();
|
||||
}
|
||||
|
||||
let mut pts = points.to_vec();
|
||||
|
||||
// Find the point with lowest y-coordinate (and leftmost if tie)
|
||||
let start_idx = pts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by(|(_, a), (_, b)| {
|
||||
a.y.partial_cmp(&b.y)
|
||||
.unwrap()
|
||||
.then(a.x.partial_cmp(&b.x).unwrap())
|
||||
})
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
pts.swap(0, start_idx);
|
||||
let start = pts[0];
|
||||
|
||||
// Sort points by polar angle with respect to start point
|
||||
pts[1..].sort_by(|a, b| {
|
||||
let angle_a = polar_angle_to(&start, a);
|
||||
let angle_b = polar_angle_to(&start, b);
|
||||
angle_a.partial_cmp(&angle_b).unwrap()
|
||||
});
|
||||
|
||||
// Build convex hull
|
||||
let mut hull = Vec::new();
|
||||
hull.push(pts[0]);
|
||||
hull.push(pts[1]);
|
||||
|
||||
for i in 2..pts.len() {
|
||||
while hull.len() > 1
|
||||
&& cross_product(&hull[hull.len() - 2], &hull[hull.len() - 1], &pts[i]) <= 0.0
|
||||
{
|
||||
hull.pop();
|
||||
}
|
||||
hull.push(pts[i]);
|
||||
}
|
||||
|
||||
hull
|
||||
}
|
||||
|
||||
/// Calculate cross product of vectors (self->p2) and (self->p3)
|
||||
/// Positive if counter-clockwise, negative if clockwise, zero if collinear
|
||||
fn cross_product(p1: &Pos2, p2: &Pos2, p3: &Pos2) -> f32 {
|
||||
(p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)
|
||||
}
|
||||
|
||||
/// Calculate polar angle from self to other point
|
||||
fn polar_angle_to(a: &Pos2, b: &Pos2) -> f32 {
|
||||
(b.y - a.y).atan2(b.x - a.x)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
mod container;
|
||||
mod flowchart;
|
||||
mod force;
|
||||
mod group;
|
||||
|
||||
use egui::{Color32, Stroke};
|
||||
pub use flowchart::FlowChart;
|
||||
|
||||
const TARGET_LINE_GAP: f32 = 80.;
|
||||
|
||||
const BG_STROKE: Stroke = Stroke {
|
||||
width: 0.3,
|
||||
color: Color32::GRAY,
|
||||
};
|
||||
|
||||
const CONNECTION_STROKE: Stroke = Stroke {
|
||||
width: 3.,
|
||||
color: Color32::WHITE,
|
||||
};
|
||||
|
||||
const GROUP_BORDER_MARGIN: f32 = 20.;
|
||||
|
||||
static REPULSION_STRENGTH: f32 = 100000.0; // repulsion_strength
|
||||
static ATTRACTION_STRENGTH: f32 = 0.01; // attraction_strength
|
||||
static CENTER_ATTRACTION_STRENGTH: f32 = 0.01; // attraction_strength
|
||||
static GROUP_ATTRACTION_STRENGTH: f32 = 0.001; // attraction_strength
|
||||
static REST_LENGTH: f32 = 50.0; // rest_length
|
||||
static DAMPING: f32 = 0.9; // damping
|
||||
@@ -1,5 +1,6 @@
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
#![macro_use]
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate log;
|
||||
|
||||
mod app;
|
||||
|
||||
Reference in New Issue
Block a user