Compare commits

..

6 Commits

Author SHA1 Message Date
da8492d559 success 2024-08-19 17:50:25 -07:00
46db92acd0 tweak teleport_to_spawn 2024-08-19 16:41:23 -07:00
35076f5c17 erm what the rust 2024-08-19 16:35:14 -07:00
63395d3928 yep 2024-08-09 16:09:51 -07:00
ce47f01375 borrow check fixups 2024-08-09 15:12:51 -07:00
fb14e023df wip 2024-08-09 15:00:26 -07:00
11 changed files with 590 additions and 955 deletions

701
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "strafe-client"
version = "0.10.5"
version = "0.10.3"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-client"
license = "Custom"
@ -18,17 +18,17 @@ roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
bytemuck = { version = "1.13.1", features = ["derive"] }
configparser = "3.0.2"
ddsfile = "0.5.1"
glam = "0.29.0"
glam = "0.28.0"
id = { version = "0.1.0", registry = "strafesnet" }
parking_lot = "0.12.1"
pollster = "0.3.0"
strafesnet_bsp_loader = { version = "0.2.1", registry = "strafesnet", optional = true }
strafesnet_common = { version = "0.5.2", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.4.0", features = ["legacy"], registry = "strafesnet", optional = true }
strafesnet_rbx_loader = { version = "0.5.1", registry = "strafesnet", optional = true }
strafesnet_snf = { version = "0.2.0", registry = "strafesnet", optional = true }
wgpu = "22.1.0"
winit = "0.30.5"
strafesnet_bsp_loader = { version = "0.1.3", registry = "strafesnet", optional = true }
strafesnet_common = { version = "0.4.0", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.3.1", features = ["legacy"], registry = "strafesnet", optional = true }
strafesnet_rbx_loader = { version = "0.3.2", registry = "strafesnet", optional = true }
strafesnet_snf = { version = "0.1.2", registry = "strafesnet", optional = true }
wgpu = "22.0.0"
winit = "0.30.4"
[profile.release]
#lto = true

View File

@ -1,33 +1,32 @@
use crate::physics::Body;
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert};
use strafesnet_common::integer::{Time,Fixed,Ratio};
use crate::model_physics::{FEV,MeshQuery,DirectedEdge};
use strafesnet_common::integer::{Time,Planar64};
use strafesnet_common::zeroes::zeroes2;
#[derive(Debug)]
enum Transition<F,E:DirectedEdge,V>{
Miss,
Next(FEV<F,E,V>,GigaTime),
Hit(F,GigaTime),
Next(FEV<F,E,V>,Time),
Hit(F,Time),
}
type MinkowskiFEV=FEV<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
fn next_transition(fev:&MinkowskiFEV,body_time:GigaTime,mesh:&MinkowskiMesh,body:&Body,mut best_time:GigaTime)->MinkowskiTransition{
fn next_transition<F:Copy,E:Copy+DirectedEdge,V:Copy>(fev:&FEV<F,E,V>,time:Time,mesh:&impl MeshQuery<F,E,V>,body:&Body,time_limit:Time)->Transition<F,E,V>{
//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=MinkowskiTransition::Miss;
let mut best_time=time_limit;
let mut best_transtition=Transition::Miss;
match fev{
&MinkowskiFEV::Face(face_id)=>{
&FEV::<F,E,V>::Face(face_id)=>{
//test own face collision time, ignoring roots with zero or conflicting derivative
//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);
//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;
best_transition=MinkowskiTransition::Hit(face_id,dt);
for t in zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
let t=body.time+Time::from(t);
if time<=t&&t<best_time&&n.dot(body.extrapolated_velocity(t))<Planar64::ZERO{
best_time=t;
best_transtition=Transition::Hit(face_id,t);
break;
}
}
@ -37,18 +36,18 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
let n=n.cross(edge_n);
let verts=mesh.edge_verts(directed_edge_id.as_undirected());
//WARNING: d is moved out of the *2 block because of adding two vertices!
//WARNING: precision is swept under the rug!
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_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;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
for t in zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))),n.dot(body.velocity)*2,n.dot(body.acceleration)){
let t=body.time+Time::from(t);
if time<=t&&t<best_time&&n.dot(body.extrapolated_velocity(t))<Planar64::ZERO{
best_time=t;
best_transtition=Transition::Next(FEV::<F,E,V>::Edge(directed_edge_id.as_undirected()),t);
break;
}
}
}
//if none:
},
&MinkowskiFEV::Edge(edge_id)=>{
&FEV::<F,E,V>::Edge(edge_id)=>{
//test each face collision time, ignoring roots with zero or conflicting derivative
let edge_n=mesh.edge_n(edge_id);
let edge_verts=mesh.edge_verts(edge_id);
@ -58,10 +57,11 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
//edge_n gets parity from the order of edge_faces
let n=face_n.cross(edge_n)*((i as i64)*2-1);
//WARNING yada yada d *2
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_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;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Face(edge_face_id),dt);
for t in zeroes2(n.dot(delta_pos),n.dot(body.velocity)*2,n.dot(body.acceleration)){
let t=body.time+Time::from(t);
if time<=t&&t<best_time&&n.dot(body.extrapolated_velocity(t))<Planar64::ZERO{
best_time=t;
best_transtition=Transition::Next(FEV::<F,E,V>::Face(edge_face_id),t);
break;
}
}
@ -70,27 +70,27 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
for (i,&vert_id) in edge_verts.iter().enumerate(){
//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(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Vert(vert_id),dt);
for t in zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
let t=body.time+Time::from(t);
if time<=t&&t<best_time&&n.dot(body.extrapolated_velocity(t))<Planar64::ZERO{
best_time=t;
best_transtition=Transition::Next(FEV::<F,E,V>::Vert(vert_id),t);
break;
}
}
}
//if none:
},
&MinkowskiFEV::Vert(vert_id)=>{
&FEV::<F,E,V>::Vert(vert_id)=>{
//test each edge collision time, ignoring roots with zero or conflicting derivative
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
//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(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt;
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
for t in zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
let t=body.time+Time::from(t);
if time<=t&&t<best_time&&n.dot(body.extrapolated_velocity(t))<Planar64::ZERO{
best_time=t;
best_transtition=Transition::Next(FEV::<F,E,V>::Edge(directed_edge_id.as_undirected()),t);
break;
}
}
@ -98,26 +98,18 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
//if none:
},
}
best_transition
best_transtition
}
pub enum CrawlResult<F,E:DirectedEdge,V>{
Miss(FEV<F,E,V>),
Hit(F,GigaTime),
Hit(F,Time),
}
type MinkowskiCrawlResult=CrawlResult<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
pub fn crawl_fev(mut fev:MinkowskiFEV,mesh:&MinkowskiMesh,relative_body:&Body,start_time:Time,time_limit:Time)->MinkowskiCrawlResult{
let mut body_time={
let r=(start_time-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
let time_limit={
let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
pub fn crawl_fev<F:Copy,E:Copy+DirectedEdge,V:Copy>(mut fev:FEV<F,E,V>,mesh:&impl MeshQuery<F,E,V>,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<F,E,V>{
let mut time=start_time;
for _ in 0..20{
match next_transition(&fev,body_time,mesh,relative_body,time_limit){
match next_transition(&fev,time,mesh,relative_body,time_limit){
Transition::Miss=>return CrawlResult::Miss(fev),
Transition::Next(next_fev,next_time)=>(fev,body_time)=(next_fev,next_time),
Transition::Next(next_fev,next_time)=>(fev,time)=(next_fev,next_time),
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
}
}

View File

@ -22,7 +22,7 @@ impl std::error::Error for ReadError{}
pub enum DataStructure{
#[cfg(feature="roblox")]
Roblox(strafesnet_rbx_loader::Model),
Roblox(strafesnet_rbx_loader::Dom),
#[cfg(feature="source")]
Source(strafesnet_bsp_loader::Bsp),
#[cfg(feature="snf")]
@ -66,16 +66,13 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::Co
#[cfg(feature="snf")]
DataStructure::StrafesNET(map)=>Ok(map),
#[cfg(feature="roblox")]
DataStructure::Roblox(model)=>{
let mut place=model.into_place();
place.run_scripts();
DataStructure::Roblox(dom)=>{
let mut loader=strafesnet_deferred_loader::roblox_legacy();
let (texture_loader,mesh_loader)=loader.get_inner_mut();
let map_step1=strafesnet_rbx_loader::convert(
&place,
&dom,
|name|texture_loader.acquire_render_config_id(name),
|name|mesh_loader.acquire_mesh_id(name),
);
@ -113,7 +110,7 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::Co
|name|mesh_loader.acquire_mesh_id(name),
);
let prop_meshes=mesh_loader.load_meshes(bsp.as_ref());
let prop_meshes=mesh_loader.load_meshes(&bsp.as_ref());
let map_step2=map_step1.add_prop_meshes(
//the type conflagulator 9000

View File

@ -6,6 +6,12 @@ use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId,
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
#[derive(Clone)]
pub struct GraphicsModelUpdate{
transform:Option<glam::Mat4>,
color:Option<glam::Vec4>,
}
struct Indices{
count:u32,
buf:wgpu::Buffer,
@ -26,6 +32,7 @@ impl Indices{
}
struct GraphicsModel{
indices:Indices,
model_buf:wgpu::Buffer,
vertex_buf:wgpu::Buffer,
bind_group:wgpu::BindGroup,
instance_count:u32,
@ -58,12 +65,12 @@ struct GraphicsCamera{
#[inline]
fn perspective_rh(fov_x_slope:f32,fov_y_slope:f32,z_near:f32,z_far:f32)->glam::Mat4{
//glam_assert!(z_near > 0.0 && z_far > 0.0);
let r=z_far/(z_near-z_far);
let r=z_far / (z_near-z_far);
glam::Mat4::from_cols(
glam::Vec4::new(1.0/fov_x_slope,0.0,0.0,0.0),
glam::Vec4::new(0.0,1.0/fov_y_slope,0.0,0.0),
glam::Vec4::new(0.0,0.0,r,-1.0),
glam::Vec4::new(0.0,0.0,r*z_near,0.0),
glam::Vec4::new(0.0,0.0,r * z_near,0.0),
)
}
impl GraphicsCamera{
@ -72,10 +79,10 @@ impl GraphicsCamera{
}
pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{
//f32 good enough for view matrix
glam::Mat4::from_translation(pos)*glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32)
glam::Mat4::from_translation(pos) * glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32)
}
pub fn to_uniform_data(&self,pos:glam::Vec3,angles:glam::Vec2)->[f32;16*4]{
pub fn to_uniform_data(&self,(pos,angles):(glam::Vec3,glam::Vec2))->[f32; 16 * 4]{
let proj=self.proj();
let proj_inv=proj.inverse();
let view_inv=self.world(pos,angles);
@ -98,12 +105,6 @@ impl std::default::Default for GraphicsCamera{
}
}
pub struct FrameState{
pub body:crate::physics::Body,
pub camera:crate::physics::PhysicsCamera,
pub time:integer::Time,
}
pub struct GraphicsState{
pipelines:GraphicsPipelines,
bind_groups:GraphicsBindGroups,
@ -219,7 +220,7 @@ impl GraphicsState{
//wow
let instance=GraphicsModelOwned{
transform:model.transform.into(),
normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
normal_transform:Into::<glam::Mat3>::into(model.transform.matrix3).inverse().transpose(),
color:GraphicsModelColor4::new(model.color),
};
//get or create owned mesh map
@ -238,9 +239,9 @@ impl GraphicsState{
//create
let owned_mesh_id=IndexedGraphicsMeshOwnedRenderConfigId::new(unique_render_config_models.len() as u32);
unique_render_config_models.push(IndexedGraphicsMeshOwnedRenderConfig{
unique_pos:mesh.unique_pos.iter().map(|v|v.to_array().map(Into::into)).collect(),
unique_pos:mesh.unique_pos.iter().map(|&v|*Into::<glam::Vec3>::into(v).as_ref()).collect(),
unique_tex:mesh.unique_tex.iter().map(|v|*v.as_ref()).collect(),
unique_normal:mesh.unique_normal.iter().map(|v|v.to_array().map(Into::into)).collect(),
unique_normal:mesh.unique_normal.iter().map(|&v|*Into::<glam::Vec3>::into(v).as_ref()).collect(),
unique_color:mesh.unique_color.iter().map(|v|*v.as_ref()).collect(),
unique_vertices:mesh.unique_vertices.clone(),
render_config:graphics_group.render,
@ -506,6 +507,7 @@ impl GraphicsState{
model_graphics::Indices::U16(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint16),
},
bind_group,
model_buf,
});
}
}
@ -814,7 +816,7 @@ impl GraphicsState{
});
let camera=GraphicsCamera::default();
let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
let camera_uniforms=camera.to_uniform_data(crate::physics::PhysicsOutputState::default().extrapolate(strafesnet_common::mouse::MouseState::default()));
let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
label:Some("Camera"),
contents:bytemuck::cast_slice(&camera_uniforms),
@ -882,17 +884,16 @@ impl GraphicsState{
view:&wgpu::TextureView,
device:&wgpu::Device,
queue:&wgpu::Queue,
frame_state:FrameState,
physics_output:crate::physics::PhysicsOutputState,
predicted_time:integer::Time,
mouse_pos:glam::IVec2,
){
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});
// update rotation
let camera_uniforms=self.camera.to_uniform_data(
frame_state.body.extrapolated_position(frame_state.time).map(Into::<f32>::into).to_array().into(),
frame_state.camera.simulate_move_angles(glam::IVec2::ZERO)
);
let camera_uniforms=self.camera.to_uniform_data(physics_output.extrapolate(strafesnet_common::mouse::MouseState{pos:mouse_pos,time:predicted_time}));
self.staging_belt
.write_buffer(
&mut encoder,

View File

@ -1,5 +1,7 @@
use strafesnet_common::integer;
pub enum Instruction{
Render(crate::graphics::FrameState),
Render(crate::physics::PhysicsOutputState,integer::Time,glam::IVec2),
//UpdateModel(crate::graphics::GraphicsModelUpdate),
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
ChangeMap(strafesnet_common::map::CompleteMap),
@ -20,7 +22,7 @@ pub fn new<'a>(
surface:wgpu::Surface<'a>,
device:wgpu::Device,
queue:wgpu::Queue,
)->crate::compat_worker::INWorker<'a,Instruction>{
)->crate::compat_worker::INWorker<'a,Instruction>{
let mut resize=None;
crate::compat_worker::INWorker::new(move |ins:Instruction|{
match ins{
@ -31,16 +33,18 @@ pub fn new<'a>(
Instruction::Resize(size,user_settings)=>{
resize=Some((size,user_settings));
}
Instruction::Render(frame_state)=>{
if let Some((size,user_settings))=resize.take(){
Instruction::Render(physics_output,predicted_time,mouse_pos)=>{
if let Some((size,user_settings))=&resize{
println!("Resizing to {:?}",size);
let t0=std::time::Instant::now();
config.width=size.width.max(1);
config.height=size.height.max(1);
surface.configure(&device,&config);
graphics.resize(&device,&config,&user_settings);
graphics.resize(&device,&config,user_settings);
println!("Resize took {:?}",t0.elapsed());
}
//clear every time w/e
resize=None;
//this has to go deeper somehow
let frame=match surface.get_current_texture(){
Ok(frame)=>frame,
@ -56,7 +60,7 @@ pub fn new<'a>(
..wgpu::TextureViewDescriptor::default()
});
graphics.render(&view,&device,&queue,frame_state);
graphics.render(&view,&device,&queue,physics_output,predicted_time,mouse_pos);
frame.present();
}

View File

@ -1,15 +1,15 @@
use std::borrow::{Borrow,Cow};
use std::collections::{HashSet,HashMap};
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::model::{self,MeshId,PolygonIter};
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
use strafesnet_common::zeroes;
use strafesnet_common::integer::{self,Planar64,Planar64Vec3};
pub trait UndirectedEdge{
type DirectedEdge:Copy+DirectedEdge;
fn as_directed(&self,parity:bool)->Self::DirectedEdge;
}
pub trait DirectedEdge{
type UndirectedEdge:Copy+std::fmt::Debug+UndirectedEdge;
type UndirectedEdge:Copy+UndirectedEdge;
fn as_undirected(&self)->Self::UndirectedEdge;
fn parity(&self)->bool;
//this is stupid but may work fine
@ -50,7 +50,6 @@ impl DirectedEdge for SubmeshDirectedEdgeId{
}
//Vertex <-> Edge <-> Face -> Collide
#[derive(Debug)]
pub enum FEV<F,E:DirectedEdge,V>{
Face(F),
Edge(E::UndirectedEdge),
@ -65,9 +64,6 @@ struct Face{
}
struct Vert(Planar64Vec3);
pub trait MeshQuery<FACE:Clone,EDGE:Clone+DirectedEdge,VERT:Clone>{
// Vertex must be Planar64Vec3 because it represents an actual position
type Normal;
type Offset;
fn edge_n(&self,edge_id:EDGE::UndirectedEdge)->Planar64Vec3{
let verts=self.edge_verts(edge_id);
self.vert(verts[1].clone())-self.vert(verts[0].clone())
@ -77,7 +73,7 @@ pub trait MeshQuery<FACE:Clone,EDGE:Clone+DirectedEdge,VERT:Clone>{
(self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1)
}
fn vert(&self,vert_id:VERT)->Planar64Vec3;
fn face_nd(&self,face_id:FACE)->(Self::Normal,Self::Offset);
fn face_nd(&self,face_id:FACE)->(Planar64Vec3,Planar64);
fn face_edges(&self,face_id:FACE)->Cow<Vec<EDGE>>;
fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>;
fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>;
@ -141,22 +137,22 @@ impl PhysicsMesh{
//go go gadget debug print mesh
let data=PhysicsMeshData{
faces:vec![
Face{normal:vec3::raw_xyz( 4294967296, 0, 0),dot:Planar64::raw(4294967296)},
Face{normal:vec3::raw_xyz( 0, 4294967296, 0),dot:Planar64::raw(4294967296)},
Face{normal:vec3::raw_xyz( 0, 0, 4294967296),dot:Planar64::raw(4294967296)},
Face{normal:vec3::raw_xyz(-4294967296, 0, 0),dot:Planar64::raw(4294967296)},
Face{normal:vec3::raw_xyz( 0,-4294967296, 0),dot:Planar64::raw(4294967296)},
Face{normal:vec3::raw_xyz( 0, 0,-4294967296),dot:Planar64::raw(4294967296)}
Face{normal:Planar64Vec3::raw_xyz( 4294967296, 0, 0),dot:Planar64::raw(4294967296)},
Face{normal:Planar64Vec3::raw_xyz( 0, 4294967296, 0),dot:Planar64::raw(4294967296)},
Face{normal:Planar64Vec3::raw_xyz( 0, 0, 4294967296),dot:Planar64::raw(4294967296)},
Face{normal:Planar64Vec3::raw_xyz(-4294967296, 0, 0),dot:Planar64::raw(4294967296)},
Face{normal:Planar64Vec3::raw_xyz( 0,-4294967296, 0),dot:Planar64::raw(4294967296)},
Face{normal:Planar64Vec3::raw_xyz( 0, 0,-4294967296),dot:Planar64::raw(4294967296)}
],
verts:vec![
Vert(vec3::raw_xyz( 4294967296,-4294967296,-4294967296)),
Vert(vec3::raw_xyz( 4294967296, 4294967296,-4294967296)),
Vert(vec3::raw_xyz( 4294967296, 4294967296, 4294967296)),
Vert(vec3::raw_xyz( 4294967296,-4294967296, 4294967296)),
Vert(vec3::raw_xyz(-4294967296, 4294967296,-4294967296)),
Vert(vec3::raw_xyz(-4294967296, 4294967296, 4294967296)),
Vert(vec3::raw_xyz(-4294967296,-4294967296, 4294967296)),
Vert(vec3::raw_xyz(-4294967296,-4294967296,-4294967296))
Vert(Planar64Vec3::raw_xyz( 4294967296,-4294967296,-4294967296)),
Vert(Planar64Vec3::raw_xyz( 4294967296, 4294967296,-4294967296)),
Vert(Planar64Vec3::raw_xyz( 4294967296, 4294967296, 4294967296)),
Vert(Planar64Vec3::raw_xyz( 4294967296,-4294967296, 4294967296)),
Vert(Planar64Vec3::raw_xyz(-4294967296, 4294967296,-4294967296)),
Vert(Planar64Vec3::raw_xyz(-4294967296, 4294967296, 4294967296)),
Vert(Planar64Vec3::raw_xyz(-4294967296,-4294967296, 4294967296)),
Vert(Planar64Vec3::raw_xyz(-4294967296,-4294967296,-4294967296))
]
};
let mesh_topology=PhysicsMeshTopology{
@ -334,7 +330,7 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
for poly_vertices in polygon_group.polys(){
let submesh_face_id=SubmeshFaceId::new(submesh_faces.len() as u32);
//one face per poly
let mut normal=Vector3::new([Fixed::ZERO,Fixed::ZERO,Fixed::ZERO]);
let mut normal=Planar64Vec3::ZERO;
let len=poly_vertices.len();
let face_edges=poly_vertices.into_iter().enumerate().map(|(i,vert_id)|{
let vert0_id=MeshVertId::new(mesh.unique_vertices[vert_id.get() as usize].pos.get() as u32);
@ -345,11 +341,11 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
//https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method)
let v0=mesh.unique_pos[vert0_id.get() as usize];
let v1=mesh.unique_pos[vert1_id.get() as usize];
normal+=Vector3::new([
(v0.y-v1.y)*(v0.z+v1.z),
(v0.z-v1.z)*(v0.x+v1.x),
(v0.x-v1.x)*(v0.y+v1.y),
]);
normal+=Planar64Vec3::new(
(v0.y()-v1.y())*(v0.z()+v1.z()),
(v0.z()-v1.z())*(v0.x()+v1.x()),
(v0.x()-v1.x())*(v0.y()+v1.y()),
);
//get/create edge and push face into it
let (edge_ref_verts,is_sorted)=EdgeRefVerts::new(submesh_vert0_id,submesh_vert1_id);
let (edge_ref_faces,edge_id)=edge_pool.push(edge_ref_verts);
@ -366,16 +362,14 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
//return directed_edge_id
edge_id.as_directed(is_sorted)
}).collect();
let mut dot=Fixed::ZERO;
// find the average dot
//choose precision loss randomly idk
normal=normal/len as i64;
let mut dot=Planar64::ZERO;
for &v in poly_vertices{
dot+=normal.dot(mesh.unique_pos[mesh.unique_vertices[v.get() as usize].pos.get() as usize]);
}
//assume face hash is stable, and there are no flush faces...
let face=Face{
normal:(normal/len as i64).divide().fix_1(),
dot:(dot/(len*len) as i64).fix_1(),
};
let face=Face{normal,dot:dot/len as i64};
let face_id=match face_id_from_face.get(&face){
Some(&face_id)=>face_id,
None=>{
@ -422,8 +416,6 @@ pub struct PhysicsMeshView<'a>{
topology:&'a PhysicsMeshTopology,
}
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for PhysicsMeshView<'_>{
type Normal=Planar64Vec3;
type Offset=Planar64;
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
let face_idx=self.topology.faces[face_id.get() as usize].get() as usize;
(self.data.faces[face_idx].normal,self.data.faces[face_idx].dot)
@ -452,14 +444,14 @@ impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for PhysicsMes
pub struct PhysicsMeshTransform{
pub vertex:integer::Planar64Affine3,
pub normal:integer::mat3::Matrix3<Fixed<2,64>>,
pub det:Fixed<3,96>,
pub normal:integer::Planar64Mat3,
pub det:Planar64,
}
impl PhysicsMeshTransform{
pub fn new(transform:integer::Planar64Affine3)->Self{
pub const fn new(transform:integer::Planar64Affine3)->Self{
Self{
normal:transform.matrix3.adjugate().transpose(),
det:transform.matrix3.det(),
normal:transform.matrix3.inverse_times_det().transpose(),
det:transform.matrix3.determinant(),
vertex:transform,
}
}
@ -470,7 +462,7 @@ pub struct TransformedMesh<'a>{
transform:&'a PhysicsMeshTransform,
}
impl TransformedMesh<'_>{
pub const fn new<'a>(
pub fn new<'a>(
view:PhysicsMeshView<'a>,
transform:&'a PhysicsMeshTransform,
)->TransformedMesh<'a>{
@ -479,33 +471,33 @@ impl TransformedMesh<'_>{
transform,
}
}
pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{
pub fn verts<'a>(&'a self)->impl Iterator<Item=Planar64Vec3>+'a{
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
}
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
let mut best_dot=Planar64::MIN;
let mut best_vert=SubmeshVertId(0);
//this happens to be well-defined. there are no virtual virtices
SubmeshVertId::new(
self.view.topology.verts.iter()
.enumerate()
.max_by_key(|(_,&vert_id)|
dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0))
)
//assume there is more than zero vertices.
.unwrap().0 as u32
)
for (i,vert_id) in self.view.topology.verts.iter().enumerate(){
let p=self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0);
let d=dir.dot(p);
if best_dot<d{
best_dot=d;
best_vert=SubmeshVertId::new(i as u32);
}
}
best_vert
}
}
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for TransformedMesh<'_>{
type Normal=Vector3<Fixed<3,96>>;
type Offset=Fixed<4,128>;
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
let (n,d)=self.view.face_nd(face_id);
let transformed_n=self.transform.normal*n;
let transformed_d=d*self.transform.det+transformed_n.dot(self.transform.vertex.translation);
(transformed_n,transformed_d)
let transformed_d=d+transformed_n.dot(self.transform.vertex.translation)/self.transform.det;
(transformed_n/self.transform.det,transformed_d)
}
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1()
self.transform.vertex.transform_point3(self.view.vert(vert_id))
}
#[inline]
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<Vec<SubmeshDirectedEdgeId>>{
@ -533,11 +525,11 @@ impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for Transforme
//(face,vertex)
//(edge,edge)
//(vertex,face)
#[derive(Clone,Copy,Debug)]
#[derive(Clone,Copy)]
pub enum MinkowskiVert{
VertVert(SubmeshVertId,SubmeshVertId),
}
#[derive(Clone,Copy,Debug)]
#[derive(Clone,Copy)]
pub enum MinkowskiEdge{
VertEdge(SubmeshVertId,SubmeshEdgeId),
EdgeVert(SubmeshEdgeId,SubmeshVertId),
@ -552,7 +544,7 @@ impl UndirectedEdge for MinkowskiEdge{
}
}
}
#[derive(Clone,Copy,Debug)]
#[derive(Clone,Copy)]
pub enum MinkowskiDirectedEdge{
VertEdge(SubmeshVertId,SubmeshDirectedEdgeId),
EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId),
@ -573,7 +565,7 @@ impl DirectedEdge for MinkowskiDirectedEdge{
}
}
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
pub enum MinkowskiFace{
VertFace(SubmeshVertId,SubmeshFaceId),
EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool),
@ -589,7 +581,6 @@ pub struct MinkowskiMesh<'a>{
}
//infinity fev algorithm state transition
#[derive(Debug)]
enum Transition{
Done,//found closest vert, no edges are better
Vert(MinkowskiVert),//transition to vert
@ -599,8 +590,6 @@ enum EV{
Edge(MinkowskiEdge),
}
pub type GigaTime=Ratio<Fixed<4,128>,Fixed<4,128>>;
impl MinkowskiMesh<'_>{
pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{
MinkowskiMesh{
@ -611,7 +600,7 @@ impl MinkowskiMesh<'_>{
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir))
}
fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{
fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{
let mut best_transition=Transition::Done;
for &directed_edge_id in self.vert_edges(vert_id).iter(){
let edge_n=self.directed_edge_n(directed_edge_id);
@ -621,7 +610,7 @@ impl MinkowskiMesh<'_>{
let test_vert_id=edge_verts[directed_edge_id.parity() as usize];
//test if it's closer
let diff=point-self.vert(test_vert_id);
if edge_n.dot(infinity_dir).is_zero(){
if zeroes::zeroes1(edge_n.dot(diff),edge_n.dot(infinity_dir)).len()==0{
let distance_squared=diff.dot(diff);
if distance_squared<*best_distance_squared{
best_transition=Transition::Vert(test_vert_id);
@ -631,21 +620,21 @@ impl MinkowskiMesh<'_>{
}
best_transition
}
fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
let mut best_transition=EV::Vert(vert_id);
let diff=point-self.vert(vert_id);
for &directed_edge_id in self.vert_edges(vert_id).iter(){
let edge_n=self.directed_edge_n(directed_edge_id);
//is boundary uncrossable by a crawl from infinity
//check if time of collision is outside Time::MIN..Time::MAX
if edge_n.dot(infinity_dir).is_zero(){
let d=edge_n.dot(diff);
if zeroes::zeroes1(d,edge_n.dot(infinity_dir)).len()==0{
//test the edge
let edge_nn=edge_n.dot(edge_n);
if !d.is_negative()&&d<=edge_nn{
if Planar64::ZERO<=d&&d<=edge_nn{
let distance_squared={
let c=diff.cross(edge_n);
(c.dot(c)/edge_nn).divide().fix_2()
c.dot(c)/edge_nn
};
if distance_squared<=*best_distance_squared{
best_transition=EV::Edge(directed_edge_id.as_undirected());
@ -691,7 +680,7 @@ impl MinkowskiMesh<'_>{
let boundary_d=boundary_n.dot(delta_pos);
//check if time of collision is outside Time::MIN..Time::MAX
//infinity_dir can always be treated as a velocity
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
if (boundary_d)<=Planar64::ZERO&&zeroes::zeroes1(boundary_d,boundary_n.dot(infinity_dir)*2).len()==0{
//both faces cannot pass this condition, return early if one does.
return FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Face(face_id);
}
@ -705,16 +694,15 @@ impl MinkowskiMesh<'_>{
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;
infinity_body.acceleration=Planar64Vec3::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?
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
crate::face_crawler::CrawlResult::Hit(_,_)=>None,
}
})
}
pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,integer::Time)>{
self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
//continue forwards along the body parabola
match crate::face_crawler::crawl_fev(fev,self,relative_body,relative_body.time,time_limit){
@ -723,7 +711,7 @@ impl MinkowskiMesh<'_>{
}
})
}
pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,integer::Time)>{
//create an extrapolated body at time_limit
let infinity_body=crate::physics::Body::new(
relative_body.extrapolated_position(time_limit),
@ -739,13 +727,10 @@ impl MinkowskiMesh<'_>{
}
})
}
pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,integer::Time)>{
//no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case)
let mut best_time={
let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
let mut best_time=time_limit;
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).iter(){
@ -755,10 +740,10 @@ impl MinkowskiMesh<'_>{
let verts=self.edge_verts(directed_edge_id.as_undirected());
let d=n.dot(self.vert(verts[0])+self.vert(verts[1]));
//WARNING! d outside of *2
//WARNING: truncated precision
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
if Ratio::new(Planar64::ZERO,Planar64::EPSILON).le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
for t in zeroes::zeroes2((n.dot(relative_body.position))*2-d,n.dot(relative_body.velocity)*2,n.dot(relative_body.acceleration)){
let t=relative_body.time+integer::Time::from(t);
if relative_body.time<t&&t<best_time&&n.dot(relative_body.extrapolated_velocity(t))<Planar64::ZERO{
best_time=t;
best_edge=Some(directed_edge_id);
break;
}
@ -766,15 +751,15 @@ impl MinkowskiMesh<'_>{
}
best_edge.map(|e|(e.as_undirected(),best_time))
}
fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,GigaTime)>{
fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,integer::Time)>{
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN,infinity_body.time){
crate::face_crawler::CrawlResult::Miss(_)=>None,
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
}
}
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
let infinity_body=crate::physics::Body::new(point,vec3::Y,vec3::ZERO,integer::Time::ZERO);
let infinity_body=crate::physics::Body::new(point,Planar64Vec3::Y,Planar64Vec3::ZERO,integer::Time::ZERO);
//movement must escape the mesh forwards and backwards in time,
//otherwise the point is not inside the mesh
self.infinity_in(infinity_body)
@ -785,9 +770,7 @@ impl MinkowskiMesh<'_>{
}
}
impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiMesh<'_>{
type Normal=Vector3<Fixed<3,96>>;
type Offset=Fixed<4,128>;
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
fn face_nd(&self,face_id:MinkowskiFace)->(Planar64Vec3,Planar64){
match face_id{
MinkowskiFace::VertFace(v0,f1)=>{
let (n,d)=self.mesh1.face_nd(f1);
@ -801,7 +784,7 @@ impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiM
let n=edge0_n.cross(edge1_n);
let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1));
let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1));
((n*(parity as i64*4-2)).fix_3(),((e0d-e1d)*(parity as i64*2-1)).fix_4())
(n*(parity as i64*4-2),(e0d-e1d)*(parity as i64*2-1))
},
MinkowskiFace::FaceVert(f0,v1)=>{
let (n,d)=self.mesh0.face_nd(f0);
@ -850,18 +833,17 @@ impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiM
let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).borrow();
Cow::Owned([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{
let mut best_edge=None;
let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let mut best_d=Planar64::ZERO;
let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0;
let edge_face1_nn=edge_face1_n.dot(edge_face1_n);
for &directed_edge_id0 in v0e.iter(){
let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0);
//must be behind other face.
let d=edge_face1_n.dot(edge0_n);
if d.is_negative(){
if d<Planar64::ZERO{
let edge0_nn=edge0_n.dot(edge0_n);
// Assume not every number is huge
// TODO: revisit this
let dd=(d*d)/(edge_face1_nn*edge0_nn);
//divide by zero???
let dd=d*d/(edge_face1_nn*edge0_nn);
if best_d<dd{
best_d=dd;
best_edge=Some(directed_edge_id0);
@ -880,15 +862,15 @@ impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiM
let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).borrow();
Cow::Owned([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{
let mut best_edge=None;
let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let mut best_d=Planar64::ZERO;
let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0;
let edge_face0_nn=edge_face0_n.dot(edge_face0_n);
for &directed_edge_id1 in v1e.iter(){
let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1);
let d=edge_face0_n.dot(edge1_n);
if d.is_negative(){
if d<Planar64::ZERO{
let edge1_nn=edge1_n.dot(edge1_n);
let dd=(d*d)/(edge_face0_nn*edge1_nn);
let dd=d*d/(edge_face0_nn*edge1_nn);
if best_d<dd{
best_d=dd;
best_edge=Some(directed_edge_id1);
@ -924,20 +906,19 @@ impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiM
//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.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();
let v0f_n:Vec<Planar64Vec3>=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect();
let v1f_n:Vec<Planar64Vec3>=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect();
let the_len=v0f.len()+v1f.len();
for &directed_edge_id in self.mesh0.vert_edges(v0).iter(){
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);
for face_n in &v1f_n{
//add reflected mesh1 faces
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3());
face_normals.push(*face_n-n*(face_n.dot(n)*2/nn));
}
if is_empty_volume(face_normals){
edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1));
@ -949,7 +930,7 @@ impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiM
let mut face_normals=Vec::with_capacity(the_len);
face_normals.clone_from(&v1f_n);
for face_n in &v0f_n{
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3());
face_normals.push(*face_n-n*(face_n.dot(n)*2/nn));
}
if is_empty_volume(face_normals){
edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id));
@ -964,7 +945,7 @@ impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiM
}
}
fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
fn is_empty_volume(normals:Vec<Planar64Vec3>)->bool{
let len=normals.len();
for i in 0..len-1{
for j in i+1..len{
@ -972,10 +953,9 @@ fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
let mut d_comp=None;
for k in 0..len{
if k!=i&&k!=j{
let d=n.dot(normals[k]).is_negative();
let d=n.dot(normals[k]);
if let Some(comp)=&d_comp{
// This is testing if d_comp*d < 0
if comp^d{
if *comp*d<Planar64::ZERO{
return true;
}
}else{
@ -990,8 +970,8 @@ fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
#[test]
fn test_is_empty_volume(){
assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec()));
assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec()));
assert!(!is_empty_volume([Planar64Vec3::X,Planar64Vec3::Y,Planar64Vec3::Z].to_vec()));
assert!(is_empty_volume([Planar64Vec3::X,Planar64Vec3::Y,Planar64Vec3::Z,Planar64Vec3::NEG_X].to_vec()));
}
#[test]

View File

@ -11,7 +11,7 @@ use strafesnet_common::gameplay_modes::{self,StageId};
use strafesnet_common::gameplay_style::{self,StyleModifiers};
use strafesnet_common::controls_bitflag::Controls;
use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,TimedInstruction};
use strafesnet_common::integer::{self,vec3,mat3,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
use gameplay::ModeState;
//external influence
@ -22,8 +22,8 @@ use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
//when the physics asks itself what happens next, this is how it's represented
#[derive(Debug)]
enum PhysicsInternalInstruction{
CollisionStart(Collision,model_physics::GigaTime),
CollisionEnd(Collision,model_physics::GigaTime),
CollisionStart(Collision),
CollisionEnd(Collision),
StrafeTick,
ReachWalkTargetVelocity,
// Water,
@ -36,7 +36,7 @@ enum PhysicsInstruction{
Input(PhysicsInputInstruction),
}
#[derive(Clone,Copy,Debug,Hash)]
#[derive(Clone,Copy,Debug,Default,Hash)]
pub struct Body{
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
@ -124,13 +124,13 @@ struct ContactMoveState{
}
impl TransientAcceleration{
fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{
if target_diff==vec3::ZERO{
if target_diff==Planar64Vec3::ZERO{
TransientAcceleration::Reached
}else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
TransientAcceleration::Reachable{
acceleration:target_diff.with_length(accel).divide().fix_1(),
time:time+Time::from((target_diff.length()/accel).divide().fix_1())
acceleration:target_diff.with_length(accel),
time:time+Time::from(target_diff.length()/accel)
}
}
}
@ -147,7 +147,7 @@ impl TransientAcceleration{
}
fn acceleration(&self)->Planar64Vec3{
match self{
TransientAcceleration::Reached=>vec3::ZERO,
TransientAcceleration::Reached=>Planar64Vec3::ZERO,
&TransientAcceleration::Reachable{acceleration,time:_}=>acceleration,
&TransientAcceleration::Unreachable{acceleration}=>acceleration,
}
@ -158,7 +158,7 @@ impl ContactMoveState{
Self{
target:TransientAcceleration::ground(walk_settings,body,gravity,target_velocity),
contact,
jump_direction:JumpDirection::Exactly(vec3::Y),
jump_direction:JumpDirection::Exactly(Planar64Vec3::Y),
}
}
fn ladder(ladder_settings:&gameplay_style::LadderSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3,contact:ContactCollision)->Self{
@ -296,7 +296,7 @@ impl PhysicsCamera{
let ay=Angle32::clamp_from_i64(a.y)
//clamp to actual vertical cam limit
.clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT);
mat3::from_rotation_yx(ax,ay)
Planar64Mat3::from_rotation_yx(ax,ay)
}
fn rotation(&self)->Planar64Mat3{
self.get_rotation(self.clamped_mouse_pos)
@ -306,7 +306,7 @@ impl PhysicsCamera{
}
fn get_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{
let ax=-self.sensitivity.x.mul_int(mouse_pos_x as i64);
mat3::from_rotation_y(Angle32::wrap_from_i64(ax))
Planar64Mat3::from_rotation_y(Angle32::wrap_from_i64(ax))
}
fn rotation_y(&self)->Planar64Mat3{
self.get_rotation_y(self.clamped_mouse_pos.x)
@ -410,16 +410,16 @@ impl HitboxMesh{
let transform=PhysicsMeshTransform::new(transform);
let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform);
for vert in transformed_mesh.verts(){
aabb.grow(vert.fix_1());
aabb.grow(vert);
}
Self{
halfsize:aabb.size()>>1,
halfsize:aabb.size()/2,
mesh,
transform,
}
}
#[inline]
const fn transformed_mesh(&self)->TransformedMesh{
fn transformed_mesh(&self)->TransformedMesh{
TransformedMesh::new(self.mesh.complete_mesh_view(),&self.transform)
}
}
@ -438,7 +438,7 @@ impl StyleHelper for StyleModifiers{
fn get_control_dir(&self,controls:Controls)->Planar64Vec3{
//don't get fancy just do it
let mut control_dir:Planar64Vec3=vec3::ZERO;
let mut control_dir:Planar64Vec3 = Planar64Vec3::ZERO;
//Apply mask after held check so you can require non-allowed keys to be held for some reason
let controls=controls.intersection(self.controls_mask);
if controls.contains(Controls::MoveForward){
@ -463,22 +463,19 @@ impl StyleHelper for StyleModifiers{
}
fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
(camera.rotation_y()*self.get_control_dir(controls)).fix_1()
camera.rotation_y()*self.get_control_dir(controls)
}
fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
//don't interpolate this! discrete mouse movement, constant acceleration
(camera.rotation()*self.get_control_dir(controls)).fix_1()
camera.rotation()*self.get_control_dir(controls)
}
fn calculate_mesh(&self)->HitboxMesh{
let mesh=match self.hitbox.mesh{
gameplay_style::HitboxMesh::Box=>PhysicsMesh::unit_cube(),
gameplay_style::HitboxMesh::Cylinder=>PhysicsMesh::unit_cylinder(),
};
let transform=integer::Planar64Affine3::new(
mat3::from_diagonal(self.hitbox.halfsize),
vec3::ZERO
);
let transform=integer::Planar64Affine3::new(Planar64Mat3::from_diagonal(self.hitbox.halfsize),Planar64Vec3::ZERO);
HitboxMesh::new(mesh,transform)
}
}
@ -494,7 +491,7 @@ impl MoveState{
//call this after state.move_state is changed
fn apply_enum(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
match self{
MoveState::Fly=>body.acceleration=vec3::ZERO,
MoveState::Fly=>body.acceleration=Planar64Vec3::ZERO,
MoveState::Air=>{
//calculate base acceleration
let a=touching.base_acceleration(models,style,camera,input_state);
@ -616,6 +613,19 @@ impl MoveState{
}
}
#[derive(Clone,Default)]
pub struct PhysicsOutputState{
body:Body,
camera:PhysicsCamera,
camera_offset:Planar64Vec3,
mouse_pos:glam::IVec2,
}
impl PhysicsOutputState{
pub fn extrapolate(&self,mouse:MouseState)->(glam::Vec3,glam::Vec2){
((self.body.extrapolated_position(mouse.time)+self.camera_offset).into(),self.camera.simulate_move_angles(mouse.pos-self.mouse_pos))
}
}
#[derive(Clone,Hash,Eq,PartialEq)]
enum PhysicsCollisionAttributes{
Contact(gameplay_attributes::ContactAttributes),
@ -769,9 +779,9 @@ impl TouchingState{
//TODO: trey push solve
for contact in &self.contacts{
let n=contact_normal(models,hitbox_mesh,contact);
let d=n.dot(*velocity);
if d.is_negative(){
*velocity-=(n*d/n.length_squared()).divide().fix_1();
let d=n.dot128(*velocity);
if d<0{
*velocity-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64);
}
}
}
@ -779,24 +789,23 @@ impl TouchingState{
//TODO: trey push solve
for contact in &self.contacts{
let n=contact_normal(models,hitbox_mesh,contact);
let d=n.dot(*acceleration);
if d.is_negative(){
*acceleration-=(n*d/n.length_squared()).divide().fix_1();
let d=n.dot128(*acceleration);
if d<0{
*acceleration-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64);
}
}
}
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
let relative_body=VirtualBody::relative(&Body::ZERO,body).body(time);
let relative_body=VirtualBody::relative(&Body::default(),body).body(time);
for contact in &self.contacts{
//detect face slide off
let model_mesh=models.contact_mesh(contact);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{
TimedInstruction{
time:relative_body.time+time.into(),
time,
instruction:PhysicsInternalInstruction::CollisionEnd(
Collision::Contact(*contact),
time
Collision::Contact(*contact)
),
}
}));
@ -807,10 +816,9 @@ impl TouchingState{
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{
TimedInstruction{
time:relative_body.time+time.into(),
time,
instruction:PhysicsInternalInstruction::CollisionEnd(
Collision::Intersect(*intersect),
time
Collision::Intersect(*intersect)
),
}
}));
@ -819,7 +827,6 @@ impl TouchingState{
}
impl Body{
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{
Self{
position,
@ -830,63 +837,20 @@ impl Body{
}
pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{
let dt=time-self.time;
self.position
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
self.position+self.velocity*dt+self.acceleration*(dt*dt/2)
}
pub fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{
let dt=time-self.time;
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
self.velocity+self.acceleration*dt
}
pub fn advance_time(&mut self,time:Time){
self.position=self.extrapolated_position(time);
self.velocity=self.extrapolated_velocity(time);
self.time=time;
}
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
// Why?
// All of this can be removed with const generics because the type can be specified as
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
// which is known to implement all the necessary traits
Num:Copy,
Den:Copy+core::ops::Mul<i64,Output=D1>,
D1:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<D1,Output=N2>,
N1:core::ops::Add<N2,Output=N3>,
Num:core::ops::Mul<N3,Output=N4>,
Den:core::ops::Mul<D1,Output=D2>,
D2:Copy,
Planar64:core::ops::Mul<D2,Output=N4>,
N4:integer::Divide<D2,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt^2/2 + v*dt + p
// (a*dt/2+v)*dt+p
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
.map(|elem|elem.divide().fix())+self.position
}
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt + v
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
}
pub fn advance_time_ratio_dt(&mut self,dt:model_physics::GigaTime){
self.position=self.extrapolated_position_ratio_dt(dt);
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
self.time+=dt.into();
}
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
if self.velocity==vec3::ZERO{
if self.acceleration==vec3::ZERO{
if self.velocity==Planar64Vec3::ZERO{
if self.acceleration==Planar64Vec3::ZERO{
None
}else{
Some(self.acceleration)
@ -900,22 +864,22 @@ impl Body{
aabb.grow(self.extrapolated_position(t1));
//v+a*t==0
//goober code
if !self.acceleration.x.is_zero(){
let t=-self.velocity.x/self.acceleration.x;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
if self.acceleration.x()!=Planar64::ZERO{
let t=Time::from(-self.velocity.x()/self.acceleration.x());
if t0<t&&t<t1{
aabb.grow(self.extrapolated_position(t));
}
}
if !self.acceleration.y.is_zero(){
let t=-self.velocity.y/self.acceleration.y;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
if self.acceleration.y()!=Planar64::ZERO{
let t=Time::from(-self.velocity.y()/self.acceleration.y());
if t0<t&&t<t1{
aabb.grow(self.extrapolated_position(t));
}
}
if !self.acceleration.z.is_zero(){
let t=-self.velocity.z/self.acceleration.z;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
if self.acceleration.z()!=Planar64::ZERO{
let t=Time::from(-self.velocity.z()/self.acceleration.z());
if t0<t&&t<t1{
aabb.grow(self.extrapolated_position(t));
}
}
}
@ -958,7 +922,7 @@ impl VirtualBody<'_>{
pub struct PhysicsState{
time:Time,
body:Body,
_world:WorldState,//currently there is only one state the world can be in
world:WorldState,//currently there is only one state the world can be in
touching:TouchingState,
//camera must exist in state because wormholes modify the camera, also camera punch
camera:PhysicsCamera,
@ -988,14 +952,14 @@ pub struct PhysicsData{
impl Default for PhysicsState{
fn default()->Self{
Self{
body:Body::new(vec3::int(0,50,0),vec3::int(0,0,0),vec3::int(0,-100,0),Time::ZERO),
body:Body::new(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0),Time::ZERO),
time:Time::ZERO,
style:StyleModifiers::default(),
touching:TouchingState::default(),
move_state:MoveState::Air,
camera:PhysicsCamera::default(),
input_state:InputState::default(),
_world:WorldState{},
world:WorldState{},
mode_state:ModeState::default(),
run:run::Run::new(),
}
@ -1083,15 +1047,14 @@ impl instruction::InstructionEmitter<PhysicsInternalInstruction> for PhysicsCont
}
}
impl PhysicsContext{
pub fn camera_body(&self)->Body{
Body{
position:self.state.body.position+self.state.style.camera_offset,
..self.state.body
pub const fn output(&self)->PhysicsOutputState{
PhysicsOutputState{
body:self.state.body,
camera:self.state.camera,
camera_offset:self.state.style.camera_offset,
mouse_pos:self.state.input_state.mouse.pos,
}
}
pub const fn camera(&self)->PhysicsCamera{
self.state.camera
}
pub const fn get_next_mouse(&self)->&MouseState{
self.state.input_state.get_next_mouse()
}
@ -1203,7 +1166,7 @@ impl PhysicsContext{
let mut aabb=aabb::Aabb::default();
let transformed_mesh=TransformedMesh::new(view,transform);
for v in transformed_mesh.verts(){
aabb.grow(v.fix_1());
aabb.grow(v);
}
(ConvexMeshId{
model_id,
@ -1278,15 +1241,12 @@ impl PhysicsContext{
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
//temp (?) code to avoid collision loops
.map_or(None,|(face,dt)|{
let time=relative_body.time+dt.into();
if time<=state.time{None}else{Some((time,face,dt))}})
.map(|(time,face,dt)|
.map_or(None,|(face,time)|if time<=state.time{None}else{Some((face,time))})
.map(|(face,time)|
TimedInstruction{
time,
instruction:PhysicsInternalInstruction::CollisionStart(
Collision::new(convex_mesh_id,face),
dt
Collision::new(convex_mesh_id,face)
)
}
)
@ -1299,8 +1259,7 @@ impl PhysicsContext{
fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{
let model_mesh=models.contact_mesh(contact);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
// TODO: normalize to i64::MAX>>1
minkowski.face_nd(contact.face_id).0.fix_1()
minkowski.face_nd(contact.face_id).0
}
fn recalculate_touching(
@ -1384,12 +1343,12 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
let mut culled=false;
touching.contacts.retain(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
let r=n.dot(v).is_positive();
if r{
let r=n.dot(v)<=Planar64::ZERO;
if !r{
culled=true;
println!("set_velocity_cull contact={:?}",contact);
}
!r
r
});
set_velocity(body,touching,models,hitbox_mesh,v);
culled
@ -1403,12 +1362,12 @@ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&Phys
let mut culled=false;
touching.contacts.retain(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
let r=n.dot(a).is_positive();
if r{
let r=n.dot(a)<=Planar64::ZERO;
if !r{
culled=true;
println!("set_acceleration_cull contact={:?}",contact);
}
!r
r
});
set_acceleration(body,touching,models,hitbox_mesh,a);
culled
@ -1456,10 +1415,8 @@ fn teleport_to_spawn(
input_state:&InputState,
time:Time,
)->Result<(),TeleportToSpawnError>{
const EPSILON:Planar64=Planar64::raw((1<<32)/16);
let transform=models.get_model_transform(stage.spawn()).ok_or(TeleportToSpawnError::NoModel)?;
//TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation
let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]);
let point=transform.vertex.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16);
teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time);
Ok(())
}
@ -1527,9 +1484,9 @@ fn run_teleport_behaviour(
match stage_element.behaviour(){
gameplay_modes::StageElementBehaviour::SpawnAt=>(),
gameplay_modes::StageElementBehaviour::Trigger
|gameplay_modes::StageElementBehaviour::Teleport=>if let Some(mode_state_stage)=mode.get_stage(mode_state.get_stage_id()){
|gameplay_modes::StageElementBehaviour::Teleport=>{
//I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird
let _=teleport_to_spawn(mode_state_stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
let _=teleport_to_spawn(stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
return;
},
gameplay_modes::StageElementBehaviour::Platform=>(),
@ -1580,7 +1537,7 @@ fn collision_start_contact(
Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1();
let reflected_velocity=body.velocity+(body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1);
set_velocity(body,touching,models,hitbox_mesh,reflected_velocity);
},
Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=>
@ -1589,7 +1546,7 @@ fn collision_start_contact(
//kill v
//actually you could do this with a booster attribute :thinking:
//it's a little bit different because maybe you want to chain ladders together
set_velocity(body,touching,models,hitbox_mesh,vec3::ZERO);//model.velocity
set_velocity(body,touching,models,hitbox_mesh,Planar64Vec3::ZERO);//model.velocity
}
//ladder walkstate
let (gravity,target_velocity)=ladder_things(ladder_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
@ -1598,7 +1555,7 @@ fn collision_start_contact(
},
Some(gameplay_attributes::ContactingBehaviour::NoJump)=>todo!("nyi"),
None=>if let Some(walk_settings)=&style.walk{
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),Planar64Vec3::Y){
//ground
let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
let walk_state=ContactMoveState::ground(walk_settings,body,gravity,target_velocity,contact);
@ -1726,20 +1683,17 @@ fn collision_end_intersect(
}
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction>){
state.time=ins.time;
let (should_advance_body,goober_time)=match ins.instruction{
PhysicsInternalInstruction::CollisionStart(_,dt)
|PhysicsInternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)),
PhysicsInternalInstruction::StrafeTick
|PhysicsInternalInstruction::ReachWalkTargetVelocity=>(true,None),
let should_advance_body=match ins.instruction{
PhysicsInternalInstruction::CollisionStart(_)
|PhysicsInternalInstruction::CollisionEnd(_)
|PhysicsInternalInstruction::StrafeTick
|PhysicsInternalInstruction::ReachWalkTargetVelocity=>true,
};
if should_advance_body{
match goober_time{
Some(dt)=>state.body.advance_time_ratio_dt(dt),
None=>state.body.advance_time(state.time),
}
state.body.advance_time(state.time);
}
match ins.instruction{
PhysicsInternalInstruction::CollisionStart(collision,_)=>{
PhysicsInternalInstruction::CollisionStart(collision)=>{
let mode=data.modes.get_mode(state.mode_state.get_mode_id());
match collision{
Collision::Contact(contact)=>collision_start_contact(
@ -1760,7 +1714,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
),
}
},
PhysicsInternalInstruction::CollisionEnd(collision,_)=>match collision{
PhysicsInternalInstruction::CollisionEnd(collision)=>match collision{
Collision::Contact(contact)=>collision_end_contact(
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
data.models.contact_attr(contact.model_id),
@ -1782,9 +1736,9 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
if strafe_settings.activates(controls){
let masked_controls=strafe_settings.mask(controls);
let control_dir=state.style.get_control_dir(masked_controls);
if control_dir!=vec3::ZERO{
if control_dir!=Planar64Vec3::ZERO{
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().fix_1()){
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE)){
//this is wrong but will work ig
//need to note which push planes activate in push solve and keep those
state.cull_velocity(data,ticked_velocity);
@ -1808,7 +1762,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
//we know that the acceleration is precisely zero because the walk target is known to be reachable
//which means that gravity can be fully cancelled
//ignore moving platforms for now
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO);
walk_state.target=TransientAcceleration::Reached;
},
//you are not supposed to reach an unreachable walk target!
@ -1903,9 +1857,9 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
data.models.get_model_transform(mode.get_start().into()).map(|transform|
transform.vertex.translation
)
).unwrap_or(vec3::ZERO);
).unwrap_or(Planar64Vec3::ZERO);
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO);
state.set_move_state(data,MoveState::Air);
b_refresh_walk_target=false;
}
@ -1942,7 +1896,6 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
if b_refresh_walk_target{
state.apply_input_and_body(data);
state.cull_velocity(data,state.body.velocity);
//also check if accelerating away from surface
}
}
@ -1956,7 +1909,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
_=>println!("{}|{:?}",ins.time,ins.instruction),
}
if ins.time<state.time{
println!("@@@@ Time travel warning! state.time={} ins.time={}\nInstruction={:?}",state.time,ins.time,ins.instruction);
println!("@@@@ Time travel warning! {:?}",ins);
}
//idle is special, it is specifically a no-op to get Internal events to catch up to real time
match ins.instruction{
@ -1968,216 +1921,215 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
#[cfg(test)]
mod test{
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
use super::*;
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(Planar64Mat3::from_diagonal(Planar64Vec3::int(5,1,5)/2),Planar64Vec3::ZERO));
let h1=StyleModifiers::roblox_bhop().calculate_mesh();
let hitbox_mesh=h1.transformed_mesh();
let platform_mesh=h0.transformed_mesh();
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
let collision=minkowski.predict_collision_in(&relative_body,Time::MAX);
assert_eq!(collision.map(|tup|tup.1),expected_collision_time,"Incorrect time of collision");
}
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),
integer::Planar64Affine3::new(
integer::Planar64Mat3::from_cols([
int3(5,0,1)>>1,
int3(0,1,0)>>1,
int3(-1,0,5)>>1,
]),
vec3::ZERO
integer::Planar64Mat3::from_cols(
Planar64Vec3::int(5,0,1)/2,
Planar64Vec3::int(0,1,0)/2,
Planar64Vec3::int(-1,0,5)/2,
),
Planar64Vec3::ZERO,
)
);
let h1=StyleModifiers::roblox_bhop().calculate_mesh();
let hitbox_mesh=h1.transformed_mesh();
let platform_mesh=h0.transformed_mesh();
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
let collision=minkowski.predict_collision_in(&relative_body,Time::MAX);
assert_eq!(collision.map(|tup|tup.1),expected_collision_time,"Incorrect time of collision");
}
fn test_collision(relative_body:Body,expected_collision_time:Option<Time>){
test_collision_axis_aligned(relative_body.clone(),expected_collision_time);
test_collision_rotated(relative_body,expected_collision_time);
}
#[test]
fn test_collision_degenerate_straight_down(){
fn test_collision_degenerate(){
test_collision(Body::new(
int3(0,5,0),
int3(0,-1,0),
vec3::ZERO,
Planar64Vec3::int(0,5,0),
Planar64Vec3::int(0,-1,0),
Planar64Vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)));
}
#[test]
fn test_collision_degenerate_east(){
test_collision(Body::new(
int3(3,5,0),
int3(0,-1,0),
vec3::ZERO,
Planar64Vec3::int(3,5,0),
Planar64Vec3::int(0,-1,0),
Planar64Vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)));
}
#[test]
fn test_collision_degenerate_south(){
test_collision(Body::new(
int3(0,5,3),
int3(0,-1,0),
vec3::ZERO,
Planar64Vec3::int(0,5,3),
Planar64Vec3::int(0,-1,0),
Planar64Vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)));
}
#[test]
fn test_collision_degenerate_west(){
test_collision(Body::new(
int3(-3,5,0),
int3(0,-1,0),
vec3::ZERO,
Planar64Vec3::int(-3,5,0),
Planar64Vec3::int(0,-1,0),
Planar64Vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)));
}
#[test]
fn test_collision_degenerate_north(){
test_collision(Body::new(
int3(0,5,-3),
int3(0,-1,0),
vec3::ZERO,
Planar64Vec3::int(0,5,-3),
Planar64Vec3::int(0,-1,0),
Planar64Vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)));
}
#[test]
fn test_collision_parabola_edge_east_from_west(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(3,3,0),
int3(100,-1,0),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(3,3,0),
Planar64Vec3::int(100,-1,0),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_south_from_north(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,3,3),
int3(0,-1,100),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(0,3,3),
Planar64Vec3::int(0,-1,100),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_west_from_east(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(-3,3,0),
int3(-100,-1,0),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(-3,3,0),
Planar64Vec3::int(-100,-1,0),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_north_from_south(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,3,-3),
int3(0,-1,-100),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(0,3,-3),
Planar64Vec3::int(0,-1,-100),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_north_from_ne(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,-7)>>1,
int3(-10,-1,1),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(0,6,-7)/2,
Planar64Vec3::int(-10,-1,1),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_north_from_nw(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,-7)>>1,
int3(10,-1,1),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(0,6,-7)/2,
Planar64Vec3::int(10,-1,1),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_east_from_se(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(7,6,0)>>1,
int3(-1,-1,-10),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(7,6,0)/2,
Planar64Vec3::int(-1,-1,-10),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_east_from_ne(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(7,6,0)>>1,
int3(-1,-1,10),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(7,6,0)/2,
Planar64Vec3::int(-1,-1,10),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_south_from_se(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,7)>>1,
int3(-10,-1,-1),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(0,6,7)/2,
Planar64Vec3::int(-10,-1,-1),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_south_from_sw(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(0,6,7)>>1,
int3(10,-1,-1),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(0,6,7)/2,
Planar64Vec3::int(10,-1,-1),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_west_from_se(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(-7,6,0)>>1,
int3(1,-1,-10),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(-7,6,0)/2,
Planar64Vec3::int(1,-1,-10),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_parabola_edge_west_from_ne(){
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
int3(-7,6,0)>>1,
int3(1,-1,10),
int3(0,-1,0),
test_collision(VirtualBody::relative(&Body::default(),&Body::new(
Planar64Vec3::int(-7,6,0)/2,
Planar64Vec3::int(1,-1,10),
Planar64Vec3::int(0,-1,0),
Time::ZERO
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_oblique(){
test_collision(Body::new(
int3(0,5,0),
int3(1,-64,2)>>6,// /64
vec3::ZERO,
Planar64Vec3::int(0,5,0),
Planar64Vec3::int(1,-64,2)/64,
Planar64Vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)));
}
#[test]
fn zoom_hit_nothing(){
test_collision(Body::new(
int3(0,10,0),
int3(1,0,0),
int3(0,1,0),
Planar64Vec3::int(0,10,0),
Planar64Vec3::int(1,0,0),
Planar64Vec3::int(0,1,0),
Time::ZERO
),None);
}
#[test]
fn already_inside_hit_nothing(){
test_collision(Body::new(
vec3::ZERO,
int3(1,0,0),
int3(0,1,0),
Planar64Vec3::ZERO,
Planar64Vec3::int(1,0,0),
Planar64Vec3::int(0,1,0),
Time::ZERO
),None);
}

View File

@ -180,12 +180,8 @@ impl MouseInterpolator{
self.empty_queue();
}
}
pub fn get_frame_state(&self,time:Time)->crate::graphics::FrameState{
crate::graphics::FrameState{
body:self.physics.camera_body(),
camera:self.physics.camera(),
time:self.timer.time(time),
}
pub fn get_render_stuff(&self,time:Time)->(crate::physics::PhysicsOutputState,Time,glam::IVec2){
(self.physics.output(),self.timer.time(time),self.physics.get_next_mouse().pos)
}
pub fn change_map(&mut self,time:Time,map:&strafesnet_common::map::CompleteMap){
//dump any pending interpolation state
@ -225,8 +221,8 @@ pub fn new<'a>(
interpolator.handle_instruction(&ins);
match ins.instruction{
Instruction::Render=>{
let frame_state=interpolator.get_frame_state(ins.time);
graphics_worker.send(crate::graphics_worker::Instruction::Render(frame_state)).unwrap();
let (physics_output,time,mouse_pos)=interpolator.get_render_stuff(ins.time);
graphics_worker.send(crate::graphics_worker::Instruction::Render(physics_output,time,mouse_pos)).unwrap();
},
Instruction::Resize(size)=>{
graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings().clone())).unwrap();

View File

@ -100,19 +100,19 @@ impl WindowContext<'_>{
if let Some(input_instruction)=match keycode{
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(InputInstruction::Jump(s)),
winit::keyboard::Key::Character(key)=>match key.as_str(){
"w"|"W"=>Some(InputInstruction::MoveForward(s)),
"a"|"A"=>Some(InputInstruction::MoveLeft(s)),
"s"|"S"=>Some(InputInstruction::MoveBack(s)),
"d"|"D"=>Some(InputInstruction::MoveRight(s)),
"e"|"E"=>Some(InputInstruction::MoveUp(s)),
"q"|"Q"=>Some(InputInstruction::MoveDown(s)),
"z"|"Z"=>Some(InputInstruction::Zoom(s)),
"r"|"R"=>if s{
"w"=>Some(InputInstruction::MoveForward(s)),
"a"=>Some(InputInstruction::MoveLeft(s)),
"s"=>Some(InputInstruction::MoveBack(s)),
"d"=>Some(InputInstruction::MoveRight(s)),
"e"=>Some(InputInstruction::MoveUp(s)),
"q"=>Some(InputInstruction::MoveDown(s)),
"z"=>Some(InputInstruction::Zoom(s)),
"r"=>if s{
//mouse needs to be reset since the position is absolute
self.mouse=strafesnet_common::mouse::MouseState::default();
Some(InputInstruction::ResetAndRestart)
}else{None},
"f"|"F"=>if s{Some(InputInstruction::PracticeFly)}else{None},
"f"=>if s{Some(InputInstruction::PracticeFly)}else{None},
_=>None,
},
_=>None,

View File

@ -181,9 +181,9 @@ mod test{
#[test]//How to run this test with printing: cargo test --release -- --nocapture
fn test_worker() {
// Create the worker thread
let test_body=physics::Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO);
let worker=QRWorker::new(physics::Body::ZERO,
|_|physics::Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO)
let test_body=physics::Body::new(integer::Planar64Vec3::ONE,integer::Planar64Vec3::ONE,integer::Planar64Vec3::ONE,integer::Time::ZERO);
let worker=QRWorker::new(physics::Body::default(),
|_|physics::Body::new(integer::Planar64Vec3::ONE,integer::Planar64Vec3::ONE,integer::Planar64Vec3::ONE,integer::Time::ZERO)
);
// Send tasks to the worker