Collision detection refactoring and improvements

This commit is contained in:
dexy 2019-12-09 22:33:36 +11:00
parent dd03e24fb0
commit 58d2293358
4 changed files with 728 additions and 413 deletions

View File

@ -32,11 +32,11 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using CodeWalker.World;
using TC = System.ComponentModel.TypeConverterAttribute;
using EXP = System.ComponentModel.ExpandableObjectConverter;
namespace CodeWalker.GameFiles
{
@ -231,9 +231,29 @@ namespace CodeWalker.GameFiles
default: return null; // throw new Exception("Unknown bound type");
}
}
public virtual SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return new SpaceSphereIntersectResult();
}
public virtual SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
return new SpaceRayIntersectResult();
}
}
[TC(typeof(EXP))] public class BoundSphere : Bounds
{ }
{
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return base.SphereIntersect(ref sph);
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
return base.RayIntersect(ref ray, maxdist, itemhitdist);
}
}
[TC(typeof(EXP))] public class BoundCapsule : Bounds
{
public override long BlockLength
@ -274,9 +294,27 @@ namespace CodeWalker.GameFiles
writer.Write(this.Unknown_78h);
writer.Write(this.Unknown_7Ch);
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return base.SphereIntersect(ref sph);
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
return base.RayIntersect(ref ray, maxdist, itemhitdist);
}
}
[TC(typeof(EXP))] public class BoundBox : Bounds
{ }
{
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return base.SphereIntersect(ref sph);
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
return base.RayIntersect(ref ray, maxdist, itemhitdist);
}
}
[TC(typeof(EXP))] public class BoundDisc : Bounds
{
public override long BlockLength
@ -317,6 +355,15 @@ namespace CodeWalker.GameFiles
writer.Write(this.Unknown_78h);
writer.Write(this.Unknown_7Ch);
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return base.SphereIntersect(ref sph);
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
return base.RayIntersect(ref ray, maxdist, itemhitdist);
}
}
[TC(typeof(EXP))] public class BoundCylinder : Bounds
{
@ -358,6 +405,15 @@ namespace CodeWalker.GameFiles
writer.Write(this.Unknown_78h);
writer.Write(this.Unknown_7Ch);
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return base.SphereIntersect(ref sph);
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
return base.RayIntersect(ref ray, maxdist, itemhitdist);
}
}
[TC(typeof(EXP))] public class BoundGeometry : Bounds
{
@ -699,6 +755,15 @@ namespace CodeWalker.GameFiles
}
return list.ToArray();
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
return base.SphereIntersect(ref sph);
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
return base.RayIntersect(ref ray, maxdist, itemhitdist);
}
}
[TC(typeof(EXP))] public class BoundUnknown1 : ResourceSystemBlock
@ -1186,6 +1251,345 @@ namespace CodeWalker.GameFiles
if (BVH != null) list.Add(BVH);
return list.ToArray();
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult();
var box = new BoundingBox();
Vector3 p1, p2, p3, p4, a1, a2, a3;
Vector3 n1 = Vector3.Zero;
var tsph = new BoundingSphere();
var spht = new BoundingSphere();
var sp = sph.Center;
var sr = sph.Radius;
if (Polygons == null)
{ return res; }
if ((BVH?.Nodes?.data_items == null) || (BVH?.Trees?.data_items == null))
{ return res; }
box.Minimum = BoundingBoxMin;
box.Maximum = BoundingBoxMax;
if (!sph.Intersects(ref box))
{ return res; }
var q = BVH.Quantum.XYZ();
var c = BVH.BoundingBoxCenter.XYZ();
var cg = CenterGeom;
for (int t = 0; t < BVH.Trees.data_items.Length; t++)
{
var tree = BVH.Trees.data_items[t];
box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c;
box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c;
if (!sph.Intersects(ref box))
{ continue; }
int nodeind = tree.NodeIndex1;
int lastind = tree.NodeIndex2;
while (nodeind < lastind)
{
var node = BVH.Nodes.data_items[nodeind];
box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c;
box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c;
bool nodehit = sph.Intersects(ref box);
bool nodeskip = !nodehit;
if (node.PolyCount <= 0) //intermediate node with child nodes
{
if (nodeskip)
{
nodeind += node.PolyId; //(child node count)
}
else
{
nodeind++;
}
}
else //leaf node, with polygons
{
if (!nodeskip)
{
var lastp = node.PolyId + node.PolyCount;
lastp = Math.Min(lastp, (int)PolygonsCount);
for (int p = node.PolyId; p < lastp; p++)
{
var polygon = Polygons[p];
bool polyhit = false;
switch (polygon.Type)
{
case BoundPolygonType.Triangle:
var ptri = polygon as BoundPolygonTriangle;
p1 = GetVertex(ptri.vertIndex1) + cg;
p2 = GetVertex(ptri.vertIndex2) + cg;
p3 = GetVertex(ptri.vertIndex3) + cg;
polyhit = sph.Intersects(ref p1, ref p2, ref p3);
if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1));
break;
case BoundPolygonType.Sphere:
var psph = polygon as BoundPolygonSphere;
tsph.Center = GetVertex(psph.sphereIndex) + cg;
tsph.Radius = psph.sphereRadius;
polyhit = sph.Intersects(ref tsph);
if (polyhit) n1 = Vector3.Normalize(sph.Center - tsph.Center);
break;
case BoundPolygonType.Capsule:
var pcap = polygon as BoundPolygonCapsule;
var tcap = new BoundingCapsule();
tcap.PointA = GetVertex(pcap.capsuleIndex1) + cg;
tcap.PointB = GetVertex(pcap.capsuleIndex2) + cg;
tcap.Radius = pcap.capsuleRadius;
polyhit = sph.Intersects(ref tcap, out n1);
break;
case BoundPolygonType.Box:
var pbox = polygon as BoundPolygonBox;
p1 = GetVertex(pbox.boxIndex1);// + cg; //corner
p2 = GetVertex(pbox.boxIndex2);// + cg;
p3 = GetVertex(pbox.boxIndex3);// + cg;
p4 = GetVertex(pbox.boxIndex4);// + cg;
a1 = ((p3 + p4) - (p1 + p2)) * 0.5f;
a2 = p3 - (p1 + a1);
a3 = p4 - (p1 + a1);
Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length());
Vector3 m1 = a1 / bs.X;
Vector3 m2 = a2 / bs.Y;
Vector3 m3 = a3 / bs.Z;
if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3);
else if (bs.Y < bs.Z) m2 = Vector3.Cross(m3, m1);
else m3 = Vector3.Cross(m1, m2);
Vector3 tp = sp - (p1 + cg);
spht.Center = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3));
spht.Radius = sph.Radius;
box.Minimum = Vector3.Zero;
box.Maximum = bs;
polyhit = spht.Intersects(ref box);
if (polyhit)
{
Vector3 smin = spht.Center - spht.Radius;
Vector3 smax = spht.Center + spht.Radius;
float eps = spht.Radius * 0.8f;
n1 = Vector3.Zero;
if (Math.Abs(smax.X) < eps) n1 -= m1;
else if (Math.Abs(smin.X - bs.X) < eps) n1 += m1;
if (Math.Abs(smax.Y) < eps) n1 -= m2;
else if (Math.Abs(smin.Y - bs.Y) < eps) n1 += m2;
if (Math.Abs(smax.Z) < eps) n1 -= m3;
else if (Math.Abs(smin.Z - bs.Z) < eps) n1 += m3;
float n1l = n1.Length();
if (n1l > 0.0f) n1 = n1 / n1l;
else n1 = Vector3.UnitZ;
}
break;
case BoundPolygonType.Cylinder:
var pcyl = polygon as BoundPolygonCylinder;
//var tcyl = new BoundingCylinder();
//tcyl.PointA = GetVertex(pcyl.cylinderIndex1) + cg;
//tcyl.PointB = GetVertex(pcyl.cylinderIndex2) + cg;
//tcyl.Radius = pcyl.cylinderRadius;
//////polyhit = ray.Intersects(ref tcyl, out polyhittestdist, out n1);
////////TODO
var ttcap = new BoundingCapsule();//just use the capsule intersection for now...
ttcap.PointA = GetVertex(pcyl.cylinderIndex1) + cg;
ttcap.PointB = GetVertex(pcyl.cylinderIndex2) + cg;
ttcap.Radius = pcyl.cylinderRadius;
polyhit = sph.Intersects(ref ttcap, out n1);
break;
default:
break;
}
if (polyhit) // && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist))
{
res.HitPolygon = polygon;
//itemhitdist = polyhittestdist;
//ybnhit = true;
res.Hit = true;
res.Normal = n1;
}
res.TestedPolyCount++;
}
}
nodeind++;
}
res.TestedNodeCount++;
}
}
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
var box = new BoundingBox();
var tsph = new BoundingSphere();
var rayt = new Ray();
Vector3 p1, p2, p3, p4, a1, a2, a3;
Vector3 n1 = Vector3.Zero;
var rp = ray.Position;
var rd = ray.Direction;
if (Polygons == null)
{ return res; }
if ((BVH?.Nodes?.data_items == null) || (BVH?.Trees?.data_items == null))
{ return res; }
box.Minimum = BoundingBoxMin;
box.Maximum = BoundingBoxMax;
float bvhboxhittest;
if (!ray.Intersects(ref box, out bvhboxhittest))
{ return res; }
if (bvhboxhittest > itemhitdist)
{ return res; } //already a closer hit.
res.HitDist = itemhitdist;
var q = BVH.Quantum.XYZ();
var c = BVH.BoundingBoxCenter.XYZ();
var cg = CenterGeom;
for (int t = 0; t < BVH.Trees.data_items.Length; t++)
{
var tree = BVH.Trees.data_items[t];
box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c;
box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c;
if (!ray.Intersects(ref box, out bvhboxhittest))
{ continue; }
if (bvhboxhittest > res.HitDist)
{ continue; } //already a closer hit.
if (bvhboxhittest > maxdist)
{ continue; }
int nodeind = tree.NodeIndex1;
int lastind = tree.NodeIndex2;
while (nodeind < lastind)
{
var node = BVH.Nodes.data_items[nodeind];
box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c;
box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c;
bool nodehit = ray.Intersects(ref box, out bvhboxhittest);
bool nodeskip = !nodehit || (bvhboxhittest > res.HitDist);
if (node.PolyCount <= 0) //intermediate node with child nodes
{
if (nodeskip)
{
nodeind += node.PolyId; //(child node count)
}
else
{
nodeind++;
}
}
else //leaf node, with polygons
{
if (!nodeskip)
{
var lastp = node.PolyId + node.PolyCount;
lastp = Math.Min(lastp, (int)PolygonsCount);
for (int p = node.PolyId; p < lastp; p++)
{
var polygon = Polygons[p];
float polyhittestdist = float.MaxValue;
bool polyhit = false;
switch (polygon.Type)
{
case BoundPolygonType.Triangle:
var ptri = polygon as BoundPolygonTriangle;
p1 = GetVertex(ptri.vertIndex1) + cg;
p2 = GetVertex(ptri.vertIndex2) + cg;
p3 = GetVertex(ptri.vertIndex3) + cg;
polyhit = ray.Intersects(ref p1, ref p2, ref p3, out polyhittestdist);
if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1));
break;
case BoundPolygonType.Sphere:
var psph = polygon as BoundPolygonSphere;
tsph.Center = GetVertex(psph.sphereIndex) + cg;
tsph.Radius = psph.sphereRadius;
polyhit = ray.Intersects(ref tsph, out polyhittestdist);
if (polyhit) n1 = Vector3.Normalize((ray.Position + ray.Direction * polyhittestdist) - tsph.Center);
break;
case BoundPolygonType.Capsule:
var pcap = polygon as BoundPolygonCapsule;
var tcap = new BoundingCapsule();
tcap.PointA = GetVertex(pcap.capsuleIndex1) + cg;
tcap.PointB = GetVertex(pcap.capsuleIndex2) + cg;
tcap.Radius = pcap.capsuleRadius;
polyhit = ray.Intersects(ref tcap, out polyhittestdist);
res.Position = (ray.Position + ray.Direction * polyhittestdist);
if (polyhit) n1 = tcap.Normal(ref res.Position);
break;
case BoundPolygonType.Box:
var pbox = polygon as BoundPolygonBox;
p1 = GetVertex(pbox.boxIndex1);// + cg; //corner
p2 = GetVertex(pbox.boxIndex2);// + cg;
p3 = GetVertex(pbox.boxIndex3);// + cg;
p4 = GetVertex(pbox.boxIndex4);// + cg;
a1 = ((p3 + p4) - (p1 + p2)) * 0.5f;
a2 = p3 - (p1 + a1);
a3 = p4 - (p1 + a1);
Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length());
Vector3 m1 = a1 / bs.X;
Vector3 m2 = a2 / bs.Y;
Vector3 m3 = a3 / bs.Z;
if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3);
else if (bs.Y < bs.Z) m2 = Vector3.Cross(m3, m1);
else m3 = Vector3.Cross(m1, m2);
Vector3 tp = rp - (p1 + cg);
rayt.Position = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3));
rayt.Direction = new Vector3(Vector3.Dot(rd, m1), Vector3.Dot(rd, m2), Vector3.Dot(rd, m3));
box.Minimum = Vector3.Zero;
box.Maximum = bs;
polyhit = rayt.Intersects(ref box, out polyhittestdist);
if (polyhit)
{
Vector3 hpt = rayt.Position + rayt.Direction * polyhittestdist;
const float eps = 0.002f;
if (Math.Abs(hpt.X) < eps) n1 = -m1;
else if (Math.Abs(hpt.X - bs.X) < eps) n1 = m1;
else if (Math.Abs(hpt.Y) < eps) n1 = -m2;
else if (Math.Abs(hpt.Y - bs.Y) < eps) n1 = m2;
else if (Math.Abs(hpt.Z) < eps) n1 = -m3;
else if (Math.Abs(hpt.Z - bs.Z) < eps) n1 = m3;
else
{ n1 = Vector3.UnitZ; } //ray starts inside the box...
}
break;
case BoundPolygonType.Cylinder:
var pcyl = polygon as BoundPolygonCylinder;
var tcyl = new BoundingCylinder();
tcyl.PointA = GetVertex(pcyl.cylinderIndex1) + cg;
tcyl.PointB = GetVertex(pcyl.cylinderIndex2) + cg;
tcyl.Radius = pcyl.cylinderRadius;
polyhit = ray.Intersects(ref tcyl, out polyhittestdist, out n1);
if (polyhit) n1.Normalize();
break;
default:
break;
}
if (polyhit && (polyhittestdist < res.HitDist) && (polyhittestdist < maxdist))
{
res.HitDist = polyhittestdist;
res.Hit = true;
res.Normal = n1;
res.HitPolygon = polygon;
byte matind = ((PolygonMaterialIndices != null) && (p < PolygonMaterialIndices.Length)) ? PolygonMaterialIndices[p] : (byte)0;
BoundMaterial_s mat = ((Materials != null) && (matind < Materials.Length)) ? Materials[matind] : new BoundMaterial_s();
res.Material = mat;
}
res.TestedPolyCount++;
}
}
nodeind++;
}
res.TestedNodeCount++;
}
}
return res;
}
}
[TC(typeof(EXP))] public class BoundComposite : Bounds
{
@ -1334,6 +1738,69 @@ namespace CodeWalker.GameFiles
if (BVH != null) list.Add(BVH);
return list.ToArray();
}
public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph)
{
var res = new SpaceSphereIntersectResult();
var compchilds = Children?.data_items;
if (compchilds == null)
{ return res; }
for (int i = 0; i < compchilds.Length; i++)
{
var c = compchilds[i];
var chit = c.SphereIntersect(ref sph);
if (chit.Hit)
{
res.Hit = true;
res.HitPolygon = chit.HitPolygon;
res.Normal = chit.Normal;
}
res.TestedPolyCount += chit.TestedPolyCount;
res.TestedNodeCount += chit.TestedNodeCount;
}
res.TestComplete = true;
return res;
}
public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue)
{
var res = new SpaceRayIntersectResult();
res.HitDist = itemhitdist;
var compchilds = Children?.data_items;
if (compchilds == null)
{ return res; }
for (int i = 0; i < compchilds.Length; i++)
{
Bounds c = compchilds[i];
if (c == null)
{ continue; }
var bghit = c.RayIntersect(ref ray, maxdist, res.HitDist);
if (bghit.Hit)
{
res.Hit = true;
res.HitDist = bghit.HitDist;
res.HitPolygon = bghit.HitPolygon;
res.Material = bghit.Material;
res.Normal = bghit.Normal;
}
res.TestedNodeCount += bghit.TestedNodeCount;
res.TestedPolyCount += bghit.TestedPolyCount;
}
return res;
}
}
[Flags] public enum EBoundCompositeFlags

