對於我目前的遊戲引擎,我將這兩個類用於Ray-> Sphere相交測試。
BoundingSphere.cs
using System;
using OpenTK;
namespace DestinyEngine.Framework.Maths
{
public struct BoundingSphere : IEquatable<BoundingSphere>
{
/// <summary>
/// The sphere center.
/// </summary>
public readonly Vector3 Center;
/// <summary>
/// The sphere radius.
/// </summary>
public readonly float Radius;
/// <summary>
/// Constructs a bounding sphere with the specified center and radius.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The sphere radius.</param>
public BoundingSphere(Vector3 center , float radius)
{
this.Center = center;
this.Radius = radius;
}
/// <summary>
/// Test if a bounding box is fully inside, outside, or just intersecting the sphere.
/// </summary>
/// <param name="box">The box for testing.</param>
/// <returns>The containment type.</returns>
public ContainmentType Contains(BoundingBox box)
{
//check if all corner is in sphere
bool inside = true;
for(int index = 0; index < box.GetCorners().Length; index++)
{
Vector3 corner = box.GetCorners()[ index ];
if(this.Contains(corner) == ContainmentType.Disjoint)
{
inside = false;
break;
}
}
if(inside)
{
return ContainmentType.Contains;
}
double dmin = 0;
if(Center.X < box.Min.X)
{
dmin += (Center.X - box.Min.X) * (Center.X - box.Min.X);
}
else if(Center.X > box.Max.X)
{
dmin += (Center.X - box.Max.X) * (Center.X - box.Max.X);
}
if(Center.Y < box.Min.Y)
{
dmin += (Center.Y - box.Min.Y) * (Center.Y - box.Min.Y);
}
else if(Center.Y > box.Max.Y)
{
dmin += (Center.Y - box.Max.Y) * (Center.Y - box.Max.Y);
}
if(Center.Z < box.Min.Z)
{
dmin += (Center.Z - box.Min.Z) * (Center.Z - box.Min.Z);
}
else if(Center.Z > box.Max.Z)
{
dmin += (Center.Z - box.Max.Z) * (Center.Z - box.Max.Z);
}
return dmin <= Radius * Radius ? ContainmentType.Intersects : ContainmentType.Disjoint;
}
/// <summary>
/// Test if a sphere is fully inside, outside, or just intersecting the sphere.
/// </summary>
/// <param name="sphere">The other sphere for testing.</param>
/// <returns>The containment type.</returns>
public ContainmentType Contains(BoundingSphere sphere)
{
float sqDistance = DestinyEngineMath.DistanceSquared(sphere.Center , Center);
if(sqDistance > (sphere.Radius + Radius) * (sphere.Radius + Radius))
{
return ContainmentType.Disjoint;
}
else if(sqDistance <= (Radius - sphere.Radius) * (Radius - sphere.Radius))
{
return ContainmentType.Contains;
}
else
{
return ContainmentType.Intersects;
}
}
/// <summary>
/// Test if a point is fully inside, outside, or just intersecting the sphere.
/// </summary>
/// <param name="point">The vector in 3D-space for testing.</param>
/// <returns>The containment type.</returns>
public ContainmentType Contains(Vector3 point)
{
float sqRadius = Radius * Radius;
float sqDistance = DestinyEngineMath.DistanceSquared(point , Center);
if(sqDistance > sqRadius)
{
return ContainmentType.Disjoint;
}
else if(sqDistance < sqRadius)
{
return ContainmentType.Contains;
}
else
{
return ContainmentType.Intersects;
}
}
/// <summary>
/// Creates the smallest <see cref="BoundingSphere"/> that can contain a specified <see cref="BoundingBox"/>.
/// </summary>
/// <param name="box">The box to create the sphere from.</param>
/// <returns>The new <see cref="BoundingSphere"/>.</returns>
public static BoundingSphere CreateFromBoundingBox(BoundingBox box)
{
Vector3 center = new Vector3(
(box.Min.X + box.Max.X)/2.0f ,
(box.Min.Y + box.Max.Y)/2.0f ,
(box.Min.Z + box.Max.Z)/2.0f);
float radius = DestinyEngineMath.Distance(center , box.Max);
return new BoundingSphere(center , radius);
}
/// <summary>
/// Compares whether current instance is equal to specified <see cref="BoundingSphere"/>.
/// </summary>
/// <param name="other">The <see cref="BoundingSphere"/> to compare.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(BoundingSphere other)
{
return this.Center == other.Center && this.Radius == other.Radius;
}
/// <summary>
/// Compares whether current instance is equal to specified <see cref="Object"/>.
/// </summary>
/// <param name="obj">The <see cref="Object"/> to compare.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public override bool Equals(object obj)
{
if(obj is BoundingSphere)
{
return this.Equals((BoundingSphere)obj);
}
return false;
}
/// <summary>
/// Gets the hash code of this <see cref="BoundingSphere"/>.
/// </summary>
/// <returns>Hash code of this <see cref="BoundingSphere"/>.</returns>
public override int GetHashCode()
{
return this.Center.GetHashCode() + this.Radius.GetHashCode();
}
/// <summary>
/// Gets whether or not a specified <see cref="BoundingBox"/> intersects with this sphere.
/// </summary>
/// <param name="box">The box for testing.</param>
/// <returns><c>true</c> if <see cref="BoundingBox"/> intersects with this sphere; <c>false</c> otherwise.</returns>
public bool Intersects(BoundingBox box)
{
return box.Intersects(this);
}
/// <summary>
/// Gets whether or not the other <see cref="BoundingSphere"/> intersects with this sphere.
/// </summary>
/// <param name="sphere">The other sphere for testing.</param>
/// <returns><c>true</c> if other <see cref="BoundingSphere"/> intersects with this sphere; <c>false</c> otherwise.</returns>
public bool Intersects(BoundingSphere sphere)
{
float sqDistance = DestinyEngineMath.DistanceSquared(sphere.Center , Center);
return !(sqDistance > (sphere.Radius + Radius) * (sphere.Radius + Radius));
}
/// <summary>
/// Returns a <see cref="String"/> representation of this <see cref="BoundingSphere"/> in the format:
/// {Center:[<see cref="Center"/>] Radius:[<see cref="Radius"/>]}
/// </summary>
/// <returns>A <see cref="String"/> representation of this <see cref="BoundingSphere"/>.</returns>
public override string ToString()
{
return "{Center:" + this.Center + " Radius:" + this.Radius + "}";
}
/// <summary>
/// Compares whether two <see cref="BoundingSphere"/> instances are equal.
/// </summary>
/// <param name="a"><see cref="BoundingSphere"/> instance on the left of the equal sign.</param>
/// <param name="b"><see cref="BoundingSphere"/> instance on the right of the equal sign.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(BoundingSphere a , BoundingSphere b)
{
return a.Equals(b);
}
/// <summary>
/// Compares whether two <see cref="BoundingSphere"/> instances are not equal.
/// </summary>
/// <param name="a"><see cref="BoundingSphere"/> instance on the left of the not equal sign.</param>
/// <param name="b"><see cref="BoundingSphere"/> instance on the right of the not equal sign.</param>
/// <returns><c>true</c> if the instances are not equal; <c>false</c> otherwise.</returns>
public static bool operator !=(BoundingSphere a , BoundingSphere b)
{
return !a.Equals(b);
}
}
}
Ray.cs
using System;
using OpenTK;
using OpenTK.Graphics.OpenGL;
namespace DestinyEngine.Framework.Maths
{
public struct Ray : IEquatable<Ray>
{
public readonly Vector3 Origin;
public readonly Vector3 Direction;
public Ray(Vector3 origin , Vector3 direction)
{
this.Origin = origin;
this.Direction = direction;
}
public Ray(float originX , float originY , float originZ , float directionX , float directionY , float directionZ)
{
this.Origin = new Vector3(originX , originY , originZ);
this.Direction = new Vector3(directionX , directionY , directionZ);
}
public override bool Equals(object obj)
{
return (obj is Ray) && this.Equals((Ray)obj);
}
public bool Equals(Ray other)
{
return this.Origin.Equals(other.Origin) && this.Direction.Equals(other.Direction);
}
public override int GetHashCode()
{
return Origin.GetHashCode()^Direction.GetHashCode();
}
public float? Intersects(BoundingBox box)
{
const float epsilon = 1e-6f;
float? tMin = null, tMax = null;
if(Math.Abs(Direction.X) < epsilon)
{
if(Origin.X < box.Min.X || Origin.X > box.Max.X)
{
return null;
}
}
else
{
tMin = (box.Min.X - Origin.X)/Direction.X;
tMax = (box.Max.X - Origin.X)/Direction.X;
if(tMin > tMax)
{
var temp = tMin;
tMin = tMax;
tMax = temp;
}
}
if(Math.Abs(Direction.Y) < epsilon)
{
if(Origin.Y < box.Min.Y || Origin.Y > box.Max.Y)
{
return null;
}
}
else
{
float tMinY = (box.Min.Y - Origin.Y)/Direction.Y;
float tMaxY = (box.Max.Y - Origin.Y)/Direction.Y;
if(tMinY > tMaxY)
{
float temp = tMinY;
tMinY = tMaxY;
tMaxY = temp;
}
if((tMin.HasValue && tMin > tMaxY) || (tMax.HasValue && tMinY > tMax))
{
return null;
}
if(!tMin.HasValue || tMinY > tMin)
{
tMin = tMinY;
}
if(!tMax.HasValue || tMaxY < tMax)
{
tMax = tMaxY;
}
}
if(Math.Abs(Direction.Z) < epsilon)
{
if(Origin.Z < box.Min.Z || Origin.Z > box.Max.Z)
{
return null;
}
}
else
{
float tMinZ = (box.Min.Z - Origin.Z)/Direction.Z;
float tMaxZ = (box.Max.Z - Origin.Z)/Direction.Z;
if(tMinZ > tMaxZ)
{
float temp = tMinZ;
tMinZ = tMaxZ;
tMaxZ = temp;
}
if((tMin.HasValue && tMin > tMaxZ) || (tMax.HasValue && tMinZ > tMax))
{
return null;
}
if(!tMin.HasValue || tMinZ > tMin)
{
tMin = tMinZ;
}
if(!tMax.HasValue || tMaxZ < tMax)
{
tMax = tMaxZ;
}
}
if((tMin.HasValue && tMin < 0) && tMax > 0)
{
return 0;
}
return tMin < 0 ? null : tMin;
}
public float? Intersects(BoundingSphere sphere)
{
Vector3 difference = sphere.Center - this.Origin;
float differenceLengthSquared = DestinyEngineMath.LengthSquared(difference);
float sphereRadiusSquared = sphere.Radius * sphere.Radius;
float distanceAlongRay;
if(differenceLengthSquared < sphereRadiusSquared)
{
return 0.0f;
}
Vector3 refDirection = this.Direction;
Vector3.Dot(ref refDirection , ref difference , out distanceAlongRay);
if(distanceAlongRay < 0)
{
return null;
}
float dist = sphereRadiusSquared + distanceAlongRay * distanceAlongRay - differenceLengthSquared;
return (dist < 0) ? null : distanceAlongRay - (float?)Math.Sqrt(dist);
}
public static bool operator !=(Ray a , Ray b)
{
return !(a.Origin.Equals(b.Origin) && a.Direction.Equals(b.Direction));
}
public static bool operator ==(Ray a , Ray b)
{
return a.Origin.Equals(b.Origin) && a.Direction.Equals(b.Direction);
}
public override string ToString()
{
return "{{Origin:" + Origin + " Direction:" + Direction + "}}";
}
public void DrawImmediate()
{
Vector3 end = this.Origin + this.Direction * 400.0f;
GL.Begin(PrimitiveType.Lines);
GL.Vertex3(this.Origin);
GL.Vertex3(end);
GL.End();
}
}
}
使用這些,你可以這樣做:
Ray ray = new Ray(new Vector3(0, 0, 0), new Vector3(1, 1, 1));
BoundingSphere sphere = new BoundingSphere(new Vector3(1,1,1), 0.5f);
Console.WriteLine(ray.Intersects(sphere)); // Gives length until intersection.
上面的代碼是用C#編寫,它可以很容易地被移植到Java。但是,我不知道爲什麼有人會使用Java;這是一種可怕的語言。
你的'ray'有多長?這很重要,因爲你使用一個標量產品來處理從相機到球體的矢量。另外,你的'omc'應該是'cmo',因爲你想從攝影機獲取矢量到球體中心。 –
射線是一個單位矢量。我想我已經嘗試了cmo的東西,但它沒有解決,但我可以再試一次。 – Nifil
@MartinHennig光線的長度不是要求。你根據Origin的方向計算它;請參閱下面提供的代碼。 – Krythic