Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions oxide_physics/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "oxide_physics"
version = "0.0.1"
authors = [
"Chris Ohk <[email protected]>",
"Changseo Jang <[email protected]>",
"Yongwook Choi <[email protected]>",
"Chaneun Yeo <[email protected]>",
"Seokwon Moon <[email protected]>",
"Oxide Engine"
]
edition = "2021"
description = "Physics library for Oxide"

repository = "https://github.com/OxideEngine/Oxide"

license = "MIT"

[lib]
name = "oxide_physics"
path = "src/lib.rs"

[dependencies]
oxide_math = {path = "../oxide_math", version = "0.0.1"}
num-traits = "0.2"
generational-arena = "0.2"
81 changes: 81 additions & 0 deletions oxide_physics/src/aabb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::collide_broad_phase::{BoundingVolume, HasBoundingVolume};
use oxide_math::commons::vector3::Vector3;

pub struct AABB {
pub mins: Vector3,
pub maxs: Vector3,
}

pub fn aabb<S>(shape: &S, tv: Vector3) -> AABB
where
S: HasBoundingVolume<AABB>,
{
shape.bounding_volume(tv)
}

pub fn local_aabb<S>(shape: &S) -> AABB
where
S: HasBoundingVolume<AABB>,
{
shape.local_bounding_volume()
}

impl AABB {
pub fn new(mins: Vector3, maxs: Vector3) -> AABB {
AABB { mins, maxs }
}

pub fn mins(&self) -> Vector3 {
Vector3 {
x: self.mins.x,
y: self.mins.y,
z: self.mins.z,
}
}

pub fn maxs(&self) -> Vector3 {
Vector3 {
x: self.maxs.x,
y: self.maxs.y,
z: self.maxs.z,
}
}
}

impl BoundingVolume for AABB {
// check if the bounding volume 'bv' intersects with self
fn intersects(&self, other: &AABB) -> bool {
self.mins.x <= other.maxs.x
&& self.mins.y <= other.maxs.y
&& self.mins.z <= other.maxs.z
&& self.maxs.x >= other.mins.x
&& self.maxs.y >= other.mins.y
&& self.maxs.z >= other.mins.z
}

// check if self contains the 'bv'
fn contains(&self, other: &AABB) -> bool {
self.mins.x <= other.mins.x
&& self.mins.y <= other.mins.y
&& self.mins.z <= other.mins.z
&& self.maxs.x >= other.maxs.x
&& self.maxs.y >= other.maxs.y
&& self.maxs.z >= other.maxs.z
}

// merge this bounding volume with the other 'bv'
fn merged(&self, other: &AABB) -> AABB {
AABB {
mins: Vector3 {
x: self.mins.x.min(other.mins.x),
y: self.mins.y.min(other.mins.y),
z: self.mins.z.min(other.mins.z),
},
maxs: Vector3 {
x: self.maxs.x.max(other.maxs.x),
y: self.maxs.y.max(other.maxs.y),
z: self.maxs.z.max(other.maxs.z),
},
}
}
}
40 changes: 40 additions & 0 deletions oxide_physics/src/aabb_ball.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::aabb::AABB;
use crate::collide_broad_phase::HasBoundingVolume;
use crate::shape::Ball;
use oxide_math::commons::vector3::Vector3;

pub fn ball_aabb(center: Vector3, radius: f32) -> AABB {
AABB::new(
Vector3 {
x: center.x - radius,
y: center.y - radius,
z: center.z - radius,
},
Vector3 {
x: center.x + radius,
y: center.y + radius,
z: center.z + radius,
},
)
}

pub fn local_ball_aabb(radius: f32) -> AABB {
ball_aabb(
Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
},
radius,
)
}

impl HasBoundingVolume<AABB> for Ball {
fn bounding_volume(&self, tv: Vector3) -> AABB {
ball_aabb(tv, self.radius())
}

fn local_bounding_volume(&self) -> AABB {
local_ball_aabb(self.radius())
}
}
23 changes: 23 additions & 0 deletions oxide_physics/src/aabb_cuboid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::aabb::AABB;
use crate::collide_broad_phase::HasBoundingVolume;
use crate::shape::Cuboid;
use oxide_math::commons::vector3::Vector3;

impl HasBoundingVolume<AABB> for Cuboid {
fn bounding_volume(&self, tv: Vector3) -> AABB {
let tv2 = Vector3 {
x: tv.x,
y: tv.y,
z: tv.z,
};
AABB::new(self.mins() + tv, self.maxs() + tv2)
}

fn local_bounding_volume(&self) -> AABB {
self.bounding_volume(Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
})
}
}
26 changes: 26 additions & 0 deletions oxide_physics/src/collide_broad_phase.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use oxide_math::commons::vector3::Vector3;

