Use Bevy ECS
This commit is contained in:
parent
dafebdabee
commit
fdd93fa7e8
340
Cargo.lock
generated
340
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
170
src/geometry.rs
170
src/geometry.rs
@ -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>,
|
||||
}
|
||||
|
||||
impl SketchEntities {
|
||||
pub fn vars(&self) -> &EntityMap<Var> {
|
||||
&self.vars
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn insert_point_at(commands: &mut Commands, 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))
|
||||
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()
|
||||
}
|
||||
|
||||
fn remove_point(&mut self, point_id: PointId) -> bool {
|
||||
// TODO: check if used by any line
|
||||
self.points.remove(point_id)
|
||||
// TODO: figure out generic for this
|
||||
#[derive(SystemParam)]
|
||||
pub struct PointPosQuery<'w, 's> {
|
||||
vars: Query<'w, 's, &'static Var>,
|
||||
}
|
||||
|
||||
pub fn point_pos(&self, point: &PointEntity) -> Var<PointPos> {
|
||||
// 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> {
|
||||
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 x = self.vars.get(point.x).unwrap();
|
||||
let y = self.vars.get(point.y).unwrap();
|
||||
x.merge(*y, PointPos::new)
|
||||
}
|
||||
let start = self.points.get(line.start)?;
|
||||
let start = self.point_pos.try_get(start)?;
|
||||
|
||||
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)
|
||||
let end = self.points.get(line.end)?;
|
||||
let end = self.point_pos.try_get(end)?;
|
||||
Ok(start.merge(end, LinePos::new))
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
487
src/main.rs
487
src/main.rs
@ -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);
|
||||
|
||||
impl ToScreen {
|
||||
fn transform_pos(&self, pos: &PointPos) -> Pos2 {
|
||||
self.0 * Pos2::new(pos.x as f32, pos.y as f32)
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
state: ViewState,
|
||||
entities: SketchEntities,
|
||||
fn inverse_transform(&self, pos: Pos2) -> Pos2 {
|
||||
self.0.inverse() * pos
|
||||
}
|
||||
|
||||
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_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,84 +72,81 @@ fn color_for_var_status(status: geometry::VarStatus) -> Hsva {
|
||||
|
||||
const POINT_RADIUS: f32 = 3.0;
|
||||
|
||||
impl MyApp {
|
||||
fn show_toolbar(&mut self, ui: &mut Ui) {
|
||||
ui.heading("sketchrs");
|
||||
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);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Tool: ");
|
||||
let mut tool = &mut self.state.tool;
|
||||
ui.selectable_value(tool, Tool::Select, "Select");
|
||||
ui.selectable_value(tool, Tool::Move, "Move");
|
||||
ui.selectable_value(tool, Tool::AddPoint, "+ Point");
|
||||
ui.selectable_value(tool, Tool::AddLine, "+ Line");
|
||||
(hover_pos - center).length() < (POINT_RADIUS * 3.)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if hovered {
|
||||
commands.entity(id).insert(Hovered);
|
||||
} else {
|
||||
commands.entity(id).remove::<Hovered>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn show_entities(&mut self, ui: &mut Ui) {
|
||||
let mut state = &mut self.state;
|
||||
|
||||
let sense = match state.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(
|
||||
Rect::from_center_size(Pos2::ZERO, response.rect.size() / 2.0),
|
||||
response.rect,
|
||||
);
|
||||
|
||||
let transform_pos = |pos: &PointPos| to_screen * Pos2::new(pos.x as f32, pos.y as f32);
|
||||
|
||||
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)];
|
||||
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);
|
||||
let hovered = ((0.)..=(1.)).contains(&p) && perp.length() < 5.0;
|
||||
((0.)..=(1.)).contains(&p) && perp.length() < 5.0
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if hovered {
|
||||
state.hover_line = Some(id);
|
||||
break;
|
||||
commands.entity(id).insert(Hovered);
|
||||
} else {
|
||||
commands.entity(id).remove::<Hovered>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ctx.output().cursor_icon = match state.tool {
|
||||
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 state.hover_point.is_some() {
|
||||
if !hovered.is_empty() {
|
||||
CursorIcon::PointingHand
|
||||
} else {
|
||||
CursorIcon::Default
|
||||
}
|
||||
}
|
||||
Tool::Move => {
|
||||
if state.select_point.is_some() {
|
||||
if !selected.is_empty() {
|
||||
CursorIcon::Grabbing
|
||||
} else if state.hover_point.is_some() {
|
||||
} else if !hovered.is_empty() {
|
||||
CursorIcon::Grab
|
||||
} else {
|
||||
CursorIcon::Move
|
||||
@ -159,71 +155,126 @@ impl MyApp {
|
||||
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)
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
#[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);
|
||||
}
|
||||
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;
|
||||
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)
|
||||
}
|
||||
Tool::AddPoint => {
|
||||
|
||||
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();
|
||||
add_point(&mut commands, hover_pos, &*to_screen);
|
||||
} 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);
|
||||
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();
|
||||
|
||||
state.select_point = None;
|
||||
match (selected.iter().next(), response.clicked()) {
|
||||
(None, false) => {
|
||||
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
|
||||
}
|
||||
(None, true) => {
|
||||
state.select_point = Some(state.hover_point.unwrap_or_else(add_point));
|
||||
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(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];
|
||||
(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);
|
||||
|
||||
@ -231,57 +282,195 @@ impl MyApp {
|
||||
|
||||
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
|
||||
}
|
||||
(None, false) => {
|
||||
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>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)];
|
||||
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 state.hover_point.is_none()
|
||||
&& (state.select_point.is_none() || state.tool != Tool::Move)
|
||||
&& (Some(id) == state.hover_line || Some(id) == state.select_line)
|
||||
{
|
||||
if hovered.contains(id) || selected.contains(id) {
|
||||
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);
|
||||
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 = transform_pos(&*pos);
|
||||
let center = to_screen.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 => {
|
||||
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)
|
||||
}
|
||||
_ => Stroke::default(),
|
||||
} 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 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");
|
||||
ui.selectable_value(tool, Tool::AddLine, "+ Line");
|
||||
});
|
||||
}
|
||||
|
||||
fn show_entities(&mut self, ui: &mut Ui) {
|
||||
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 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);
|
||||
|
||||
self.show_entities_stage.run(&mut self.world);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user