Add boid example (#1540)

* It appears!

* boid example works well (#1539)

* remove clippy and fmt warnings (#1539)

* fix typo and remove vector.add, sub, mul (#1539)

* remove triangle

* rename the filename

* remove fmt errors

* define size square function (#1540)
This commit is contained in:
Motoki saito 2020-09-17 00:55:08 +09:00 committed by GitHub
parent 008577ebb4
commit 63b79bf213
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 277 additions and 0 deletions

View File

@ -57,4 +57,5 @@ members = [
"examples/todomvc",
"examples/two_apps",
"examples/webgl",
"examples/boids",
]

2
examples/boids/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

14
examples/boids/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "boids"
version = "0.1.0"
authors = ["motoki saito <stmtk13044032@gmail.com>"]
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" }

111
examples/boids/src/boid.rs Normal file
View File

@ -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::<f64>() * PI * 2.0;
Boid {
position: Vector::new(WIDTH * rng.gen::<f64>(), HEIGHT * rng.gen::<f64>()),
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();
}
}

87
examples/boids/src/lib.rs Normal file
View File

@ -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<Boid>,
#[allow(unused)]
job: Box<dyn Task>,
}
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>) -> 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! {
<svg width=WIDTH height=HEIGHT viewBox={ format!("0 0 {} {}", WIDTH, HEIGHT) } xmlns={ "http://www.w3.org/2000/svg" }>
{ for boids }
</svg>
}
}
}
fn boid2triangle(boid: &Boid) -> Html {
let points = get_points_str(&boid.position, &boid.velocity);
html! {
<polygon points=points />
}
}
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::<Vec<String>>()
.join(" ")
}

View File

@ -0,0 +1,3 @@
fn main() {
yew::start_app::<boids::Model>();
}

View File

@ -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<f64> for Vector {
fn mul_assign(&mut self, scalar: f64) {
self.x *= scalar;
self.y *= scalar;
}
}
impl DivAssign<f64> 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 {}