Use Bevy ECS

This commit is contained in:
Alex Mikhalev 2022-05-24 13:08:01 -07:00
parent dafebdabee
commit fdd93fa7e8
6 changed files with 838 additions and 414 deletions

340
Cargo.lock generated
View File

@ -92,6 +92,37 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "async-channel"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
[[package]]
name = "async-task"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
[[package]] [[package]]
name = "atomic_refcell" name = "atomic_refcell"
version = "0.1.8" version = "0.1.8"
@ -115,6 +146,157 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bevy_app"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32660ae99fa3498ca379de28b7e2f447e6531b0e432bf200901efeec075553c1"
dependencies = [
"bevy_derive",
"bevy_ecs",
"bevy_reflect",
"bevy_utils",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "bevy_core"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12c0f8614b6014671ab60bacb8bf681373d08b0bb15633b8ef72b895cf966d29"
dependencies = [
"bevy_app",
"bevy_derive",
"bevy_ecs",
"bevy_math",
"bevy_reflect",
"bevy_tasks",
"bevy_utils",
"bytemuck",
]
[[package]]
name = "bevy_derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6abddf2ed415f31d28a9bf9ab3c0bc857e98a722858d38dba65bdda481f8d714"
dependencies = [
"bevy_macro_utils",
"quote",
"syn",
]
[[package]]
name = "bevy_ecs"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e67dd06b14e787d2026fe6e2b63f67482afcc62284f20ea2784d8b0662e95f"
dependencies = [
"async-channel",
"bevy_ecs_macros",
"bevy_reflect",
"bevy_tasks",
"bevy_utils",
"downcast-rs",
"fixedbitset",
"fxhash",
"serde",
"thiserror",
]
[[package]]
name = "bevy_ecs_macros"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718923a491490bd81074492d61fc08134f9c62a29ba8666818cd7a6630421246"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bevy_macro_utils"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7ddfc33a99547e36718e56e414541e461c74ec318ff987a1e9f4ff46d0dacbb"
dependencies = [
"cargo-manifest",
"quote",
"syn",
]
[[package]]
name = "bevy_math"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20288df0f70ff258bbaffaf55209f1271a7436438591bbffc3d81e4d84b423f2"
dependencies = [
"bevy_reflect",
"glam",
]
[[package]]
name = "bevy_reflect"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d0793107bc4b7c6bd04232d739fc8d70aa5fb313bfad6e850f91f79b2557eed"
dependencies = [
"bevy_reflect_derive",
"bevy_utils",
"downcast-rs",
"erased-serde",
"glam",
"parking_lot 0.11.2",
"serde",
"smallvec",
"thiserror",
]
[[package]]
name = "bevy_reflect_derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81c88de8067d19dfde31662ee78e3ee6971e2df27715799f91b515b37a636677"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
"quote",
"syn",
"uuid",
]
[[package]]
name = "bevy_tasks"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2b0f0b86c8f78c53a2d4c669522f45e725ed9d9c3d734f54ec30876494e04e"
dependencies = [
"async-channel",
"async-executor",
"event-listener",
"futures-lite",
"num_cpus",
"wasm-bindgen-futures",
]
[[package]]
name = "bevy_utils"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f354c584812996febd48cc885f36b23004b49d6680e73fc95a69a2bb17a48e5"
dependencies = [
"ahash",
"bevy_derive",
"getrandom",
"hashbrown",
"instant",
"tracing",
"uuid",
]
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.3" version = "1.3.3"
@ -174,12 +356,24 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]] [[package]]
name = "calloop" name = "calloop"
version = "0.9.3" version = "0.9.3"
@ -190,6 +384,17 @@ dependencies = [
"nix", "nix",
] ]
[[package]]
name = "cargo-manifest"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af6d65c7592744998c67947ec771c62687c76f00179a83ffd563c0482046bb98"
dependencies = [
"serde",
"serde_derive",
"toml",
]
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.2.7" version = "0.2.7"
@ -295,6 +500,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
dependencies = [
"cache-padded",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.7.0" version = "0.7.0"
@ -655,6 +869,15 @@ dependencies = [
"parking_lot 0.12.0", "parking_lot 0.12.0",
] ]
[[package]]
name = "erased-serde"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "error-code" name = "error-code"
version = "2.3.1" version = "2.3.1"
@ -665,6 +888,27 @@ dependencies = [
"str-buf", "str-buf",
] ]
[[package]]
name = "event-listener"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "fastrand"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
"instant",
]
[[package]]
name = "fixedbitset"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -696,6 +940,42 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-io"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
[[package]]
name = "futures-lite"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "gethostname" name = "gethostname"
version = "0.2.3" version = "0.2.3"
@ -730,6 +1010,16 @@ dependencies = [
"xml-rs", "xml-rs",
] ]
[[package]]
name = "glam"
version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f"
dependencies = [
"bytemuck",
"serde",
]
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.11.2" version = "0.11.2"
@ -825,6 +1115,10 @@ name = "hashbrown"
version = "0.11.2" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
"serde",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@ -895,6 +1189,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "iyes_loopless"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e37cbe1fdc9aeed2d5ec064d3306ba4318723c20c5b0e4662d03c49fedcd491b"
dependencies = [
"bevy_app",
"bevy_core",
"bevy_ecs",
"bevy_utils",
]
[[package]] [[package]]
name = "jni" name = "jni"
version = "0.19.0" version = "0.19.0"
@ -1389,6 +1695,12 @@ dependencies = [
"ttf-parser", "ttf-parser",
] ]
[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.2" version = "0.11.2"
@ -1761,15 +2073,23 @@ name = "sketchrs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"argmin", "argmin",
"bevy_ecs",
"criterion", "criterion",
"eframe", "eframe",
"indexmap", "indexmap",
"iyes_loopless",
"levenberg-marquardt", "levenberg-marquardt",
"nalgebra", "nalgebra",
"nalgebra-sparse", "nalgebra-sparse",
"nohash-hasher", "nohash-hasher",
] ]
[[package]]
name = "slab"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
[[package]] [[package]]
name = "slog" name = "slog"
version = "2.7.0" version = "2.7.0"
@ -1827,6 +2147,9 @@ name = "smallvec"
version = "1.8.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "smithay-client-toolkit" name = "smithay-client-toolkit"
@ -1974,6 +2297,7 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [ dependencies = [
"indexmap",
"serde", "serde",
] ]
@ -2060,12 +2384,28 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
"serde",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.2" version = "2.3.2"