View File

@ -96,4 +96,200 @@ namespace CodeWalker
}
}
public struct BoundingCapsule
{
public Vector3 PointA;
public Vector3 PointB;
public float Radius;
}
public static class BoundingCapsuleMath
{
public static bool Intersects(this Ray r, ref BoundingCapsule capsule, out float dist)
{
// intersect capsule : http://www.iquilezles.org/www/articles/intersectors/intersectors.htm
Vector3 ba = capsule.PointB - capsule.PointA;
Vector3 oa = r.Position - capsule.PointA;
float baba = Vector3.Dot(ba,ba);
float bard = Vector3.Dot(ba,r.Direction);
float baoa = Vector3.Dot(ba,oa);
float rdoa = Vector3.Dot(r.Direction,oa);
float oaoa = Vector3.Dot(oa,oa);
float r2 = capsule.Radius * capsule.Radius;
float a = baba - bard*bard;
float b = baba*rdoa - baoa*bard;
float c = baba*oaoa - baoa*baoa - r2*baba;
float h = b*b - a*c;
if( h>=0.0f )
{
float t = (-b-(float)Math.Sqrt(h))/a;
float y = baoa + t*bard;
// body
if (y > 0.0f && y < baba)
{
dist = t;
return true;
}
// caps
Vector3 oc = (y<=0.0f) ? oa : r.Position - capsule.PointB;
b = Vector3.Dot(r.Direction,oc);
c = Vector3.Dot(oc,oc) - r2;
h = b*b - c;
if( h>0.0f )
{
dist = -b - (float)Math.Sqrt(h);
return true;
}
}
dist = -1.0f;
return false;
}
public static Vector3 Normal(this BoundingCapsule c, ref Vector3 position)
{
Vector3 ba = c.PointB - c.PointA;
Vector3 pa = position - c.PointA;
float h = Math.Min(Math.Max(Vector3.Dot(pa,ba)/Vector3.Dot(ba,ba),0.0f),1.0f);
return (pa - h*ba)/c.Radius;
}
public static bool Intersects(this BoundingSphere sph, ref BoundingCapsule capsule, out Vector3 norm)
{
var dist = LineMath.PointSegmentDistance(ref sph.Center, ref capsule.PointA, ref capsule.PointB);
var rads = sph.Radius + capsule.Radius;
if (dist <= rads)
{
norm = LineMath.PointSegmentNormal(ref sph.Center, ref capsule.PointA, ref capsule.PointB);
return true;
}
else
{
norm = Vector3.Up;
return false;
}
}
}
public struct BoundingCylinder
{
public Vector3 PointA;
public Vector3 PointB;
public float Radius;
}
public static class BoundingCylinderMath
{
public static bool Intersects(this Ray r, ref BoundingCylinder cylinder, out float dist, out Vector3 norm)
{
// intersect cylinder : https://www.shadertoy.com/view/4lcSRn
Vector3 ba = cylinder.PointB - cylinder.PointA;
Vector3 oc = r.Position - cylinder.PointA;
float baba = Vector3.Dot(ba, ba);
float bard = Vector3.Dot(ba, r.Direction);
float baoc = Vector3.Dot(ba, oc);
float r2 = cylinder.Radius * cylinder.Radius;
float k2 = baba - bard * bard;
float k1 = baba * Vector3.Dot(oc, r.Direction) - baoc * bard;
float k0 = baba * Vector3.Dot(oc, oc) - baoc * baoc - r2 * baba;
float h = k1 * k1 - k2 * k0;
if (h < 0.0f)
{
dist = -1.0f;
norm = Vector3.Up;
return false;
}
h = (float)Math.Sqrt(h);
float t = (-k1 - h) / k2;
// body
float y = baoc + t * bard;
if (y > 0.0f && y < baba)
{
dist = t;
norm = (oc + t * r.Direction - ba * y / baba) / cylinder.Radius;
return true;
}
// caps
t = (((y < 0.0f) ? 0.0f : baba) - baoc) / bard;
if (Math.Abs(k1 + k2 * t) < h)
{
dist = t;
norm = ba * Math.Sign(y) / baba;
return true;
}
dist = -1.0f;
norm = Vector3.Up;
return false;
}
}
public static class LineMath
{
public static float PointSegmentDistance(ref Vector3 v, ref Vector3 a, ref Vector3 b)
{
//https://stackoverflow.com/questions/4858264/find-the-distance-from-a-3d-point-to-a-line-segment
Vector3 ab = b - a;
Vector3 av = v - a;
if (Vector3.Dot(av, ab) <= 0.0f)// Point is lagging behind start of the segment, so perpendicular distance is not viable.
{
return av.Length(); // Use distance to start of segment instead.
}
Vector3 bv = v - b;
if (Vector3.Dot(bv, ab) >= 0.0f)// Point is advanced past the end of the segment, so perpendicular distance is not viable.
{
return bv.Length(); // Use distance to end of the segment instead.
}
return Vector3.Cross(ab, av).Length() / ab.Length();// Perpendicular distance of point to segment.
}
public static Vector3 PointSegmentNormal(ref Vector3 v, ref Vector3 a, ref Vector3 b)
{
Vector3 ab = b - a;
Vector3 av = v - a;
if (Vector3.Dot(av, ab) <= 0.0f)
{
return Vector3.Normalize(av);
}
Vector3 bv = v - b;
if (Vector3.Dot(bv, ab) >= 0.0f)
{
return Vector3.Normalize(bv);
}
return Vector3.Normalize(Vector3.Cross(Vector3.Cross(ab, av), ab));
}
}
}

