2019-11-03 14:27:22 +08:00

490 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using _Math = System.Math;
namespace THREE
{
/// <summary>
/// @author bhouston / http://clara.io
/// @author tengge / https://github.com/tengge1
/// </summary>
public class Ray
{
Vector3 origin;
Vector3 direction;
public Ray(Vector3 origin = null, Vector3 direction = null)
{
this.origin = origin ?? new Vector3();
this.direction = direction ?? new Vector3();
}
public Ray Set(Vector3 origin, Vector3 direction)
{
this.origin.Copy(origin);
this.direction.Copy(direction);
return this;
}
public Ray Clone()
{
return new Ray().Copy(this);
}
public Ray Copy(Ray ray)
{
this.origin.Copy(ray.origin);
this.direction.Copy(ray.direction);
return this;
}
public Vector3 At(double t, Vector3 target = null)
{
if (target == null)
{
Console.WriteLine("THREE.Ray: .at() target is now required");
target = new Vector3();
}
return target.Copy(this.direction).MultiplyScalar(t).Add(this.origin);
}
public Ray LookAt(Vector3 v)
{
this.direction.Copy(v).Sub(this.origin).Normalize();
return this;
}
public Ray Recast(double t)
{
var v1 = new Vector3();
this.origin.Copy(this.At(t, v1));
return this;
}
public Vector3 ClosestPointToPoint(Vector3 point, Vector3 target = null)
{
if (target == null)
{
Console.WriteLine("THREE.Ray: .closestPointToPoint() target is now required");
target = new Vector3();
}
target.SubVectors(point, this.origin);
var directionDistance = target.Dot(this.direction);
if (directionDistance < 0)
{
return target.Copy(this.origin);
}
return target.Copy(this.direction).MultiplyScalar(directionDistance).Add(this.origin);
}
public double DistanceToPoint(Vector3 point)
{
return _Math.Sqrt(this.DistanceSqToPoint(point));
}
public double DistanceSqToPoint(Vector3 point)
{
var v1 = new Vector3();
var directionDistance = v1.SubVectors(point, this.origin).Dot(this.direction);
// point behind the ray
if (directionDistance < 0)
{
return this.origin.DistanceToSquared(point);
}
v1.Copy(this.direction).MultiplyScalar(directionDistance).Add(this.origin);
return v1.DistanceToSquared(point);
}
public double DistanceSqToSegment(Vector3 v0, Vector3 v1, Vector3 optionalPointOnRay = null, Vector3 optionalPointOnSegment = null)
{
var segCenter = new Vector3();
var segDir = new Vector3();
var diff = new Vector3();
// from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h
// It returns the min distance between the ray and the segment
// defined by v0 and v1
// It can also set two optional targets :
// - The closest point on the ray
// - The closest point on the segment
segCenter.Copy(v0).Add(v1).MultiplyScalar(0.5);
segDir.Copy(v1).Sub(v0).Normalize();
diff.Copy(this.origin).Sub(segCenter);
var segExtent = v0.DistanceTo(v1) * 0.5;
var a01 = -this.direction.Dot(segDir);
var b0 = diff.Dot(this.direction);
var b1 = -diff.Dot(segDir);
var c = diff.LengthSq();
var det = _Math.Abs(1 - a01 * a01);
double s0, s1, sqrDist, extDet;
if (det > 0)
{
// The ray and segment are not parallel.
s0 = a01 * b1 - b0;
s1 = a01 * b0 - b1;
extDet = segExtent * det;
if (s0 >= 0)
{
if (s1 >= -extDet)
{
if (s1 <= extDet)
{
// region 0
// Minimum at interior points of ray and segment.
var invDet = 1 / det;
s0 *= invDet;
s1 *= invDet;
sqrDist = s0 * (s0 + a01 * s1 + 2 * b0) + s1 * (a01 * s0 + s1 + 2 * b1) + c;
}
else
{
// region 1
s1 = segExtent;
s0 = _Math.Max(0, -(a01 * s1 + b0));
sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;
}
}
else
{
// region 5
s1 = -segExtent;
s0 = _Math.Max(0, -(a01 * s1 + b0));
sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;
}
}
else
{
if (s1 <= -extDet)
{
// region 4
s0 = _Math.Max(0, -(-a01 * segExtent + b0));
s1 = (s0 > 0) ? -segExtent : _Math.Min(_Math.Max(-segExtent, -b1), segExtent);
sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;
}
else if (s1 <= extDet)
{
// region 3
s0 = 0;
s1 = _Math.Min(_Math.Max(-segExtent, -b1), segExtent);
sqrDist = s1 * (s1 + 2 * b1) + c;
}
else
{
// region 2
s0 = _Math.Max(0, -(a01 * segExtent + b0));
s1 = (s0 > 0) ? segExtent : _Math.Min(_Math.Max(-segExtent, -b1), segExtent);
sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;
}
}
}
else
{
// Ray and segment are parallel.
s1 = (a01 > 0) ? -segExtent : segExtent;
s0 = _Math.Max(0, -(a01 * s1 + b0));
sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;
}
if (optionalPointOnRay != null)
{
optionalPointOnRay.Copy(this.direction).MultiplyScalar(s0).Add(this.origin);
}
if (optionalPointOnSegment != null)
{
optionalPointOnSegment.Copy(segDir).MultiplyScalar(s1).Add(segCenter);
}
return sqrDist;
}
public Vector3 IntersectSphere(Sphere sphere, Vector3 target)
{
var v1 = new Vector3();
v1.SubVectors(sphere.center, this.origin);
var tca = v1.Dot(this.direction);
var d2 = v1.Dot(v1) - tca * tca;
var radius2 = sphere.radius * sphere.radius;
if (d2 > radius2) return null;
var thc = _Math.Sqrt(radius2 - d2);
// t0 = first intersect point - entrance on front of sphere
var t0 = tca - thc;
// t1 = second intersect point - exit point on back of sphere
var t1 = tca + thc;
// test to see if both t0 and t1 are behind the ray - if so, return null
if (t0 < 0 && t1 < 0) return null;
// test to see if t0 is behind the ray:
// if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
// in order to always return an intersect point that is in front of the ray.
if (t0 < 0) return this.At(t1, target);
// else t0 is in front of the ray, so return the first collision point scaled by t0
return this.At(t0, target);
}
public bool IntersectsSphere(Sphere sphere)
{
return this.DistanceSqToPoint(sphere.center) <= sphere.radius * sphere.radius;
}
public double? DistanceToPlane(Plane plane)
{
var denominator = plane.normal.Dot(this.direction);
if (denominator == 0)
{
// line is coplanar, return origin
if (plane.DistanceToPoint(this.origin) == 0)
{
return 0;
}
// Null is preferable to undefined since undefined means.... it is undefined
return null;
}
var t = -(this.origin.Dot(plane.normal) + plane.constant) / denominator;
// Return if the ray never intersects the plane
if (t >= 0)
{
return t;
}
return null;
}
public Vector3 IntersectPlane(Plane plane, Vector3 target)
{
var t = this.DistanceToPlane(plane);
if (t == null)
{
return null;
}
return this.At(t.Value, target);
}
public bool IntersectsPlane(Plane plane)
{
// check if the ray lies on the plane first
var distToPoint = plane.DistanceToPoint(this.origin);
if (distToPoint == 0)
{
return true;
}
var denominator = plane.normal.Dot(this.direction);
if (denominator * distToPoint < 0)
{
return true;
}
// ray origin is behind the plane (and is pointing behind it)
return false;
}
public Vector3 IntersectBox(Box3 box, Vector3 target)
{
double tmin, tmax, tymin, tymax, tzmin, tzmax;
double invdirx = 1 / this.direction.x,
invdiry = 1 / this.direction.y,
invdirz = 1 / this.direction.z;
var origin = this.origin;
if (invdirx >= 0)
{
tmin = (box.min.x - origin.x) * invdirx;
tmax = (box.max.x - origin.x) * invdirx;
}
else
{
tmin = (box.max.x - origin.x) * invdirx;
tmax = (box.min.x - origin.x) * invdirx;
}
if (invdiry >= 0)
{
tymin = (box.min.y - origin.y) * invdiry;
tymax = (box.max.y - origin.y) * invdiry;
}
else
{
tymin = (box.max.y - origin.y) * invdiry;
tymax = (box.min.y - origin.y) * invdiry;
}
if ((tmin > tymax) || (tymin > tmax)) return null;
// These lines also handle the case where tmin or tmax is NaN
// (result of 0 * Infinity). x !== x returns true if x is NaN
if (tymin > tmin || double.IsNaN(tmin)) tmin = tymin;
if (tymax < tmax || double.IsNaN(tmax)) tmax = tymax;
if (invdirz >= 0)
{
tzmin = (box.min.z - origin.z) * invdirz;
tzmax = (box.max.z - origin.z) * invdirz;
}
else
{
tzmin = (box.max.z - origin.z) * invdirz;
tzmax = (box.min.z - origin.z) * invdirz;
}
if ((tmin > tzmax) || (tzmin > tmax)) return null;
if (tzmin > tmin || double.IsNaN(tmin)) tmin = tzmin;
if (tzmax < tmax || double.IsNaN(tmax)) tmax = tzmax;
//return point closest to the ray (positive side)
if (tmax < 0) return null;
return this.At(tmin >= 0 ? tmin : tmax, target);
}
public bool IntersectsBox(Box3 box)
{
var v = new Vector3();
return this.IntersectBox(box, v) != null;
}
public Vector3 IntersectTriangle(Vector3 a, Vector3 b, Vector3 c, bool backfaceCulling, Vector3 target)
{
// Compute the offset origin, edges, and normal.
var diff = new Vector3();
var edge1 = new Vector3();
var edge2 = new Vector3();
var normal = new Vector3();
// from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
edge1.SubVectors(b, a);
edge2.SubVectors(c, a);
normal.CrossVectors(edge1, edge2);
// Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
// E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
// |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
// |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
// |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
var DdN = this.direction.Dot(normal);
double sign;
if (DdN > 0)
{
if (backfaceCulling) return null;
sign = 1;
}
else if (DdN < 0)
{
sign = -1;
DdN = -DdN;
}
else
{
return null;
}
diff.SubVectors(this.origin, a);
var DdQxE2 = sign * this.direction.Dot(edge2.CrossVectors(diff, edge2));
// b1 < 0, no intersection
if (DdQxE2 < 0)
{
return null;
}
var DdE1xQ = sign * this.direction.Dot(edge1.Cross(diff));
// b2 < 0, no intersection
if (DdE1xQ < 0)
{
return null;
}
// b1+b2 > 1, no intersection
if (DdQxE2 + DdE1xQ > DdN)
{
return null;
}
// Line intersects triangle, check if ray does.
var QdN = -sign * diff.Dot(normal);
// t < 0, no intersection
if (QdN < 0)
{
return null;
}
// Ray intersects triangle.
return this.At(QdN / DdN, target);
}
public Ray ApplyMatrix4(Matrix4 matrix4)
{
this.origin.ApplyMatrix4(matrix4);
this.direction.TransformDirection(matrix4);
return this;
}
public bool Equals(Ray ray)
{
return ray.origin.Equals(this.origin) && ray.direction.Equals(this.direction);
}
}
}