Ray - Sphere Intersection

时间:2015-11-26 20:47:32

标签: java opengl math vector raycasting

我最近一直试图检测玩家是否“瞄准”某个物体,但我的检测有问题。

所以这是我用过的公式:

(ray * (origin-center))^2 - ||origin-center||^2 + r^2 >= 0

其中ray是玩家目标的方向(来源http://antongerdelan.net/opengl/raycasting.html),原点是相机的位置,中心是球体的中心

我在https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection

找到了这个公式

将此转换为java to

public boolean collideWithSphere(Vector3f c, Vector3f o, float r) {
    float delta = 0;
    Vector3f omc = Vector3f.sub(o, c);
    float b = Vector3f.Dot(ray, omc);
    delta = b*b-Vector3f.Dot(omc,omc)+r*r;
    return delta >= 0;
}

我做了一些测量,对我来说问题是当从原点到球体的距离很大时ray * omc不是>=||omc||因为没有组件光线的确是+ -1。

有人有办法解决这个问题吗?

由于

====================================

修改

好的,所以在一天结束的时候,我不得不反转omc的y分量才能让它工作,我不明白为什么,但我会处理它。

2 个答案:

答案 0 :(得分:0)

对于我目前的游戏引擎,我使用这两个类进行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;这是一种可怕的语言。

答案 1 :(得分:-1)

您的ray必须是向量cmo的确切长度。因此,如果您的ray已标准化,则必须

ray = ray * lengthOfCmO

所以转动你的相机球形矢量,让你的光线到正确的长度,你的功能应该工作。您应该注意,这个公式在数学上不正确。您只需检查相机球面矢量上的光线投影是否长于相机到球体的长度减去球体半径。它会起作用,但是你必须使用比实际想要被光线击中的更大的球体。