View File

@ -860,14 +860,14 @@ namespace CodeWalker.World
if (!r.SphereHit.Hit)
{
if (absdisp > (e.Radius * 2.0f)) //fast-moving... do a ray test to make sure it's not tunnelling
if (absdisp > e.Radius) //fast-moving... do a ray test to make sure it's not tunnelling
{
Ray rayt = new Ray(sphpos, r.HitVelDir);
float rayl = absdisp + e.Radius * 4.0f; //include some extra incase of glancing hit
var rayhit = RayIntersect(rayt, rayl);
if (rayhit.Hit) //looks like it is tunnelling... need to find the sphere hit point
{
sph.Center = rayhit.Position;
sph.Center = rayhit.Position - (r.HitVelDir*Math.Min(e.Radius*0.5f, rayhit.HitDist));
float hitd = rayhit.HitDist;
r.HitT = hitd / absdisp;
if (r.HitT > 1.0f)
@ -1066,24 +1066,9 @@ namespace CodeWalker.World
{
var res = new SpaceRayIntersectResult();
if (GameFileCache == null) return res;
int polytestcount = 0;
int nodetestcount = 0;
bool testcomplete = true;
res.HitDist = float.MaxValue;
var box = new BoundingBox();
var tsph = new BoundingSphere();
var rayt = new Ray();
var rp = ray.Position;
var rd = ray.Direction;
var boxhitdist = float.MaxValue;
var itemhitdist = float.MaxValue;
Vector3 p1, p2, p3, p4, a1, a2, a3;
Vector3 n1 = Vector3.Zero;
float polyhittestdist = 0;
bool hit = false;
BoundPolygon hitpoly = null;
BoundMaterial_s hitmat = new BoundMaterial_s();
Vector3 hitnorm = Vector3.Zero;
Vector3 hitpos = Vector3.Zero;
if (BoundsStore == null) return res;
var boundslist = BoundsStore.GetItems(ref ray);
@ -1101,216 +1086,51 @@ namespace CodeWalker.World
float boxhitdisttest;
if (ray.Intersects(ref box, out boxhitdisttest))
{
if (boxhitdisttest > res.HitDist)
{ continue; } //already a closer hit
if (boxhitdisttest > maxdist)
{ continue; }
YbnFile ybn = GameFileCache.GetYbn(bound.Name);
if (ybn == null)
{ continue; } //ybn not found?
if (!ybn.Loaded)
{ testcomplete = false; continue; } //ybn not loaded yet...
bool ybnhit = false;
var b = ybn.Bounds;
box.Minimum = b.BoundingBoxMin;
box.Maximum = b.BoundingBoxMax;
float itemboxhitdisttest;
if (!ray.Intersects(ref box, out itemboxhitdisttest))
{ continue; } //ray doesn't hit this ybn
if (itemboxhitdisttest > itemhitdist)
{ continue; } //already a closer hit.
if (itemboxhitdisttest > maxdist)
{ continue; }
switch (b.Type)
//box.Minimum = b.BoundingBoxMin;
//box.Maximum = b.BoundingBoxMax;
//float itemboxhitdisttest;
//if (!ray.Intersects(ref box, out itemboxhitdisttest))
//{ continue; } //ray doesn't hit this ybn
//if (itemboxhitdisttest > res.HitDist)
//{ continue; } //already a closer hit.
//if (itemboxhitdisttest > maxdist)
//{ continue; }
var bhit = b.RayIntersect(ref ray, maxdist, res.HitDist);
if (bhit.Hit)
{
case 10: //BoundComposite
BoundComposite boundcomp = b as BoundComposite;
if (boundcomp == null)
{ continue; }
var compchilds = boundcomp.Children?.data_items;
if (compchilds == null)
{ continue; }
for (int i = 0; i < compchilds.Length; i++)
{
BoundBVH bgeom = compchilds[i] as BoundBVH;
if (bgeom == null)
{ continue; }
if (bgeom.Polygons == null)
{ continue; }
if ((bgeom.BVH?.Nodes?.data_items == null) || (bgeom.BVH?.Trees?.data_items == null))
{ continue; }
box.Minimum = bgeom.BoundingBoxMin;
box.Maximum = bgeom.BoundingBoxMax;
float bvhboxhittest;
if (!ray.Intersects(ref box, out bvhboxhittest))
{ continue; }
if (bvhboxhittest > itemhitdist)
{ continue; } //already a closer hit.
var q = bgeom.BVH.Quantum.XYZ();
var c = bgeom.BVH.BoundingBoxCenter.XYZ();
var cg = bgeom.CenterGeom;
for (int t = 0; t < bgeom.BVH.Trees.data_items.Length; t++)
{
var tree = bgeom.BVH.Trees.data_items[t];
box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c;
box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c;
if (!ray.Intersects(ref box, out bvhboxhittest))
{ continue; }
if (bvhboxhittest > itemhitdist)
{ continue; } //already a closer hit.
if (bvhboxhittest > maxdist)
{ continue; }
int nodeind = tree.NodeIndex1;
int lastind = tree.NodeIndex2;
while (nodeind < lastind)
{
var node = bgeom.BVH.Nodes.data_items[nodeind];
box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c;
box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c;
bool nodehit = ray.Intersects(ref box, out bvhboxhittest);
bool nodeskip = !nodehit || (bvhboxhittest > itemhitdist);
if (node.PolyCount <= 0) //intermediate node with child nodes
{
if (nodeskip)
{
nodeind += node.PolyId; //(child node count)
}
else
{
nodeind++;
}
}
else //leaf node, with polygons
{
if (!nodeskip)
{
var lastp = node.PolyId + node.PolyCount;
lastp = Math.Min(lastp, (int)bgeom.PolygonsCount);
for (int p = node.PolyId; p < lastp; p++)
{
var polygon = bgeom.Polygons[p];
bool polyhit = false;
switch (polygon.Type)
{
case BoundPolygonType.Triangle:
var ptri = polygon as BoundPolygonTriangle;
p1 = bgeom.GetVertex(ptri.vertIndex1) + cg;
p2 = bgeom.GetVertex(ptri.vertIndex2) + cg;
p3 = bgeom.GetVertex(ptri.vertIndex3) + cg;
polyhit = ray.Intersects(ref p1, ref p2, ref p3, out polyhittestdist);
if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1));
break;
case BoundPolygonType.Sphere:
var psph = polygon as BoundPolygonSphere;
tsph.Center = bgeom.GetVertex(psph.sphereIndex) + cg;
tsph.Radius = psph.sphereRadius;
polyhit = ray.Intersects(ref tsph, out polyhittestdist);
if (polyhit) n1 = Vector3.Normalize((ray.Position + ray.Direction * polyhittestdist) - tsph.Center);
break;
case BoundPolygonType.Capsule:
//TODO
break;
case BoundPolygonType.Box:
var pbox = polygon as BoundPolygonBox;
p1 = bgeom.GetVertex(pbox.boxIndex1);// + cg; //corner
p2 = bgeom.GetVertex(pbox.boxIndex2);// + cg;
p3 = bgeom.GetVertex(pbox.boxIndex3);// + cg;
p4 = bgeom.GetVertex(pbox.boxIndex4);// + cg;
a1 = ((p3 + p4) - (p1 + p2)) * 0.5f;
a2 = p3 - (p1 + a1);
a3 = p4 - (p1 + a1);
Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length());
Vector3 m1 = a1 / bs.X;
Vector3 m2 = a2 / bs.Y;
Vector3 m3 = a3 / bs.Z;
if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3);
else if (bs.Y < bs.Z) m2 = Vector3.Cross(m1, m3);
else m3 = Vector3.Cross(m1, m2);
Vector3 tp = rp - (p1 + cg);
rayt.Position = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3));
rayt.Direction = new Vector3(Vector3.Dot(rd, m1), Vector3.Dot(rd, m2), Vector3.Dot(rd, m3));
box.Minimum = Vector3.Zero;
box.Maximum = bs;
polyhit = rayt.Intersects(ref box, out polyhittestdist);
if (polyhit)
{
Vector3 hpt = rayt.Position + rayt.Direction * polyhittestdist;
const float eps = 0.002f;
if (Math.Abs(hpt.X) < eps) n1 = -m1;
else if (Math.Abs(hpt.X - bs.X) < eps) n1 = m1;
else if (Math.Abs(hpt.Y) < eps) n1 = -m2;
else if (Math.Abs(hpt.Y - bs.Y) < eps) n1 = m2;
else if (Math.Abs(hpt.Z) < eps) n1 = -m3;
else if (Math.Abs(hpt.Z - bs.Z) < eps) n1 = m3;
else
{ n1 = Vector3.UnitZ; } //ray starts inside the box...
}
break;
case BoundPolygonType.Cylinder:
//TODO
break;
}
if (polyhit && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist))
{
itemhitdist = polyhittestdist;
ybnhit = true;
hit = true;
hitnorm = n1;
hitpoly = polygon;
byte matind = ((bgeom.PolygonMaterialIndices != null) && (p < bgeom.PolygonMaterialIndices.Length)) ? bgeom.PolygonMaterialIndices[p] : (byte)0;
BoundMaterial_s mat = ((bgeom.Materials != null) && (matind < bgeom.Materials.Length)) ? bgeom.Materials[matind] : new BoundMaterial_s();
hitmat = mat;
}
polytestcount++;
}
}
nodeind++;
}
nodetestcount++;
}
}
}
break;
case 3: //BoundBox - found in drawables - TODO
BoundBox boundbox = b as BoundBox;
if (boundbox == null)
{ continue; }
break;
case 0: //BoundSphere - found in drawables - TODO
BoundSphere boundsphere = b as BoundSphere;
if (boundsphere == null)
{ continue; }
break;
default:
break;
res.Hit = true;
res.HitDist = bhit.HitDist;
res.HitPolygon = bhit.HitPolygon;
res.Material = bhit.Material;
res.Normal = bhit.Normal;
}
if (ybnhit)
{
boxhitdist = boxhitdisttest;
//hit = true;
}
res.TestedNodeCount += bhit.TestedNodeCount;
res.TestedPolyCount += bhit.TestedPolyCount;
}
}
if (hit)
if (res.Hit)
{
hitpos = ray.Position + ray.Direction * itemhitdist;
res.Position = ray.Position + ray.Direction * res.HitDist;
}
res.TestedNodeCount = nodetestcount;
res.TestedPolyCount = polytestcount;
res.TestComplete = testcomplete;
res.Hit = hit;
res.HitDist = itemhitdist;
res.HitPolygon = hitpoly;
res.Material = hitmat;
res.Position = hitpos;
res.Normal = hitnorm;
return res;
}
@ -1325,20 +1145,6 @@ namespace CodeWalker.World
Vector3 sphmin = sph.Center - sph.Radius;
Vector3 sphmax = sph.Center + sph.Radius;
var box = new BoundingBox();
var tsph = new BoundingSphere();
var spht = new BoundingSphere();
var sp = sph.Center;
var sr = sph.Radius;
//var boxhitdist = float.MaxValue;
//var itemhitdist = float.MaxValue;
Vector3 p1, p2, p3, p4, a1, a2, a3;
Vector3 n1 = Vector3.Zero;
//float polyhittestdist = 0;
bool hit = false;
BoundPolygon hitpoly = null;
Vector3 hitnorm = Vector3.Zero;
Vector3 hitpos = Vector3.Zero;
if (BoundsStore == null) return res;
var boundslist = BoundsStore.GetItems(ref sphmin, ref sphmax);
@ -1356,184 +1162,25 @@ namespace CodeWalker.World
if (!ybn.Loaded)
{ testcomplete = false; continue; } //ybn not loaded yet...
//bool ybnhit = false;
var b = ybn.Bounds;
box.Minimum = b.BoundingBoxMin;
box.Maximum = b.BoundingBoxMax;
if (!sph.Intersects(ref box))
{ continue; } //ray doesn't hit this ybn
switch (b.Type)
//box.Minimum = b.BoundingBoxMin;
//box.Maximum = b.BoundingBoxMax;
//if (!sph.Intersects(ref box))
//{ continue; } //ray doesn't hit this ybn
var bhit = b.SphereIntersect(ref sph);
if (bhit.Hit)
{
case 10: //BoundComposite
BoundComposite boundcomp = b as BoundComposite;
if (boundcomp == null)
{ continue; }
var compchilds = boundcomp.Children?.data_items;
if (compchilds == null)
{ continue; }
for (int i = 0; i < compchilds.Length; i++)
{
BoundBVH bgeom = compchilds[i] as BoundBVH;
if (bgeom == null)
{ continue; }
if (bgeom.Polygons == null)
{ continue; }
if ((bgeom.BVH?.Nodes?.data_items == null) || (bgeom.BVH?.Trees?.data_items == null))
{ continue; }
box.Minimum = bgeom.BoundingBoxMin;
box.Maximum = bgeom.BoundingBoxMax;
if (!sph.Intersects(ref box))
{ continue; }
var q = bgeom.BVH.Quantum.XYZ();
var c = bgeom.BVH.BoundingBoxCenter.XYZ();
var cg = bgeom.CenterGeom;
for (int t = 0; t < bgeom.BVH.Trees.data_items.Length; t++)
{
var tree = bgeom.BVH.Trees.data_items[t];
box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c;
box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c;
if (!sph.Intersects(ref box))
{ continue; }
int nodeind = tree.NodeIndex1;
int lastind = tree.NodeIndex2;
while (nodeind < lastind)
{
var node = bgeom.BVH.Nodes.data_items[nodeind];
box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c;
box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c;
bool nodehit = sph.Intersects(ref box);
bool nodeskip = !nodehit;
if (node.PolyCount <= 0) //intermediate node with child nodes
{
if (nodeskip)
{
nodeind += node.PolyId; //(child node count)
}
else
{
nodeind++;
}
}
else //leaf node, with polygons
{
if (!nodeskip)
{
var lastp = node.PolyId + node.PolyCount;
lastp = Math.Min(lastp, (int)bgeom.PolygonsCount);
for (int p = node.PolyId; p < lastp; p++)
{
var polygon = bgeom.Polygons[p];
bool polyhit = false;
switch (polygon.Type)
{
case BoundPolygonType.Triangle:
var ptri = polygon as BoundPolygonTriangle;
p1 = bgeom.GetVertex(ptri.vertIndex1) + cg;
p2 = bgeom.GetVertex(ptri.vertIndex2) + cg;
p3 = bgeom.GetVertex(ptri.vertIndex3) + cg;
polyhit = sph.Intersects(ref p1, ref p2, ref p3);
if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1));
break;
case BoundPolygonType.Sphere:
var psph = polygon as BoundPolygonSphere;
tsph.Center = bgeom.GetVertex(psph.sphereIndex) + cg;
tsph.Radius = psph.sphereRadius;
polyhit = sph.Intersects(ref tsph);
if (polyhit) n1 = Vector3.Normalize(sph.Center - tsph.Center);
break;
case BoundPolygonType.Capsule:
//TODO
break;
case BoundPolygonType.Box:
var pbox = polygon as BoundPolygonBox;
p1 = bgeom.GetVertex(pbox.boxIndex1);// + cg; //corner
p2 = bgeom.GetVertex(pbox.boxIndex2);// + cg;
p3 = bgeom.GetVertex(pbox.boxIndex3);// + cg;
p4 = bgeom.GetVertex(pbox.boxIndex4);// + cg;
a1 = ((p3 + p4) - (p1 + p2)) * 0.5f;
a2 = p3 - (p1 + a1);
a3 = p4 - (p1 + a1);
Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length());
Vector3 m1 = a1 / bs.X;
Vector3 m2 = a2 / bs.Y;
Vector3 m3 = a3 / bs.Z;
if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3);
else if (bs.Y < bs.Z) m2 = Vector3.Cross(m1, m3);
else m3 = Vector3.Cross(m1, m2);
Vector3 tp = sp - (p1 + cg);
spht.Center = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3));
spht.Radius = sph.Radius;
box.Minimum = Vector3.Zero;
box.Maximum = bs;
polyhit = spht.Intersects(ref box);
if (polyhit)
{
Vector3 smin = spht.Center - spht.Radius;
Vector3 smax = spht.Center + spht.Radius;
float eps = spht.Radius * 0.8f;
n1 = Vector3.Zero;
if (Math.Abs(smax.X) < eps) n1 -= m1;
else if (Math.Abs(smin.X - bs.X) < eps) n1 += m1;
if (Math.Abs(smax.Y) < eps) n1 -= m2;
else if (Math.Abs(smin.Y - bs.Y) < eps) n1 += m2;
if (Math.Abs(smax.Z) < eps) n1 -= m3;
else if (Math.Abs(smin.Z - bs.Z) < eps) n1 += m3;
float n1l = n1.Length();
if (n1l > 0.0f) n1 = n1 / n1l;
else n1 = Vector3.UnitZ;
}
break;
case BoundPolygonType.Cylinder:
//TODO
break;
}
if (polyhit) // && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist))
{
hitpoly = polygon;
//itemhitdist = polyhittestdist;
//ybnhit = true;
hit = true;
hitnorm = n1;
}
polytestcount++;
}
}
nodeind++;
}
nodetestcount++;
}
}
}
break;
case 3: //BoundBox - found in drawables - TODO
BoundBox boundbox = b as BoundBox;
if (boundbox == null)
{ continue; }
break;
case 0: //BoundSphere - found in drawables - TODO
BoundSphere boundsphere = b as BoundSphere;
if (boundsphere == null)
{ continue; }
break;
default:
break;
res.Hit = true;
res.HitPolygon = bhit.HitPolygon;
res.Normal = bhit.Normal;
}
//if (ybnhit)
//{
// //boxhitdist = boxhitdisttest;
// //hit = true;
//}
polytestcount += bhit.TestedPolyCount;
nodetestcount += bhit.TestedNodeCount;
}
}
//if (hit)
//{
// hitpos = ray.Position + ray.Direction * itemhitdist;
@ -1542,11 +1189,6 @@ namespace CodeWalker.World
res.TestedNodeCount = nodetestcount;
res.TestedPolyCount = polytestcount;
res.TestComplete = testcomplete;
res.Hit = hit;
res.HitDist = 0;// itemhitdist;
res.HitPolygon = hitpoly;
res.Position = hitpos;
res.Normal = hitnorm;
return res;
}

