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",
]
[[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]]
name = "atomic_refcell"
version = "0.1.8"
@ -115,6 +146,157 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "bincode"
version = "1.3.3"
@ -174,12 +356,24 @@ dependencies = [
"syn",
]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "calloop"
version = "0.9.3"
@ -190,6 +384,17 @@ dependencies = [
"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]]
name = "cast"
version = "0.2.7"
@ -295,6 +500,15 @@ dependencies = [
"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]]
name = "core-foundation"
version = "0.7.0"
@ -655,6 +869,15 @@ dependencies = [
"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]]
name = "error-code"
version = "2.3.1"
@ -665,6 +888,27 @@ dependencies = [
"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]]
name = "fnv"
version = "1.0.7"
@ -696,6 +940,42 @@ dependencies = [
"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]]
name = "gethostname"
version = "0.2.3"
@ -730,6 +1010,16 @@ dependencies = [
"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]]
name = "glow"
version = "0.11.2"
@ -825,6 +1115,10 @@ name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
"serde",
]
[[package]]
name = "hermit-abi"
@ -895,6 +1189,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "jni"
version = "0.19.0"
@ -1389,6 +1695,12 @@ dependencies = [
"ttf-parser",
]
[[package]]
name = "parking"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -1761,15 +2073,23 @@ name = "sketchrs"
version = "0.1.0"
dependencies = [
"argmin",
"bevy_ecs",
"criterion",
"eframe",
"indexmap",
"iyes_loopless",
"levenberg-marquardt",
"nalgebra",
"nalgebra-sparse",
"nohash-hasher",
]
[[package]]
name = "slab"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
[[package]]
name = "slog"
version = "2.7.0"
@ -1827,6 +2147,9 @@ name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
dependencies = [
"serde",
]
[[package]]
name = "smithay-client-toolkit"
@ -1974,6 +2297,7 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"indexmap",
"serde",
]
@ -2060,12 +2384,28 @@ dependencies = [
"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]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "walkdir"
version = "2.3.2"

View File

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

View File

@ -1,131 +1,139 @@
mod entity;
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 type Scalar = f64;
#[derive(Clone, Debug)]
pub struct PointEntity<V = VarId> {
#[derive(Clone, Debug, Component)]
pub struct Point<V = VarId> {
pub x: V,
pub y: V,
}
impl<V> PointEntity<V> {
impl<V> Point<V> {
pub fn new(x: V, y: V) -> Self {
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 {
Self { x: p.0, y: p.1 }
}
}
pub type PointPos = PointEntity<Scalar>;
pub type PointId = EntityId<PointEntity>;
pub type PointPos = Point<Scalar>;
pub type PointId = Entity;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Component)]
// TODO: segment, ray and full line
// TODO: what if start and end are coincident?
pub struct LineEntity<P = PointId> {
pub struct Line<P = PointId> {
pub start: P,
pub end: P,
}
impl<P> LineEntity<P> {
impl<P> Line<P> {
pub fn new(start: P, end: P) -> Self {
Self { start, end }
}
}
pub type LinePos = LineEntity<PointPos>;
pub type LineId = EntityId<LineEntity>;
pub type LinePos = Line<PointPos>;
#[derive(Clone)]
#[derive(Clone, Component)]
// TODO: arc. how to represent start and end of arc?
// angles, unit vectors, or a point for each?
pub struct CircleEntity {
pub struct Circle {
pub center: PointId,
pub radius: VarId,
}
// pub type CirclePos = CircleEntity<PointPos>;
pub type CircleId = EntityId<CircleEntity>;
#[derive(Clone, Default)]
pub struct SketchEntities {
vars: EntityMap<Var>,
points: EntityMap<PointEntity>,
lines: EntityMap<LineEntity>,
circles: EntityMap<CircleEntity>,
pub fn insert_point_at(commands: &mut Commands, point: impl Into<PointPos>) -> PointId {
let point = point.into();
let x = commands.spawn().insert(Var::new_free(point.x)).id();
let y = commands.spawn().insert(Var::new_free(point.y)).id();
commands.spawn().insert(Point::new(x, y)).id()
}
impl SketchEntities {
pub fn vars(&self) -> &EntityMap<Var> {
&self.vars
// TODO: figure out generic for this
#[derive(SystemParam)]
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 insert_var(&mut self, var: Var) -> VarId {
self.vars.insert(var)
}
pub fn get_var_mut(&mut self, id: VarId) -> Option<&mut Var> {
self.vars.get_mut(id)
}
pub fn points(&self) -> &EntityMap<PointEntity> {
&self.points
}
pub fn insert_point(&mut self, point: PointEntity) -> PointId {
assert!(self.vars.contains(point.x));
assert!(self.vars.contains(point.y));
self.points.insert(point)
}
pub fn insert_point_at(&mut self, point: impl Into<PointPos>) -> PointId {
let point = point.into();
let x = self.insert_var(Var::new_free(point.x));
let y = self.insert_var(Var::new_free(point.y));
self.insert_point(PointEntity::new(x, y))
}
fn remove_point(&mut self, point_id: PointId) -> bool {
// TODO: check if used by any line
self.points.remove(point_id)
}
pub fn point_pos(&self, point: &PointEntity) -> Var<PointPos> {
// TODO: error handling?
let x = self.vars.get(point.x).unwrap();
let y = self.vars.get(point.y).unwrap();
x.merge(*y, PointPos::new)
}
pub fn lines(&self) -> &EntityMap<LineEntity> {
&self.lines
}
pub fn insert_line(&mut self, line: LineEntity) -> LineId {
assert!(self.points.contains(line.start));
assert!(self.points.contains(line.end));
self.lines.insert(line)
}
fn remove_line(&mut self, line_id: LineId) -> bool {
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)
pub fn get(&self, point: &Point) -> Var<PointPos> {
self.try_get(point).unwrap()
}
}
#[derive(SystemParam)]
pub struct PointPosQueryMut<'w, 's> {
vars: Query<'w, 's, &'static mut Var>,
}
impl<'w, 's> PointPosQueryMut<'w, 's>
// impl<'w, 's, R> PointPosQueryImpl<'w, 's, Query<'w, 's, R>>
// where
// R: WorldQuery,
// for<'w2, 's2> <<R as WorldQuery>::Fetch as Fetch<'w2, 's2>>::Item: DerefMut<Target = Var>,
{
pub fn 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 set(&mut self, point: &Point, move_to: PointPos) -> Result<(), QueryEntityError> {
for (id, val) in [(point.x, move_to.x), (point.y, move_to.y)] {
self.vars.get_mut(id)?.deref_mut().value = val;
}
Ok(())
}
}
#[derive(SystemParam)]
pub struct LinePosQuery<'w, 's> {
point_pos: PointPosQuery<'w, 's>,
points: Query<'w, 's, &'static Point>,
}
impl<'w, 's> LinePosQuery<'w, 's> {
pub fn get(&self, line: &Line) -> Var<LinePos> {
self.try_get(line).unwrap()
}
pub fn try_get(&self, line: &Line) -> Result<Var<LinePos>, QueryEntityError> {
// TODO: error handling?
let start = self.points.get(line.start)?;
let start = self.point_pos.try_get(start)?;
let end = self.points.get(line.end)?;
let end = self.point_pos.try_get(end)?;
Ok(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)]
pub enum VarStatus {
@ -20,7 +22,7 @@ impl VarStatus {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Component)]
pub struct Var<T = Scalar> {
pub value: T,
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::{
egui::{self, CursorIcon, Frame, PointerButton, Sense, Ui},
egui::{self, CursorIcon, Painter, Response, Sense, Ui},
emath::{Pos2, Rect, RectTransform, Vec2},
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 optimization;
@ -18,7 +23,7 @@ fn main() {
);
}
#[derive(PartialEq)]
#[derive(Clone, Copy, PartialEq)]
enum Tool {
Select,
Move,
@ -32,35 +37,29 @@ impl Default for Tool {
}
}
#[derive(Default)]
struct ViewState {
tool: Tool,
hover_point: Option<PointId>,
select_point: Option<PointId>,
hover_line: Option<LineId>,
select_line: Option<LineId>,
drag_delta: Vec2,
}
struct ToScreen(RectTransform);
struct MyApp {
state: ViewState,
entities: SketchEntities,
}
impl ToScreen {
fn transform_pos(&self, pos: &PointPos) -> Pos2 {
self.0 * Pos2::new(pos.x as f32, pos.y as f32)
}
impl Default for MyApp {
fn default() -> Self {
let mut entities = SketchEntities::default();
let p1 = entities.insert_point_at((10., 30.));
let p2 = entities.insert_point_at((-20., 15.));
entities.insert_point_at((0., -10.));
entities.insert_line(LineEntity::new(p1, p2));
Self {
state: ViewState::default(),
entities,
}
fn inverse_transform(&self, pos: Pos2) -> Pos2 {
self.0.inverse() * pos
}
fn inverse_transform_to_point(&self, pos: Pos2) -> PointPos {
let pos = self.inverse_transform(pos);
PointPos::new(pos.x as f64, pos.y as f64)
}
}
#[derive(Component)]
struct Hovered;
#[derive(Component)]
struct Selected;
fn color_for_var_status(status: geometry::VarStatus) -> Hsva {
use geometry::VarStatus::*;
match status {
@ -73,13 +72,359 @@ fn color_for_var_status(status: geometry::VarStatus) -> Hsva {
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 {
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) {
ui.heading("sketchrs");
ui.horizontal(|ui| {
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::Move, "Move");
ui.selectable_value(tool, Tool::AddPoint, "+ Point");
@ -88,200 +433,44 @@ impl MyApp {
}
fn show_entities(&mut self, ui: &mut Ui) {
let mut state = &mut self.state;
let sense = match state.tool {
let sense = match *self.world.resource::<Tool>() {
Tool::Move => Sense::drag(),
Tool::Select | Tool::AddPoint | Tool::AddLine => Sense::click(),
};
let (response, painter) = ui.allocate_painter(ui.available_size(), sense);
let ctx = response.ctx.clone();
let to_screen = RectTransform::from_to(
let to_screen = ToScreen(RectTransform::from_to(
Rect::from_center_size(Pos2::ZERO, response.rect.size() / 2.0),
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;
state.hover_line = None;
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);
}
self.world.remove_resource::<Painter>();
self.world.remove_resource::<Response>();
self.world.remove_resource::<ToScreen>();
}
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 => {
if let Some(point_id) = self.state.select_point {
ui.label(format!("Selected point {}", point_id.id()));
} else if let Some(line_id) = self.state.select_line {
ui.label(format!("Selected line {}", line_id.id()));
} else {
let mut selected = self
.world
.query_filtered::<(Entity, Option<&Point>, Option<&Line>), With<Selected>>();
let mut count = 0;
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");
}
}