pub trait BoundingVolume {
// check if the bounding volume 'bv' intersects with self
fn intersects(&self, bv: &Self) -> bool;

// check if self contains the 'bv'
fn contains(&self, bv: &Self) -> bool;

// merge this bounding volume with the other 'bv'
fn merged(&self, bv: &Self) -> Self;
}

pub trait HasBoundingVolume<BV> {
// TBD: rotation by 4x4 matrix
// bounding volume of 'self' translated by 'tv'
fn bounding_volume(&self, tv: Vector3) -> BV;

fn local_bounding_volume(&self) -> BV {
self.bounding_volume(Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
})
}
}
18 changes: 18 additions & 0 deletions oxide_physics/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pub mod particle;
pub mod pfgen;

pub mod shape;

pub mod aabb;
mod aabb_ball;
mod aabb_cuboid;
pub mod collide_broad_phase;

#[cfg(test)]
mod tests {
#[test]
fn aabb_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
148 changes: 148 additions & 0 deletions oxide_physics/src/particle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#![allow(dead_code)]
#![allow(unused_variables)]

use num_traits::pow;
use oxide_math::commons::vector::*;
use oxide_math::commons::vector3::Vector3;

extern crate generational_arena;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extern crate is no longer needed.

use generational_arena::Arena;

pub struct Particle {
inverse_mass: f32,
damping: f32,
position: Vector3,
pub velocity: Vector3,
force_accum: Vector3,
acceleration: Vector3,
}

impl Particle {
// returns integrated velocity
fn integrate(&mut self, duration: f32) -> Result<Vector3, &str> {
// not to integrate things with infinite mass
if self.inverse_mass <= 0.0f32 {
return Ok(Vector3 {
x: self.velocity.x,
y: self.velocity.y,
z: self.velocity.z,
});
}
if duration <= 0.0 {
return Err("Cannot integrate with zero duration");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think defining custom error using enum is better than using string as error

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// update linear position
self.position.x += self.velocity.scale(duration).x;
self.position.y += self.velocity.scale(duration).y;
self.position.z += self.velocity.scale(duration).z;

// work out the acceleration from the force
let delta = self.force_accum.scale(self.inverse_mass);
let resulting_acc = Vector3 {
x: self.acceleration.x + delta.x,
y: self.acceleration.y + delta.y,
z: self.acceleration.z + delta.z,
};

// update linear velocity from the acceleration
self.velocity.x += resulting_acc.scale(duration).x;
self.velocity.y += resulting_acc.scale(duration).y;
self.velocity.z += resulting_acc.scale(duration).z;

// impose drag
self.velocity = self.velocity.scale(pow(self.damping, duration as usize));

Particle::clear_accumulator(self);

Ok(Vector3 {
x: self.velocity.x,
y: self.velocity.y,
z: self.velocity.z,
})
}

// Returns inverse of mass
fn set_mass(&mut self, mass: f32) -> Result<f32, &str> {
if mass == 0.0f32 {
return Err("Cannot set zero mass");
}
self.inverse_mass = (1.0f32) / mass;
Ok(self.inverse_mass)
}

// Returns mass of the particle
pub fn get_mass(&self) -> f32 {
if self.inverse_mass == 0.0f32 {
f32::MAX
} else {
1.0f32 / self.inverse_mass
}
}

// Returns the velocity of the particle
pub fn get_velocity(&self) -> Vector3 {
Vector3 {
x: self.velocity.x,
y: self.velocity.y,
z: self.velocity.z,
}
}

pub fn has_finite_mass(&self) -> bool {
self.inverse_mass > 0.0f32
}

fn clear_accumulator(&mut self) {
self.force_accum = Vector3 {
x: 0.0f32,
y: 0.0f32,
z: 0.0f32,
};
}

pub fn add_force(&mut self, force: &Vector3) {
self.force_accum = Vector3 {
x: self.force_accum.x + force.x,
y: self.force_accum.y + force.y,
z: self.force_accum.z + force.z,
};
}
}

// The default particle set containing all the particles added to the world
// Uses arena to avoid ABA problem
pub struct DefaultParticleSet {
particles: Arena<Box<Particle>>,
removed: Vec<DefaultParticleHandle>,
}

impl DefaultParticleSet {
// Creates an empty set
pub fn new() -> Self {
DefaultParticleSet {
particles: Arena::new(),
removed: Vec::new(),
}
}

// Adds a particle to this set
pub fn insert(&mut self, particle: Particle) -> DefaultParticleHandle {
self.particles.insert(Box::new(particle))
}

// Removes a particle from this set
pub fn remove(&mut self, particle_handle: DefaultParticleHandle) -> Option<Box<Particle>> {
let result = self.particles.remove(particle_handle)?;
self.removed.push(particle_handle);
Some(result)
}
}

impl Default for DefaultParticleSet {
fn default() -> Self {
Self::new()
}
}

pub type DefaultParticleHandle = generational_arena::Index;
Loading