View File

@ -154,7 +154,7 @@ namespace CodeWalker
MapSelection PrevMouseHit = new MapSelection();
bool MouseRayCollisionEnabled = true;
bool MouseRayCollisionVisible = true;
bool MouseRayCollisionVisible = false;
SpaceRayIntersectResult MouseRayCollision = new SpaceRayIntersectResult();
string SelectionModeStr = "Entity";
@ -628,7 +628,7 @@ namespace CodeWalker
Vector3 move = lftxy * movecontrol.X + fwdxy * movecontrol.Y;
Vector2 movexy = new Vector2(move.X, move.Y);
movexy *= (1.0f + (Input.xblt * 15.0f)); //boost with left trigger
movexy *= (1.0f + (Math.Min(Math.Max(Input.xblt, 0.0f), 1.0f) * 15.0f)); //boost with left trigger
pedEntity.ControlMovement = movexy;
pedEntity.ControlJump = Input.kbjump || Input.ControllerButtonPressed(GamepadButtonFlags.X);
@ -1281,13 +1281,15 @@ namespace CodeWalker
const uint caqu = 4294967040;// (uint)new Color4(0.0f, 1.0f, 1.0f, 1.0f).ToRgba();
//const uint cyel = 4278255615;//
if (MouseRayCollisionEnabled && MouseRayCollisionVisible)
if (ControlBrushEnabled && MouseRayCollision.Hit)
{
if (MouseRayCollision.Hit)
{
var arup = GetPerpVec(MouseRayCollision.Normal);
Renderer.RenderBrushRadiusOutline(MouseRayCollision.Position, MouseRayCollision.Normal, arup, ProjectForm.GetInstanceBrushRadius(), cgrn);
}
var arup = GetPerpVec(MouseRayCollision.Normal);
Renderer.RenderBrushRadiusOutline(MouseRayCollision.Position, MouseRayCollision.Normal, arup, ProjectForm.GetInstanceBrushRadius(), cgrn);
}
if (MouseRayCollisionVisible && MouseRayCollision.Hit)
{
var arup = GetPerpVec(MouseRayCollision.Normal);
Renderer.RenderSelectionArrowOutline(MouseRayCollision.Position, MouseRayCollision.Normal, arup, Quaternion.Identity, 1.0f, 0.05f, cgrn);
}
if (!ShowSelectionBounds)
@ -2200,13 +2202,21 @@ namespace CodeWalker
if (Input.CtrlPressed && ProjectForm != null && ProjectForm.CanPaintInstances())
{
ControlBrushEnabled = true;
MouseRayCollisionEnabled = true;
MouseRayCollisionVisible = false;
MouseRayCollision = GetSpaceMouseRay();
}
else if (MouseRayCollisionEnabled)
else
{
ControlBrushEnabled = false;
MouseRayCollisionEnabled = false;
if (Input.CtrlPressed && MouseRayCollisionEnabled)
{
MouseRayCollisionVisible = true;
MouseRayCollision = GetSpaceMouseRay();
}
else
{
MouseRayCollisionVisible = false;
}
}