View File

@ -7,8 +7,10 @@ edition = "2021"
[dependencies] [dependencies]
argmin = { version = "0.5.1", features = ["nalgebra"] } argmin = { version = "0.5.1", features = ["nalgebra"] }
bevy_ecs = "0.7.0"
eframe = { version = "0.18.0", features = [] } eframe = { version = "0.18.0", features = [] }
indexmap = "1.8.1" indexmap = "1.8.1"
iyes_loopless = "0.5.1"
levenberg-marquardt = "0.12.0" levenberg-marquardt = "0.12.0"
nalgebra = "0.30.1" nalgebra = "0.30.1"
nalgebra-sparse = "0.6.0" nalgebra-sparse = "0.6.0"

View File

@ -1,131 +1,139 @@
mod entity;
mod var; mod var;
use self::entity::{EntityId, EntityMap}; use std::ops::DerefMut;
use bevy_ecs::{
entity::Entity,
prelude::Component,
query::QueryEntityError,
system::{Commands, Query, SystemParam},
};
pub use self::var::*; pub use self::var::*;
pub type Scalar = f64; pub type Scalar = f64;
#[derive(Clone, Debug)] #[derive(Clone, Debug, Component)]
pub struct PointEntity<V = VarId> { pub struct Point<V = VarId> {
pub x: V, pub x: V,
pub y: V, pub y: V,
} }
impl<V> PointEntity<V> { impl<V> Point<V> {
pub fn new(x: V, y: V) -> Self { pub fn new(x: V, y: V) -> Self {
Self { x, y } Self { x, y }
} }
} }
impl<V> From<(V, V)> for PointEntity<V> { impl<V> From<(V, V)> for Point<V> {
fn from(p: (V, V)) -> Self { fn from(p: (V, V)) -> Self {
Self { x: p.0, y: p.1 } Self { x: p.0, y: p.1 }
} }
} }
pub type PointPos = PointEntity<Scalar>; pub type PointPos = Point<Scalar>;
pub type PointId = EntityId<PointEntity>; pub type PointId = Entity;
#[derive(Clone, Debug)] #[derive(Clone, Debug, Component)]
// TODO: segment, ray and full line // TODO: segment, ray and full line
// TODO: what if start and end are coincident? // TODO: what if start and end are coincident?
pub struct LineEntity<P = PointId> { pub struct Line<P = PointId> {
pub start: P, pub start: P,
pub end: P, pub end: P,
} }
impl<P> LineEntity<P> { impl<P> Line<P> {
pub fn new(start: P, end: P) -> Self { pub fn new(start: P, end: P) -> Self {
Self { start, end } Self { start, end }
} }
} }
pub type LinePos = LineEntity<PointPos>; pub type LinePos = Line<PointPos>;
pub type LineId = EntityId<LineEntity>;
#[derive(Clone)] #[derive(Clone, Component)]
// TODO: arc. how to represent start and end of arc? // TODO: arc. how to represent start and end of arc?
// angles, unit vectors, or a point for each? // angles, unit vectors, or a point for each?
pub struct CircleEntity { pub struct Circle {
pub center: PointId, pub center: PointId,
pub radius: VarId, pub radius: VarId,
} }
// pub type CirclePos = CircleEntity<PointPos>; // pub type CirclePos = CircleEntity<PointPos>;
pub type CircleId = EntityId<CircleEntity>;
#[derive(Clone, Default)] pub fn insert_point_at(commands: &mut Commands, point: impl Into<PointPos>) -> PointId {
pub struct SketchEntities { let point = point.into();
vars: EntityMap<Var>, let x = commands.spawn().insert(Var::new_free(point.x)).id();
points: EntityMap<PointEntity>, let y = commands.spawn().insert(Var::new_free(point.y)).id();
lines: EntityMap<LineEntity>, commands.spawn().insert(Point::new(x, y)).id()
circles: EntityMap<CircleEntity>,
} }
impl SketchEntities { // TODO: figure out generic for this
pub fn vars(&self) -> &EntityMap<Var> { #[derive(SystemParam)]
&self.vars pub struct PointPosQuery<'w, 's> {
vars: Query<'w, 's, &'static Var>,
}
// type PointPosQuery<'w, 's> = PointPosQueryImpl<'w, 's, &'static Var>;
// type PointPosQueryMut<'w, 's> = PointPosQueryImpl<'w, 's, &'static mut Var>;
// impl<'w, 's, Q> PointPosQueryImpl<'w, 's, Q>
// where
// Q: WorldQuery + 'static,
// for<'w2, 's2> <<Q as WorldQuery>::ReadOnlyFetch as Fetch<'w2, 's2>>::Item: Deref<Target = Var>,
impl<'w, 's> PointPosQuery<'w, 's> {
pub fn try_get(&self, point: &Point) -> Result<Var<PointPos>, QueryEntityError> {
let x = self.vars.get(point.x)?;
let y = self.vars.get(point.y)?;
Ok(x.merge(*y, PointPos::new))
} }
pub fn get(&self, point: &Point) -> Var<PointPos> {
pub fn insert_var(&mut self, var: Var) -> VarId { self.try_get(point).unwrap()
self.vars.insert(var) }
} }
pub fn get_var_mut(&mut self, id: VarId) -> Option<&mut Var> { #[derive(SystemParam)]
self.vars.get_mut(id) pub struct PointPosQueryMut<'w, 's> {
} vars: Query<'w, 's, &'static mut Var>,
}
pub fn points(&self) -> &EntityMap<PointEntity> {
&self.points impl<'w, 's> PointPosQueryMut<'w, 's>
} // impl<'w, 's, R> PointPosQueryImpl<'w, 's, Query<'w, 's, R>>
// where
pub fn insert_point(&mut self, point: PointEntity) -> PointId { // R: WorldQuery,
assert!(self.vars.contains(point.x)); // for<'w2, 's2> <<R as WorldQuery>::Fetch as Fetch<'w2, 's2>>::Item: DerefMut<Target = Var>,
assert!(self.vars.contains(point.y)); {
self.points.insert(point) pub fn get(&self, point: &Point) -> Result<Var<PointPos>, QueryEntityError> {
} let x = self.vars.get(point.x)?;
let y = self.vars.get(point.y)?;
pub fn insert_point_at(&mut self, point: impl Into<PointPos>) -> PointId { Ok(x.merge(*y, PointPos::new))
let point = point.into(); }
let x = self.insert_var(Var::new_free(point.x));
let y = self.insert_var(Var::new_free(point.y)); pub fn set(&mut self, point: &Point, move_to: PointPos) -> Result<(), QueryEntityError> {
self.insert_point(PointEntity::new(x, y)) for (id, val) in [(point.x, move_to.x), (point.y, move_to.y)] {
} self.vars.get_mut(id)?.deref_mut().value = val;
}
fn remove_point(&mut self, point_id: PointId) -> bool { Ok(())
// TODO: check if used by any line }
self.points.remove(point_id) }
}
#[derive(SystemParam)]
pub fn point_pos(&self, point: &PointEntity) -> Var<PointPos> { pub struct LinePosQuery<'w, 's> {
// TODO: error handling? point_pos: PointPosQuery<'w, 's>,
let x = self.vars.get(point.x).unwrap(); points: Query<'w, 's, &'static Point>,
let y = self.vars.get(point.y).unwrap(); }
x.merge(*y, PointPos::new)
} impl<'w, 's> LinePosQuery<'w, 's> {
pub fn get(&self, line: &Line) -> Var<LinePos> {
pub fn lines(&self) -> &EntityMap<LineEntity> { self.try_get(line).unwrap()
&self.lines }
}
pub fn try_get(&self, line: &Line) -> Result<Var<LinePos>, QueryEntityError> {
pub fn insert_line(&mut self, line: LineEntity) -> LineId { // TODO: error handling?
assert!(self.points.contains(line.start)); let start = self.points.get(line.start)?;
assert!(self.points.contains(line.end)); let start = self.point_pos.try_get(start)?;
self.lines.insert(line)
} let end = self.points.get(line.end)?;
let end = self.point_pos.try_get(end)?;
fn remove_line(&mut self, line_id: LineId) -> bool { Ok(start.merge(end, LinePos::new))
self.lines.remove(line_id)
}
pub fn line_pos(&self, line: &LineEntity) -> Var<LinePos> {
// TODO: error handling?
let start = self.points.get(line.start).unwrap();
let start = self.point_pos(start);
let end = self.points.get(line.end).unwrap();
let end = self.point_pos(end);
start.merge(end, LinePos::new)
} }
} }

View File

@ -1,117 +0,0 @@
use std::marker::PhantomData;
use indexmap::IndexMap;
// HashMap with fast no-op hash function and preserves insertion order
type IntIndexMap<K, V> = IndexMap<K, V, nohash_hasher::BuildNoHashHasher<K>>;
pub struct EntityId<T>(u32, PhantomData<T>);
impl<T> Default for EntityId<T> {
fn default() -> Self {
Self(1, Default::default())
}
}
impl<T> nohash_hasher::IsEnabled for EntityId<T> {}
impl<T> EntityId<T> {
pub fn id(&self) -> u32 {
self.0
}
fn take_next(&mut self) -> Self {
let id = *self;
self.0 += 1;
id
}
fn invalid() -> Self {
Self(u32::MAX, Default::default())
}
}
#[derive(Clone)]
pub struct EntityMap<T> {
map: IntIndexMap<EntityId<T>, T>,
next_id: EntityId<T>,
}
impl<T> EntityMap<T> {
pub fn insert(&mut self, entity: T) -> EntityId<T> {
let id = self.next_id.take_next();
self.map.insert(id, entity);
id
}
pub fn contains(&self, id: EntityId<T>) -> bool {
self.map.contains_key(&id)
}
pub fn remove(&mut self, id: EntityId<T>) -> bool {
self.map.remove(&id).is_some()
}
pub fn get(&self, id: EntityId<T>) -> Option<&T> {
self.map.get(&id)
}
pub fn get_mut(&mut self, id: EntityId<T>) -> Option<&mut T> {
self.map.get_mut(&id)
}
pub fn iter(&self) -> impl Iterator<Item = (EntityId<T>, &T)> {
self.map.iter().map(|(id, v)| (*id, v))
}
}
// Need to reimplement all of this without the T bound :/
impl<T> PartialEq for EntityId<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0 && self.1 == other.1
}
}
impl<T> Eq for EntityId<T> {}
impl<T> PartialOrd for EntityId<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<T> Ord for EntityId<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T> Clone for EntityId<T> {
fn clone(&self) -> Self {
Self(self.0.clone(), self.1.clone())
}
}
impl<T> Copy for EntityId<T> {}
impl<T> std::hash::Hash for EntityId<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T> std::fmt::Debug for EntityId<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("EntityId").field(&self.0).finish()
}
}
impl<T> Default for EntityMap<T> {
fn default() -> Self {
Self {
map: Default::default(),
next_id: Default::default(),
}
}
}

