Compare commits
20 Commits
rbx-dom_ra
...
debug-bug7
Author | SHA1 | Date | |
---|---|---|---|
d0f7799448
|
|||
81c9e3470b
|
|||
b45e02c487
|
|||
8698ca4a7e
|
|||
8f04953326
|
|||
ae81d8ceaf
|
|||
2ecaeb1615
|
|||
768cd4ad1a
|
|||
708462441a
|
|||
da3ab52fe0
|
|||
20f3e79cde
|
|||
217f7fd7c3
|
|||
1e4d98f386
|
|||
71bce361e6
|
|||
d36c184f7e
|
|||
2c3f257f0e
|
|||
9e7e115809
|
|||
2ea60b07fe
|
|||
2fe884175e
|
|||
9f570d0f3e
|
engine/physics/src
lib
bsp_loader
src
common
src
deferred_loader
rbx_loader
@ -1,7 +1,10 @@
|
||||
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
||||
use crate::model::{into_giga_time,GigaTime,FEV,MeshQuery,DirectedEdge};
|
||||
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
||||
use crate::physics::{Time,Body};
|
||||
|
||||
use core::ops::Bound;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Transition<M:MeshQuery>{
|
||||
Miss,
|
||||
Next(FEV<M>,GigaTime),
|
||||
@ -27,17 +30,60 @@ impl<M:MeshQuery> CrawlResult<M>{
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move predict_collision_face_out algorithm in here or something
|
||||
|
||||
/// check_lower_bound
|
||||
pub fn low<LhsNum,LhsDen,RhsNum,RhsDen,T>(lower_bound:&Bound<Ratio<LhsNum,LhsDen>>,dt:&Ratio<RhsNum,RhsDen>)->bool
|
||||
where
|
||||
RhsNum:Copy,
|
||||
RhsDen:Copy,
|
||||
LhsNum:Copy,
|
||||
LhsDen:Copy,
|
||||
LhsDen:strafesnet_common::integer::Parity,
|
||||
RhsDen:strafesnet_common::integer::Parity,
|
||||
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
||||
LhsDen:core::ops::Mul<RhsNum,Output=T>,
|
||||
T:Ord+Copy,
|
||||
{
|
||||
match lower_bound{
|
||||
Bound::Included(time)=>time.le_ratio(*dt),
|
||||
Bound::Excluded(time)=>time.lt_ratio(*dt),
|
||||
Bound::Unbounded=>true,
|
||||
}
|
||||
}
|
||||
/// check_upper_bound
|
||||
pub fn upp<LhsNum,LhsDen,RhsNum,RhsDen,T>(dt:&Ratio<LhsNum,LhsDen>,upper_bound:&Bound<Ratio<RhsNum,RhsDen>>)->bool
|
||||
where
|
||||
RhsNum:Copy,
|
||||
RhsDen:Copy,
|
||||
LhsNum:Copy,
|
||||
LhsDen:Copy,
|
||||
LhsDen:strafesnet_common::integer::Parity,
|
||||
RhsDen:strafesnet_common::integer::Parity,
|
||||
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
||||
LhsDen:core::ops::Mul<RhsNum,Output=T>,
|
||||
T:Ord+Copy,
|
||||
{
|
||||
match upper_bound{
|
||||
Bound::Included(time)=>dt.le_ratio(*time),
|
||||
Bound::Excluded(time)=>dt.lt_ratio(*time),
|
||||
Bound::Unbounded=>true,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
where
|
||||
// This is hardcoded for MinkowskiMesh lol
|
||||
M::Face:Copy,
|
||||
M::Edge:Copy,
|
||||
M::Vert:Copy,
|
||||
M:std::fmt::Debug,
|
||||
F:std::fmt::Display,
|
||||
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
|
||||
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
|
||||
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
||||
M::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
||||
{
|
||||
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
|
||||
fn next_transition(&self,mesh:&M,body:&Body,lower_bound:Bound<GigaTime>,mut upper_bound:Bound<GigaTime>)->Transition<M>{
|
||||
//conflicting derivative means it crosses in the wrong direction.
|
||||
//if the transition time is equal to an already tested transition, do not replace the current best.
|
||||
let mut best_transition=Transition::Miss;
|
||||
@ -47,11 +93,16 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
//n=face.normal d=face.dot
|
||||
//n.a t^2+n.v t+n.p-d==0
|
||||
let (n,d)=mesh.face_nd(face_id);
|
||||
println!("Face n={} d={}",n,d);
|
||||
//TODO: use higher precision d value?
|
||||
//use the mesh transform translation instead of baking it into the d value.
|
||||
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
let low=low(&lower_bound,&dt);
|
||||
let upp=upp(&dt,&upper_bound);
|
||||
let into=n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative();
|
||||
println!("dt={} low={low} upp={upp} into={into}",dt.divide());
|
||||
if low&&upp&&into{
|
||||
upper_bound=Bound::Included(dt);
|
||||
best_transition=Transition::Hit(face_id,dt);
|
||||
break;
|
||||
}
|
||||
@ -65,8 +116,8 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
//WARNING: precision is swept under the rug!
|
||||
//wrap for speed
|
||||
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(v0)+mesh.vert(v1))).wrap_4(),n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
upper_bound=Bound::Included(dt);
|
||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||
break;
|
||||
}
|
||||
@ -84,11 +135,17 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
let face_n=mesh.face_nd(edge_face_id).0;
|
||||
//edge_n gets parity from the order of edge_faces
|
||||
let n=face_n.cross(edge_n)*((i as i64)*2-1);
|
||||
let d=n.dot(delta_pos).wrap_4();
|
||||
println!("Edge Face={:?} boundary_n={} boundary_d={}",edge_face_id,n,d>>1);
|
||||
//WARNING yada yada d *2
|
||||
//wrap for speed
|
||||
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).wrap_4(),n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
for dt in Fixed::<4,128>::zeroes2(d,n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
|
||||
let low=low(&lower_bound,&dt);
|
||||
let upp=upp(&dt,&upper_bound);
|
||||
let into=n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative();
|
||||
println!("dt={} low={low} upp={upp} into={into}",dt.divide());
|
||||
if low&&upp&&into{
|
||||
upper_bound=Bound::Included(dt);
|
||||
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
||||
break;
|
||||
}
|
||||
@ -99,9 +156,9 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
//vertex normal gets parity from vert index
|
||||
let n=edge_n*(1-2*(i as i64));
|
||||
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
let dt=Ratio::new(dt.num.widen_4(),dt.den.widen_4());
|
||||
best_time=dt;
|
||||
upper_bound=Bound::Included(dt);
|
||||
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
|
||||
break;
|
||||
}
|
||||
@ -115,9 +172,9 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
//edge is directed away from vertex, but we want the dot product to turn out negative
|
||||
let n=-mesh.directed_edge_n(directed_edge_id);
|
||||
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
let dt=Ratio::new(dt.num.widen_4(),dt.den.widen_4());
|
||||
best_time=dt;
|
||||
upper_bound=Bound::Included(dt);
|
||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||
break;
|
||||
}
|
||||
@ -128,19 +185,16 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
}
|
||||
best_transition
|
||||
}
|
||||
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
|
||||
let mut body_time={
|
||||
let r=(start_time-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num.widen_4(),r.den.widen_4())
|
||||
};
|
||||
let time_limit={
|
||||
let r=(time_limit-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num.widen_4(),r.den.widen_4())
|
||||
};
|
||||
pub fn crawl(mut self,mesh:&M,relative_body:&Body,lower_bound:Bound<&Time>,upper_bound:Bound<&Time>)->CrawlResult<M>{
|
||||
let mut lower_bound=lower_bound.map(|&t|into_giga_time(t,relative_body.time));
|
||||
let upper_bound=upper_bound.map(|&t|into_giga_time(t,relative_body.time));
|
||||
println!("crawl begin={self:?}");
|
||||
for _ in 0..20{
|
||||
match self.next_transition(body_time,mesh,relative_body,time_limit){
|
||||
let transition=self.next_transition(mesh,relative_body,lower_bound,upper_bound);
|
||||
println!("transition={transition:?}");
|
||||
match transition{
|
||||
Transition::Miss=>return CrawlResult::Miss(self),
|
||||
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
|
||||
Transition::Next(next_fev,next_time)=>(self,lower_bound)=(next_fev,Bound::Included(next_time)),
|
||||
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use std::collections::{HashSet,HashMap};
|
||||
use core::ops::Range;
|
||||
use core::ops::{Bound,RangeBounds};
|
||||
use strafesnet_common::integer::vec3::Vector3;
|
||||
use strafesnet_common::model::{self,MeshId,PolygonIter};
|
||||
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
|
||||
@ -68,16 +68,17 @@ pub enum FEV<M:MeshQuery>{
|
||||
}
|
||||
|
||||
//use Unit32 #[repr(C)] for map files
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
struct Face{
|
||||
normal:Planar64Vec3,
|
||||
dot:Planar64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Vert(Planar64Vec3);
|
||||
pub trait MeshQuery{
|
||||
type Face:Copy;
|
||||
type Edge:Copy+DirectedEdge;
|
||||
type Vert:Copy;
|
||||
type Face:Copy+std::fmt::Debug;
|
||||
type Edge:Copy+DirectedEdge+std::fmt::Debug;
|
||||
type Vert:Copy+std::fmt::Debug;
|
||||
// Vertex must be Planar64Vec3 because it represents an actual position
|
||||
type Normal;
|
||||
type Offset;
|
||||
@ -97,18 +98,22 @@ pub trait MeshQuery{
|
||||
fn vert_edges(&self,vert_id:Self::Vert)->impl AsRef<[Self::Edge]>;
|
||||
fn vert_faces(&self,vert_id:Self::Vert)->impl AsRef<[Self::Face]>;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct FaceRefs{
|
||||
edges:Vec<SubmeshDirectedEdgeId>,
|
||||
//verts:Vec<VertId>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct EdgeRefs{
|
||||
faces:[SubmeshFaceId;2],//left, right
|
||||
verts:[SubmeshVertId;2],//bottom, top
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct VertRefs{
|
||||
faces:Vec<SubmeshFaceId>,
|
||||
edges:Vec<SubmeshDirectedEdgeId>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicsMeshData{
|
||||
//this contains all real and virtual faces used in both the complete mesh and convex submeshes
|
||||
//faces are sorted such that all faces that belong to the complete mesh appear first, and then
|
||||
@ -118,6 +123,7 @@ pub struct PhysicsMeshData{
|
||||
faces:Vec<Face>,//MeshFaceId indexes this list
|
||||
verts:Vec<Vert>,//MeshVertId indexes this list
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicsMeshTopology{
|
||||
//mapping of local ids to PhysicsMeshData ids
|
||||
faces:Vec<MeshFaceId>,//SubmeshFaceId indexes this list
|
||||
@ -143,10 +149,12 @@ impl From<MeshId> for PhysicsMeshId{
|
||||
pub struct PhysicsSubmeshId(u32);
|
||||
pub struct PhysicsMesh{
|
||||
data:PhysicsMeshData,
|
||||
complete_mesh:PhysicsMeshTopology,
|
||||
//Most objects in roblox maps are already convex, so the list length is 0
|
||||
//as soon as the mesh is divided into 2 submeshes, the list length jumps to 2.
|
||||
//length 1 is unnecessary since the complete mesh would be a duplicate of the only submesh, but would still function properly
|
||||
// The complete mesh is unused at this time.
|
||||
// complete_mesh:PhysicsMeshTopology,
|
||||
// Submeshes are guaranteed to be convex and may contain
|
||||
// "virtual" faces which are not part of the complete mesh.
|
||||
// Physics calculations should never resolve to hitting
|
||||
// a virtual face.
|
||||
submeshes:Vec<PhysicsMeshTopology>,
|
||||
}
|
||||
impl PhysicsMesh{
|
||||
@ -210,19 +218,24 @@ impl PhysicsMesh{
|
||||
};
|
||||
Self{
|
||||
data,
|
||||
complete_mesh:mesh_topology,
|
||||
submeshes:Vec::new(),
|
||||
// complete_mesh:mesh_topology.clone(),
|
||||
submeshes:vec![mesh_topology],
|
||||
}
|
||||
}
|
||||
pub fn unit_cylinder()->Self{
|
||||
Self::unit_cube()
|
||||
}
|
||||
#[inline]
|
||||
pub const fn complete_mesh(&self)->&PhysicsMeshTopology{
|
||||
&self.complete_mesh
|
||||
pub fn complete_mesh(&self)->&PhysicsMeshTopology{
|
||||
// If there is exactly one submesh, then the complete mesh is identical to it.
|
||||
if self.submeshes.len()==1{
|
||||
self.submeshes.first().unwrap()
|
||||
}else{
|
||||
panic!("PhysicsMesh complete mesh is not known");
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn complete_mesh_view(&self)->PhysicsMeshView{
|
||||
pub fn complete_mesh_view(&self)->PhysicsMeshView{
|
||||
PhysicsMeshView{
|
||||
data:&self.data,
|
||||
topology:self.complete_mesh(),
|
||||
@ -230,12 +243,7 @@ impl PhysicsMesh{
|
||||
}
|
||||
#[inline]
|
||||
pub fn submeshes(&self)->&[PhysicsMeshTopology]{
|
||||
//the complete mesh is already a convex mesh when len()==0, len()==1 is invalid but will still work
|
||||
if self.submeshes.len()==0{
|
||||
std::slice::from_ref(&self.complete_mesh)
|
||||
}else{
|
||||
&self.submeshes.as_slice()
|
||||
}
|
||||
&self.submeshes
|
||||
}
|
||||
#[inline]
|
||||
pub fn submesh_view(&self,submesh_id:PhysicsSubmeshId)->PhysicsMeshView{
|
||||
@ -313,14 +321,20 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
||||
if mesh.unique_pos.len()==0{
|
||||
return Err(PhysicsMeshError::ZeroVertices);
|
||||
}
|
||||
// An empty physics mesh is a waste of resources
|
||||
if mesh.physics_groups.len()==0{
|
||||
return Err(PhysicsMeshError::NoPhysicsGroups);
|
||||
}
|
||||
let verts=mesh.unique_pos.iter().copied().map(Vert).collect();
|
||||
//TODO: fix submeshes
|
||||
//flat map mesh.physics_groups[$1].groups.polys()[$2] as face_id
|
||||
//lower face_id points to upper face_id
|
||||
//the same face is not allowed to be in multiple polygon groups
|
||||
// because SubmeshFaceId -> CompleteMeshFaceId -> SubmeshFaceId is ambiguous
|
||||
// when multiple SubmeshFaceId point to one MeshFaceId
|
||||
let mut faces=Vec::new();
|
||||
let mut face_id_from_face=HashMap::new();
|
||||
let mut mesh_topologies:Vec<PhysicsMeshTopology>=mesh.physics_groups.iter().map(|physics_group|{
|
||||
let mesh_topologies:Vec<PhysicsMeshTopology>=mesh.physics_groups.iter().map(|physics_group|{
|
||||
//construct submesh
|
||||
let mut submesh_faces=Vec::new();//these contain a map from submeshId->meshId
|
||||
let mut submesh_verts=Vec::new();
|
||||
@ -381,15 +395,11 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
||||
normal:(normal/len as i64).divide().narrow_1().unwrap(),
|
||||
dot:(dot/(len*len) as i64).narrow_1().unwrap(),
|
||||
};
|
||||
let face_id=match face_id_from_face.get(&face){
|
||||
Some(&face_id)=>face_id,
|
||||
None=>{
|
||||
let face_id=MeshFaceId::new(faces.len() as u32);
|
||||
face_id_from_face.insert(face.clone(),face_id);
|
||||
faces.push(face);
|
||||
face_id
|
||||
}
|
||||
};
|
||||
let face_id=*face_id_from_face.entry(face).or_insert_with(||{
|
||||
let face_id=MeshFaceId::new(faces.len() as u32);
|
||||
faces.push(face);
|
||||
face_id
|
||||
});
|
||||
submesh_faces.push(face_id);
|
||||
face_ref_guys.push(FaceRefEdges(face_edges));
|
||||
}
|
||||
@ -397,16 +407,16 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
||||
PhysicsMeshTopology{
|
||||
faces:submesh_faces,
|
||||
verts:submesh_verts,
|
||||
face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{
|
||||
FaceRefs{edges:face_ref_guy.0}
|
||||
face_topology:face_ref_guys.into_iter().map(|FaceRefEdges(edges)|{
|
||||
FaceRefs{edges}
|
||||
}).collect(),
|
||||
edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)|
|
||||
EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0}
|
||||
edge_topology:edge_pool.edge_guys.into_iter().map(|(EdgeRefVerts(verts),EdgeRefFaces(faces))|
|
||||
EdgeRefs{faces,verts}
|
||||
).collect(),
|
||||
vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy|
|
||||
vert_topology:vert_ref_guys.into_iter().map(|VertRefGuy{edges,faces}|
|
||||
VertRefs{
|
||||
edges:vert_ref_guy.edges.into_iter().collect(),
|
||||
faces:vert_ref_guy.faces.into_iter().collect(),
|
||||
edges:edges.into_iter().collect(),
|
||||
faces:faces.into_iter().collect(),
|
||||
}
|
||||
).collect(),
|
||||
}
|
||||
@ -416,12 +426,13 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
||||
faces,
|
||||
verts,
|
||||
},
|
||||
complete_mesh:mesh_topologies.pop().ok_or(PhysicsMeshError::NoPhysicsGroups)?,
|
||||
// complete_mesh:None,
|
||||
submeshes:mesh_topologies,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicsMeshView<'a>{
|
||||
data:&'a PhysicsMeshData,
|
||||
topology:&'a PhysicsMeshTopology,
|
||||
@ -458,6 +469,7 @@ impl MeshQuery for PhysicsMeshView<'_>{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicsMeshTransform{
|
||||
pub vertex:integer::Planar64Affine3,
|
||||
pub normal:integer::mat3::Matrix3<Fixed<2,64>>,
|
||||
@ -473,6 +485,7 @@ impl PhysicsMeshTransform{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TransformedMesh<'a>{
|
||||
view:PhysicsMeshView<'a>,
|
||||
transform:&'a PhysicsMeshTransform,
|
||||
@ -598,6 +611,7 @@ pub enum MinkowskiFace{
|
||||
//FaceFace
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MinkowskiMesh<'a>{
|
||||
mesh0:TransformedMesh<'a>,
|
||||
mesh1:TransformedMesh<'a>,
|
||||
@ -615,6 +629,10 @@ enum EV{
|
||||
}
|
||||
|
||||
pub type GigaTime=Ratio<Fixed<4,128>,Fixed<4,128>>;
|
||||
pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{
|
||||
let r=(time-relative_to).to_ratio();
|
||||
Ratio::new(r.num.widen_4(),r.den.widen_4())
|
||||
}
|
||||
|
||||
impl MinkowskiMesh<'_>{
|
||||
pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{
|
||||
@ -730,44 +748,46 @@ impl MinkowskiMesh<'_>{
|
||||
//
|
||||
// Most of the calculation time is just calculating the starting point
|
||||
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
|
||||
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Time)->Option<FEV<MinkowskiMesh>>{
|
||||
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Bound<&Time>)->Option<FEV<MinkowskiMesh>>{
|
||||
infinity_body.infinity_dir().and_then(|dir|{
|
||||
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
||||
//a line is simpler to solve than a parabola
|
||||
infinity_body.velocity=dir;
|
||||
infinity_body.acceleration=vec3::ZERO;
|
||||
//crawl in from negative infinity along a tangent line to get the closest fev
|
||||
// TODO: change crawl_fev args to delta time? Optional values?
|
||||
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,start_time).miss()
|
||||
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,start_time).miss()
|
||||
})
|
||||
}
|
||||
pub fn predict_collision_in(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||
self.closest_fev_not_inside(relative_body.clone(),start_time).and_then(|fev|{
|
||||
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||
println!("@@@BEGIN SETUP@@@");
|
||||
self.closest_fev_not_inside(relative_body.clone(),range.start_bound()).and_then(|fev|{
|
||||
println!("@@@BEGIN REAL CRAWL@@@");
|
||||
//continue forwards along the body parabola
|
||||
fev.crawl(self,relative_body,start_time,time_limit).hit()
|
||||
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
|
||||
})
|
||||
}
|
||||
pub fn predict_collision_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||
//create an extrapolated body at time_limit
|
||||
pub fn predict_collision_out(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||
let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound());
|
||||
// swap and negate bounds to do a time inversion
|
||||
let (lower_bound,upper_bound)=(upper_bound.map(|&t|-t),lower_bound.map(|&t|-t));
|
||||
let infinity_body=-relative_body.clone();
|
||||
self.closest_fev_not_inside(infinity_body,-time_limit).and_then(|fev|{
|
||||
self.closest_fev_not_inside(infinity_body,lower_bound.as_ref()).and_then(|fev|{
|
||||
//continue backwards along the body parabola
|
||||
fev.crawl(self,&infinity_body,-time_limit,-start_time).hit()
|
||||
fev.crawl(self,&infinity_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
|
||||
//no need to test -time<time_limit because of the first step
|
||||
.map(|(face,time)|(face,-time))
|
||||
})
|
||||
}
|
||||
pub fn predict_collision_face_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
||||
pub fn predict_collision_face_out(&self,relative_body:&Body,range:impl RangeBounds<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiDirectedEdge,GigaTime)>{
|
||||
// TODO: make better
|
||||
use crate::face_crawler::{low,upp};
|
||||
//no algorithm needed, there is only one state and two cases (Edge,None)
|
||||
//determine when it passes an edge ("sliding off" case)
|
||||
let start_time={
|
||||
let r=(start_time-relative_body.time).to_ratio();
|
||||
let start_time=range.start_bound().map(|&t|{
|
||||
let r=(t-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num,r.den)
|
||||
};
|
||||
let mut best_time={
|
||||
let r=(time_limit-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num.widen_4(),r.den.widen_4())
|
||||
};
|
||||
});
|
||||
let mut best_time=range.end_bound().map(|&t|into_giga_time(t,relative_body.time));
|
||||
let mut best_edge=None;
|
||||
let face_n=self.face_nd(contact_face_id).0;
|
||||
for &directed_edge_id in self.face_edges(contact_face_id).as_ref(){
|
||||
@ -780,18 +800,19 @@ impl MinkowskiMesh<'_>{
|
||||
//WARNING: truncated precision
|
||||
//wrap for speed
|
||||
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).wrap_4(),n.dot(relative_body.velocity).wrap_4()*2,n.dot(relative_body.acceleration).wrap_4()){
|
||||
if start_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
best_edge=Some(directed_edge_id);
|
||||
if low(&start_time,&dt)&&upp(&dt,&best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=Bound::Included(dt);
|
||||
best_edge=Some((directed_edge_id,dt));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
best_edge.map(|e|(e.as_undirected(),best_time))
|
||||
best_edge
|
||||
}
|
||||
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
|
||||
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
||||
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time).hit()
|
||||
// Bound::Included means that the surface of the mesh is included in the mesh
|
||||
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,Bound::Included(&infinity_body.time)).hit()
|
||||
}
|
||||
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
||||
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
|
||||
@ -929,10 +950,10 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
||||
}
|
||||
fn edge_verts(&self,edge_id:MinkowskiEdge)->impl AsRef<[MinkowskiVert;2]>{
|
||||
AsRefHelper(match edge_id{
|
||||
MinkowskiEdge::VertEdge(v0,e1)=>(*self.mesh1.edge_verts(e1).as_ref()).map(|vert_id1|
|
||||
MinkowskiEdge::VertEdge(v0,e1)=>self.mesh1.edge_verts(e1).as_ref().map(|vert_id1|
|
||||
MinkowskiVert::VertVert(v0,vert_id1)
|
||||
),
|
||||
MinkowskiEdge::EdgeVert(e0,v1)=>(*self.mesh0.edge_verts(e0).as_ref()).map(|vert_id0|
|
||||
MinkowskiEdge::EdgeVert(e0,v1)=>self.mesh0.edge_verts(e0).as_ref().map(|vert_id0|
|
||||
MinkowskiVert::VertVert(vert_id0,v1)
|
||||
),
|
||||
})
|
||||
@ -942,38 +963,43 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
||||
MinkowskiVert::VertVert(v0,v1)=>{
|
||||
let mut edges=Vec::new();
|
||||
//detect shared volume when the other mesh is mirrored along a test edge dir
|
||||
let v0f=self.mesh0.vert_faces(v0);
|
||||
let v1f=self.mesh1.vert_faces(v1);
|
||||
let v0f_n:Vec<_>=v0f.as_ref().iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect();
|
||||
let v1f_n:Vec<_>=v1f.as_ref().iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect();
|
||||
let the_len=v0f.as_ref().len()+v1f.as_ref().len();
|
||||
let v0f_thing=self.mesh0.vert_faces(v0);
|
||||
let v1f_thing=self.mesh1.vert_faces(v1);
|
||||
let v0f=v0f_thing.as_ref();
|
||||
let v1f=v1f_thing.as_ref();
|
||||
let v0f_n:Vec<_>=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect();
|
||||
let v1f_n:Vec<_>=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect();
|
||||
// scratch vector
|
||||
let mut face_normals=Vec::with_capacity(v0f.len()+v1f.len());
|
||||
face_normals.clone_from(&v0f_n);
|
||||
for &directed_edge_id in self.mesh0.vert_edges(v0).as_ref(){
|
||||
let n=self.mesh0.directed_edge_n(directed_edge_id);
|
||||
let nn=n.dot(n);
|
||||
// TODO: there's gotta be a better way to do this
|
||||
//make a set of faces
|
||||
let mut face_normals=Vec::with_capacity(the_len);
|
||||
//add mesh0 faces as-is
|
||||
face_normals.clone_from(&v0f_n);
|
||||
// drop faces beyond v0f_n
|
||||
face_normals.truncate(v0f.len());
|
||||
// make a set of faces from mesh0's perspective
|
||||
for face_n in &v1f_n{
|
||||
//add reflected mesh1 faces
|
||||
//wrap for speed
|
||||
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_3());
|
||||
}
|
||||
if is_empty_volume(face_normals){
|
||||
if is_empty_volume(&face_normals){
|
||||
edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1));
|
||||
}
|
||||
}
|
||||
face_normals.clone_from(&v1f_n);
|
||||
for &directed_edge_id in self.mesh1.vert_edges(v1).as_ref(){
|
||||
let n=self.mesh1.directed_edge_n(directed_edge_id);
|
||||
let nn=n.dot(n);
|
||||
let mut face_normals=Vec::with_capacity(the_len);
|
||||
face_normals.clone_from(&v1f_n);
|
||||
// drop faces beyond v1f_n
|
||||
face_normals.truncate(v1f.len());
|
||||
// make a set of faces from mesh1's perspective
|
||||
for face_n in &v0f_n{
|
||||
//wrap for speed
|
||||
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_3());
|
||||
}
|
||||
if is_empty_volume(face_normals){
|
||||
if is_empty_volume(&face_normals){
|
||||
edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id));
|
||||
}
|
||||
}
|
||||
@ -987,7 +1013,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
|
||||
fn is_empty_volume(normals:&[Vector3<Fixed<3,96>>])->bool{
|
||||
let len=normals.len();
|
||||
for i in 0..len-1{
|
||||
for j in i+1..len{
|
||||
@ -1013,6 +1039,6 @@ fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
|
||||
|
||||
#[test]
|
||||
fn test_is_empty_volume(){
|
||||
assert!(!is_empty_volume([vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3()].to_vec()));
|
||||
assert!(is_empty_volume([vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3(),vec3::NEG_X.widen_3()].to_vec()));
|
||||
assert!(!is_empty_volume(&[vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3()]));
|
||||
assert!(is_empty_volume(&[vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3(),vec3::NEG_X.widen_3()]));
|
||||
}
|
||||
|
@ -417,7 +417,7 @@ impl HitboxMesh{
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
const fn transformed_mesh(&self)->TransformedMesh{
|
||||
fn transformed_mesh(&self)->TransformedMesh{
|
||||
TransformedMesh::new(self.mesh.complete_mesh_view(),&self.transform)
|
||||
}
|
||||
}
|
||||
@ -1193,7 +1193,7 @@ fn recalculate_touching(
|
||||
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(contact.model_id),contact)
|
||||
}
|
||||
while let Some(&intersect)=touching.intersects.iter().next(){
|
||||
collision_end_intersect(touching,mode,run,models.intersect_attr(intersect.model_id),intersect,time);
|
||||
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(intersect.model_id),intersect,time);
|
||||
}
|
||||
//find all models in the teleport region
|
||||
let mut aabb=aabb::Aabb::default();
|
||||
@ -1605,6 +1605,7 @@ fn collision_start_intersect(
|
||||
None=>(),
|
||||
}
|
||||
}
|
||||
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
run_teleport_behaviour(intersect.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||
}
|
||||
|
||||
@ -1637,7 +1638,14 @@ fn collision_end_contact(
|
||||
}
|
||||
}
|
||||
fn collision_end_intersect(
|
||||
move_state:&mut MoveState,
|
||||
body:&mut Body,
|
||||
touching:&mut TouchingState,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
mode:Option<&gameplay_modes::Mode>,
|
||||
run:&mut run::Run,
|
||||
_attr:&gameplay_attributes::IntersectAttributes,
|
||||
@ -1645,6 +1653,7 @@ fn collision_end_intersect(
|
||||
time:Time,
|
||||
){
|
||||
touching.remove(&Collision::Intersect(intersect));
|
||||
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
if let Some(mode)=mode{
|
||||
let zone=mode.get_zone(intersect.model_id.into());
|
||||
match zone{
|
||||
@ -1701,7 +1710,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
contact
|
||||
),
|
||||
Collision::Intersect(intersect)=>collision_end_intersect(
|
||||
&mut state.touching,
|
||||
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
|
||||
data.modes.get_mode(state.mode_state.get_mode_id()),
|
||||
&mut state.run,
|
||||
data.models.intersect_attr(intersect.model_id),
|
||||
@ -1924,6 +1933,15 @@ mod test{
|
||||
),Some(Time::from_secs(2)));
|
||||
}
|
||||
#[test]
|
||||
fn test_collision_small_mv(){
|
||||
test_collision(Body::new(
|
||||
int3(0,5,0),
|
||||
int3(0,-1,0)+(vec3::X>>32),
|
||||
vec3::ZERO,
|
||||
Time::ZERO
|
||||
),Some(Time::from_secs(2)));
|
||||
}
|
||||
#[test]
|
||||
fn test_collision_degenerate_east(){
|
||||
test_collision(Body::new(
|
||||
int3(3,5,0),
|
||||
|
@ -349,7 +349,7 @@ pub struct PartialMap1{
|
||||
impl PartialMap1{
|
||||
pub fn add_prop_meshes<'a>(
|
||||
self,
|
||||
prop_meshes:Meshes,
|
||||
prop_meshes:Meshes<model::Mesh>,
|
||||
)->PartialMap2{
|
||||
PartialMap2{
|
||||
attributes:self.attributes,
|
||||
|
@ -1,5 +1,5 @@
|
||||
pub use fixed_wide::fixed::*;
|
||||
pub use ratio_ops::ratio::{Ratio,Divide};
|
||||
pub use ratio_ops::ratio::{Ratio,Divide,Parity};
|
||||
|
||||
//integer units
|
||||
|
||||
@ -132,20 +132,21 @@ impl<T> std::ops::Mul for Time<T>{
|
||||
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Div<i64> for Time<T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn div(self,rhs:i64)->Self::Output{
|
||||
Self::raw(self.0/rhs)
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Mul<i64> for Time<T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn mul(self,rhs:i64)->Self::Output{
|
||||
Self::raw(self.0*rhs)
|
||||
macro_rules! impl_time_i64_rhs_operator {
|
||||
($op:ident,$method:ident)=>{
|
||||
impl<T> core::ops::$op<i64> for Time<T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn $method(self,rhs:i64)->Self::Output{
|
||||
Self::raw(self.0.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_time_i64_rhs_operator!(Div,div);
|
||||
impl_time_i64_rhs_operator!(Mul,mul);
|
||||
impl_time_i64_rhs_operator!(Shr,shr);
|
||||
impl_time_i64_rhs_operator!(Shl,shl);
|
||||
impl<T> core::ops::Mul<Time<T>> for Planar64{
|
||||
type Output=Ratio<Fixed<2,64>,Planar64>;
|
||||
fn mul(self,rhs:Time<T>)->Self::Output{
|
||||
@ -656,7 +657,7 @@ pub mod mat3{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
|
||||
#[derive(Clone,Copy,Debug,Default,Hash,Eq,PartialEq)]
|
||||
pub struct Planar64Affine3{
|
||||
pub matrix3:Planar64Mat3,//includes scale above 1
|
||||
pub translation:Planar64Vec3,
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use crate::loader::Loader;
|
||||
use crate::mesh::Meshes;
|
||||
use crate::texture::{RenderConfigs,Texture};
|
||||
use strafesnet_common::model::{Mesh,MeshId,RenderConfig,RenderConfigId,TextureId};
|
||||
use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub enum LoadFailureMode{
|
||||
@ -93,7 +93,7 @@ impl<H:core::hash::Hash+Eq> MeshDeferredLoader<H>{
|
||||
pub fn into_indices(self)->impl Iterator<Item=H>{
|
||||
self.mesh_id_from_asset_id.into_keys()
|
||||
}
|
||||
pub fn into_meshes<'a,L:Loader<Resource=Mesh,Index<'a>=H>+'a>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes,L::Error>{
|
||||
pub fn into_meshes<'a,M:Clone,L:Loader<Resource=M,Index<'a>=H>+'a>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes<M>,L::Error>{
|
||||
let mut mesh_list=vec![None;self.mesh_id_from_asset_id.len()];
|
||||
for (index,mesh_id) in self.mesh_id_from_asset_id{
|
||||
let resource_result=loader.load(index);
|
||||
|
@ -1,15 +1,15 @@
|
||||
use strafesnet_common::model::{Mesh,MeshId};
|
||||
use strafesnet_common::model::MeshId;
|
||||
|
||||
pub struct Meshes{
|
||||
meshes:Vec<Option<Mesh>>,
|
||||
pub struct Meshes<M>{
|
||||
meshes:Vec<Option<M>>,
|
||||
}
|
||||
impl Meshes{
|
||||
pub(crate) const fn new(meshes:Vec<Option<Mesh>>)->Self{
|
||||
impl<M> Meshes<M>{
|
||||
pub(crate) const fn new(meshes:Vec<Option<M>>)->Self{
|
||||
Self{
|
||||
meshes,
|
||||
}
|
||||
}
|
||||
pub fn consume(self)->impl Iterator<Item=(MeshId,Mesh)>{
|
||||
pub fn consume(self)->impl Iterator<Item=(MeshId,M)>{
|
||||
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
||||
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
||||
)
|
||||
|
@ -143,6 +143,12 @@ impl MeshIndex<'_>{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MeshWithSize{
|
||||
pub(crate) mesh:Mesh,
|
||||
pub(crate) size:strafesnet_common::integer::Planar64Vec3,
|
||||
}
|
||||
|
||||
pub struct MeshLoader;
|
||||
impl MeshLoader{
|
||||
pub fn new()->Self{
|
||||
@ -152,7 +158,7 @@ impl MeshLoader{
|
||||
impl Loader for MeshLoader{
|
||||
type Error=MeshError;
|
||||
type Index<'a>=MeshIndex<'a>;
|
||||
type Resource=Mesh;
|
||||
type Resource=MeshWithSize;
|
||||
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
|
||||
let mesh=match index.mesh_type{
|
||||
MeshType::FileMesh=>{
|
||||
|
@ -1,7 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_mesh::mesh::{Vertex2,Vertex2Truncated};
|
||||
use strafesnet_common::{integer::vec3,model::{self,ColorId,IndexedVertex,NormalId,PolygonGroup,PolygonList,PositionId,RenderConfigId,TextureCoordinateId,VertexId}};
|
||||
use strafesnet_common::aabb::Aabb;
|
||||
use strafesnet_common::integer::vec3;
|
||||
use strafesnet_common::model::{self,ColorId,IndexedVertex,NormalId,PolygonGroup,PolygonList,PositionId,RenderConfigId,TextureCoordinateId,VertexId};
|
||||
|
||||
use crate::loader::MeshWithSize;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
@ -95,7 +99,7 @@ fn ingest_faces2_lods3(
|
||||
))
|
||||
}
|
||||
|
||||
pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Mesh,Error>{
|
||||
pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<MeshWithSize,Error>{
|
||||
//generate that mesh boi
|
||||
let mut unique_pos=Vec::new();
|
||||
let mut pos_id_from=HashMap::new();
|
||||
@ -108,8 +112,10 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Me
|
||||
let mut unique_vertices=Vec::new();
|
||||
let mut vertex_id_from=HashMap::new();
|
||||
let mut polygon_groups=Vec::new();
|
||||
let mut aabb=Aabb::default();
|
||||
let mut acquire_pos_id=|pos|{
|
||||
let p=vec3::try_from_f32_array(pos).map_err(Error::Planar64Vec3)?;
|
||||
aabb.grow(p);
|
||||
Ok(PositionId::new(*pos_id_from.entry(p).or_insert_with(||{
|
||||
let pos_id=unique_pos.len();
|
||||
unique_pos.push(p);
|
||||
@ -197,7 +203,7 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Me
|
||||
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
|
||||
},
|
||||
}
|
||||
Ok(model::Mesh{
|
||||
let mesh=model::Mesh{
|
||||
unique_pos,
|
||||
unique_normal,
|
||||
unique_tex,
|
||||
@ -213,5 +219,6 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Me
|
||||
}],
|
||||
//disable physics
|
||||
physics_groups:Vec::new(),
|
||||
})
|
||||
};
|
||||
Ok(MeshWithSize{mesh,size:aabb.size()})
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[
|
||||
TextureCoordinate::new(1.0,1.0),
|
||||
TextureCoordinate::new(0.0,1.0),
|
||||
];
|
||||
const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
|
||||
pub const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
|
||||
vec3::int(-1,-1, 1),//0 left bottom back
|
||||
vec3::int( 1,-1, 1),//1 right bottom back
|
||||
vec3::int( 1, 1, 1),//2 right top back
|
||||
@ -66,7 +66,7 @@ const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
|
||||
vec3::int( 1,-1,-1),//6 right bottom front
|
||||
vec3::int(-1,-1,-1),//7 left bottom front
|
||||
];
|
||||
const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
|
||||
pub const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
|
||||
vec3::int( 1, 0, 0),//CubeFace::Right
|
||||
vec3::int( 0, 1, 0),//CubeFace::Top
|
||||
vec3::int( 0, 0, 1),//CubeFace::Back
|
||||
@ -121,8 +121,7 @@ impl FaceDescription{
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Mesh{
|
||||
const CUBE_DEFAULT_POLYS:[[[u32;2];4];6]=[
|
||||
pub const CUBE_DEFAULT_POLYS:[[[u32;2];4];6]=[
|
||||
// right (1, 0, 0)
|
||||
[
|
||||
[6,2],//[vertex,tex]
|
||||
@ -166,6 +165,7 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
|
||||
[7,2],
|
||||
],
|
||||
];
|
||||
pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Mesh{
|
||||
let mut generated_pos=Vec::new();
|
||||
let mut generated_tex=Vec::new();
|
||||
let mut generated_normal=Vec::new();
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::loader::MeshIndex;
|
||||
use crate::loader::{MeshWithSize,MeshIndex};
|
||||
use crate::primitives::{self,CubeFace,CubeFaceDescription,WedgeFaceDescription,CornerWedgeFaceDescription,FaceDescription,Primitives};
|
||||
use strafesnet_common::aabb::Aabb;
|
||||
use strafesnet_common::map;
|
||||
use strafesnet_common::model;
|
||||
use strafesnet_common::gameplay_modes::{NormalizedModes,Mode,ModeId,ModeUpdate,ModesBuilder,Stage,StageElement,StageElementBehaviour,StageId,Zone};
|
||||
@ -57,15 +56,19 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
||||
let mut contacting=attr::ContactingAttributes::default();
|
||||
let mut force_can_collide=can_collide;
|
||||
let mut force_intersecting=false;
|
||||
let mut allow_booster=true;
|
||||
match name{
|
||||
"Water"=>{
|
||||
force_can_collide=false;
|
||||
allow_booster=false;
|
||||
//TODO: read stupid CustomPhysicalProperties
|
||||
intersecting.water=Some(attr::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity});
|
||||
},
|
||||
"Accelerator"=>{
|
||||
//although the new game supports collidable accelerators, this is a roblox compatability map loader
|
||||
force_can_collide=false;
|
||||
// Accelerator is not allowed to be booster in roblox
|
||||
allow_booster=false;
|
||||
general.accelerator=Some(attr::Accelerator{acceleration:velocity});
|
||||
},
|
||||
// "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(attr::StageElement{
|
||||
@ -74,7 +77,10 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
||||
// force:false,
|
||||
// behaviour:model::StageElementBehaviour::Unordered
|
||||
// })),
|
||||
"SetVelocity"=>general.trajectory=Some(attr::SetTrajectory::Velocity(velocity)),
|
||||
"SetVelocity"=>{
|
||||
allow_booster=false;
|
||||
general.trajectory=Some(attr::SetTrajectory::Velocity(velocity));
|
||||
},
|
||||
"MapStart"=>{
|
||||
force_can_collide=false;
|
||||
force_intersecting=true;
|
||||
@ -234,7 +240,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
||||
}
|
||||
}
|
||||
//need some way to skip this
|
||||
if velocity!=vec3::ZERO{
|
||||
if allow_booster&&velocity!=vec3::ZERO{
|
||||
general.booster=Some(attr::Booster::Velocity(velocity));
|
||||
}
|
||||
match force_can_collide{
|
||||
@ -650,7 +656,7 @@ pub fn convert<'a>(
|
||||
// texture is allowed to be none
|
||||
object.properties.get(&static_ustr("TextureContent")),
|
||||
){
|
||||
let mesh_asset_id=get_content_url(mesh_content).expect("MeshPart Mesh is not a Uri");
|
||||
let mesh_asset_id=get_content_url(mesh_content).unwrap_or_default();
|
||||
let texture_asset_id=get_content_url(texture_content);
|
||||
(
|
||||
MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(texture_asset_id)),
|
||||
@ -709,23 +715,23 @@ pub fn convert<'a>(
|
||||
deferred_unions_deferred_attributes,
|
||||
}
|
||||
}
|
||||
struct MeshWithAabb{
|
||||
mesh:model::Mesh,
|
||||
aabb:Aabb,
|
||||
struct MeshIdWithSize{
|
||||
mesh:model::MeshId,
|
||||
size:Planar64Vec3,
|
||||
}
|
||||
fn acquire_mesh_id_from_render_config_id<'a>(
|
||||
primitive_meshes:&mut Vec<model::Mesh>,
|
||||
mesh_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RenderConfigId,model::MeshId>>,
|
||||
loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
|
||||
loaded_meshes:&'a HashMap<model::MeshId,MeshWithSize>,
|
||||
old_mesh_id:model::MeshId,
|
||||
render:RenderConfigId,
|
||||
)->Option<(model::MeshId,&'a Aabb)>{
|
||||
)->Option<MeshIdWithSize>{
|
||||
//ignore meshes that fail to load completely for now
|
||||
loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
|
||||
*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
|
||||
loaded_meshes.get(&old_mesh_id).map(|&MeshWithSize{ref mesh,size}|MeshIdWithSize{
|
||||
mesh:*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
|
||||
.entry(render).or_insert_with(||{
|
||||
let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
|
||||
let mut mesh_clone=mesh_with_aabb.mesh.clone();
|
||||
let mut mesh_clone=mesh.clone();
|
||||
//set the render group lool
|
||||
if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){
|
||||
graphics_group.render=render;
|
||||
@ -733,22 +739,22 @@ fn acquire_mesh_id_from_render_config_id<'a>(
|
||||
primitive_meshes.push(mesh_clone);
|
||||
mesh_id
|
||||
}),
|
||||
&mesh_with_aabb.aabb,
|
||||
))
|
||||
size,
|
||||
})
|
||||
}
|
||||
fn acquire_union_id_from_render_config_id<'a>(
|
||||
primitive_meshes:&mut Vec<model::Mesh>,
|
||||
union_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RobloxPartDescription,model::MeshId>>,
|
||||
loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
|
||||
loaded_meshes:&'a HashMap<model::MeshId,MeshWithSize>,
|
||||
old_union_id:model::MeshId,
|
||||
part_texture_description:RobloxPartDescription,
|
||||
)->Option<(model::MeshId,&'a Aabb)>{
|
||||
)->Option<MeshIdWithSize>{
|
||||
//ignore uniones that fail to load completely for now
|
||||
loaded_meshes.get(&old_union_id).map(|union_with_aabb|(
|
||||
*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new())
|
||||
loaded_meshes.get(&old_union_id).map(|&MeshWithSize{ref mesh,size}|MeshIdWithSize{
|
||||
mesh:*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new())
|
||||
.entry(part_texture_description.clone()).or_insert_with(||{
|
||||
let union_id=model::MeshId::new(primitive_meshes.len() as u32);
|
||||
let mut union_clone=union_with_aabb.mesh.clone();
|
||||
let mut union_clone=mesh.clone();
|
||||
//set the render groups
|
||||
for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description.0){
|
||||
if let Some(face_texture_description)=maybe_face_texture_description{
|
||||
@ -758,8 +764,8 @@ fn acquire_union_id_from_render_config_id<'a>(
|
||||
primitive_meshes.push(union_clone);
|
||||
union_id
|
||||
}),
|
||||
&union_with_aabb.aabb,
|
||||
))
|
||||
size,
|
||||
})
|
||||
}
|
||||
pub struct PartialMap1<'a>{
|
||||
primitive_meshes:Vec<model::Mesh>,
|
||||
@ -770,7 +776,7 @@ pub struct PartialMap1<'a>{
|
||||
impl PartialMap1<'_>{
|
||||
pub fn add_meshpart_meshes_and_calculate_attributes(
|
||||
mut self,
|
||||
meshpart_meshes:Meshes,
|
||||
meshpart_meshes:Meshes<MeshWithSize>,
|
||||
)->PartialMap2{
|
||||
//calculate attributes
|
||||
let mut modes_builder=ModesBuilder::default();
|
||||
@ -782,17 +788,8 @@ impl PartialMap1<'_>{
|
||||
|
||||
//decode roblox meshes
|
||||
//generate mesh_id_map based on meshes that failed to load
|
||||
let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
|
||||
meshpart_meshes.consume().map(|(old_mesh_id,mesh)|{
|
||||
let mut aabb=strafesnet_common::aabb::Aabb::default();
|
||||
for &pos in &mesh.unique_pos{
|
||||
aabb.grow(pos);
|
||||
}
|
||||
(old_mesh_id,MeshWithAabb{
|
||||
mesh,
|
||||
aabb,
|
||||
})
|
||||
}).collect();
|
||||
let loaded_meshes:HashMap<model::MeshId,MeshWithSize>=
|
||||
meshpart_meshes.consume().collect();
|
||||
|
||||
// SAFETY: I have no idea what I'm doing and this is definitely unsound in some subtle way
|
||||
// I just want to chain iterators together man
|
||||
@ -805,23 +802,22 @@ impl PartialMap1<'_>{
|
||||
self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
|
||||
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
||||
//insert into primitive_meshes
|
||||
let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
|
||||
let MeshIdWithSize{mesh,size:mesh_size}=acquire_mesh_id_from_render_config_id(
|
||||
unsafe{*aint_no_way.get()},
|
||||
&mut mesh_id_from_render_config_id,
|
||||
&loaded_meshes,
|
||||
deferred_model_deferred_attributes.model.mesh,
|
||||
deferred_model_deferred_attributes.render
|
||||
)?;
|
||||
let size=aabb.size();
|
||||
Some(ModelDeferredAttributes{
|
||||
mesh,
|
||||
deferred_attributes:deferred_model_deferred_attributes.model.deferred_attributes,
|
||||
color:deferred_model_deferred_attributes.model.color,
|
||||
transform:Planar64Affine3::new(
|
||||
Planar64Mat3::from_cols([
|
||||
(deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().narrow_1().unwrap(),
|
||||
(deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().narrow_1().unwrap(),
|
||||
(deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().narrow_1().unwrap(),
|
||||
(deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/mesh_size.x).divide().narrow_1().unwrap(),
|
||||
(deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/mesh_size.y).divide().narrow_1().unwrap(),
|
||||
(deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/mesh_size.z).divide().narrow_1().unwrap(),
|
||||
]),
|
||||
deferred_model_deferred_attributes.model.transform.translation
|
||||
),
|
||||
@ -829,14 +825,13 @@ impl PartialMap1<'_>{
|
||||
}).chain(self.deferred_unions_deferred_attributes.into_iter().flat_map(|deferred_union_deferred_attributes|{
|
||||
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
||||
//insert into primitive_meshes
|
||||
let (mesh,aabb)=acquire_union_id_from_render_config_id(
|
||||
let MeshIdWithSize{mesh,size}=acquire_union_id_from_render_config_id(
|
||||
unsafe{*aint_no_way.get()},
|
||||
&mut union_id_from_render_config_id,
|
||||
&loaded_meshes,
|
||||
deferred_union_deferred_attributes.model.mesh,
|
||||
deferred_union_deferred_attributes.render
|
||||
)?;
|
||||
let size=aabb.size();
|
||||
Some(ModelDeferredAttributes{
|
||||
mesh,
|
||||
deferred_attributes:deferred_union_deferred_attributes.model.deferred_attributes,
|
||||
|
@ -1,4 +1,8 @@
|
||||
use rbx_mesh::mesh_data::{NormalId2 as MeshDataNormalId2,VertexId as MeshDataVertexId};
|
||||
use crate::loader::MeshWithSize;
|
||||
use crate::rbx::RobloxPartDescription;
|
||||
use crate::primitives::{CUBE_DEFAULT_VERTICES,CUBE_DEFAULT_POLYS};
|
||||
|
||||
use rbx_mesh::mesh_data::{VertexId as MeshDataVertexId,NormalId2 as MeshDataNormalId2};
|
||||
use rbx_mesh::physics_data::VertexId as PhysicsDataVertexId;
|
||||
use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId};
|
||||
use strafesnet_common::integer::vec3;
|
||||
@ -57,10 +61,10 @@ pub fn convert(
|
||||
roblox_physics_data:&[u8],
|
||||
roblox_mesh_data:&[u8],
|
||||
size:glam::Vec3,
|
||||
crate::rbx::RobloxPartDescription(part_texture_description):crate::rbx::RobloxPartDescription,
|
||||
)->Result<model::Mesh,Error>{
|
||||
RobloxPartDescription(part_texture_description):RobloxPartDescription,
|
||||
)->Result<MeshWithSize,Error>{
|
||||
const NORMAL_FACES:usize=6;
|
||||
let mut polygon_groups_normal_id=vec![Vec::new();NORMAL_FACES];
|
||||
let mut polygon_groups_normal_id:[_;NORMAL_FACES]=[vec![],vec![],vec![],vec![],vec![],vec![]];
|
||||
|
||||
// build graphics and physics meshes
|
||||
let mut mb=strafesnet_common::model::MeshBuilder::new();
|
||||
@ -80,6 +84,15 @@ pub fn convert(
|
||||
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL2(mesh_data2))=>mesh_data2.mesh,
|
||||
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL4(mesh_data4))=>mesh_data4.mesh,
|
||||
};
|
||||
//autoscale to size, idk what roblox is doing with the graphics mesh size
|
||||
let mut pos_min=glam::Vec3::MAX;
|
||||
let mut pos_max=glam::Vec3::MIN;
|
||||
for vertex in &graphics_mesh.vertices{
|
||||
let p=vertex.pos.into();
|
||||
pos_min=pos_min.min(p);
|
||||
pos_max=pos_max.max(p);
|
||||
}
|
||||
let graphics_size=pos_max-pos_min;
|
||||
for [MeshDataVertexId(vertex_id0),MeshDataVertexId(vertex_id1),MeshDataVertexId(vertex_id2)] in graphics_mesh.faces{
|
||||
let face=[
|
||||
graphics_mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
|
||||
@ -89,7 +102,8 @@ pub fn convert(
|
||||
let mut normal_agreement_checker=MeshDataNormalChecker::new();
|
||||
let face=face.into_iter().map(|vertex|{
|
||||
normal_agreement_checker.check(vertex.normal_id);
|
||||
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?);
|
||||
let pos=glam::Vec3::from_array(vertex.pos)/graphics_size;
|
||||
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(pos.to_array())?);
|
||||
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?);
|
||||
let tex_coord=glam::Vec2::from_array(vertex.tex);
|
||||
let maybe_face_description=&cube_face_description[vertex.normal_id as usize-1];
|
||||
@ -126,7 +140,11 @@ pub fn convert(
|
||||
};
|
||||
|
||||
//physics
|
||||
let physics_convex_meshes=if !roblox_physics_data.is_empty(){
|
||||
let polygon_groups_normal_it=polygon_groups_normal_id.into_iter().map(|faces|
|
||||
// graphics polygon groups (to be rendered)
|
||||
Ok(PolygonGroup::PolygonList(PolygonList::new(faces)))
|
||||
);
|
||||
let polygon_groups:Vec<PolygonGroup>=if !roblox_physics_data.is_empty(){
|
||||
let physics_data=rbx_mesh::read_physics_data_versioned(
|
||||
std::io::Cursor::new(roblox_physics_data)
|
||||
).map_err(Error::RobloxPhysicsData)?;
|
||||
@ -140,39 +158,48 @@ pub fn convert(
|
||||
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim))
|
||||
=>vec![pim.mesh],
|
||||
};
|
||||
physics_convex_meshes
|
||||
let physics_convex_meshes_it=physics_convex_meshes.into_iter().map(|mesh|{
|
||||
// this can be factored out of the loop but I am lazy
|
||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
||||
// physics polygon groups (to do physics)
|
||||
Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{
|
||||
let face=[
|
||||
mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
|
||||
mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
|
||||
mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
|
||||
].map(|v|glam::Vec3::from_slice(v)/size);
|
||||
let vertex_norm=(face[1]-face[0])
|
||||
.cross(face[2]-face[0]);
|
||||
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex_norm.to_array()).map_err(Error::Planar64Vec3)?);
|
||||
face.into_iter().map(|vertex_pos|{
|
||||
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex_pos.to_array()).map_err(Error::Planar64Vec3)?);
|
||||
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
|
||||
}).collect()
|
||||
}).collect::<Result<_,_>>()?)))
|
||||
});
|
||||
polygon_groups_normal_it.chain(physics_convex_meshes_it).collect::<Result<_,_>>()?
|
||||
}else{
|
||||
Vec::new()
|
||||
};
|
||||
let polygon_groups:Vec<PolygonGroup>=polygon_groups_normal_id.into_iter().map(|faces|
|
||||
// graphics polygon groups (to be rendered)
|
||||
Ok(PolygonGroup::PolygonList(PolygonList::new(faces)))
|
||||
).chain(physics_convex_meshes.into_iter().map(|mesh|{
|
||||
// this can be factored out of the loop but I am lazy
|
||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||
// generate a unit cube as default physics
|
||||
let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1));
|
||||
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
||||
// physics polygon groups (to do physics)
|
||||
Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{
|
||||
let face=[
|
||||
mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
|
||||
mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
|
||||
mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
|
||||
].map(|v|glam::Vec3::from_slice(v)/size);
|
||||
let vertex_norm=(face[1]-face[0])
|
||||
.cross(face[2]-face[0]);
|
||||
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex_norm.to_array()).map_err(Error::Planar64Vec3)?);
|
||||
face.into_iter().map(|vertex_pos|{
|
||||
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex_pos.to_array()).map_err(Error::Planar64Vec3)?);
|
||||
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
|
||||
}).collect()
|
||||
}).collect::<Result<_,_>>()?)))
|
||||
})).collect::<Result<_,_>>()?;
|
||||
let normal=mb.acquire_normal_id(vec3::ZERO);
|
||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||
let polygon_group=PolygonGroup::PolygonList(PolygonList::new(CUBE_DEFAULT_POLYS.map(|poly|poly.map(|[pos_id,_]|
|
||||
mb.acquire_vertex_id(IndexedVertex{pos:pos_list[pos_id as usize],tex,normal,color})
|
||||
).to_vec()).to_vec()));
|
||||
polygon_groups_normal_it.chain([Ok(polygon_group)]).collect::<Result<_,_>>()?
|
||||
};
|
||||
let physics_groups=(NORMAL_FACES..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{
|
||||
groups:vec![PolygonGroupId::new(id as u32)]
|
||||
}).collect();
|
||||
Ok(mb.build(
|
||||
let mesh=mb.build(
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups,
|
||||
))
|
||||
);
|
||||
Ok(MeshWithSize{
|
||||
mesh,
|
||||
size:vec3::ONE,
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user