diff --git a/Cargo.toml b/Cargo.toml index 1f4990111..b09ec8d05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,4 +57,5 @@ members = [ "examples/todomvc", "examples/two_apps", "examples/webgl", + "examples/boids", ] diff --git a/examples/boids/.gitignore b/examples/boids/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/examples/boids/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/examples/boids/Cargo.toml b/examples/boids/Cargo.toml new file mode 100644 index 000000000..b1bf5b022 --- /dev/null +++ b/examples/boids/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "boids" +version = "0.1.0" +authors = ["motoki saito "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +rand = { version = "0.7.3", features = ["wasm-bindgen"] } +yew = { path = "../../yew" } diff --git a/examples/boids/src/boid.rs b/examples/boids/src/boid.rs new file mode 100644 index 000000000..2f850a1d0 --- /dev/null +++ b/examples/boids/src/boid.rs @@ -0,0 +1,111 @@ +use crate::vector::Vector; +use rand::Rng; +use std::f64::consts::PI; + +const HEIGHT: f64 = 400.0; +const WIDTH: f64 = 600.0; +const VELOCITY_SIZE: f64 = 5.0; +const ALIGNMENT_RADIUS: f64 = 100.0; +const ALIGNMENT_WEIGHT: f64 = 3.0; +const COHESION_RADIUS: f64 = 200.0; +const COHESION_WEIGHT: f64 = 1.0; +const SEPARATION_RADIUS: f64 = 50.0; +const SEPARATION_WEIGHT: f64 = 1.0; + +#[derive(Clone, PartialEq, Eq)] +pub struct Boid { + pub position: Vector, + pub velocity: Vector, +} + +impl Boid { + pub fn new(rng: &mut rand::rngs::ThreadRng) -> Boid { + let theta = rng.gen::() * PI * 2.0; + Boid { + position: Vector::new(WIDTH * rng.gen::(), HEIGHT * rng.gen::()), + velocity: Vector::new(theta.cos() * VELOCITY_SIZE, theta.sin() * VELOCITY_SIZE), + } + } + + fn calc_alignment(&self, boids: &[Boid]) -> Vector { + let mut ret = Vector::new(0.0, 0.0); + for other in boids { + let mut position = other.position.clone(); + position -= self.position.clone(); + let position_size = position.size(); + if position_size == 0.0 || position_size > ALIGNMENT_RADIUS { + continue; + } + + ret += other.velocity.clone(); + } + + ret.normalize(); + ret *= ALIGNMENT_WEIGHT; + ret + } + + fn calc_cohesion(&self, boids: &[Boid]) -> Vector { + let mut ret = Vector::new(0.0, 0.0); + for other in boids { + let mut position = other.position.clone(); + position -= self.position.clone(); + let position_size = position.size(); + if position_size == 0.0 || position_size > COHESION_RADIUS { + continue; + } + + ret += position; + } + + ret.normalize(); + ret *= COHESION_WEIGHT; + ret + } + + fn calc_separation(&self, boids: &[Boid]) -> Vector { + let mut ret = Vector::new(0.0, 0.0); + for other in boids { + let mut position = other.position.clone(); + position -= self.position.clone(); + let position_size = position.size(); + if position_size == 0.0 || position_size > SEPARATION_RADIUS { + continue; + } + + position /= position.size_square(); + ret -= position; + } + + ret.normalize(); + ret *= SEPARATION_WEIGHT; + ret + } + + fn move_self(&mut self) { + self.position += self.velocity.clone(); + if self.position.x < 0.0 { + self.position.x += WIDTH; + } else if self.position.x > WIDTH { + self.position.x -= WIDTH; + } + + if self.position.y < 0.0 { + self.position.y += HEIGHT; + } else if self.position.y > HEIGHT { + self.position.y -= HEIGHT; + } + } + + pub fn next_state(&mut self, boids: &[Boid]) { + let mut acceleration = Vector::new(0.0, 0.0); + acceleration += self.calc_separation(boids); + acceleration += self.calc_cohesion(boids); + acceleration += self.calc_alignment(boids); + self.velocity += acceleration; + self.velocity.normalize(); + self.velocity *= VELOCITY_SIZE; + + self.move_self(); + } +} diff --git a/examples/boids/src/lib.rs b/examples/boids/src/lib.rs new file mode 100644 index 000000000..020ac8a54 --- /dev/null +++ b/examples/boids/src/lib.rs @@ -0,0 +1,87 @@ +mod boid; +mod vector; + +use crate::boid::Boid; +use crate::vector::Vector; +use rand::prelude::thread_rng; +use std::f64::consts::PI; +use std::time::Duration; +use yew::prelude::{html, Component, ComponentLink, Html, ShouldRender}; +use yew::services::{IntervalService, Task}; + +pub struct Model { + boids: Vec, + #[allow(unused)] + job: Box, +} + +pub enum Msg { + Tick, +} + +const WIDTH: u64 = 600; +const HEIGHT: u64 = 400; + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + let callback = link.callback(|_| Msg::Tick); + let handle = IntervalService::spawn(Duration::from_millis(50), callback); + + let mut rng = thread_rng(); + Self { + boids: (0..100).map(|_| Boid::new(&mut rng)).collect(), + job: Box::new(handle), + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Tick => { + let boids = self.boids.clone(); + for boid in &mut self.boids { + boid.next_state(&boids); + } + } + } + true + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + let boids = (&self.boids).iter().map(|boid| boid2triangle(&boid)); + html! { + + { for boids } + + } + } +} + +fn boid2triangle(boid: &Boid) -> Html { + let points = get_points_str(&boid.position, &boid.velocity); + html! { + + } +} + +fn get_points_str(position: &Vector, velocity: &Vector) -> String { + let direction = velocity.y.atan2(velocity.x); + let size = 10.0; + let convert_position = |i: usize| { + ( + position.x + size * (direction + ((i as f64) * 2.0 * PI / 3.0)).cos(), + position.y + size * (direction + ((i as f64) * 2.0 * PI / 3.0)).sin(), + ) + }; + (0..3) + .map(convert_position) + .map(|(x, y): (f64, f64)| format!("{},{}", x, y)) + .collect::>() + .join(" ") +} diff --git a/examples/boids/src/main.rs b/examples/boids/src/main.rs new file mode 100644 index 000000000..f6bb1454b --- /dev/null +++ b/examples/boids/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/boids/src/vector.rs b/examples/boids/src/vector.rs new file mode 100644 index 000000000..344e88eb6 --- /dev/null +++ b/examples/boids/src/vector.rs @@ -0,0 +1,59 @@ +use std::ops::{AddAssign, DivAssign, MulAssign, SubAssign}; + +#[derive(Clone, PartialEq)] +pub struct Vector { + pub x: f64, + pub y: f64, +} + +impl AddAssign for Vector { + fn add_assign(&mut self, other: Vector) { + self.x += other.x; + self.y += other.y; + } +} + +impl SubAssign for Vector { + fn sub_assign(&mut self, other: Vector) { + self.x -= other.x; + self.y -= other.y; + } +} + +impl MulAssign for Vector { + fn mul_assign(&mut self, scalar: f64) { + self.x *= scalar; + self.y *= scalar; + } +} + +impl DivAssign for Vector { + fn div_assign(&mut self, scalar: f64) { + self.x /= scalar; + self.y /= scalar; + } +} + +impl Vector { + pub fn new(x: f64, y: f64) -> Vector { + Vector { x, y } + } + + pub fn size(&self) -> f64 { + (self.x * self.x + self.y * self.y).sqrt() + } + + pub fn normalize(&mut self) { + let size = self.size(); + if size == 0.0 { + return; + } + *self /= size; + } + + pub fn size_square(&self) -> f64 { + self.x * self.x + self.y * self.y + } +} + +impl Eq for Vector {}