From d5c0bc24777cbb06bec581584d3e4c5fe99af429 Mon Sep 17 00:00:00 2001 From: dexy Date: Tue, 10 Dec 2019 18:33:45 +1100 Subject: [PATCH] Collision detection refactoring and improvements --- CodeWalker.Core/GameFiles/Resources/Bounds.cs | 754 +++++++++++------- CodeWalker.Core/Utils/Vectors.cs | 12 +- CodeWalker.Core/World/Entity.cs | 2 + CodeWalker.Core/World/Space.cs | 594 ++++++++++++-- Rendering/Renderable.cs | 3 +- Rendering/Renderer.cs | 27 +- WorldForm.cs | 5 +- 7 files changed, 1022 insertions(+), 375 deletions(-) diff --git a/CodeWalker.Core/GameFiles/Resources/Bounds.cs b/CodeWalker.Core/GameFiles/Resources/Bounds.cs index 7dd3a8c..32e023b 100644 --- a/CodeWalker.Core/GameFiles/Resources/Bounds.cs +++ b/CodeWalker.Core/GameFiles/Resources/Bounds.cs @@ -144,6 +144,9 @@ namespace CodeWalker.GameFiles return n; } + public Matrix Transform { get; set; } = Matrix.Identity; //when it's the child of a bound composite + public Matrix TransformInv { get; set; } = Matrix.Identity; + /// /// Reads the data-block from a stream. @@ -237,7 +240,7 @@ namespace CodeWalker.GameFiles { return new SpaceSphereIntersectResult(); } - public virtual SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public virtual SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { return new SpaceRayIntersectResult(); } @@ -247,11 +250,33 @@ namespace CodeWalker.GameFiles { public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { - return base.SphereIntersect(ref sph); + var res = new SpaceSphereIntersectResult(); + var bsph = new BoundingSphere(); + bsph.Center = Center; + bsph.Radius = BoundingSphereRadius; + if (sph.Intersects(ref bsph)) + { + res.Hit = true; + res.Normal = Vector3.Normalize(sph.Center - Center); + } + return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { - return base.RayIntersect(ref ray, maxdist, itemhitdist); + var res = new SpaceRayIntersectResult(); + var bsph = new BoundingSphere(); + bsph.Center = Center; + bsph.Radius = BoundingSphereRadius; + float testdist; + if (ray.Intersects(ref bsph, out testdist) && (testdist < maxdist)) + { + res.Hit = true; + res.HitDist = testdist; + res.Position = ray.Position + ray.Direction * testdist; + res.Normal = Vector3.Normalize(res.Position - Center); + res.Material.Type = MaterialIndex; + } + return res; } } [TC(typeof(EXP))] public class BoundCapsule : Bounds @@ -267,9 +292,6 @@ namespace CodeWalker.GameFiles public uint Unknown_78h { get; set; } // 0x00000000 public uint Unknown_7Ch { get; set; } // 0x00000000 - /// - /// Reads the data-block from a stream. - /// public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -280,10 +302,6 @@ namespace CodeWalker.GameFiles this.Unknown_78h = reader.ReadUInt32(); this.Unknown_7Ch = reader.ReadUInt32(); } - - /// - /// Writes the data-block to a stream. - /// public override void Write(ResourceDataWriter writer, params object[] parameters) { base.Write(writer, parameters); @@ -297,22 +315,88 @@ namespace CodeWalker.GameFiles public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { - return base.SphereIntersect(ref sph); + var res = new SpaceSphereIntersectResult(); + var bcap = new BoundingCapsule(); + var extent = new Vector3(0, BoundingSphereRadius - Margin, 0); + bcap.PointA = Center - extent; + bcap.PointB = Center + extent; + bcap.Radius = Margin; + if (sph.Intersects(ref bcap, out res.Normal)) + { + res.Hit = true; + } + return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { - return base.RayIntersect(ref ray, maxdist, itemhitdist); + var res = new SpaceRayIntersectResult(); + var bcap = new BoundingCapsule(); + var extent = new Vector3(0, BoundingSphereRadius - Margin, 0); + bcap.PointA = Center - extent; + bcap.PointB = Center + extent; + bcap.Radius = Margin; + float testdist; + if (ray.Intersects(ref bcap, out testdist) && (testdist < maxdist)) + { + res.Hit = true; + res.HitDist = testdist; + res.Position = ray.Position + ray.Direction * testdist; + res.Normal = bcap.Normal(ref res.Position); + res.Material.Type = MaterialIndex; + } + return res; } } [TC(typeof(EXP))] public class BoundBox : Bounds { public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { - return base.SphereIntersect(ref sph); + var res = new SpaceSphereIntersectResult(); + var bbox = new BoundingBox(BoundingBoxMin, BoundingBoxMax); + if (sph.Intersects(ref bbox)) + { + var sphmin = sph.Center - sph.Radius; + var sphmax = sph.Center + sph.Radius; + var n = Vector3.Zero; + float eps = sph.Radius * 0.8f; + if (Math.Abs(sphmax.X - BoundingBoxMin.X) < eps) n -= Vector3.UnitX; + else if (Math.Abs(sphmin.X - BoundingBoxMax.X) < eps) n += Vector3.UnitX; + else if (Math.Abs(sphmax.Y - BoundingBoxMin.Y) < eps) n -= Vector3.UnitY; + else if (Math.Abs(sphmin.Y - BoundingBoxMax.Y) < eps) n += Vector3.UnitY; + else if (Math.Abs(sphmax.Z - BoundingBoxMin.Z) < eps) n -= Vector3.UnitZ; + else if (Math.Abs(sphmin.Z - BoundingBoxMax.Z) < eps) n += Vector3.UnitZ; + else + { n = Vector3.UnitZ; } //ray starts inside the box... + res.Normal = Vector3.Normalize(n); + res.Hit = true; + } + return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { - return base.RayIntersect(ref ray, maxdist, itemhitdist); + var res = new SpaceRayIntersectResult(); + var bbox = new BoundingBox(BoundingBoxMin, BoundingBoxMax); + float testdist; + if (ray.Intersects(ref bbox, out testdist) && (testdist < maxdist)) + { + const float eps = 0.002f; + var n = Vector3.Zero; + var hpt = ray.Position + ray.Direction * testdist; + if (Math.Abs(hpt.X - BoundingBoxMin.X) < eps) n = -Vector3.UnitX; + else if (Math.Abs(hpt.X - BoundingBoxMax.X) < eps) n = Vector3.UnitX; + else if (Math.Abs(hpt.Y - BoundingBoxMin.Y) < eps) n = -Vector3.UnitY; + else if (Math.Abs(hpt.Y - BoundingBoxMax.Y) < eps) n = Vector3.UnitY; + else if (Math.Abs(hpt.Z - BoundingBoxMin.Z) < eps) n = -Vector3.UnitZ; + else if (Math.Abs(hpt.Z - BoundingBoxMax.Z) < eps) n = Vector3.UnitZ; + else + { n = Vector3.UnitZ; } //ray starts inside the box... + res.Hit = true; + res.HitDist = testdist; + res.Position = hpt; + res.Normal = n; + res.Material.Type = MaterialIndex; + } + return res; } } [TC(typeof(EXP))] public class BoundDisc : Bounds @@ -328,9 +412,6 @@ namespace CodeWalker.GameFiles public uint Unknown_78h { get; set; } // 0x00000000 public uint Unknown_7Ch { get; set; } // 0x00000000 - /// - /// Reads the data-block from a stream. - /// public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -341,10 +422,6 @@ namespace CodeWalker.GameFiles this.Unknown_78h = reader.ReadUInt32(); this.Unknown_7Ch = reader.ReadUInt32(); } - - /// - /// Writes the data-block to a stream. - /// public override void Write(ResourceDataWriter writer, params object[] parameters) { base.Write(writer, parameters); @@ -358,11 +435,37 @@ namespace CodeWalker.GameFiles public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { - return base.SphereIntersect(ref sph); + //as a temporary hack, just use the sphere-sphere intersection //TODO: sphere-disc intersection + var res = new SpaceSphereIntersectResult(); + var bsph = new BoundingSphere(); + bsph.Center = Center; + bsph.Radius = BoundingSphereRadius; + if (sph.Intersects(ref bsph)) + { + res.Hit = true; + res.Normal = Vector3.Normalize(sph.Center - Center); + } + return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { - return base.RayIntersect(ref ray, maxdist, itemhitdist); + var res = new SpaceRayIntersectResult(); + var bcyl = new BoundingCylinder(); + var size = new Vector3(Margin, 0, 0); + bcyl.PointA = Center - size; + bcyl.PointB = Center + size; + bcyl.Radius = BoundingSphereRadius; + Vector3 n; + float testdist; + if (ray.Intersects(ref bcyl, out testdist, out n) && (testdist < maxdist)) + { + res.Hit = true; + res.HitDist = testdist; + res.Position = ray.Position + ray.Direction * testdist; + res.Normal = n; + res.Material.Type = MaterialIndex; + } + return res; } } [TC(typeof(EXP))] public class BoundCylinder : Bounds @@ -378,9 +481,6 @@ namespace CodeWalker.GameFiles public uint Unknown_78h { get; set; } // 0x00000000 public uint Unknown_7Ch { get; set; } // 0x00000000 - /// - /// Reads the data-block from a stream. - /// public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -391,10 +491,6 @@ namespace CodeWalker.GameFiles this.Unknown_78h = reader.ReadUInt32(); this.Unknown_7Ch = reader.ReadUInt32(); } - - /// - /// Writes the data-block to a stream. - /// public override void Write(ResourceDataWriter writer, params object[] parameters) { base.Write(writer, parameters); @@ -408,11 +504,40 @@ namespace CodeWalker.GameFiles public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { - return base.SphereIntersect(ref sph); + //as a temporary hack, just use the sphere-capsule intersection //TODO: sphere-cylinder intersection + var res = new SpaceSphereIntersectResult(); + var bcap = new BoundingCapsule(); + var extent = (BoundingBoxMax - BoundingBoxMin).Abs(); + var size = new Vector3(0, extent.Y * 0.5f, 0); + bcap.PointA = Center - size; + bcap.PointB = Center + size; + bcap.Radius = extent.X * 0.5f; + if (sph.Intersects(ref bcap, out res.Normal)) + { + res.Hit = true; + } + return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { - return base.RayIntersect(ref ray, maxdist, itemhitdist); + var res = new SpaceRayIntersectResult(); + var bcyl = new BoundingCylinder(); + var extent = (BoundingBoxMax - BoundingBoxMin).Abs(); + var size = new Vector3(0, extent.Y * 0.5f, 0); + bcyl.PointA = Center - size; + bcyl.PointB = Center + size; + bcyl.Radius = extent.X * 0.5f; + Vector3 n; + float testdist; + if (ray.Intersects(ref bcyl, out testdist, out n) && (testdist < maxdist)) + { + res.Hit = true; + res.HitDist = testdist; + res.Position = ray.Position + ray.Direction * testdist; + res.Normal = n; + res.Material.Type = MaterialIndex; + } + return res; } } [TC(typeof(EXP))] public class BoundGeometry : Bounds @@ -758,11 +883,255 @@ namespace CodeWalker.GameFiles public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { - return base.SphereIntersect(ref sph); + var res = new SpaceSphereIntersectResult(); + var box = new BoundingBox(); + + if (Polygons == null) + { return res; } + + box.Minimum = BoundingBoxMin; + box.Maximum = BoundingBoxMax; + if (!sph.Intersects(ref box)) + { return res; } + + SphereIntersectPolygons(ref sph, ref res, 0, Polygons.Length); + + return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { - return base.RayIntersect(ref ray, maxdist, itemhitdist); + var res = new SpaceRayIntersectResult(); + var box = new BoundingBox(); + + if (Polygons == null) + { return res; } + + box.Minimum = BoundingBoxMin; + box.Maximum = BoundingBoxMax; + float bvhboxhittest; + if (!ray.Intersects(ref box, out bvhboxhittest)) + { return res; } + if (bvhboxhittest > maxdist) + { return res; } //already a closer hit. + + res.HitDist = maxdist; + + RayIntersectPolygons(ref ray, ref res, 0, Polygons.Length); + + return res; + } + + + protected void SphereIntersectPolygons(ref BoundingSphere sph, ref SpaceSphereIntersectResult res, int startIndex, int endIndex) + { + var box = new BoundingBox(); + var tsph = new BoundingSphere(); + var spht = new BoundingSphere(); + var sp = sph.Center; + var sr = sph.Radius; + var cg = CenterGeom; + Vector3 p1, p2, p3, p4, a1, a2, a3; + Vector3 n1 = Vector3.Zero; + + for (int p = startIndex; p < endIndex; 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++; + } + } + protected void RayIntersectPolygons(ref Ray ray, ref SpaceRayIntersectResult res, int startIndex, int endIndex) + { + var box = new BoundingBox(); + var tsph = new BoundingSphere(); + var rayt = new Ray(); + var rp = ray.Position; + var rd = ray.Direction; + var cg = CenterGeom; + Vector3 p1, p2, p3, p4, a1, a2, a3; + Vector3 n1 = Vector3.Zero; + + for (int p = startIndex; p < endIndex; 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); + break; + default: + break; + } + if (polyhit && (polyhittestdist < res.HitDist)) + { + 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++; + } } } @@ -1258,13 +1627,6 @@ namespace CodeWalker.GameFiles { 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; } @@ -1278,7 +1640,6 @@ namespace CodeWalker.GameFiles 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]; @@ -1311,103 +1672,10 @@ namespace CodeWalker.GameFiles { 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++; - } + var lastp = Math.Min(node.PolyId + node.PolyCount, (int)PolygonsCount); + + SphereIntersectPolygons(ref sph, ref res, node.PolyId, lastp); + } nodeind++; } @@ -1418,18 +1686,10 @@ namespace CodeWalker.GameFiles return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = 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; } @@ -1441,14 +1701,13 @@ namespace CodeWalker.GameFiles float bvhboxhittest; if (!ray.Intersects(ref box, out bvhboxhittest)) { return res; } - if (bvhboxhittest > itemhitdist) + if (bvhboxhittest > maxdist) { return res; } //already a closer hit. - res.HitDist = itemhitdist; + res.HitDist = maxdist; 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]; @@ -1458,8 +1717,6 @@ namespace CodeWalker.GameFiles { continue; } if (bvhboxhittest > res.HitDist) { continue; } //already a closer hit. - if (bvhboxhittest > maxdist) - { continue; } int nodeind = tree.NodeIndex1; int lastind = tree.NodeIndex2; @@ -1485,101 +1742,10 @@ namespace CodeWalker.GameFiles { 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; + var lastp = Math.Min(node.PolyId + node.PolyCount, (int)PolygonsCount); + + RayIntersectPolygons(ref ray, ref res, node.PolyId, lastp); - 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++; } @@ -1653,14 +1819,6 @@ namespace CodeWalker.GameFiles this.ChildrenCount1 ); - if ((Children != null) && (Children.data_items != null)) - { - foreach (var child in Children.data_items) - { - if (child != null) child.Parent = this; - } - } - this.ChildrenTransformation1 = reader.ReadStructsAt(this.ChildrenTransformation1Pointer, this.ChildrenCount1); this.ChildrenTransformation2 = reader.ReadStructsAt(this.ChildrenTransformation2Pointer, this.ChildrenCount1); this.ChildrenBoundingBoxes = reader.ReadStructsAt(this.ChildrenBoundingBoxesPointer, this.ChildrenCount1); @@ -1670,6 +1828,29 @@ namespace CodeWalker.GameFiles this.BVH = reader.ReadBlockAt( this.BVHPointer // offset ); + + + + + var childTransforms = ChildrenTransformation1 ?? ChildrenTransformation2; + if ((Children != null) && (Children.data_items != null)) + { + for (int i = 0; i < Children.data_items.Length; i++) + { + var child = Children.data_items[i]; + if (child != null) + { + child.Parent = this; + + var xform = ((childTransforms != null) && (i < childTransforms.Length)) ? childTransforms[i] : Matrix.Identity; + xform.Column4 = new Vector4(0.0f, 0.0f, 0.0f, 1.0f); + child.Transform = xform; + child.TransformInv = Matrix.Invert(xform); + } + } + } + + } /// @@ -1745,6 +1926,7 @@ namespace CodeWalker.GameFiles public override SpaceSphereIntersectResult SphereIntersect(ref BoundingSphere sph) { var res = new SpaceSphereIntersectResult(); + var tsph = sph; var compchilds = Children?.data_items; if (compchilds == null) @@ -1753,49 +1935,45 @@ namespace CodeWalker.GameFiles for (int i = 0; i < compchilds.Length; i++) { var c = compchilds[i]; + if (c == null) continue; - 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; + tsph.Center = c.TransformInv.Multiply(sph.Center); + + var chit = c.SphereIntersect(ref tsph); + + chit.Normal = c.Transform.MultiplyRot(chit.Normal); + + res.TryUpdate(ref chit); } - res.TestComplete = true; - return res; } - public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue, float itemhitdist = float.MaxValue) + public override SpaceRayIntersectResult RayIntersect(ref Ray ray, float maxdist = float.MaxValue) { var res = new SpaceRayIntersectResult(); - res.HitDist = itemhitdist; + res.HitDist = maxdist; + + var tray = ray; 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 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; + tray.Position = c.TransformInv.Multiply(ray.Position); + tray.Direction = c.TransformInv.MultiplyRot(ray.Direction); + var chit = c.RayIntersect(ref tray, res.HitDist); + + chit.Position = c.Transform.Multiply(chit.Position); + chit.Normal = c.Transform.MultiplyRot(chit.Normal); + + res.TryUpdate(ref chit); } return res; diff --git a/CodeWalker.Core/Utils/Vectors.cs b/CodeWalker.Core/Utils/Vectors.cs index 5a78ada..79ecd67 100644 --- a/CodeWalker.Core/Utils/Vectors.cs +++ b/CodeWalker.Core/Utils/Vectors.cs @@ -157,10 +157,10 @@ namespace CodeWalker } 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; + 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 Vector3.Normalize((pa - h * ba) / c.Radius); } @@ -222,7 +222,7 @@ namespace CodeWalker if (y > 0.0f && y < baba) { dist = t; - norm = (oc + t * r.Direction - ba * y / baba) / cylinder.Radius; + norm = Vector3.Normalize((oc + t * r.Direction - ba * y / baba) / cylinder.Radius); return true; } @@ -231,7 +231,7 @@ namespace CodeWalker if (Math.Abs(k1 + k2 * t) < h) { dist = t; - norm = ba * Math.Sign(y) / baba; + norm = Vector3.Normalize(ba * Math.Sign(y) / baba); return true; } diff --git a/CodeWalker.Core/World/Entity.cs b/CodeWalker.Core/World/Entity.cs index ff5180f..1304e42 100644 --- a/CodeWalker.Core/World/Entity.cs +++ b/CodeWalker.Core/World/Entity.cs @@ -31,6 +31,8 @@ namespace CodeWalker.World public bool EnableCollisions; public bool Enabled; + public float Lifetime; + public float Age; //public CollisionShape .. diff --git a/CodeWalker.Core/World/Space.cs b/CodeWalker.Core/World/Space.cs index 9afe85d..46f88ea 100644 --- a/CodeWalker.Core/World/Space.cs +++ b/CodeWalker.Core/World/Space.cs @@ -44,7 +44,11 @@ namespace CodeWalker.World public SpaceNavGrid NavGrid; - public List Collisions = new List(); + public List Collisions = new List(); + private bool[] CollisionLayers = new[] { true, false, false }; + + private int CurrentHour; + private MetaHash CurrentWeather; public void Init(GameFileCache gameFileCache, Action updateStatus) @@ -749,6 +753,7 @@ namespace CodeWalker.World e.Velocity += dvgrav; //apply gravity e.Momentum = e.Velocity * e.Mass; + e.Age += elapsed; e.PreUpdate(elapsed); @@ -831,6 +836,11 @@ namespace CodeWalker.World } + if ((e.Lifetime > 0.0f) && (e.Age > e.Lifetime)) + { + TemporaryEntities.Remove(e); + } + } @@ -856,7 +866,7 @@ namespace CodeWalker.World BoundingSphere sph = new BoundingSphere(r.HitPos + e.Center, e.Radius); - r.SphereHit = SphereIntersect(sph); + r.SphereHit = SphereIntersect(sph, CollisionLayers); if (!r.SphereHit.Hit) { @@ -876,7 +886,7 @@ namespace CodeWalker.World sph.Center = r.HitPos + e.Center; //this really shouldn't happen... but just in case of glancing hit.. } - r.SphereHit = SphereIntersect(sph); //this really should be a hit! + r.SphereHit = SphereIntersect(sph, CollisionLayers); //this really should be a hit! } } } @@ -891,7 +901,7 @@ namespace CodeWalker.World while (curiter < maxiter) //iterate to find a closer hit time... improve this! { sph.Center = sphpos + disp * curt; - var tcollres = SphereIntersect(sph); + var tcollres = SphereIntersect(sph, CollisionLayers); if (tcollres.Hit) { r.HitT = curt; @@ -977,6 +987,8 @@ namespace CodeWalker.World { if (!Inited) return; if (MapDataStore == null) return; + CurrentHour = hour; + CurrentWeather = weather; var items = MapDataStore.GetItems(ref cam.Position); for (int i = 0; i < items.Count; i++) { @@ -1067,29 +1079,24 @@ namespace CodeWalker.World var res = new SpaceRayIntersectResult(); if (GameFileCache == null) return res; bool testcomplete = true; - res.HitDist = float.MaxValue; + res.HitDist = maxdist; var box = new BoundingBox(); + float boxhitdisttest; - if (BoundsStore == null) return res; - var boundslist = BoundsStore.GetItems(ref ray); + if ((BoundsStore == null) || (MapDataStore == null)) return res; - foreach (var bound in boundslist) + var boundslist = BoundsStore.GetItems(ref ray, layers); + var mapdatalist = MapDataStore.GetItems(ref ray); + + for (int i = 0; i < boundslist.Count; i++) { - uint l = bound.Layer; - if ((layers != null) && (l < 3)) - { - if (!layers[l]) continue; - } - + var bound = boundslist[i]; box.Minimum = bound.Min; box.Maximum = bound.Max; - 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) @@ -1098,33 +1105,68 @@ namespace CodeWalker.World { testcomplete = false; continue; } //ybn not loaded yet... 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 > res.HitDist) - //{ continue; } //already a closer hit. - //if (itemboxhitdisttest > maxdist) - //{ continue; } - - var bhit = b.RayIntersect(ref ray, maxdist, res.HitDist); - if (bhit.Hit) - { - res.Hit = true; - res.HitDist = bhit.HitDist; - res.HitPolygon = bhit.HitPolygon; - res.Material = bhit.Material; - res.Normal = bhit.Normal; - } - - res.TestedNodeCount += bhit.TestedNodeCount; - res.TestedPolyCount += bhit.TestedPolyCount; - + var bhit = b.RayIntersect(ref ray, res.HitDist); + res.TryUpdate(ref bhit); } } + for (int i = 0; i < mapdatalist.Count; i++) + { + var mapdata = mapdatalist[i]; + if ((mapdata.ContentFlags & 1) == 0) + { continue; } //only test HD ymaps + + box.Minimum = mapdata.entitiesExtentsMin; + box.Maximum = mapdata.entitiesExtentsMax; + if (ray.Intersects(ref box, out boxhitdisttest)) + { + if (boxhitdisttest > res.HitDist) + { continue; } //already a closer hit + + var hash = mapdata.Name; + var ymap = (hash > 0) ? GameFileCache.GetYmap(hash) : null; + if ((ymap != null) && (ymap.Loaded) && (ymap.AllEntities != null)) + { + if (!IsYmapAvailable(hash, CurrentHour, CurrentWeather)) + { continue; } + + for (int e = 0; e < ymap.AllEntities.Length; e++) + { + var ent = ymap.AllEntities[e]; + + if (!EntityCollisionsEnabled(ent)) + { continue; } + + box.Minimum = ent.BBMin; + box.Maximum = ent.BBMax; + if (ray.Intersects(ref box, out boxhitdisttest)) + { + if (boxhitdisttest > res.HitDist) + { continue; } //already a closer hit + + if (ent.IsMlo) + { + var ihit = RayIntersectInterior(ref ray, ent, res.HitDist); + res.TryUpdate(ref ihit); + } + else + { + var ehit = RayIntersectEntity(ref ray, ent, res.HitDist); + res.TryUpdate(ref ehit); + } + } + } + } + else if ((ymap != null) && (!ymap.Loaded)) + { + testcomplete = false; + } + } + } + + + + if (res.Hit) { res.Position = ray.Position + ray.Direction * res.HitDist; @@ -1134,24 +1176,163 @@ namespace CodeWalker.World return res; } + public SpaceRayIntersectResult RayIntersectEntity(ref Ray ray, YmapEntityDef ent, float maxdist = float.MaxValue) + { + var res = new SpaceRayIntersectResult(); + res.HitDist = maxdist; - public SpaceSphereIntersectResult SphereIntersect(BoundingSphere sph) + var drawable = GameFileCache.TryGetDrawable(ent.Archetype); + if (drawable != null) + { + var eori = ent.Orientation; + var eorinv = Quaternion.Invert(ent.Orientation); + var eray = new Ray(); + eray.Position = eorinv.Multiply(ray.Position - ent.Position); + eray.Direction = eorinv.Multiply(ray.Direction); + + if ((drawable is Drawable sdrawable) && (sdrawable.Bound != null)) + { + var dhit = sdrawable.Bound.RayIntersect(ref eray, res.HitDist); + if (dhit.Hit) + { + dhit.Position = eori.Multiply(dhit.Position) + ent.Position; + dhit.Normal = eori.Multiply(dhit.Normal); + } + res.TryUpdate(ref dhit); + } + else if (drawable is FragDrawable fdrawable) + { + if (fdrawable.Bound != null) + { + var fhit = fdrawable.Bound.RayIntersect(ref eray, res.HitDist); + if (fhit.Hit) + { + fhit.Position = eori.Multiply(fhit.Position) + ent.Position; + fhit.Normal = eori.Multiply(fhit.Normal); + } + res.TryUpdate(ref fhit); + } + var fbound = fdrawable.OwnerFragment?.PhysicsLODGroup?.PhysicsLOD1?.Bound; + if (fbound != null) + { + var fhit = fbound.RayIntersect(ref eray, res.HitDist);//TODO: these probably have extra transforms..! + if (fhit.Hit) + { + fhit.Position = eori.Multiply(fhit.Position) + ent.Position; + fhit.Normal = eori.Multiply(fhit.Normal); + } + res.TryUpdate(ref fhit); + } + } + } + + return res; + } + public SpaceRayIntersectResult RayIntersectInterior(ref Ray ray, YmapEntityDef mlo, float maxdist = float.MaxValue) + { + var res = new SpaceRayIntersectResult(); + res.HitDist = maxdist; + + if (mlo.Archetype == null) + { return res; } + + var iori = mlo.Orientation; + var iorinv = Quaternion.Invert(mlo.Orientation); + var iray = new Ray(); + iray.Position = iorinv.Multiply(ray.Position - mlo.Position); + iray.Direction = iorinv.Multiply(ray.Direction); + + var hash = mlo.Archetype.Hash; + var ybn = GameFileCache.GetYbn(hash); + if ((ybn != null) && (ybn.Loaded)) + { + var ihit = ybn.Bounds.RayIntersect(ref iray, res.HitDist); + if (ihit.Hit) + { + ihit.Position = iori.Multiply(ihit.Position) + mlo.Position; + ihit.Normal = iori.Multiply(ihit.Normal); + } + res.TryUpdate(ref ihit); + } + + var mlodat = mlo.MloInstance; + if (mlodat == null) + { return res; } + + var box = new BoundingBox(); + float boxhitdisttest; + + if (mlodat.Entities != null) + { + for (int j = 0; j < mlodat.Entities.Length; j++) //should really improve this by using rooms! + { + var intent = mlodat.Entities[j]; + if (intent.Archetype == null) continue; //missing archetype... + + if (!EntityCollisionsEnabled(intent)) + { continue; } + + box.Minimum = intent.BBMin; + box.Maximum = intent.BBMax; + if (ray.Intersects(ref box, out boxhitdisttest)) + { + if (boxhitdisttest > res.HitDist) + { continue; } //already a closer hit + + var ehit = RayIntersectEntity(ref ray, intent, res.HitDist); + res.TryUpdate(ref ehit); + } + } + } + if (mlodat.EntitySets != null) + { + foreach (var entitysets in mlodat.EntitySets) + { + var entityset = entitysets.Value; + if (!entityset.Visible) continue; + var entities = entityset.Entities; + for (int i = 0; i < entities.Count; i++) //should really improve this by using rooms! + { + var intent = entities[i]; + if (intent.Archetype == null) continue; //missing archetype... + + if (!EntityCollisionsEnabled(intent)) + { continue; } + + box.Minimum = intent.BBMin; + box.Maximum = intent.BBMax; + if (ray.Intersects(ref box, out boxhitdisttest)) + { + if (boxhitdisttest > res.HitDist) + { continue; } //already a closer hit + + var ehit = RayIntersectEntity(ref ray, intent, res.HitDist); + res.TryUpdate(ref ehit); + } + } + } + } + + return res; + } + + public SpaceSphereIntersectResult SphereIntersect(BoundingSphere sph, bool[] layers = null) { var res = new SpaceSphereIntersectResult(); if (GameFileCache == null) return res; - int polytestcount = 0; - int nodetestcount = 0; bool testcomplete = true; Vector3 sphmin = sph.Center - sph.Radius; Vector3 sphmax = sph.Center + sph.Radius; var box = new BoundingBox(); - if (BoundsStore == null) return res; - var boundslist = BoundsStore.GetItems(ref sphmin, ref sphmax); + if ((BoundsStore == null) || (MapDataStore == null)) return res; + var boundslist = BoundsStore.GetItems(ref sphmin, ref sphmax, layers); + var mapdatalist = MapDataStore.GetItems(ref sphmin, ref sphmax); - foreach (var bound in boundslist) + for (int i = 0; i < boundslist.Count; i++) { + var bound = boundslist[i]; box.Minimum = bound.Min; box.Maximum = bound.Max; if (sph.Intersects(ref box)) @@ -1163,35 +1344,208 @@ namespace CodeWalker.World { testcomplete = false; continue; } //ybn not loaded yet... var b = ybn.Bounds; - - //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) - { - res.Hit = true; - res.HitPolygon = bhit.HitPolygon; - res.Normal = bhit.Normal; - } - polytestcount += bhit.TestedPolyCount; - nodetestcount += bhit.TestedNodeCount; + res.TryUpdate(ref bhit); } } + for (int i = 0; i < mapdatalist.Count; i++) + { + var mapdata = mapdatalist[i]; + if ((mapdata.ContentFlags & 1) == 0) + { continue; } //only test HD ymaps + + box.Minimum = mapdata.entitiesExtentsMin; + box.Maximum = mapdata.entitiesExtentsMax; + if (sph.Intersects(ref box)) + { + var hash = mapdata.Name; + var ymap = (hash > 0) ? GameFileCache.GetYmap(hash) : null; + if ((ymap != null) && (ymap.Loaded) && (ymap.AllEntities != null)) + { + if (!IsYmapAvailable(hash, CurrentHour, CurrentWeather)) + { continue; } + + for (int e = 0; e < ymap.AllEntities.Length; e++) + { + var ent = ymap.AllEntities[e]; + + if (!EntityCollisionsEnabled(ent)) + { continue; } + + box.Minimum = ent.BBMin; + box.Maximum = ent.BBMax; + if (sph.Intersects(ref box)) + { + if (ent.IsMlo) + { + var ihit = SphereIntersectInterior(ref sph, ent); + res.TryUpdate(ref ihit); + } + else + { + var ehit = SphereIntersectEntity(ref sph, ent); + res.TryUpdate(ref ehit); + } + } + } + } + else if ((ymap != null) && (!ymap.Loaded)) + { + testcomplete = false; + } + } + } + + //if (hit) //{ // hitpos = ray.Position + ray.Direction * itemhitdist; //} - res.TestedNodeCount = nodetestcount; - res.TestedPolyCount = polytestcount; res.TestComplete = testcomplete; return res; } + public SpaceSphereIntersectResult SphereIntersectEntity(ref BoundingSphere sph, YmapEntityDef ent) + { + var res = new SpaceSphereIntersectResult(); + + var drawable = GameFileCache.TryGetDrawable(ent.Archetype); + if (drawable != null) + { + var eori = ent.Orientation; + var eorinv = Quaternion.Invert(ent.Orientation); + var esph = sph; + esph.Center = eorinv.Multiply(sph.Center - ent.Position); + + if ((drawable is Drawable sdrawable) && (sdrawable.Bound != null)) + { + var dhit = sdrawable.Bound.SphereIntersect(ref esph); + if (dhit.Hit) + { + dhit.Position = eori.Multiply(dhit.Position) + ent.Position; + dhit.Normal = eori.Multiply(dhit.Normal); + } + res.TryUpdate(ref dhit); + } + else if (drawable is FragDrawable fdrawable) + { + if (fdrawable.Bound != null) + { + var fhit = fdrawable.Bound.SphereIntersect(ref esph); + if (fhit.Hit) + { + fhit.Position = eori.Multiply(fhit.Position) + ent.Position; + fhit.Normal = eori.Multiply(fhit.Normal); + } + res.TryUpdate(ref fhit); + } + var fbound = fdrawable.OwnerFragment?.PhysicsLODGroup?.PhysicsLOD1?.Bound; + if (fbound != null) + { + var fhit = fbound.SphereIntersect(ref esph);//TODO: these probably have extra transforms..! + if (fhit.Hit) + { + fhit.Position = eori.Multiply(fhit.Position) + ent.Position; + fhit.Normal = eori.Multiply(fhit.Normal); + } + res.TryUpdate(ref fhit); + } + } + } + + return res; + } + public SpaceSphereIntersectResult SphereIntersectInterior(ref BoundingSphere sph, YmapEntityDef mlo) + { + var res = new SpaceSphereIntersectResult(); + + if (mlo.Archetype == null) + { return res; } + + var iori = mlo.Orientation; + var iorinv = Quaternion.Invert(mlo.Orientation); + var isph = sph; + isph.Center = iorinv.Multiply(sph.Center - mlo.Position); + + var hash = mlo.Archetype.Hash; + var ybn = GameFileCache.GetYbn(hash); + if ((ybn != null) && (ybn.Loaded)) + { + var ihit = ybn.Bounds.SphereIntersect(ref isph); + if (ihit.Hit) + { + ihit.Position = iori.Multiply(ihit.Position) + mlo.Position; + ihit.Normal = iori.Multiply(ihit.Normal); + } + res.TryUpdate(ref ihit); + } + + var mlodat = mlo.MloInstance; + if (mlodat == null) + { return res; } + + var box = new BoundingBox(); + + if (mlodat.Entities != null) + { + for (int j = 0; j < mlodat.Entities.Length; j++) //should really improve this by using rooms! + { + var intent = mlodat.Entities[j]; + if (intent.Archetype == null) continue; //missing archetype... + + if (!EntityCollisionsEnabled(intent)) + { continue; } + + box.Minimum = intent.BBMin; + box.Maximum = intent.BBMax; + if (sph.Intersects(ref box)) + { + var ehit = SphereIntersectEntity(ref sph, intent); + res.TryUpdate(ref ehit); + } + } + } + if (mlodat.EntitySets != null) + { + foreach (var entitysets in mlodat.EntitySets) + { + var entityset = entitysets.Value; + if (!entityset.Visible) continue; + var entities = entityset.Entities; + for (int i = 0; i < entities.Count; i++) //should really improve this by using rooms! + { + var intent = entities[i]; + if (intent.Archetype == null) continue; //missing archetype... + + if (!EntityCollisionsEnabled(intent)) + { continue; } + + box.Minimum = intent.BBMin; + box.Maximum = intent.BBMax; + if (sph.Intersects(ref box)) + { + var ehit = SphereIntersectEntity(ref sph, intent); + res.TryUpdate(ref ehit); + } + } + } + } + + return res; + } + + private bool EntityCollisionsEnabled(YmapEntityDef ent) + { + if ((ent._CEntityDef.lodLevel != rage__eLodType.LODTYPES_DEPTH_ORPHANHD) && (ent._CEntityDef.lodLevel != rage__eLodType.LODTYPES_DEPTH_HD)) + { return false; } //only test HD entities + + if ((ent._CEntityDef.flags & 4) > 0) + { return false; } //embedded collisions disabled + + return true; + } } @@ -1227,7 +1581,7 @@ namespace CodeWalker.World RootNode.TrySplit(SplitThreshold); } - public List GetItems(ref Vector3 p) + public List GetItems(ref Vector3 p) //get items at a point, using the streaming extents { VisibleItems.Clear(); @@ -1236,6 +1590,28 @@ namespace CodeWalker.World RootNode.GetItems(ref p, VisibleItems); } + return VisibleItems; + } + public List GetItems(ref Vector3 min, ref Vector3 max) //get items intersecting a box, using the entities extents + { + VisibleItems.Clear(); + + if (RootNode != null) + { + RootNode.GetItems(ref min, ref max, VisibleItems); + } + + return VisibleItems; + } + public List GetItems(ref Ray ray) //get items intersecting a ray, using the entities extents + { + VisibleItems.Clear(); + + if (RootNode != null) + { + RootNode.GetItems(ref ray, VisibleItems); + } + return VisibleItems; } } @@ -1312,7 +1688,7 @@ namespace CodeWalker.World Items = newItems; } - public void GetItems(ref Vector3 p, List items) + public void GetItems(ref Vector3 p, List items) //get items at a point, using the streaming extents { if ((p.X >= BBMin.X) && (p.X <= BBMax.X) && (p.Y >= BBMin.Y) && (p.Y <= BBMax.Y)) { @@ -1342,6 +1718,67 @@ namespace CodeWalker.World } } } + public void GetItems(ref Vector3 min, ref Vector3 max, List items) //get items intersecting a box, using the entities extents + { + if ((max.X >= BBMin.X) && (min.X <= BBMax.X) && (max.Y >= BBMin.Y) && (min.Y <= BBMax.Y)) + { + if (Items != null) + { + for (int i = 0; i < Items.Count; i++) + { + var item = Items[i]; + var imin = item.entitiesExtentsMin; + var imax = item.entitiesExtentsMax; + if ((max.X >= imin.X) && (min.X <= imax.X) && (max.Y >= imin.Y) && (min.Y <= imax.Y)) + { + items.Add(item); + } + } + } + if (Children != null) + { + for (int i = 0; i < 4; i++) + { + var c = Children[i]; + if (c != null) + { + c.GetItems(ref min, ref max, items); + } + } + } + } + } + public void GetItems(ref Ray ray, List items) //get items intersecting a ray, using the entities extents + { + var bb = new BoundingBox(BBMin, BBMax); + if (ray.Intersects(ref bb)) + { + if (Items != null) + { + for (int i = 0; i < Items.Count; i++) + { + var item = Items[i]; + bb.Minimum = item.entitiesExtentsMin; + bb.Maximum = item.entitiesExtentsMax; + if (ray.Intersects(ref bb)) + { + items.Add(item); + } + } + } + if (Children != null) + { + for (int i = 0; i < 4; i++) + { + var c = Children[i]; + if (c != null) + { + c.GetItems(ref ray, items); + } + } + } + } + } } @@ -1708,6 +2145,21 @@ namespace CodeWalker.World public int TestedPolyCount; public bool TestComplete; public BoundMaterial_s Material; + + public void TryUpdate(ref SpaceRayIntersectResult r) + { + if (r.Hit) + { + Hit = true; + HitDist = r.HitDist; + HitPolygon = r.HitPolygon; + Material = r.Material; + Position = r.Position; + Normal = r.Normal; + } + TestedNodeCount += r.TestedNodeCount; + TestedPolyCount += r.TestedPolyCount; + } } public struct SpaceSphereIntersectResult { @@ -1719,6 +2171,18 @@ namespace CodeWalker.World public int TestedNodeCount; public int TestedPolyCount; public bool TestComplete; + + public void TryUpdate(ref SpaceSphereIntersectResult r) + { + if (r.Hit) + { + Hit = true; + HitPolygon = r.HitPolygon; + Normal = r.Normal; + } + TestedPolyCount += r.TestedPolyCount; + TestedNodeCount += r.TestedNodeCount; + } } public struct SpaceEntityCollision diff --git a/Rendering/Renderable.cs b/Rendering/Renderable.cs index fe56fed..499ef11 100644 --- a/Rendering/Renderable.cs +++ b/Rendering/Renderable.cs @@ -1899,13 +1899,12 @@ namespace CodeWalker.Rendering } RenderableBoundGeometry[] geoms = new RenderableBoundGeometry[bound.Children.data_items.Length]; - var childTransforms = bound.ChildrenTransformation1 ?? bound.ChildrenTransformation2; long dsize = 0; for (int i = 0; i < bound.Children.data_items.Length; i++) { var rgeom = new RenderableBoundGeometry(this); var child = bound.Children.data_items[i]; - var xform = ((childTransforms != null) && (i < childTransforms.Length)) ? childTransforms[i] : Matrix.Identity; xform.Column4 = new Vector4(0.0f, 0.0f, 0.0f, 1.0f); + var xform = (child != null) ? child.Transform : Matrix.Identity; if (child is BoundGeometry bgeom) { rgeom.Init(bgeom); diff --git a/Rendering/Renderer.cs b/Rendering/Renderer.cs index 6b34822..9f102cb 100644 --- a/Rendering/Renderer.cs +++ b/Rendering/Renderer.cs @@ -2804,22 +2804,25 @@ namespace CodeWalker.Rendering if ((rendercollisionmeshes || (SelectionMode == MapSelectionMode.Collision)) && rendercollisionmeshlayerdrawable) { - Drawable sdrawable = rndbl.Key as Drawable; - if ((sdrawable != null) && (sdrawable.Bound != null)) + if ((entity == null) || ((entity._CEntityDef.flags & 4) == 0)) //skip if entity embedded collisions disabled { - RenderCollisionMesh(sdrawable.Bound, entity); - } - FragDrawable fdrawable = rndbl.Key as FragDrawable; - if (fdrawable != null) - { - if (fdrawable.Bound != null) + Drawable sdrawable = rndbl.Key as Drawable; + if ((sdrawable != null) && (sdrawable.Bound != null)) { - RenderCollisionMesh(fdrawable.Bound, entity); + RenderCollisionMesh(sdrawable.Bound, entity); } - var fbound = fdrawable.OwnerFragment?.PhysicsLODGroup?.PhysicsLOD1?.Bound; - if (fbound != null) + FragDrawable fdrawable = rndbl.Key as FragDrawable; + if (fdrawable != null) { - RenderCollisionMesh(fbound, entity);//TODO: these probably have extra transforms..! + if (fdrawable.Bound != null) + { + RenderCollisionMesh(fdrawable.Bound, entity); + } + var fbound = fdrawable.OwnerFragment?.PhysicsLODGroup?.PhysicsLOD1?.Bound; + if (fbound != null) + { + RenderCollisionMesh(fbound, entity);//TODO: these probably have extra transforms..! + } } } } diff --git a/WorldForm.cs b/WorldForm.cs index b2781fb..243fb19 100644 --- a/WorldForm.cs +++ b/WorldForm.cs @@ -2119,6 +2119,7 @@ namespace CodeWalker e.Radius = arch.BSRadius * 0.7f; e.EnableCollisions = true; e.Enabled = true; + e.Lifetime = 20.0f; lock (Renderer.RenderSyncRoot) { @@ -2238,14 +2239,14 @@ namespace CodeWalker Ray mray = new Ray(); mray.Position = camera.MouseRay.Position + camera.Position; mray.Direction = camera.MouseRay.Direction; - return space.RayIntersect(mray); + return space.RayIntersect(mray, float.MaxValue, collisionmeshlayers); } return ret; } public SpaceRayIntersectResult Raycast(Ray ray) { - return space.RayIntersect(ray); + return space.RayIntersect(ray, float.MaxValue, collisionmeshlayers); } private void UpdateMouseHitsFromRenderer()