View File

@ -1,4 +1,6 @@
use super::{EntityId, Scalar}; use bevy_ecs::{prelude::Component, entity::Entity};
use super::Scalar;
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum VarStatus { pub enum VarStatus {
@ -20,7 +22,7 @@ impl VarStatus {
} }
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Component)]
pub struct Var<T = Scalar> { pub struct Var<T = Scalar> {
pub value: T, pub value: T,
pub status: VarStatus, pub status: VarStatus,
@ -59,4 +61,4 @@ impl<T> std::ops::DerefMut for Var<T> {
} }
} }
pub type VarId = EntityId<Var>; pub type VarId = Entity;

View File

@ -1,9 +1,14 @@
use bevy_ecs::{
prelude::*,
system::{Commands, IntoSystem, Query, Res, ResMut, System},
};
use eframe::{ use eframe::{
egui::{self, CursorIcon, Frame, PointerButton, Sense, Ui}, egui::{self, CursorIcon, Painter, Response, Sense, Ui},
emath::{Pos2, Rect, RectTransform, Vec2}, emath::{Pos2, Rect, RectTransform, Vec2},
epaint::{color::Hsva, Color32, Stroke}, epaint::{color::Hsva, Color32, Stroke},
}; };
use geometry::{LineEntity, LineId, PointId, PointPos, SketchEntities}; use geometry::{Line, LinePosQuery, Point, PointId, PointPos, PointPosQuery, PointPosQueryMut};
use iyes_loopless::prelude::*;
mod geometry; mod geometry;
mod optimization; mod optimization;
@ -18,7 +23,7 @@ fn main() {
); );
} }
#[derive(PartialEq)] #[derive(Clone, Copy, PartialEq)]
enum Tool { enum Tool {
Select, Select,
Move, Move,
@ -32,35 +37,29 @@ impl Default for Tool {
} }
} }
#[derive(Default)] struct ToScreen(RectTransform);
struct ViewState {
tool: Tool,
hover_point: Option<PointId>,
select_point: Option<PointId>,
hover_line: Option<LineId>,
select_line: Option<LineId>,
drag_delta: Vec2,
}
struct MyApp { impl ToScreen {
state: ViewState, fn transform_pos(&self, pos: &PointPos) -> Pos2 {
entities: SketchEntities, self.0 * Pos2::new(pos.x as f32, pos.y as f32)
} }
impl Default for MyApp { fn inverse_transform(&self, pos: Pos2) -> Pos2 {
fn default() -> Self { self.0.inverse() * pos
let mut entities = SketchEntities::default(); }
let p1 = entities.insert_point_at((10., 30.));
let p2 = entities.insert_point_at((-20., 15.)); fn inverse_transform_to_point(&self, pos: Pos2) -> PointPos {
entities.insert_point_at((0., -10.)); let pos = self.inverse_transform(pos);
entities.insert_line(LineEntity::new(p1, p2)); PointPos::new(pos.x as f64, pos.y as f64)
Self {
state: ViewState::default(),
entities,
}
} }
} }
#[derive(Component)]
struct Hovered;
#[derive(Component)]
struct Selected;
fn color_for_var_status(status: geometry::VarStatus) -> Hsva { fn color_for_var_status(status: geometry::VarStatus) -> Hsva {
use geometry::VarStatus::*; use geometry::VarStatus::*;
match status { match status {
@ -73,13 +72,359 @@ fn color_for_var_status(status: geometry::VarStatus) -> Hsva {
const POINT_RADIUS: f32 = 3.0; const POINT_RADIUS: f32 = 3.0;
fn update_hover_point(
response: Res<Response>,
to_screen: Res<ToScreen>,
point_pos: PointPosQuery,
points: Query<(PointId, &Point)>,
mut commands: Commands,
) {
points.for_each(|(id, point)| {
let hovered = if let Some(hover_pos) = response.hover_pos() {
let pos = point_pos.get(point);
let center = to_screen.transform_pos(&*pos);
(hover_pos - center).length() < (POINT_RADIUS * 3.)
} else {
false
};
if hovered {
commands.entity(id).insert(Hovered);
} else {
commands.entity(id).remove::<Hovered>();
}
});
}
fn update_hover_line(
response: Res<Response>,
to_screen: Res<ToScreen>,
lines: Query<(Entity, &Line)>,
line_pos: LinePosQuery,
mut commands: Commands,
) {
lines.for_each(|(id, line)| {
let hovered = if let Some(hover_pos) = response.hover_pos() {
let pos = line_pos.get(line);
let points = [
to_screen.transform_pos(&pos.start),
to_screen.transform_pos(&pos.end),
];
let b = points[1] - points[0];
let a = hover_pos - points[0];
let p = a.dot(b) / b.dot(b);
let perp = a - (p * b);
((0.)..=(1.)).contains(&p) && perp.length() < 5.0
} else {
false
};
if hovered {
commands.entity(id).insert(Hovered);
} else {
commands.entity(id).remove::<Hovered>();
}
});
}
fn update_cursor_icon(
response: Res<Response>,
tool: Res<Tool>,
hovered: Query<(), With<Hovered>>,
selected: Query<(), With<Selected>>,
) {
response.ctx.output().cursor_icon = match *tool {
Tool::Select => {
if !hovered.is_empty() {
CursorIcon::PointingHand
} else {
CursorIcon::Default
}
}
Tool::Move => {
if !selected.is_empty() {
CursorIcon::Grabbing
} else if !hovered.is_empty() {
CursorIcon::Grab
} else {
CursorIcon::Move
}
}
Tool::AddPoint => CursorIcon::None,
Tool::AddLine => CursorIcon::None,
};
}
fn select_tool(
response: Res<Response>,
hovered: Query<Entity, With<Hovered>>,
selected: Query<Entity, With<Selected>>,
mut commands: Commands,
) {
response.ctx.output().cursor_icon = if !hovered.is_empty() {
CursorIcon::PointingHand
} else {
CursorIcon::Default
};
if response.clicked() {
selected.for_each(|selected| {
commands.entity(selected).remove::<Selected>();
});
// TODO: choose which to select
if let Some(hovered) = hovered.iter().next() {
commands.entity(hovered).insert(Selected);
}
}
}
#[derive(Default)]
struct DragDelta(Vec2);
// TODO: move other entities
fn move_tool(
response: Res<Response>,
to_screen: Res<ToScreen>,
mut drag_delta: Local<DragDelta>,
mut point_pos: PointPosQueryMut,
hovered: Query<(Entity, &Point), With<Hovered>>,
selected: Query<(Entity, &Point), With<Selected>>,
mut commands: Commands,
) {
let hover_pos = response.hover_pos().unwrap();
response.ctx.output().cursor_icon = if !selected.is_empty() {
CursorIcon::Grabbing
} else if !hovered.is_empty() {
CursorIcon::Grab
} else {
CursorIcon::Move
};
let selected = if response.drag_started() {
// TODO: choose which to select
let to_select = hovered.iter().next();
if let Some(hovered) = to_select {
commands.entity(hovered.0).insert(Selected);
}
to_select
} else if response.drag_released() {
selected.for_each(|selected| {
commands.entity(selected.0).remove::<Selected>();
});
drag_delta.0 = Vec2::ZERO;
None
} else {
selected.iter().next()
};
if let Some((_, point)) = selected {
if response.drag_started() {
let drag_point_pos = point_pos.get(point).unwrap();
drag_delta.0 = hover_pos - to_screen.transform_pos(&*drag_point_pos);
}
let move_to = to_screen.inverse_transform_to_point(hover_pos - drag_delta.0);
point_pos.set(point, move_to).unwrap();
}
}
fn add_point(commands: &mut Commands, pos: Pos2, to_screen: &ToScreen) -> PointId {
let point_pos = to_screen.inverse_transform(pos);
let point_pos = (point_pos.x as f64, point_pos.y as f64);
geometry::insert_point_at(commands, point_pos)
}
fn add_point_tool(
response: Res<Response>,
to_screen: Res<ToScreen>,
painter: ResMut<egui::Painter>,
mut commands: Commands,
) {
let hover_pos = response.hover_pos().unwrap();
if response.clicked() {
add_point(&mut commands, hover_pos, &*to_screen);
} else {
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::WHITE);
}
}
fn add_line_tool(
response: Res<Response>,
to_screen: Res<ToScreen>,
painter: ResMut<egui::Painter>,
hovered: Query<Entity, (With<Hovered>, With<Point>)>,
selected: Query<(Entity, &Point), With<Selected>>,
point_pos: PointPosQuery,
mut commands: Commands,
) {
let hover_pos = response.hover_pos().unwrap();
match (selected.iter().next(), response.clicked()) {
(None, false) => {
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
}
(None, true) => {
let point_id = match hovered.iter().next() {
Some(hovered) => hovered,
None => add_point(&mut commands, hover_pos, &*to_screen),
};
commands.entity(point_id).insert(Selected);
}
(Some(start_point), false) => {
let start_point_pos = point_pos.get(start_point.1);
let points = [to_screen.transform_pos(&start_point_pos), hover_pos];
let stroke = Stroke::new(2.0, Color32::DARK_GRAY);
painter.line_segment(points, stroke);
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
}
(Some(start_point), true) => {
// TODO: add point if no hover point
let end_point = hovered
.iter()
.next()
.unwrap_or_else(|| add_point(&mut commands, hover_pos, &*to_screen));
let line = Line::new(start_point.0, end_point);
commands.spawn().insert(line);
selected.for_each(|selected| {
commands.entity(selected.0).remove::<Selected>();
});
}
}
}
fn is_hovered(response: Res<Response>) -> bool {
response.hover_pos().is_some()
}
fn is_tool_active(tool: Tool) -> impl System<In = (), Out = bool> {
IntoSystem::into_system(move |active_tool: Res<Tool>| *active_tool == tool)
}
fn paint_lines(
to_screen: Res<ToScreen>,
painter: ResMut<Painter>,
lines: Query<(Entity, &Line)>,
hovered: Query<(), With<Hovered>>,
selected: Query<(), With<Selected>>,
line_pos: LinePosQuery,
) {
lines.for_each(|(id, line)| {
let pos = line_pos.get(line);
let points = [
to_screen.transform_pos(&pos.start),
to_screen.transform_pos(&pos.end),
];
let mut color = color_for_var_status(pos.status);
color.v -= 0.6;
if hovered.contains(id) || selected.contains(id) {
color.s -= 0.8;
}
let stroke = Stroke::new(2.0, color);
painter.line_segment(points, stroke);
});
}
fn paint_points(
to_screen: Res<ToScreen>,
painter: ResMut<Painter>,
points: Query<(Entity, &Point)>,
hovered: Query<(), With<Hovered>>,
selected: Query<(), With<Selected>>,
point_pos: PointPosQuery,
) {
points.for_each(|(id, point)| {
let pos = point_pos.get(point);
let center = to_screen.transform_pos(&*pos);
let color = color_for_var_status(pos.status);
let stroke = if selected.contains(id) || hovered.contains(id) {
// color.s -= 0.8;
Stroke::new(1.0, Color32::WHITE)
} else {
Stroke::default()
};
painter.circle(center, POINT_RADIUS, color, stroke);
});
}
struct MyApp {
world: World,
show_entities_stage: SystemStage,
}
fn init(mut commands: Commands) {
let p1 = geometry::insert_point_at(&mut commands, (10., 30.));
let p2 = geometry::insert_point_at(&mut commands, (-20., 15.));
geometry::insert_point_at(&mut commands, (0., -10.));
commands.spawn().insert(Line::new(p1, p2));
}
impl Default for MyApp {
fn default() -> Self {
let mut world = World::default();
world.init_resource::<Tool>();
let mut init_stage = SystemStage::single_threaded().with_system(init);
init_stage.run(&mut world);
Self {
world,
show_entities_stage: Self::create_show_entities_stage(),
}
}
}
impl MyApp { impl MyApp {
fn create_show_entities_stage() -> SystemStage {
#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
struct InputGroup;
#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
struct ToolsGroup;
SystemStage::single_threaded()
.with_system_set(
ConditionSet::new()
.label("input")
.with_system(update_hover_point)
.with_system(update_hover_line)
.into(),
)
.with_system_set(
ConditionSet::new()
.label("tools")
.after("input")
.run_if(is_hovered)
.with_system(update_cursor_icon)
.with_system(select_tool.run_if(is_tool_active(Tool::Select)))
.with_system(move_tool.run_if(is_tool_active(Tool::Move)))
.with_system(add_point_tool.run_if(is_tool_active(Tool::AddPoint)))
.with_system(add_line_tool.run_if(is_tool_active(Tool::AddLine)))
.into(),
)
.with_system(paint_lines.label("paint_lines").after("tools")) // TODO order dependency
.with_system(paint_points.after("paint_lines"))
}
fn show_toolbar(&mut self, ui: &mut Ui) { fn show_toolbar(&mut self, ui: &mut Ui) {
ui.heading("sketchrs"); ui.heading("sketchrs");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Tool: "); ui.label("Tool: ");
let mut tool = &mut self.state.tool;
let tool = &mut *self.world.resource_mut::<Tool>();
ui.selectable_value(tool, Tool::Select, "Select"); ui.selectable_value(tool, Tool::Select, "Select");
ui.selectable_value(tool, Tool::Move, "Move"); ui.selectable_value(tool, Tool::Move, "Move");
ui.selectable_value(tool, Tool::AddPoint, "+ Point"); ui.selectable_value(tool, Tool::AddPoint, "+ Point");
@ -88,200 +433,44 @@ impl MyApp {
} }
fn show_entities(&mut self, ui: &mut Ui) { fn show_entities(&mut self, ui: &mut Ui) {
let mut state = &mut self.state; let sense = match *self.world.resource::<Tool>() {
let sense = match state.tool {
Tool::Move => Sense::drag(), Tool::Move => Sense::drag(),
Tool::Select | Tool::AddPoint | Tool::AddLine => Sense::click(), Tool::Select | Tool::AddPoint | Tool::AddLine => Sense::click(),
}; };
let (response, painter) = ui.allocate_painter(ui.available_size(), sense); let (response, painter) = ui.allocate_painter(ui.available_size(), sense);
let ctx = response.ctx.clone(); let to_screen = ToScreen(RectTransform::from_to(
let to_screen = RectTransform::from_to(
Rect::from_center_size(Pos2::ZERO, response.rect.size() / 2.0), Rect::from_center_size(Pos2::ZERO, response.rect.size() / 2.0),
response.rect, response.rect,
); ));
self.world.insert_resource(to_screen);
self.world.insert_resource(response);
self.world.insert_resource(painter);
let transform_pos = |pos: &PointPos| to_screen * Pos2::new(pos.x as f32, pos.y as f32); self.show_entities_stage.run(&mut self.world);
state.hover_point = None; self.world.remove_resource::<Painter>();
state.hover_line = None; self.world.remove_resource::<Response>();
self.world.remove_resource::<ToScreen>();
if let Some(hover_pos) = response.hover_pos() {
for (id, point) in self
.entities
.points()
.iter()
.filter(|(id, _)| Some(*id) != state.select_point)
{
let pos = self.entities.point_pos(point);
let center = transform_pos(&*pos);
if (hover_pos - center).length() < (POINT_RADIUS * 3.) {
state.hover_point = Some(id);
break;
}
}
for (id, line) in self.entities.lines().iter() {
let pos = self.entities.line_pos(line);
let points = [transform_pos(&pos.start), transform_pos(&pos.end)];
let b = points[1] - points[0];
let a = hover_pos - points[0];
let p = a.dot(b) / b.dot(b);
let perp = a - (p * b);
let hovered = ((0.)..=(1.)).contains(&p) && perp.length() < 5.0;
if hovered {
state.hover_line = Some(id);
break;
}
}
ctx.output().cursor_icon = match state.tool {
Tool::Select => {
if state.hover_point.is_some() {
CursorIcon::PointingHand
} else {
CursorIcon::Default
}
}
Tool::Move => {
if state.select_point.is_some() {
CursorIcon::Grabbing
} else if state.hover_point.is_some() {
CursorIcon::Grab
} else {
CursorIcon::Move
}
}
Tool::AddPoint => CursorIcon::None,
Tool::AddLine => CursorIcon::None,
};
let mut add_point = || {
let point_pos = to_screen.inverse() * hover_pos;
let point_pos = (point_pos.x as f64, point_pos.y as f64);
self.entities.insert_point_at(point_pos)
};
match state.tool {
Tool::Select => {
if response.clicked() {
state.select_point = None;
state.select_line = None;
if state.hover_point.is_some() {
state.select_point = state.hover_point;
} else if state.hover_line.is_some() {
state.select_line = state.hover_line;
}
}
}
Tool::Move => {
let drag_started = response.drag_started();
if drag_started {
state.select_point = state.hover_point;
} else if response.drag_released() {
state.select_point = None;
state.drag_delta = Vec2::ZERO;
}
if let Some(point_id) = state.select_point {
let point = self.entities.points().get(point_id).unwrap();
if drag_started {
let point_pos = self.entities.point_pos(point);
state.drag_delta = hover_pos - transform_pos(&*point_pos);
}
let move_to = to_screen.inverse() * (hover_pos - state.drag_delta);
for (id, val) in [(point.x, move_to.x), (point.y, move_to.y)] {
self.entities.get_var_mut(id).unwrap().value = val as f64;
}
}
}
Tool::AddPoint => {
if response.clicked() {
add_point();
} else {
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::WHITE);
}
}
Tool::AddLine => {
match (state.select_point, response.clicked()) {
(Some(start_point_id), true) => {
// TODO: add point if no hover point
let end_point = state.hover_point.unwrap_or_else(add_point);
let line = LineEntity::new(start_point_id, end_point);
self.entities.insert_line(line);
state.select_point = None;
}
(None, true) => {
state.select_point = Some(state.hover_point.unwrap_or_else(add_point));
}
(Some(first_point_id), false) => {
let first_point = self.entities.points().get(first_point_id).unwrap();
let first_point_pos = self.entities.point_pos(first_point);
let points = [transform_pos(&first_point_pos), hover_pos];
let stroke = Stroke::new(2.0, Color32::DARK_GRAY);
painter.line_segment(points, stroke);
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
}
(None, false) => {
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
}
}
}
}
}
for (id, line) in self.entities.lines().iter() {
let pos = self.entities.line_pos(line);
let points = [transform_pos(&pos.start), transform_pos(&pos.end)];
let mut color = color_for_var_status(pos.status);
color.v -= 0.6;
if state.hover_point.is_none()
&& (state.select_point.is_none() || state.tool != Tool::Move)
&& (Some(id) == state.hover_line || Some(id) == state.select_line)
{
color.s -= 0.8;
}
let stroke = Stroke::new(2.0, color);
painter.line_segment(points, stroke);
}
for (id, point) in self.entities.points().iter() {
let pos = self.entities.point_pos(point);
let center = transform_pos(&*pos);
let mut color = color_for_var_status(pos.status);
let stroke = match (state.select_point, state.hover_point) {
(Some(sid), _) | (_, Some(sid)) if id == sid => {
// color.s -= 0.8;
Stroke::new(1.0, Color32::WHITE)
}
_ => Stroke::default(),
};
painter.circle(center, POINT_RADIUS, color, stroke);
}
} }
fn show_side_panel(&mut self, ui: &mut Ui) { fn show_side_panel(&mut self, ui: &mut Ui) {
ui.vertical(|ui| match self.state.tool { let tool = *self.world.resource::<Tool>();
ui.vertical(|ui| match tool {
Tool::Select => { Tool::Select => {
if let Some(point_id) = self.state.select_point { let mut selected = self
ui.label(format!("Selected point {}", point_id.id())); .world
} else if let Some(line_id) = self.state.select_line { .query_filtered::<(Entity, Option<&Point>, Option<&Line>), With<Selected>>();
ui.label(format!("Selected line {}", line_id.id())); let mut count = 0;
} else { selected.for_each(&self.world, |(id, point, line)| {
count += 1;
if point.is_some() {
ui.label(format!("Selected point {}", id.id()));
} else if line.is_some() {
ui.label(format!("Selected line {}", id.id()));
}
});
if count == 0 {
ui.label("No selection"); ui.label("No selection");
} }
} }