Compare commits
62 Commits
strafe-sta
...
luauu
| Author | SHA1 | Date | |
|---|---|---|---|
|
8801a82ee9
|
|||
|
9c4e49ce15
|
|||
|
fee511e0db
|
|||
|
c57423553a
|
|||
|
9f5af7060f
|
|||
|
6ef2e41421
|
|||
|
84e55c557a
|
|||
|
f96398cc84
|
|||
|
9ff0736d50
|
|||
|
c3d390dda0
|
|||
|
eeb47abde1
|
|||
|
567354cebb
|
|||
|
a787bf2ab2
|
|||
|
623cc8d822
|
|||
|
6da4f3b3fb
|
|||
|
982b837143
|
|||
|
02b52ef748
|
|||
|
e32523e626
|
|||
|
9a0c26cc97
|
|||
|
45c2fdbb3f
|
|||
|
c1d3045a77
|
|||
|
f1ca5a3735
|
|||
|
3fa048434b
|
|||
|
0909a62caf
|
|||
|
97012940cd
|
|||
|
b601667d6a
|
|||
|
6d5c7df14a
|
|||
|
3140c0a552
|
|||
|
f9a92b7a8d
|
|||
|
1fa6d5c031
|
|||
|
05954bc487
|
|||
|
e11d96a9b3
|
|||
|
5ee8a02693
|
|||
|
efba2a8f19
|
|||
|
de44e19909
|
|||
|
c7e29d05d4
|
|||
|
6171ceab6d
|
|||
|
41aeeefb5b
|
|||
|
1473fe8fe1
|
|||
|
ac987a2efd
|
|||
|
1050d824e6
|
|||
|
496f838408
|
|||
|
38a7aaa046
|
|||
|
ce246acb43
|
|||
|
3a7eeaee7f
|
|||
|
9afbd0a91d
|
|||
|
2ffa9cbe6c
|
|||
|
8dee2140e1
|
|||
|
af9f1a218e
|
|||
|
b8442274a5
|
|||
|
72ba5bad63
|
|||
|
d7b779170e
|
|||
|
ff28c0a311
|
|||
|
69f49d3dcf
|
|||
|
a91dfe07ec
|
|||
|
922f80657c
|
|||
|
f05e6b5995
|
|||
|
e2ba15880a
|
|||
|
749ace538d
|
|||
|
6d9fc38ef1
|
|||
|
565b53138c
|
|||
|
b0668136d6
|
630
Cargo.lock
generated
630
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ edition = "2024"
|
||||
arrayvec = "0.7.6"
|
||||
glam = "0.30.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
mlua = { version = "0.11.5", features = ["luau"] }
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
mod body;
|
||||
mod push_solve;
|
||||
mod face_crawler;
|
||||
mod model;
|
||||
mod push_solve;
|
||||
mod minimum_difference;
|
||||
mod minimum_difference_lua;
|
||||
|
||||
pub mod physics;
|
||||
|
||||
|
||||
648
engine/physics/src/minimum_difference.rs
Normal file
648
engine/physics/src/minimum_difference.rs
Normal file
@@ -0,0 +1,648 @@
|
||||
use strafesnet_common::integer::vec3;
|
||||
use strafesnet_common::integer::vec3::Vector3;
|
||||
use strafesnet_common::integer::{Fixed,Planar64Vec3};
|
||||
|
||||
use crate::model::{MeshQuery,MinkowskiMesh,MinkowskiVert};
|
||||
|
||||
// This algorithm is based on Lua code
|
||||
// written by Trey Reynolds in 2021
|
||||
|
||||
type Simplex<const N:usize>=[MinkowskiVert;N];
|
||||
enum Simplex1_3{
|
||||
Simplex1(Simplex<1>),
|
||||
Simplex2(Simplex<2>),
|
||||
Simplex3(Simplex<3>),
|
||||
}
|
||||
impl Simplex1_3{
|
||||
fn push_front(self,v:MinkowskiVert)->Simplex2_4{
|
||||
match self{
|
||||
Simplex1_3::Simplex1([v0])=>Simplex2_4::Simplex2([v,v0]),
|
||||
Simplex1_3::Simplex2([v0,v1])=>Simplex2_4::Simplex3([v,v0,v1]),
|
||||
Simplex1_3::Simplex3([v0,v1,v2])=>Simplex2_4::Simplex4([v,v0,v1,v2]),
|
||||
}
|
||||
}
|
||||
}
|
||||
enum Simplex2_4{
|
||||
Simplex2(Simplex<2>),
|
||||
Simplex3(Simplex<3>),
|
||||
Simplex4(Simplex<4>),
|
||||
}
|
||||
|
||||
/*
|
||||
local function absDet(r, u, v, w)
|
||||
if w then
|
||||
return math.abs((u - r):Cross(v - r):Dot(w - r))
|
||||
elseif v then
|
||||
return (u - r):Cross(v - r).magnitude
|
||||
elseif u then
|
||||
return (u - r).magnitude
|
||||
else
|
||||
return 1
|
||||
end
|
||||
end
|
||||
*/
|
||||
impl Simplex2_4{
|
||||
fn det_is_zero(&self,mesh:&MinkowskiMesh)->bool{
|
||||
match self{
|
||||
&Self::Simplex4([p0,p1,p2,p3])=>{
|
||||
let p0=mesh.vert(p0);
|
||||
let p1=mesh.vert(p1);
|
||||
let p2=mesh.vert(p2);
|
||||
let p3=mesh.vert(p3);
|
||||
(p1-p0).cross(p2-p0).dot(p3-p0)==Fixed::ZERO
|
||||
},
|
||||
&Self::Simplex3([p0,p1,p2])=>{
|
||||
let p0=mesh.vert(p0);
|
||||
let p1=mesh.vert(p1);
|
||||
let p2=mesh.vert(p2);
|
||||
(p1-p0).cross(p2-p0)==vec3::zero()
|
||||
},
|
||||
&Self::Simplex2([p0,p1])=>{
|
||||
let p0=mesh.vert(p0);
|
||||
let p1=mesh.vert(p1);
|
||||
p1-p0==vec3::zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
local function choosePerpendicularDirection(d)
|
||||
local x, y, z = d.x, d.y, d.z
|
||||
local best = math.min(x*x, y*y, z*z)
|
||||
if x*x == best then
|
||||
return Vector3.new(y*y + z*z, -x*y, -x*z)
|
||||
elseif y*y == best then
|
||||
return Vector3.new(-x*y, x*x + z*z, -y*z)
|
||||
else
|
||||
return Vector3.new(-x*z, -y*z, x*x + y*y)
|
||||
end
|
||||
end
|
||||
*/
|
||||
fn choose_perpendicular_direction(d:Planar64Vec3)->Planar64Vec3{
|
||||
let x=d.x.abs();
|
||||
let y=d.y.abs();
|
||||
let z=d.z.abs();
|
||||
if x<y&&x<z{
|
||||
Vector3::new([Fixed::ZERO,-d.z,d.y])
|
||||
}else if y<z{
|
||||
Vector3::new([d.z,Fixed::ZERO,-d.x])
|
||||
}else{
|
||||
Vector3::new([-d.y,d.x,Fixed::ZERO])
|
||||
}
|
||||
}
|
||||
|
||||
const fn choose_any_direction()->Planar64Vec3{
|
||||
vec3::X
|
||||
}
|
||||
|
||||
fn reduce1(
|
||||
[v0]:Simplex<1>,
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
)->Reduced{
|
||||
// --debug.profilebegin("reduceSimplex0")
|
||||
// local a = a1 - a0
|
||||
let p0=-mesh.vert(v0);
|
||||
|
||||
// local p = -a
|
||||
let p=-(p0+point);
|
||||
|
||||
// local direction = p
|
||||
let mut dir=p;
|
||||
|
||||
// if direction.magnitude == 0 then
|
||||
// direction = chooseAnyDirection()
|
||||
if dir==vec3::zero(){
|
||||
dir=choose_any_direction();
|
||||
}
|
||||
|
||||
// return direction, a0, a1
|
||||
Reduced{
|
||||
dir,
|
||||
simplex:Simplex1_3::Simplex1([v0]),
|
||||
}
|
||||
}
|
||||
|
||||
// local function reduceSimplex1(a0, a1, b0, b1)
|
||||
fn reduce2(
|
||||
[v0,v1]:Simplex<2>,
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
)->Reduced{
|
||||
// --debug.profilebegin("reduceSimplex1")
|
||||
// local a = a1 - a0
|
||||
// local b = b1 - b0
|
||||
let p0=-mesh.vert(v0);
|
||||
let p1=-mesh.vert(v1);
|
||||
|
||||
// local p = -a
|
||||
// local u = b - a
|
||||
let p=-(p0+point);
|
||||
let u=p1-p0;
|
||||
|
||||
// -- modify to take into account the radiuses
|
||||
// local p_u = p:Dot(u)
|
||||
let p_u=p.dot(u);
|
||||
|
||||
// if p_u >= 0 then
|
||||
if !p_u.is_negative(){
|
||||
// local direction = u:Cross(p):Cross(u)
|
||||
let direction=u.cross(p).cross(u);
|
||||
|
||||
// if direction.magnitude == 0 then
|
||||
if direction==vec3::zero(){
|
||||
return Reduced{
|
||||
dir:choose_perpendicular_direction(u),
|
||||
simplex:Simplex1_3::Simplex2([v0,v1]),
|
||||
};
|
||||
}
|
||||
|
||||
// -- modify the direction to take into account a0R and b0R
|
||||
// return direction, a0, a1, b0, b1
|
||||
return Reduced{
|
||||
dir:direction.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex2([v0,v1]),
|
||||
};
|
||||
}
|
||||
|
||||
// local direction = p
|
||||
let mut dir=p;
|
||||
|
||||
// if direction.magnitude == 0 then
|
||||
if dir==vec3::zero(){
|
||||
dir=choose_perpendicular_direction(u);
|
||||
}
|
||||
|
||||
// return direction, a0, a1
|
||||
Reduced{
|
||||
dir,
|
||||
simplex:Simplex1_3::Simplex1([v0]),
|
||||
}
|
||||
}
|
||||
|
||||
// local function reduceSimplex2(a0, a1, b0, b1, c0, c1)
|
||||
fn reduce3(
|
||||
[v0,mut v1,v2]:Simplex<3>,
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
)->Reduced{
|
||||
// --debug.profilebegin("reduceSimplex2")
|
||||
// local a = a1 - a0
|
||||
// local b = b1 - b0
|
||||
// local c = c1 - c0
|
||||
let p0=-mesh.vert(v0);
|
||||
let p1=-mesh.vert(v1);
|
||||
let p2=-mesh.vert(v2);
|
||||
|
||||
// local p = -a
|
||||
// local u = b - a
|
||||
// local v = c - a
|
||||
let p=-(p0+point);
|
||||
let mut u=p1-p0;
|
||||
let v=p2-p0;
|
||||
|
||||
// local uv = u:Cross(v)
|
||||
// local up = u:Cross(p)
|
||||
// local pv = p:Cross(v)
|
||||
// local uv_up = uv:Dot(up)
|
||||
// local uv_pv = uv:Dot(pv)
|
||||
let mut uv=u.cross(v);
|
||||
let mut up=u.cross(p);
|
||||
let pv=p.cross(v);
|
||||
let uv_up=uv.dot(up);
|
||||
let uv_pv=uv.dot(pv);
|
||||
|
||||
// if uv_up >= 0 and uv_pv >= 0 then
|
||||
if !uv_up.is_negative()&&!uv_pv.is_negative(){
|
||||
// local uvp = uv:Dot(p)
|
||||
let uvp=uv.dot(p);
|
||||
|
||||
// local direction = uvp < 0 and -uv or uv
|
||||
let direction=if uvp.is_negative(){
|
||||
-uv
|
||||
}else{
|
||||
uv
|
||||
};
|
||||
|
||||
// return direction, a0, a1, b0, b1, c0, c1
|
||||
return Reduced{
|
||||
dir:direction.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex3([v0,v1,v2]),
|
||||
};
|
||||
}
|
||||
|
||||
// local u_u = u:Dot(u)
|
||||
// local v_v = v:Dot(v)
|
||||
// local uDist = uv_up/(u_u*v.magnitude)
|
||||
// local vDist = uv_pv/(v_v*u.magnitude)
|
||||
// local minDist2 = math.min(uDist, vDist)
|
||||
let u_dist=uv_up*v.length();
|
||||
let v_dist=uv_pv*u.length();
|
||||
|
||||
// if vDist == minDist2 then
|
||||
if v_dist<u_dist{
|
||||
u=v;
|
||||
up=-pv;
|
||||
uv=-uv;
|
||||
// b0 = c0
|
||||
// b1 = c1
|
||||
v1=v2;
|
||||
}
|
||||
|
||||
// local p_u = p:Dot(u)
|
||||
let p_u=p.dot(u);
|
||||
|
||||
// if p_u >= 0 then
|
||||
if !p_u.is_negative(){
|
||||
// local direction = up:Cross(u)
|
||||
let direction=up.cross(u);
|
||||
// if direction.magnitude == 0 then
|
||||
if direction==vec3::zero(){
|
||||
// direction = uv
|
||||
return Reduced{
|
||||
dir:uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex2([v0,v1]),
|
||||
};
|
||||
}
|
||||
|
||||
// return direction, a0, a1, b0, b1
|
||||
return Reduced{
|
||||
dir:direction.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex2([v0,v1]),
|
||||
};
|
||||
}
|
||||
|
||||
// local direction = p
|
||||
let dir=p;
|
||||
// if direction.magnitude == 0 then
|
||||
if dir==vec3::zero(){
|
||||
// direction = uv
|
||||
return Reduced{
|
||||
dir:uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex1([v0]),
|
||||
};
|
||||
}
|
||||
// return direction, a0, a0
|
||||
Reduced{
|
||||
dir,
|
||||
simplex:Simplex1_3::Simplex1([v0]),
|
||||
}
|
||||
}
|
||||
|
||||
// local function reduceSimplex3(a0, a1, b0, b1, c0, c1, d0, d1)
|
||||
fn reduce4(
|
||||
[v0,mut v1,mut v2,v3]:Simplex<4>,
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
)->Reduce{
|
||||
// --debug.profilebegin("reduceSimplex3")
|
||||
// local a = a1 - a0
|
||||
// local b = b1 - b0
|
||||
// local c = c1 - c0
|
||||
// local d = d1 - d0
|
||||
let p0=-mesh.vert(v0);
|
||||
let p1=-mesh.vert(v1);
|
||||
let p2=-mesh.vert(v2);
|
||||
let p3=-mesh.vert(v3);
|
||||
|
||||
// local p = -a
|
||||
// local u = b - a
|
||||
// local v = c - a
|
||||
// local w = d - a
|
||||
let p=-(p0+point);
|
||||
let mut u=p1-p0;
|
||||
let mut v=p2-p0;
|
||||
let w=p3-p0;
|
||||
|
||||
// local uv = u:Cross(v)
|
||||
// local vw = v:Cross(w)
|
||||
// local wu = w:Cross(u)
|
||||
// local uvw = uv:Dot(w)
|
||||
// local pvw = vw:Dot(p)
|
||||
// local upw = wu:Dot(p)
|
||||
// local uvp = uv:Dot(p)
|
||||
let mut uv=u.cross(v);
|
||||
let vw=v.cross(w);
|
||||
let wu=w.cross(u);
|
||||
let uv_w=uv.dot(w);
|
||||
let pv_w=vw.dot(p);
|
||||
let up_w=wu.dot(p);
|
||||
let uv_p=uv.dot(p);
|
||||
|
||||
// if pvw/uvw >= 0 and upw/uvw >= 0 and uvp/uvw >= 0 then
|
||||
if !pv_w.div_sign(uv_w).is_negative()
|
||||
||!up_w.div_sign(uv_w).is_negative()
|
||||
||!uv_p.div_sign(uv_w).is_negative(){
|
||||
// origin is contained, this is a positive detection
|
||||
// local direction = Vector3.new(0, 0, 0)
|
||||
// return direction, a0, a1, b0, b1, c0, c1, d0, d1
|
||||
return Reduce::Escape([v0,v1,v2,v3]);
|
||||
}
|
||||
|
||||
// local uvwSign = uvw < 0 and -1 or uvw > 0 and 1 or 0
|
||||
// local uvDist = uvp*uvwSign/uv.magnitude
|
||||
// local vwDist = pvw*uvwSign/vw.magnitude
|
||||
// local wuDist = upw*uvwSign/wu.magnitude
|
||||
// local minDist3 = math.min(uvDist, vwDist, wuDist)
|
||||
let uv_dist=uv_p.mul_sign(uv_w);
|
||||
let vw_dist=pv_w.mul_sign(uv_w);
|
||||
let wu_dist=up_w.mul_sign(uv_w);
|
||||
let wu_len=wu.length();
|
||||
let uv_len=uv.length();
|
||||
let vw_len=vw.length();
|
||||
|
||||
if vw_dist*wu_len<wu_dist*vw_len{
|
||||
// if vwDist == minDist3 then
|
||||
if vw_dist*uv_len<uv_dist*vw_len{
|
||||
(u,v)=(v,w);
|
||||
uv=vw;
|
||||
// uv_p=pv_w; // unused
|
||||
// b0, c0 = c0, d0
|
||||
// b1, c1 = c1, d1
|
||||
(v1,v2)=(v2,v3);
|
||||
}else{
|
||||
v2=v3;
|
||||
}
|
||||
}else{
|
||||
// elseif wuDist == minDist3 then
|
||||
if wu_dist*uv_len<uv_dist*wu_len{
|
||||
(u,v)=(w,u);
|
||||
uv=wu;
|
||||
// uv_p=up_w; // unused
|
||||
// b0, c0 = d0, b0
|
||||
// b1, c1 = d1, b1
|
||||
// before [a,b,c,d]
|
||||
(v1,v2)=(v3,v1);
|
||||
// after [a,d,b]
|
||||
}else{
|
||||
v2=v3;
|
||||
}
|
||||
}
|
||||
|
||||
// local up = u:Cross(p)
|
||||
// local pv = p:Cross(v)
|
||||
// local uv_up = uv:Dot(up)
|
||||
// local uv_pv = uv:Dot(pv)
|
||||
let mut up=u.cross(p);
|
||||
let pv=p.cross(v);
|
||||
let uv_up=uv.dot(up);
|
||||
let uv_pv=uv.dot(pv);
|
||||
|
||||
// if uv_up >= 0 and uv_pv >= 0 then
|
||||
if !uv_up.is_negative()&&!uv_pv.is_negative(){
|
||||
// local direction = uvw < 0 and uv or -uv
|
||||
// return direction, a0, a1, b0, b1, c0, c1
|
||||
if uv_w.is_negative(){
|
||||
return Reduce::Reduced(Reduced{
|
||||
dir:uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex3([v0,v1,v2]),
|
||||
});
|
||||
}else{
|
||||
return Reduce::Reduced(Reduced{
|
||||
dir:-uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex3([v0,v1,v2]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// local u_u = u:Dot(u)
|
||||
// local v_v = v:Dot(v)
|
||||
// local uDist = uv_up/(u_u*v.magnitude)
|
||||
// local vDist = uv_pv/(v_v*u.magnitude)
|
||||
// local minDist2 = math.min(uDist, vDist)
|
||||
let u_dist=uv_up*v.length();
|
||||
let v_dist=uv_pv*u.length();
|
||||
|
||||
// if vDist == minDist2 then
|
||||
if v_dist<u_dist{
|
||||
u=v;
|
||||
up=-pv;
|
||||
uv=-uv;
|
||||
// b0 = c0
|
||||
// b1 = c1
|
||||
v1=v2;
|
||||
}
|
||||
|
||||
// local p_u = p:Dot(u)
|
||||
let p_u=p.dot(u);
|
||||
|
||||
// if p_u >= 0 then
|
||||
if !p_u.is_negative(){
|
||||
// local direction = up:Cross(u)
|
||||
let direction=up.cross(u);
|
||||
// if direction.magnitude == 0 then
|
||||
if direction==vec3::zero(){
|
||||
// direction = uvw < 0 and uv or -uv
|
||||
// return direction, a0, a1, b0, b1
|
||||
if uv_w.is_negative(){
|
||||
return Reduce::Reduced(Reduced{
|
||||
dir:uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex2([v0,v1]),
|
||||
});
|
||||
}else{
|
||||
return Reduce::Reduced(Reduced{
|
||||
dir:-uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex2([v0,v1]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// return direction, a0, a1, b0, b1
|
||||
return Reduce::Reduced(Reduced{
|
||||
dir:direction.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex2([v0,v1]),
|
||||
});
|
||||
}
|
||||
|
||||
// local direction = p
|
||||
let dir=p;
|
||||
// if direction.magnitude == 0 then
|
||||
if dir==vec3::zero(){
|
||||
// direction = uvw < 0 and uv or -uv
|
||||
if uv_w.is_negative(){
|
||||
return Reduce::Reduced(Reduced{
|
||||
dir:uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex1([v0]),
|
||||
});
|
||||
}else{
|
||||
return Reduce::Reduced(Reduced{
|
||||
dir:-uv.narrow_1().unwrap(),
|
||||
simplex:Simplex1_3::Simplex1([v0]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// return direction, a0, a1
|
||||
Reduce::Reduced(Reduced{
|
||||
dir,
|
||||
simplex:Simplex1_3::Simplex1([v0]),
|
||||
})
|
||||
}
|
||||
|
||||
struct Reduced{
|
||||
dir:Planar64Vec3,
|
||||
simplex:Simplex1_3,
|
||||
}
|
||||
|
||||
enum Reduce{
|
||||
Escape(Simplex<4>),
|
||||
Reduced(Reduced),
|
||||
}
|
||||
|
||||
impl Simplex2_4{
|
||||
fn reduce(self,mesh:&MinkowskiMesh,point:Planar64Vec3)->Reduce{
|
||||
match self{
|
||||
Self::Simplex2(simplex)=>Reduce::Reduced(reduce2(simplex,mesh,point)),
|
||||
Self::Simplex3(simplex)=>Reduce::Reduced(reduce3(simplex,mesh,point)),
|
||||
Self::Simplex4(simplex)=>reduce4(simplex,mesh,point),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// local function expand(
|
||||
// queryP, queryQ,
|
||||
// vertA0, vertA1,
|
||||
// vertB0, vertB1,
|
||||
// vertC0, vertC1,
|
||||
// vertD0, vertD1,
|
||||
// accuracy
|
||||
// )
|
||||
fn refine_to_exact(mesh:&MinkowskiMesh,simplex:Simplex<4>)->Simplex2_4{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Intermediate data structure containing a partially complete calculation.
|
||||
/// Sometimes you only care about the topology, and not about the
|
||||
/// exact point of intersection details.
|
||||
pub struct Topology{
|
||||
simplex:Simplex2_4,
|
||||
}
|
||||
impl Topology{
|
||||
/// Returns None if the point is intersecting the mesh.
|
||||
pub fn closest_point_details(self,mesh:&MinkowskiMesh)->Option<Details>{
|
||||
// NOTE: if hits is true, this if statement necessarily evaluates to true.
|
||||
// i.e. hits implies this statement
|
||||
// if -dist <= exitRadius + radiusP + radiusQ then
|
||||
// local posP, posQ = decompose(-point, a0, a1, b0, b1, c0, c1)
|
||||
// return hits, -dist - radiusP - radiusQ,
|
||||
// posP - radiusP*norm, -norm,
|
||||
// posQ + radiusQ*norm, norm
|
||||
// end
|
||||
// return false
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
pub struct Details{
|
||||
// distance:Planar64,
|
||||
// p_pos:Planar64Vec3,
|
||||
// p_norm:Planar64Vec3,
|
||||
// q_pos:Planar64Vec3,
|
||||
// q_norm:Planar64Vec3,
|
||||
}
|
||||
|
||||
pub fn contains_point(mesh:&MinkowskiMesh,point:Planar64Vec3)->bool{
|
||||
const ENABLE_FAST_FAIL:bool=true;
|
||||
minimum_difference::<ENABLE_FAST_FAIL,_>(mesh,point,
|
||||
// on_exact
|
||||
|last_pos,direction|{
|
||||
// local norm = direction.unit
|
||||
// local dist = a:Dot(norm)
|
||||
// local hits = -dist < radiusP + radiusQ
|
||||
// return hits
|
||||
(last_pos+point).dot(direction).is_positive()
|
||||
},
|
||||
// on_escape
|
||||
|_simplex|{
|
||||
// intersection is guaranteed at this point
|
||||
true
|
||||
},
|
||||
// fast_fail value
|
||||
||false
|
||||
)
|
||||
}
|
||||
pub fn closest_fev(mesh:&MinkowskiMesh,point:Planar64Vec3)->Topology{
|
||||
const ENABLE_FAST_FAIL:bool=false;
|
||||
minimum_difference::<ENABLE_FAST_FAIL,_>(mesh,point,
|
||||
// on_exact
|
||||
|_last_pos,_direction|unimplemented!(),
|
||||
// on_escape
|
||||
|simplex|{
|
||||
// local norm, dist, u0, u1, v0, v1, w0, w1 = expand(queryP, queryQ, a0, a1, b0, b1, c0, c1, d0, d1, 1e-5)
|
||||
let simplex=refine_to_exact(mesh,simplex);
|
||||
Topology{simplex}
|
||||
},
|
||||
// fast_fail value is irrelevant and will never be returned!
|
||||
||unreachable!()
|
||||
)
|
||||
}
|
||||
|
||||
// local function minimumDifference(
|
||||
// queryP, radiusP,
|
||||
// queryQ, radiusQ,
|
||||
// exitRadius, testIntersection
|
||||
// )
|
||||
fn minimum_difference<const ENABLE_FAST_FAIL:bool,T>(
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
on_exact:impl FnOnce(Planar64Vec3,Planar64Vec3)->T,
|
||||
on_escape:impl FnOnce(Simplex<4>)->T,
|
||||
on_fast_fail:impl FnOnce()->T,
|
||||
)->T{
|
||||
// local initialAxis = queryQ() - queryP()
|
||||
// local new_point_p = queryP(initialAxis)
|
||||
// local new_point_q = queryQ(-initialAxis)
|
||||
// local direction, a0, a1, b0, b1, c0, c1, d0, d1
|
||||
let mut initial_axis=-mesh.hint_point()+point;
|
||||
// degenerate case
|
||||
if initial_axis==vec3::zero(){
|
||||
initial_axis=choose_any_direction();
|
||||
}
|
||||
let last_point=mesh.farthest_vert(direction);
|
||||
// this represents the 'a' value in the commented code
|
||||
let mut last_pos=-mesh.vert(last_point);
|
||||
let Reduced{dir:mut direction,simplex:mut simplex_small}=reduce1([last_point],mesh,point);
|
||||
|
||||
// exitRadius = testIntersection and 0 or exitRadius or 1/0
|
||||
// for _ = 1, 100 do
|
||||
loop{
|
||||
// new_point_p = queryP(-direction)
|
||||
// new_point_q = queryQ(direction)
|
||||
// local next_point = new_point_q - new_point_p
|
||||
let next_point=mesh.farthest_vert(-direction);
|
||||
let next_pos=-mesh.vert(next_point);
|
||||
|
||||
// if -direction:Dot(next_point) > (exitRadius + radiusP + radiusQ)*direction.magnitude then
|
||||
if ENABLE_FAST_FAIL&&direction.dot(next_pos+point).is_negative(){
|
||||
return on_fast_fail();
|
||||
}
|
||||
|
||||
let simplex_big=simplex_small.push_front(next_point);
|
||||
|
||||
// if
|
||||
// direction:Dot(next_point - a) <= 0 or
|
||||
// absDet(next_point, a, b, c) < 1e-6
|
||||
if !direction.dot(next_pos-last_pos).is_positive()
|
||||
||simplex_big.det_is_zero(mesh){
|
||||
// Found enough information to compute the exact closest point.
|
||||
return on_exact(last_pos,direction);
|
||||
}
|
||||
|
||||
// direction, a0, a1, b0, b1, c0, c1, d0, d1 = reduceSimplex(new_point_p, new_point_q, a0, a1, b0, b1, c0, c1)
|
||||
match simplex_big.reduce(mesh,point){
|
||||
// if a and b and c and d then
|
||||
Reduce::Escape(simplex)=>{
|
||||
// Enough information to conclude that the meshes are intersecting.
|
||||
// Topology information is computed if needed.
|
||||
return on_escape(simplex);
|
||||
},
|
||||
Reduce::Reduced(reduced)=>{
|
||||
direction=reduced.dir;
|
||||
simplex_small=reduced.simplex;
|
||||
},
|
||||
}
|
||||
|
||||
// next loop this will be a
|
||||
last_pos=next_pos;
|
||||
}
|
||||
}
|
||||
159
engine/physics/src/minimum_difference_lua.rs
Normal file
159
engine/physics/src/minimum_difference_lua.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use mlua::{Lua,FromLuaMulti,IntoLuaMulti,Function,Result as LuaResult,Vector};
|
||||
use strafesnet_common::integer::{Planar64,Planar64Vec3,FixedFromFloatError};
|
||||
|
||||
use crate::model::{MeshQuery,MinkowskiMesh};
|
||||
|
||||
pub fn contains_point(
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
)->LuaResult<bool>{
|
||||
Ok(minimum_difference(mesh,point,true)?.hits)
|
||||
}
|
||||
pub fn minimum_difference_details(
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
)->LuaResult<(bool,Details)>{
|
||||
let md=minimum_difference(mesh,point,false)?;
|
||||
Ok((md.hits,md.details.unwrap()))
|
||||
}
|
||||
fn p64v3(v:Vector)->Result<Planar64Vec3,FixedFromFloatError>{
|
||||
Ok(Planar64Vec3::new([
|
||||
v.x().try_into()?,
|
||||
v.y().try_into()?,
|
||||
v.z().try_into()?,
|
||||
]))
|
||||
}
|
||||
fn vec(v:Planar64Vec3)->Vector{
|
||||
Vector::new(v.x.into(),v.y.into(),v.z.into())
|
||||
}
|
||||
struct MinimumDifference{
|
||||
hits:bool,
|
||||
details:Option<Details>
|
||||
}
|
||||
pub struct Details{
|
||||
pub distance:Planar64,
|
||||
pub p_pos:Planar64Vec3,
|
||||
pub p_norm:Planar64Vec3,
|
||||
pub q_pos:Planar64Vec3,
|
||||
pub q_norm:Planar64Vec3,
|
||||
}
|
||||
impl FromLuaMulti for MinimumDifference{
|
||||
fn from_lua_multi(mut values:mlua::MultiValue,_lua:&Lua)->LuaResult<Self>{
|
||||
match values.make_contiguous(){
|
||||
&mut [
|
||||
mlua::Value::Boolean(hits),
|
||||
mlua::Value::Nil,
|
||||
mlua::Value::Nil,
|
||||
mlua::Value::Nil,
|
||||
mlua::Value::Nil,
|
||||
mlua::Value::Nil,
|
||||
]=>Ok(Self{hits,details:None}),
|
||||
&mut [
|
||||
mlua::Value::Boolean(hits),
|
||||
mlua::Value::Number(distance),
|
||||
mlua::Value::Vector(p_pos),
|
||||
mlua::Value::Vector(p_norm),
|
||||
mlua::Value::Vector(q_pos),
|
||||
mlua::Value::Vector(q_norm),
|
||||
]=>Ok(Self{
|
||||
hits,
|
||||
details:Some(Details{
|
||||
distance:distance.try_into().unwrap(),
|
||||
p_pos:p64v3(p_pos).unwrap(),
|
||||
p_norm:p64v3(p_norm).unwrap(),
|
||||
q_pos:p64v3(q_pos).unwrap(),
|
||||
q_norm:p64v3(q_norm).unwrap(),
|
||||
}),
|
||||
}),
|
||||
values=>Err(mlua::Error::runtime(format!("Invalid return values: {values:?}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Args{
|
||||
query_p:Function,
|
||||
radius_p:f64,
|
||||
query_q:Function,
|
||||
radius_q:f64,
|
||||
exit_radius:f64,
|
||||
test_intersection:bool,
|
||||
}
|
||||
impl Args{
|
||||
fn new(
|
||||
lua:&Lua,
|
||||
mesh:&'static MinkowskiMesh<'static>,
|
||||
point:Planar64Vec3,
|
||||
test_intersection:bool,
|
||||
)->LuaResult<Self>{
|
||||
let radius_p=0.0;
|
||||
let radius_q=0.0;
|
||||
let exit_radius=0.0;
|
||||
// Query the farthest point on the mesh in the given direction.
|
||||
let query_p=lua.create_function(move|_,dir:Option<Vector>|{
|
||||
let Some(dir)=dir else{
|
||||
return Ok(vec(mesh.mesh0.hint_point()));
|
||||
};
|
||||
let dir=p64v3(dir).unwrap();
|
||||
let vert_id=mesh.mesh0.farthest_vert(dir);
|
||||
let dir=mesh.mesh0.vert(vert_id);
|
||||
Ok(vec(dir))
|
||||
})?;
|
||||
// query_q is different since it includes the test point offset.
|
||||
let query_q=lua.create_function(move|_,dir:Option<Vector>|{
|
||||
let Some(dir)=dir else{
|
||||
return Ok(vec(mesh.mesh1.hint_point()+point));
|
||||
};
|
||||
let dir=p64v3(dir).unwrap();
|
||||
let vert_id=mesh.mesh1.farthest_vert(dir);
|
||||
let dir=mesh.mesh1.vert(vert_id)+point;
|
||||
Ok(vec(dir))
|
||||
})?;
|
||||
Ok(Args{
|
||||
query_p,
|
||||
radius_p,
|
||||
query_q,
|
||||
radius_q,
|
||||
exit_radius,
|
||||
test_intersection,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl IntoLuaMulti for Args{
|
||||
fn into_lua_multi(self,lua:&Lua)->LuaResult<mlua::MultiValue>{
|
||||
use mlua::IntoLua;
|
||||
Ok(mlua::MultiValue::from_vec(vec![
|
||||
self.query_p.into_lua(lua)?,
|
||||
self.radius_p.into_lua(lua)?,
|
||||
self.query_q.into_lua(lua)?,
|
||||
self.radius_q.into_lua(lua)?,
|
||||
self.exit_radius.into_lua(lua)?,
|
||||
self.test_intersection.into_lua(lua)?,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
fn minimum_difference(
|
||||
mesh:&MinkowskiMesh,
|
||||
point:Planar64Vec3,
|
||||
test_intersection:bool,
|
||||
)->LuaResult<MinimumDifference>{
|
||||
let ctx=init_lua()?;
|
||||
// SAFETY: mesh lifetime must outlive args usages
|
||||
let mesh=unsafe{core::mem::transmute(mesh)};
|
||||
let args=Args::new(&ctx.lua,mesh,point,test_intersection)?;
|
||||
ctx.f.call(args)
|
||||
}
|
||||
|
||||
struct Ctx{
|
||||
lua:Lua,
|
||||
f:Function,
|
||||
}
|
||||
fn init_lua()->LuaResult<Ctx>{
|
||||
static SOURCE:std::sync::LazyLock<String>=std::sync::LazyLock::new(||std::fs::read_to_string("/home/quat/strafesnet/game/src/ReplicatedStorage/Shared/Trey-MinimumDifference.lua").unwrap());
|
||||
let lua=Lua::new();
|
||||
lua.sandbox(true)?;
|
||||
let lib_f=lua.load(SOURCE.as_str()).set_name("Trey-MinimumDifference").into_function()?;
|
||||
let lib:mlua::Table=lib_f.call(())?;
|
||||
let f=lib.raw_get("difference")?;
|
||||
Ok(Ctx{lua,f})
|
||||
}
|
||||
@@ -91,7 +91,6 @@ pub trait MeshQuery{
|
||||
(self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
|
||||
}
|
||||
/// This must return a point inside the mesh.
|
||||
#[expect(dead_code)]
|
||||
fn hint_point(&self)->Planar64Vec3;
|
||||
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
|
||||
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
|
||||
@@ -510,7 +509,7 @@ impl TransformedMesh<'_>{
|
||||
pub fn verts<'a>(&'a self)->impl Iterator<Item=Vector3<Fixed<2,64>>>+'a{
|
||||
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
|
||||
}
|
||||
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
|
||||
pub fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
|
||||
//this happens to be well-defined. there are no virtual virtices
|
||||
SubmeshVertId::new(
|
||||
self.view.topology.verts.iter()
|
||||
@@ -568,7 +567,7 @@ impl MeshQuery for TransformedMesh<'_>{
|
||||
//(face,vertex)
|
||||
//(edge,edge)
|
||||
//(vertex,face)
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
#[derive(Clone,Copy,Debug,Eq,PartialEq)]
|
||||
pub enum MinkowskiVert{
|
||||
VertVert(SubmeshVertId,SubmeshVertId),
|
||||
}
|
||||
@@ -587,7 +586,7 @@ impl UndirectedEdge for MinkowskiEdge{
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
#[derive(Clone,Copy,Debug,Eq,PartialEq)]
|
||||
pub enum MinkowskiDirectedEdge{
|
||||
VertEdge(SubmeshVertId,SubmeshDirectedEdgeId),
|
||||
EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId),
|
||||
@@ -620,8 +619,8 @@ pub enum MinkowskiFace{
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MinkowskiMesh<'a>{
|
||||
mesh0:TransformedMesh<'a>,
|
||||
mesh1:TransformedMesh<'a>,
|
||||
pub mesh0:TransformedMesh<'a>,
|
||||
pub mesh1:TransformedMesh<'a>,
|
||||
}
|
||||
|
||||
//infinity fev algorithm state transition
|
||||
@@ -648,7 +647,7 @@ impl MinkowskiMesh<'_>{
|
||||
mesh1,
|
||||
}
|
||||
}
|
||||
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
|
||||
pub 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{
|
||||
@@ -811,20 +810,11 @@ impl MinkowskiMesh<'_>{
|
||||
}
|
||||
best_edge
|
||||
}
|
||||
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
|
||||
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
||||
// 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);
|
||||
//movement must escape the mesh forwards and backwards in time,
|
||||
//otherwise the point is not inside the mesh
|
||||
self.infinity_in(infinity_body)
|
||||
.is_some_and(|_|
|
||||
self.infinity_in(-infinity_body)
|
||||
.is_some()
|
||||
)
|
||||
pub fn contains_point(&self,point:Planar64Vec3)->bool{
|
||||
let contains_point_lua=crate::minimum_difference_lua::contains_point(self,point).unwrap();
|
||||
let contains_point=crate::minimum_difference::contains_point(self,point);
|
||||
println!("contains_point={contains_point} contains_point_lua={contains_point_lua}");
|
||||
contains_point
|
||||
}
|
||||
}
|
||||
impl MeshQuery for MinkowskiMesh<'_>{
|
||||
@@ -864,8 +854,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
||||
}
|
||||
}
|
||||
fn hint_point(&self)->Planar64Vec3{
|
||||
self.mesh1.transform.vertex.translation-
|
||||
self.mesh0.transform.vertex.translation
|
||||
self.mesh0.transform.vertex.translation-self.mesh1.transform.vertex.translation
|
||||
}
|
||||
fn face_edges(&self,face_id:MinkowskiFace)->impl AsRef<[MinkowskiDirectedEdge]>{
|
||||
match face_id{
|
||||
|
||||
@@ -488,21 +488,8 @@ impl StyleHelper for StyleModifiers{
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
struct StrafeTickState{
|
||||
tick_number:u64,
|
||||
}
|
||||
impl StrafeTickState{
|
||||
fn next_tick(&self,settings:&gameplay_style::StrafeSettings)->Time{
|
||||
let n=self.tick_number as i128;
|
||||
let ticks=settings.tick_rate.num() as i128;
|
||||
let seconds=settings.tick_rate.den() as i128;
|
||||
let time=n*seconds/ticks;
|
||||
Time::from_nanos(time as i64)
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
enum MoveState{
|
||||
Air(StrafeTickState),
|
||||
Air,
|
||||
Walk(ContactMoveState),
|
||||
Ladder(ContactMoveState),
|
||||
#[expect(dead_code)]
|
||||
@@ -510,21 +497,11 @@ enum MoveState{
|
||||
Fly,
|
||||
}
|
||||
impl MoveState{
|
||||
fn air(time:Time,settings:Option<&gameplay_style::StrafeSettings>)->Self{
|
||||
let tick_number=if let Some(settings)=settings{
|
||||
// let time=n*seconds/ticks;
|
||||
let time=time.nanos() as i128;
|
||||
let ticks=settings.tick_rate.num() as i128;
|
||||
let seconds=settings.tick_rate.den() as i128;
|
||||
(time*ticks/seconds) as u64
|
||||
}else{0};
|
||||
MoveState::Air(StrafeTickState{tick_number})
|
||||
}
|
||||
//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::Air(_)=>{
|
||||
MoveState::Air=>{
|
||||
//calculate base acceleration
|
||||
let a=touching.base_acceleration(models,style,camera,input_state);
|
||||
//set_acceleration clips according to contacts
|
||||
@@ -536,7 +513,7 @@ impl MoveState{
|
||||
//function to coerce &mut self into &self
|
||||
fn apply_to_body(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
|
||||
match self{
|
||||
MoveState::Air(_)=>(),
|
||||
MoveState::Air=>(),
|
||||
MoveState::Water=>(),
|
||||
MoveState::Fly=>{
|
||||
//set velocity according to current control state
|
||||
@@ -557,7 +534,7 @@ impl MoveState{
|
||||
fn apply_input(&mut self,body:&Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
|
||||
match self{
|
||||
MoveState::Fly
|
||||
|MoveState::Air(_)
|
||||
|MoveState::Air
|
||||
|MoveState::Water=>(),
|
||||
MoveState::Walk(ContactMoveState{target,contact,jump_direction:_})=>{
|
||||
if let Some(walk_settings)=&style.walk{
|
||||
@@ -582,13 +559,13 @@ impl MoveState{
|
||||
MoveState::Walk(walk_state)
|
||||
|MoveState::Ladder(walk_state)
|
||||
=>Some(walk_state),
|
||||
MoveState::Air(_)
|
||||
MoveState::Air
|
||||
|MoveState::Water
|
||||
|MoveState::Fly
|
||||
=>None,
|
||||
}
|
||||
}
|
||||
fn next_move_instruction(&self,strafe:Option<&gameplay_style::StrafeSettings>)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||
//check if you have a valid walk state and create an instruction
|
||||
match self{
|
||||
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
|
||||
@@ -600,9 +577,9 @@ impl MoveState{
|
||||
|TransientAcceleration::Reached
|
||||
=>None,
|
||||
}
|
||||
MoveState::Air(strafe_tick_state)=>strafe.as_ref().map(|strafe|{
|
||||
MoveState::Air=>strafe.as_ref().map(|strafe|{
|
||||
TimedInstruction{
|
||||
time:strafe_tick_state.next_tick(strafe),
|
||||
time:strafe.next_tick(time),
|
||||
//only poll the physics if there is a before and after mouse event
|
||||
instruction:InternalInstruction::StrafeTick
|
||||
}
|
||||
@@ -630,7 +607,7 @@ impl MoveState{
|
||||
//this function call reads the above state that was just set
|
||||
self.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
}
|
||||
fn cull_velocity(&mut self,velocity:Planar64Vec3,body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState,time:Time){
|
||||
fn cull_velocity(&mut self,velocity:Planar64Vec3,body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
|
||||
//TODO: be more precise about contacts
|
||||
if set_velocity_cull(body,touching,models,hitbox_mesh,velocity){
|
||||
// TODO do better
|
||||
@@ -638,7 +615,7 @@ impl MoveState{
|
||||
match self.get_walk_state(){
|
||||
// did you stop touching the thing you were walking on?
|
||||
Some(walk_state)=>if !touching.contains_contact(&walk_state.contact.convex_mesh_id){
|
||||
self.set_move_state(MoveState::air(time,style.strafe.as_ref()),body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
}else{
|
||||
// stopped touching something else while walking
|
||||
self.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
@@ -913,7 +890,7 @@ impl Default for PhysicsState{
|
||||
time:Time::ZERO,
|
||||
style:StyleModifiers::default(),
|
||||
touching:TouchingState::default(),
|
||||
move_state:MoveState::air(Time::ZERO,None),
|
||||
move_state:MoveState::Air,
|
||||
camera:PhysicsCamera::default(),
|
||||
input_state:InputState::default(),
|
||||
_world:WorldState{},
|
||||
@@ -958,10 +935,10 @@ impl PhysicsState{
|
||||
*self=Self::default();
|
||||
}
|
||||
fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||
self.move_state.next_move_instruction(self.style.strafe.as_ref())
|
||||
self.move_state.next_move_instruction(&self.style.strafe,self.time)
|
||||
}
|
||||
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3,time:Time){
|
||||
self.move_state.cull_velocity(velocity,&mut self.body,&mut self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state,time);
|
||||
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
|
||||
self.move_state.cull_velocity(velocity,&mut self.body,&mut self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state);
|
||||
}
|
||||
fn set_move_state(&mut self,data:&PhysicsData,move_state:MoveState){
|
||||
self.move_state.set_move_state(move_state,&mut self.body,&self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state);
|
||||
@@ -1285,7 +1262,7 @@ fn recalculate_touching(
|
||||
//I would have preferred while let Some(contact)=contacts.pop()
|
||||
//but there is no such method
|
||||
while let Some((&convex_mesh_id,_face_id))=touching.contacts.iter().next(){
|
||||
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(convex_mesh_id.model_id),&convex_mesh_id,time)
|
||||
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(convex_mesh_id.model_id),&convex_mesh_id)
|
||||
}
|
||||
while let Some(&convex_mesh_id)=touching.intersects.iter().next(){
|
||||
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(convex_mesh_id.model_id),&convex_mesh_id,time);
|
||||
@@ -1300,7 +1277,7 @@ fn recalculate_touching(
|
||||
//no checks are needed because of the time limits.
|
||||
let model_mesh=models.mesh(convex_mesh_id);
|
||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
||||
if minkowski.is_point_in_mesh(body.position){
|
||||
if minkowski.contains_point(body.position){
|
||||
match convex_mesh_id.model_id{
|
||||
//being inside of contact objects is an invalid physics state
|
||||
//but the physics isn't advanced enough to do anything about it yet
|
||||
@@ -1621,7 +1598,7 @@ fn collision_start_contact(
|
||||
gameplay_attributes::SetTrajectory::TargetPointTime{..}=>todo!(),
|
||||
gameplay_attributes::SetTrajectory::TargetPointSpeed{..}=>todo!(),
|
||||
&gameplay_attributes::SetTrajectory::Velocity(velocity)=>{
|
||||
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state,time);
|
||||
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
},
|
||||
gameplay_attributes::SetTrajectory::DotVelocity{..}=>todo!(),
|
||||
}
|
||||
@@ -1651,7 +1628,7 @@ fn collision_start_contact(
|
||||
}else{
|
||||
let jump_dir=walk_state.jump_direction.direction(models,hitbox_mesh,&walk_state.contact);
|
||||
let jumped_velocity=jump_settings.jumped_velocity(style,jump_dir,body.velocity,attr.general.booster.as_ref());
|
||||
move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state,time);
|
||||
move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1681,7 +1658,7 @@ fn collision_start_intersect(
|
||||
touching.insert_intersect(intersect);
|
||||
//insta booster!
|
||||
if let Some(booster)=&attr.general.booster{
|
||||
move_state.cull_velocity(booster.boost(body.velocity),body,touching,models,hitbox_mesh,style,camera,input_state,time);
|
||||
move_state.cull_velocity(booster.boost(body.velocity),body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
}
|
||||
if let Some(mode)=mode{
|
||||
let zone=mode.get_zone(intersect.convex_mesh_id.model_id.into());
|
||||
@@ -1715,7 +1692,6 @@ fn collision_end_contact(
|
||||
input_state:&InputState,
|
||||
_attr:&gameplay_attributes::ContactAttributes,
|
||||
convex_mesh_id:&ConvexMeshId<ContactModelId>,
|
||||
time:Time,
|
||||
){
|
||||
touching.remove_contact(convex_mesh_id);//remove contact before calling contact_constrain_acceleration
|
||||
//check ground
|
||||
@@ -1726,7 +1702,7 @@ fn collision_end_contact(
|
||||
// This does not check the face! Is that a bad thing? It should be
|
||||
// impossible to stop touching a different face than you started touching...
|
||||
Some(walk_state)=>if &walk_state.contact.convex_mesh_id==convex_mesh_id{
|
||||
move_state.set_move_state(MoveState::air(time,style.strafe.as_ref()),body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
}else{
|
||||
// stopped touching something else while walking
|
||||
move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
@@ -1805,8 +1781,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
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.convex_mesh_id.model_id),
|
||||
&contact.convex_mesh_id,
|
||||
state.time,
|
||||
&contact.convex_mesh_id
|
||||
),
|
||||
Collision::Intersect(intersect)=>collision_end_intersect(
|
||||
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
|
||||
@@ -1818,11 +1793,6 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
),
|
||||
},
|
||||
InternalInstruction::StrafeTick=>{
|
||||
let strafe_tick_state=match &mut state.move_state{
|
||||
MoveState::Air(strafe_tick_state)=>strafe_tick_state,
|
||||
_=>panic!("StrafeTick fired for non-air MoveState"),
|
||||
};
|
||||
strafe_tick_state.tick_number+=1;
|
||||
//TODO make this less huge
|
||||
if let Some(strafe_settings)=&state.style.strafe{
|
||||
let controls=state.input_state.controls;
|
||||
@@ -1834,7 +1804,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){
|
||||
//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,state.time);
|
||||
state.cull_velocity(data,ticked_velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1842,7 +1812,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
}
|
||||
InternalInstruction::ReachWalkTargetVelocity=>{
|
||||
match &mut state.move_state{
|
||||
MoveState::Air(_)
|
||||
MoveState::Air
|
||||
|MoveState::Water
|
||||
|MoveState::Fly
|
||||
=>println!("ReachWalkTargetVelocity fired for non-walking MoveState"),
|
||||
@@ -1884,7 +1854,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
||||
|MoveState::Water
|
||||
|MoveState::Walk(_)
|
||||
|MoveState::Ladder(_)=>true,
|
||||
MoveState::Air(_)=>false,
|
||||
MoveState::Air=>false,
|
||||
}
|
||||
},
|
||||
//the body must be updated unconditionally
|
||||
@@ -1918,7 +1888,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
||||
let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
|
||||
let booster_option=data.models.contact_attr(walk_state.contact.convex_mesh_id.model_id).general.booster.as_ref();
|
||||
let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
|
||||
state.cull_velocity(data,jumped_velocity,state.time);
|
||||
state.cull_velocity(data,jumped_velocity);
|
||||
}
|
||||
}
|
||||
b_refresh_walk_target=false;
|
||||
@@ -1948,7 +1918,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
||||
}
|
||||
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());
|
||||
state.set_move_state(data,MoveState::air(state.time,state.style.strafe.as_ref()));
|
||||
state.set_move_state(data,MoveState::Air);
|
||||
b_refresh_walk_target=false;
|
||||
}
|
||||
// Spawn does not necessarily imply reset
|
||||
@@ -1972,7 +1942,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
||||
Instruction::Misc(MiscInstruction::PracticeFly)=>{
|
||||
match &state.move_state{
|
||||
MoveState::Fly=>{
|
||||
state.set_move_state(data,MoveState::air(state.time,state.style.strafe.as_ref()));
|
||||
state.set_move_state(data,MoveState::Air);
|
||||
},
|
||||
_=>{
|
||||
state.set_move_state(data,MoveState::Fly);
|
||||
@@ -1987,7 +1957,7 @@ 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,state.time);
|
||||
state.cull_velocity(data,state.body.velocity);
|
||||
//also check if accelerating away from surface
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
||||
|
||||
use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
|
||||
use crate::controls_bitflag::Controls;
|
||||
use crate::physics::Time as PhysicsTime;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct StyleModifiers{
|
||||
@@ -272,6 +273,9 @@ impl StrafeSettings{
|
||||
false=>None,
|
||||
}
|
||||
}
|
||||
pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{
|
||||
PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
|
||||
}
|
||||
pub const fn activates(&self,controls:Controls)->bool{
|
||||
self.enable.activates(controls)
|
||||
}
|
||||
|
||||
@@ -366,7 +366,7 @@ impl_additive_operator!( Fixed, BitXor, bitxor, Self );
|
||||
// non-wide operators. The result is the same width as the inputs.
|
||||
|
||||
// This macro is not used in the default configuration.
|
||||
#[expect(unused_macros)]
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_multiplicative_operator_not_const_generic {
|
||||
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
|
||||
@@ -545,7 +545,7 @@ impl_shift_operator!( Fixed, Shr, shr, Self );
|
||||
|
||||
// wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
|
||||
|
||||
#[expect(unused_macros)]
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_wide_operators{
|
||||
($lhs:expr,$rhs:expr)=>{
|
||||
impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||
|
||||
@@ -13,16 +13,16 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
bytemuck = "1.14.3"
|
||||
glam = "0.30.0"
|
||||
regex = { version = "1.11.3", default-features = false }
|
||||
rbx_binary = { version = "1.0.1-sn5", registry = "strafesnet" }
|
||||
rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" }
|
||||
rbx_mesh = "0.5.0"
|
||||
rbx_reflection = "5.0.0"
|
||||
rbx_reflection_database = "1.0.0"
|
||||
rbx_xml = { version = "1.0.1-sn5", registry = "strafesnet" }
|
||||
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
|
||||
roblox_emulator = { version = "0.5.1", path = "../roblox_emulator", default-features = false, registry = "strafesnet" }
|
||||
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
|
||||
strafesnet_deferred_loader = { version = "0.5.1", path = "../deferred_loader", registry = "strafesnet" }
|
||||
rbx_binary = "2.0.1"
|
||||
rbx_dom_weak = "4.1.0"
|
||||
rbx_reflection = "6.1.0"
|
||||
rbx_reflection_database = "2.0.2"
|
||||
rbx_xml = "2.0.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -559,7 +559,7 @@ pub fn convert<'a>(
|
||||
//just going to leave it like this for now instead of reworking the data structures for this whole thing
|
||||
let textureless_render_group=render_config_deferred_loader.acquire_render_config_id(None);
|
||||
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
let basepart=&db.classes["BasePart"];
|
||||
let baseparts=dom.descendants().filter(|&instance|
|
||||
db.classes.get(instance.class.as_str()).is_some_and(|class|
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "roblox_emulator"
|
||||
version = "0.5.2"
|
||||
version = "0.5.1"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -15,10 +15,10 @@ run-service=[]
|
||||
glam = "0.30.0"
|
||||
mlua = { version = "0.11.3", features = ["luau"] }
|
||||
phf = { version = "0.13.1", features = ["macros"] }
|
||||
rbx_dom_weak = "4.1.0"
|
||||
rbx_reflection = "6.1.0"
|
||||
rbx_reflection_database = "2.0.2"
|
||||
rbx_types = "3.1.0"
|
||||
rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" }
|
||||
rbx_reflection = "5.0.0"
|
||||
rbx_reflection_database = "1.0.0"
|
||||
rbx_types = "2.0.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Context{
|
||||
}
|
||||
/// Creates an iterator over all items of a particular class.
|
||||
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
let Some(superclass)=db.classes.get(superclass)else{
|
||||
panic!("Invalid class");
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ impl PartialEq for EnumItem<'_>{
|
||||
pub struct Enums;
|
||||
impl Enums{
|
||||
pub fn get(&self,index:&str)->Option<EnumItems<'static>>{
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
db.enums.get(index).map(|ed|EnumItems{ed})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>
|
||||
}
|
||||
|
||||
pub fn class_is_a(class:&str,superclass:&str)->bool{
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{
|
||||
return false;
|
||||
};
|
||||
@@ -80,14 +80,14 @@ pub fn find_first_descendant_of_class<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak
|
||||
}
|
||||
|
||||
pub fn find_first_child_which_is_a<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
let superclass_descriptor=db.classes.get(superclass)?;
|
||||
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|{
|
||||
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
|
||||
})
|
||||
}
|
||||
pub fn find_first_descendant_which_is_a<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
let superclass_descriptor=db.classes.get(superclass)?;
|
||||
dom.descendants_of(instance.referent()).find(|inst|{
|
||||
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
|
||||
@@ -282,7 +282,7 @@ impl mlua::UserData for Instance{
|
||||
dom_mut(lua,|dom|{
|
||||
let instance=this.get(dom)?;
|
||||
//println!("__index t={} i={index:?}",instance.name);
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
||||
// Find existing property
|
||||
// Interestingly, ustr can know ahead of time if
|
||||
@@ -344,7 +344,7 @@ impl mlua::UserData for Instance{
|
||||
let index_str=&*index.to_str()?;
|
||||
dom_mut(lua,|dom|{
|
||||
let instance=this.get_mut(dom)?;
|
||||
let db=rbx_reflection_database::get().unwrap();
|
||||
let db=rbx_reflection_database::get();
|
||||
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
||||
let property=db.superclasses_iter(class).find_map(|cls|
|
||||
cls.properties.get(index_str)
|
||||
|
||||
@@ -13,10 +13,10 @@ futures = "0.3.31"
|
||||
image = "0.25.2"
|
||||
image_dds = "0.7.1"
|
||||
rbx_asset = { version = "0.5.0", registry = "strafesnet" }
|
||||
rbx_binary = "2.0.1"
|
||||
rbx_dom_weak = "4.1.0"
|
||||
rbx_reflection_database = "2.0.2"
|
||||
rbx_xml = "2.0.1"
|
||||
rbx_binary = { version = "1.0.1-sn5", registry = "strafesnet" }
|
||||
rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" }
|
||||
rbx_reflection_database = "1.0.0"
|
||||
rbx_xml = { version = "1.0.1-sn5", registry = "strafesnet" }
|
||||
rbxassetid = { version = "0.1.0", registry = "strafesnet" }
|
||||
strafesnet_bsp_loader = { version = "0.3.1", path = "../lib/bsp_loader", registry = "strafesnet" }
|
||||
strafesnet_deferred_loader = { version = "0.5.1", path = "../lib/deferred_loader", registry = "strafesnet" }
|
||||
|
||||
Reference in New Issue
Block a user