pub mod components;
use ncollide3d::world::{CollisionGroups, GeometricQueryType, CollisionWorld, CollisionObjectHandle, CollisionObjects};
use ncollide3d::bounding_volume::*;
use ncollide3d::shape::*;
use ncollide3d::transformation::ToTriMesh;
use ncollide3d::procedural;
use ncollide3d::query::Ray;
use ncollide3d::narrow_phase::ContactPairs;
#[cfg(feature="blender")]
use rinblender;
use rinecs::{self, Entities, EntitiesThreadLocal, Read, Write, Entity, CreationContext,};
use rin::math::*;
use rin::graphics::{self, CameraExt, NodeRef, Vertex};
use std::marker::PhantomData;
use smallvec::SmallVec;
use std::cmp::Ordering;
use std::fmt::{self, Debug};
use crate::transformation::{Transformation, PreviousTransformation};
#[cfg(feature="skinning")]
use crate::skinning::components::{SkeletonRef, AnimatedGeometry};
use serde::{Serialize, Deserialize};
use rin::graphics::IndexT;
use crate::Scene;
pub struct Physics;
impl crate::Bundle for Physics{
fn setup(self, _world: &mut Scene){
}
fn register(world: &mut Scene){
world.register_component::<components::RigidBody>();
world.register_component::<Shape>();
world.register_component::<Isometry>();
world.register_component::<Offset>();
world.register_component::<CollisionHandle>();
}
}
#[derive(Component, Clone)]
#[debug_as_string]
pub struct Shape(pub ShapeHandle<f32>);
impl Debug for Shape{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result{
fmt.debug_tuple("mutiny::physics::Shape")
.finish()
}
}
#[derive(Component, Clone, Copy, Debug)]
#[debug_as_string]
pub struct Isometry(pub Isometry3);
#[derive(Component, Clone, Copy, Debug)]
#[debug_as_string]
pub struct Offset(pub Isometry3);
#[derive(Component, Clone, Copy, Debug)]
#[debug_as_string]
pub struct CollisionHandle(pub CollisionObjectHandle);
#[cfg(any(feature="gl", feature="gles", feature="webgl"))]
pub mod gpu{
use rin::graphics as graphics;
use rin::gl as gl;
#[derive(Component, Debug)]
#[debug_as_string]
pub struct DebugGeometry(pub gl::SimpleVao<graphics::Vertex3D>);
}
pub trait MouseGroup{
fn mouse_group() -> Self;
}
pub struct Collisions<Group: Into<usize>, UserData = rinecs::Entity>{
pub world: CollisionWorld<f32, UserData>,
pub contacts_query: GeometricQueryType<f32>,
_marker: PhantomData<Group>
}
unsafe impl<Group: Into<usize>> Send for Collisions<Group>{}
pub fn ray_from_screen(screen_pos: &Pnt2, camera_pos: &Pnt3, viewport: &Rect<i32>, projection: &Mat4, view: &Mat4) -> Ray<f32>{
let x = 2.0f32 * (screen_pos.x - viewport.pos.x as f32) / viewport.width as f32 - 1.0f32;
let y = 1.0f32 - 2.0f32 * (screen_pos.y - viewport.pos.y as f32) / viewport.height as f32;
let ray_nds = vec3(x,y,1.0);
let ray_clip = vec4!(ray_nds.xy(), -1.0, 1.0);
let ray_eye = projection.try_inverse().unwrap() * ray_clip;
let ray_eye = vec4!(ray_eye.xy(), -1.0, 0.0);
let ray_world = (view.fast_orthonormal_inverse() * ray_eye).xyz();
let ray_dir = ray_world.normalize();
Ray::new(*camera_pos, ray_dir)
}
pub fn ray_from_screen_camera(screen_pos: &Pnt2, viewport: &Rect<i32>, camera: &CameraExt) -> Ray<f32>{
ray_from_screen(screen_pos, &camera.position(), viewport, &camera.projection(), &camera.view())
}
pub fn ray_shape_intersection<T, S>(ray: &Ray<T>, m: &Isometry3<T>, shape: &S) -> Option<Pnt3<T>>
where T: alga::general::Real, S: ncollide3d::query::RayCast<T>
{
shape.toi_with_ray(&one(), &ray, true).map(|toi| ray.origin + ray.dir * toi)
}
impl<Group: Into<usize> + Copy, UserData> Collisions<Group, UserData>{
pub fn new() -> Collisions<Group, UserData>{
let world = CollisionWorld::new(0.02);
let contacts_query = GeometricQueryType::Contacts(0.0, 0.0);
Collisions{
world,
contacts_query,
_marker: PhantomData
}
}
pub fn add(&mut self,
group: Group,
whitelist: &[Group],
blacklist: &[Group],
data: UserData,
shape: ShapeHandle<f32>,
iso: Isometry3) -> CollisionObjectHandle
{
let mut groups = CollisionGroups::new();
groups.set_membership(&[group.into()]);
for &group in whitelist {
groups.modify_whitelist(group.into(), true)
}
for &group in blacklist {
groups.modify_blacklist(group.into(), true)
}
self.world.add(iso, shape, groups, self.contacts_query, data)
}
pub fn remove(&mut self, handles: &[CollisionObjectHandle]) {
self.world.remove(handles)
}
pub fn update(&mut self){
self.world.update();
}
pub fn contacts(&self) -> ContactPairs<f32, UserData>{
self.world.contact_pairs()
}
pub fn set_position(&mut self, handle: CollisionObjectHandle, iso: Isometry3){
self.world.set_position(handle, iso);
}
pub fn objects(&self) -> CollisionObjects<f32, UserData>{
self.world.collision_objects()
}
}
impl<Group: Into<usize> + Copy + MouseGroup, UserData> Collisions<Group, UserData>{
pub fn mouse_select(&self, whitelist: &[Group], blacklist: &[Group], mouse: &Pnt2, viewport: &Rect<i32>, camera: &CameraExt) -> Option<&UserData>{
let ray = ray_from_screen_camera(mouse, viewport, camera);
let mut collision_groups = CollisionGroups::new();
collision_groups.set_membership(&[Group::mouse_group().into()]);
for &group in whitelist {
collision_groups.modify_whitelist(group.into(), true);
}
for &group in blacklist {
collision_groups.modify_blacklist(group.into(), true);
}
let ray_collisions = self.world.interferences_with_ray(&ray, &collision_groups);
ray_collisions
.min_by_key(|c| NonNan::new(c.1.toi).unwrap())
.map(|(obj, _intersection)| obj.data())
}
}
impl<Group: Into<usize> + Copy, UserData: From<rinecs::Entity>> Collisions<Group, UserData>{
pub fn add_entity<C: CreationContext>(&mut self,
group: Group,
whitelist: &[Group],
blacklist: &[Group],
world: &mut C,
entity: Entity,
shape: ShapeHandle<f32>,
iso: Isometry3) -> CollisionObjectHandle
{
let handle = self.add(group, whitelist, blacklist, entity.into(), shape, iso);
world.add_component_to(&entity, CollisionHandle(handle));
handle
}
#[cfg(feature="skinning")]
fn add_character<C: CreationContext, V: Vertex<Position = Vec4> + Serialize + Deserialize<'static> + 'static>(&mut self,
group: Group,
whitelist: &[Group],
blacklist: &[Group],
world: &mut C,
character_entity: &Entity,
character_models: &[Entity]) -> CollisionObjectHandle
{
let (mins, maxs) = {
let character_models = world.iter_for_entities::<(
Read<Transformation>,
Read<AnimatedGeometry<V>>,
Read<SkeletonRef>,
), _>(character_models.iter().cloned());
let mut vertices = character_models.flat_map(|(trafo, geom, skeleton_ref)|{
let skeleton_trafo = world
.component_for::<Transformation>(skeleton_ref)
.unwrap();
let skeleton_scale = skeleton_trafo.scale();
let inv_scale = vec3!(1./skeleton_scale.x, 1./skeleton_scale.y, 1./skeleton_scale.z);
let scale = trafo.global_scale().component_mul(&inv_scale);
geom.geom.iter().map(move |v| v.position().xyz().component_mul(&scale).to_pnt())
});
let first = vertices.next().unwrap();
vertices.fold((first, first), |(mins, maxs), v|{
let mins = pnt3( mins.x.min(v.x), mins.y.min(v.y), mins.z.min(v.z) );
let maxs = pnt3( maxs.x.max(v.x), maxs.y.max(v.y), maxs.z.max(v.z) );
(mins, maxs)
})
};
let bounding = AABB::new(mins, maxs);
let cuboid = Cuboid::new(bounding.half_extents());
let offset = Isometry3::new(bounding.center().to_vec(), zero());
let trimesh = procedural_to_rinmesh(&cuboid.to_trimesh(()), &offset, &vec3!(1.));
let shape = ShapeHandle::new(cuboid);
let trafo = world
.component_for::<Transformation>(&character_entity)
.unwrap()
.clone();
let iso = to_iso(&trafo.node);
let handle = self.add_entity(group, whitelist, blacklist, world, *character_entity,
shape.clone(), iso * offset);
world.add_component_to(character_entity, Shape(shape));
world.add_component_to(character_entity, Isometry(iso));
world.add_component_to(character_entity, Offset(offset));
handle
}
pub fn remove_entity<C: CreationContext>(&mut self, entity: &Entity, entities: &mut C){
let handle = entities.component_for::<CollisionHandle>(entity).unwrap();
debug!("Removing handle {:?} for entity {}", *handle, entity.guid());
self.remove(&[handle.0]);
}
fn updater<'a, I: Iterator<Item = (Entity, &'a CollisionHandle, &'a Transformation, &'a Offset, &'a mut Isometry)>>(&mut self, trafos: I){
for (e, handle, trafo, offset, physics_trafo) in trafos{
physics_trafo.0 = to_iso(&trafo.node);
self.world.set_position(handle.0, physics_trafo.0 * offset.0);
}
self.world.update();
}
pub fn update_entities(&mut self, entities: &Entities){
let trafos = entities.iter_for::<(
Entity,
Read<CollisionHandle>,
Read<Transformation>,
Read<Offset>,
Write<Isometry>
)>();
self.updater(trafos);
}
pub fn update_entities_thread_local(&mut self, entities: &EntitiesThreadLocal){
let trafos = entities.iter_for::<(
Entity,
Read<CollisionHandle>,
Read<Transformation>,
Read<Offset>,
Write<Isometry>
)>();
self.updater(trafos);
}
}
#[cfg(feature="blender")]
fn collide_convex_hull(mesh: &rinblender::Mesh, scale: Vec3) -> Option<ConvexHull<f32>>{
let vertices = mesh.mvert.iter()
.map(|v| v.position.component_mul(&scale).to_pnt())
.collect::<Vec<_>>();
ConvexHull::try_from_points(&vertices)
}
#[cfg(feature="blender")]
fn blend_mesh_indices(blend_mesh: &rinblender::Mesh, offset: usize) -> Vec<Pnt3<usize>>{
if blend_mesh.mvert.is_empty() || blend_mesh.mloop.is_empty(){
return vec![];
}
let loops = &blend_mesh.mloop;
blend_mesh.mpoly.iter().flat_map(|poly|{
let loopstart = poly.loopstart as usize;
let totloop = poly.totloop;
if totloop == 4{
let v0 = loops[loopstart].v as usize + offset;
let v1 = loops[loopstart+1].v as usize + offset;
let v2 = loops[loopstart+2].v as usize + offset;
let v3 = loops[loopstart+3].v as usize + offset;
vec![Pnt3::new(v0,v1,v2), Pnt3::new(v2,v3,v0)]
}else if totloop == 3{
let v0 = loops[loopstart].v as usize + offset;
let v1 = loops[loopstart+1].v as usize + offset;
let v2 = loops[loopstart+2].v as usize + offset;
vec![Pnt3::new(v0, v1, v2)]
}else if totloop > 4{
let v0 = loops[loopstart];
loops[loopstart + 1 .. loopstart + totloop as usize]
.windows(2)
.map(|v1v2| pnt3!(v0.v as usize + offset, v1v2[0].v as usize + offset, v1v2[1].v as usize + offset))
.collect()
}else{
vec![]
}
}).collect()
}
#[cfg(feature="blender")]
fn collide_blend_mesh(mesh: &rinblender::Mesh, scale: Vec3) -> TriMesh<f32>{
let vertices = mesh.mvert.iter()
.map(|v| v.position.component_mul(&scale).to_pnt())
.collect();
let indices = blend_mesh_indices(mesh, 0);
TriMesh::new(
vertices,
indices,
None,
)
}
pub fn rinmesh_to_trimesh(mesh: &graphics::Mesh3D, scale: Vec3) -> TriMesh<f32>{
let vertices = mesh.iter()
.map(|v| v.position.component_mul(&scale).to_pnt())
.collect();
let indices = if mesh.indices().is_empty(){
mesh.vertices().chunks(3).enumerate()
.map(|(idx, _)| pnt3(idx*3, idx*3+1, idx*3+2))
.collect::<Vec<_>>()
}else{
mesh.indices().chunks(3)
.map(|idx| pnt3(idx[0] as usize, idx[1] as usize, idx[2] as usize))
.collect()
};
TriMesh::new(
vertices,
indices,
None,
)
}
#[inline]
fn transform(p: Pnt3, offset: &Isometry3, scale: &Vec3) -> Pnt3{
(offset * p).to_vec().component_mul(scale).to_pnt()
}
#[cfg(feature="blender")]
fn bounding_box(mesh: &rinblender::Mesh, node: &graphics::Node, scale: Option<Vec3>) -> AABB<f32>{
let one = Isometry3::identity();
let scale = scale.unwrap_or(vec3!(1.)).component_mul(&node.global_scale());
aabb(&collide_blend_mesh(mesh, scale), &one)
}
fn trimesh_to_rinmesh(trimesh: &TriMesh<f32>, scale: Vec3) -> graphics::Mesh3D{
let vertices = trimesh.vertices().iter()
.map(|p| graphics::vertex3d(p.to_vec().component_mul(&scale)) );
let indices = trimesh.indices().iter()
.flat_map(|p| SmallVec::from([p.x as IndexT, p.y as IndexT, p.z as IndexT]))
.collect();
graphics::Mesh::from_vertices_indices(vertices.collect(), indices)
}
pub fn procedural_to_rinmesh(trimesh: &procedural::TriMesh<f32>, offset: &Isometry3, scale: &Vec3) -> graphics::Mesh3D{
let vertices = trimesh.coords.iter().map(|p| p.to_vec())
.map(|p| {
let pos = transform(p.to_pnt(), offset, scale);
graphics::vertex3d(pos.to_vec())
});
let indices = match trimesh.indices {
procedural::IndexBuffer::Unified(ref indices) => indices.iter()
.flat_map(|p| SmallVec::from([p.x as IndexT, p.y as IndexT, p.z as IndexT]))
.collect(),
procedural::IndexBuffer::Split(ref indices) => {
indices.iter()
.flat_map(|p|{
SmallVec::from([p.x.x as IndexT, p.y.x as IndexT, p.z.x as IndexT])
})
.collect()
}
};
graphics::Mesh::from_vertices_indices(vertices.collect(), indices)
}
pub fn to_iso(n: &graphics::Node) -> Isometry3{
Isometry3::from_parts(Translation::from(n.global_position().to_vec()), n.global_orientation())
}
#[derive(PartialEq,PartialOrd)]
pub struct NonNan(pub f32);
impl NonNan {
pub fn new(val: f32) -> Option<NonNan> {
if val.is_nan() {
None
} else {
Some(NonNan(val))
}
}
}
impl Eq for NonNan {}
impl Ord for NonNan {
fn cmp(&self, other: &NonNan) -> Ordering {
self.partial_cmp(other).unwrap()
}
}