Skip to content

Commit

Permalink
fix/field trait (#4)
Browse files Browse the repository at this point in the history
* Convert Field into a trait

* Box Field instances in Vec

* Store bodies in Vec and implement update()

* Add Environment constructor

* Add Environment description

* Update header file for Environment

* Update ffi for Environment

* Change return type of Distributor to Vec<Body>

* Refactor gravitational force into new module

* Add Attractor force

* Replace sun with Attractor

* Tidy up

* Add Gravity tests

* Add Attractor test

* Remove unneccesary Body id

* Guard against negative min_dist
  • Loading branch information
johnxnguyen authored Nov 25, 2018
1 parent 8ff6d98 commit 1292bb8
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 187 deletions.
52 changes: 16 additions & 36 deletions src/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use geometry::types::{Point, Vector};
use physics::types::{Body, Field};
use geometry::types::{Point};
use physics::types::{Body, Environment};

//////////////////////////////////////////////////////////////////////////////

Expand All @@ -19,40 +19,20 @@ impl NewtonPoint {
}

#[no_mangle]
pub extern "C" fn newton_new_field(
g: f32,
solar_mass: f32,
min_dist: f32,
max_dist: f32,
) -> *mut Field {
let field = Field::new(g, solar_mass, min_dist, max_dist);
let boxed = Box::new(field);
pub extern "C" fn newton_new_environment() -> *mut Environment {
let environment = Environment::new();
let boxed = Box::new(environment);
Box::into_raw(boxed)
}

#[no_mangle]
pub unsafe extern "C" fn newton_destroy_field(field: *mut Field) {
let _ = Box::from_raw(field);
}

#[no_mangle]
pub unsafe extern "C" fn newton_add_body(
field: *mut Field,
id: u32,
mass: f32,
x: f32,
y: f32,
dx: f32,
dy: f32,
) {
let body = Body::new(id, mass, Point { x, y }, Vector { dx, dy });
let field = &mut *field;
field.bodies.insert(id, body);
pub unsafe extern "C" fn newton_destroy_environment(environment: *mut Environment) {
let _ = Box::from_raw(environment);
}

#[no_mangle]
pub unsafe extern "C" fn newton_distribute_bodies(
field: *mut Field,
environment: *mut Environment,
num_bodies: u32,
min_dist: f32,
max_dist: f32,
Expand All @@ -66,20 +46,20 @@ pub unsafe extern "C" fn newton_distribute_bodies(
};

let bodies = distributor.distribution();
let field = &mut *field;
field.bodies = bodies;
let environment = &mut *environment;
environment.bodies = bodies;
}

#[no_mangle]
pub unsafe extern "C" fn newton_step(field: *mut Field) {
let field = &mut *field;
field.update()
pub unsafe extern "C" fn newton_step(environment: *mut Environment) {
let environment = &mut *environment;
environment.update()
}

#[no_mangle]
pub unsafe extern "C" fn newton_body_pos(field: *const Field, id: u32) -> NewtonPoint {
let field = &*field;
match field.bodies.get(&id) {
pub unsafe extern "C" fn newton_body_pos(environment: *const Environment, id: u32) -> NewtonPoint {
let environment = &*environment;
match environment.bodies.get(id as usize) {
Some(val) => NewtonPoint::from(&((val as &Body).position)),
None => NewtonPoint {
x: 0.0,
Expand Down
11 changes: 5 additions & 6 deletions src/geometry/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use physics::types::Body;
use rand::prelude::*;
use std::f32::consts::PI;
use std::ops::Mul;
use std::collections::HashMap;

// Transformation ////////////////////////////////////////////////////////////
//
Expand Down Expand Up @@ -40,12 +39,12 @@ pub struct Distributor {
}

impl Distributor {
pub fn distribution(&self) -> HashMap<u32, Body> {
let mut result: HashMap<u32, Body> = HashMap::new();
pub fn distribution(&self) -> Vec<Body> {
let mut result: Vec<Body> = vec![];
let mut angle_rand = thread_rng();
let mut dist_rand = thread_rng();

for i in 0..self.num_bodies {
for _ in 0..self.num_bodies {
let angle = angle_rand.gen_range(0.0, 2.0 * PI);
let dist = dist_rand.gen_range(self.min_dist, self.max_dist);

Expand All @@ -65,8 +64,8 @@ impl Distributor {
y: position.dy,
};

let body = Body::new(i, 0.1, pos, velocity);
result.insert(i, body);
let body = Body::new(0.1, pos, velocity);
result.push(body);
}

result
Expand Down
70 changes: 24 additions & 46 deletions src/newton.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,34 @@

#include <stdint.h>

/**
* A simple wrapper struct to encapsulate
* point data.
*/
// A simple wrapper struct to encapsulate point data.
struct NewtonPoint {
float x;
float y;
};

/**
* A opaque struct representing the
* gravitational field.
*/
struct field;

/**
* Allocates a new field instance with the given gravitational
* constant, solar mass, min and max effective distance.
*/
struct field *newton_new_field(float g, float solar_mass, float min_dist, float max_dist);

/**
* Destroys the field instance referred
* to by the given pointer.
*/
void newton_destroy_field(struct field *field);

/**
* Creates a new body instance with the
* given properties (id, mass, position, velocity)
* and adds it to the field.
*/
void newton_add_body(struct field *field, uint32_t id, float mass, float x, float y, float dx, float dy);

/**
* Generates a radial distribution of num_bodies between
* min_dist and max_dist from a central point. These are
* assigned to the field.
*/
void newton_distribute_bodies(struct field *field, uint32_t num_bodies, float min_dist, float max_dist, float dy);

/**
* Advances the field state by a single step.
*/
void newton_step(struct field *field);

/**
* Returns the coordinate of the body with the
* given id, if it exists, else the origin.
*/
struct NewtonPoint newton_body_pos(const struct field *field, uint32_t id);
// An opaque struct representing the environment.
//
struct environment;

// Allocates a new Environment instance.
//
struct environment *newton_new_environment();

// Destroys the Environment instance referred to by the given pointer.
//
void newton_destroy_environment(struct environment *environment);

// Generates a radial distribution of bodies around a central point.
//
void newton_distribute_bodies(struct environment *environment, uint32_t num_bodies, float min_dist, float max_dist, float dy);

// Advances teh field state by a single step.
//
void newton_step(struct environment *environment);

// Returns the coordinates of the body with the given ID, if it exists,
// else the origin is returned.
struct NewtonPoint newton_body_pos(const struct environment *environment, uint32_t id);

#endif //NEWTON_NEWTON_H
177 changes: 177 additions & 0 deletions src/physics/force.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use super::types::Body;
use geometry::types::{Point, Vector};

// Gravity ///////////////////////////////////////////////////////////////////
//
// Newton's Law of Universal Gravitation.

pub struct Gravity {
g: f32,
min_dist: f32,
}

impl Gravity {
pub fn new(g: f32, min_dist: f32) -> Gravity {
if min_dist <= 0.0 {
panic!("The minimum gravitational distance \
must be greater than 0. Got {}", min_dist);
}
Gravity {
g,
min_dist,
}
}

pub fn between(&self, b1: &Body, b2: &Body) -> Vector {
// Force is undefined for two bodies that occupy the same space.
if b1.position == b2.position {
return Vector::zero();
}

let difference = Vector::difference(&b2.position, &b1.position);
let distance = difference.magnitude().max(self.min_dist);
let force = (self.g * b1.mass * b2.mass) / (distance * distance);

let direction = match difference.normalized() {
None => Vector::zero(),
Some(normalized) => normalized,
};

&direction * force
}
}

// Attractor /////////////////////////////////////////////////////////////////
//
// Gravitational attraction to a point.

pub struct Attractor {
body: Body,
gravity: Gravity,
}

impl Attractor {
pub fn new(mass: f32, point: Point, g: f32, min_dist: f32) -> Attractor {
Attractor {
body: Body::new(mass, point, Vector::zero()),
gravity: Gravity::new(g, min_dist),
}
}

pub fn force(&self, body: &Body) -> Vector {
self.gravity.between(body, &self.body)
}
}

// Tests /////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
use super::{Gravity, Attractor, Body};
use geometry::types::{Point, Vector};

#[test]
#[should_panic(expected = "The minimum gravitational distance must be greater than 0.")]
fn gravity_with_negative_minimum_distance() {
// given
Gravity::new(1.0, -2.0);
}

#[test]
fn gravity_calculates_force() {
// given
let sut = Gravity::new(1.5, 4.0);

let b1 = Body::new(
1.0,
Point { x: 1.0, y: 2.0},
Vector::zero()
);

let b2 = Body::new(
2.0,
Point { x: -3.5, y: 0.0},
Vector::zero()
);

// when
let result = sut.between(&b1, &b2);

// then
assert_eq!(result, Vector { dx: -0.1130488514, dy: -0.0502439339});
}

#[test]
fn gravity_obeys_minimum_distance() {
// given
let sut = Gravity::new(1.5, 4.0);

let b1 = Body::new(
1.0,
Point { x: 1.0, y: 2.0},
Vector::zero()
);

let b2 = Body::new(
2.0,
Point { x: 2.0, y: 2.5},
Vector::zero()
);

assert!(b1.position.distance_to(&b2.position) < 4.0);

// when
let result = sut.between(&b1, &b2);

// then
let result_if_dist_was_4 = Vector { dx: 0.1677050983, dy: 0.0838525491};
assert_eq!(result, result_if_dist_was_4);
}

#[test]
fn gravity_is_undefined_for_bodies_with_equal_position() {
// given
let sut = Gravity::new(1.5, 4.0);

let b1 = Body::new(
1.0,
Point { x: 1.0, y: 2.0},
Vector::zero()
);

let b2 = Body::new(
2.0,
Point { x: 1.0, y: 2.0},
Vector::zero()
);

// when
let result = sut.between(&b1, &b2);

// then
assert_eq!(result, Vector::zero());
}

#[test]
fn attractor_calculates_force() {
// given
let sut = Attractor::new(
100.0,
Point::origin(),
2.3,
1.0
);

let body = Body::new(
3.8,
Point { x: 1.0, y: 2.0},
Vector::zero()
);

// when
let result = sut.force(&body);

// then
assert_eq!(result, Vector { dx: -78.17293649, dy: -156.345873});
}
}
1 change: 1 addition & 0 deletions src/physics/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod types;
pub mod force;
Loading

0 comments on commit 1292bb8

Please sign in to comment.