空间中两个 3D 磁盘之间的碰撞检测

Posted

技术标签:

【中文标题】空间中两个 3D 磁盘之间的碰撞检测【英文标题】:Collision Detection Between Two 3D Disks In Space 【发布时间】:2021-07-10 13:01:02 【问题描述】:

我想开发一种算法,用于在 3D 空间中执行两个 3D 磁盘之间的碰撞检测。是否可以建议一种分析方法,以便我可以开发此程序。

谢谢,

【问题讨论】:

详细描述什么信息在任何时候以什么形式提供? @jAlex 非常感谢您提供如此详细的答案。我所说的 3D 磁盘是指如果磁盘有厚度会怎样。这使得碰撞检测变得如此困难。我开发了一个代码,用很多球体填充 3D 磁盘,并执行球体之间的碰撞检测,这需要 log 才能完成。非常感谢您对此的想法。 您可以调整我的答案以轻松包含厚度。修改Disk.Containts() 函数以检查DistanceTo(point)<= Thickness/2 就是这样。请参阅下面的修改。 @jAlex 再次感谢您的回答。我将在 MATLAB 中重写此代码,因为我会在其中生成随机数。 在我的回答中查看我的更新,并链接到Matlab 一般几何材料的代码。 【参考方案1】:

所以每个圆盘都位于一个无限平面的顶部,每个圆盘都有一个法线向量和到原点的距离。两个平面相交的地方形成一条线。

这条线将中心 c1c2 投影到线上 上的两个点p1p2 具有垂直距离 h1h2。投影点之间的距离为l

如果磁盘接触,公共线将包含接触点。如果它们没有接触,或者它们正在相互穿透,那么这个点 pC 就是直线上同时离两个磁盘中心最近的点。

t1p1p的距离t1 sub>C 使用相似三角形求解 (t1)/h1 = (l - t1)/h2

t_1 = h_1*ell/(h_1+h_2)

从距离t1和公共线方向,计算点pC如果两个圆盘各自到中心的距离等于或小于它们的半径,则这两个圆盘相交

intersect = ( distance(c_1-p_C)<=R_1 ) && ( distance(c_2-p_C)<=R_2 )

有一种特殊情况,两个圆心都在公共线上,也需要考虑。 C# 中的完整解决方案是

主要

    static void Main(string[] args)
    
        var disk_1 = new Disk(Point.Origin, Vector3.UnitZ, 1);
        var disk_2 = new Disk(
            Point.Origin + Vector3.UnitY,
            Vector3.UnitY,
            0.6f);
        if (Disk.Intersect(disk_1, disk_2))
        
            Debug.WriteLine($"Crash");
        
    

磁盘

using System.Numerics;
public class Disk : Plane

    public Disk(Point center, Vector3 normal, float radius, float thickness)
        : base(center, normal)
    
        this.Center=center;
        this.Radius=radius;
        this.Thickness = thickness;
    

    public Point Center  get; 
    public float Radius  get; 
    public float Thickness  get; 

    public bool Containts(Point point)
    
        if (DistanceTo(point)<= Thickness/2)
        
            var d = (point - Center).Length();
            return d<=Radius;
        
        return false;
    

    public static bool Intersect(Disk disk_1, Disk disk_2)
    
        var commonLine = Line.Meet(disk_1, disk_2);
        var p_1 = commonLine.Project(disk_1.Center);
        var p_2 = commonLine.Project(disk_2.Center);
        var ell = p_1.DistanceTo(p_2);
        var h_1 = commonLine.DistanceTo(disk_1.Center);
        var h_2 = commonLine.DistanceTo(disk_2.Center);
        if (Math.Abs(h_1+h_2)>1e-8)
        
            var t_1 = h_1*ell/(h_1+h_2);
            var contact = p_1 - commonLine.Direction * t_1;
            return disk_1.Containts(contact) && disk_2.Containts(contact);
        
        else
        
            return ell <= disk_1.Radius + disk_2.Radius;
        
    

几何

using System.Numerics;
public class Point 

    public Vector3 Vector  get; 
    public float Scalar  get; 

    public Point(Vector3 position)
        : this(position, 1)  
    public Point(Vector3 vector, float scalar)
    
        this.Vector = vector;
        this.Scalar=scalar;
    
    public Point(Vector4 coordinates)
        : this(new Vector3(coordinates.X, coordinates.Y, coordinates.Z), coordinates.W)
     
    public Point(Plane plane)
        : this(-plane.Scalar*plane.Vector, plane.Vector.LengthSquared())
     
    public Point(Line line)
        : this(Vector3.Cross(line.Vector, line.Moment), line.Vector.LengthSquared())
     

    public static implicit operator Point(Vector3 position)
        => new Point(position, 1);
    public static implicit operator Point(Vector4 coordinates)
        => new Point(coordinates);

    public static readonly Point Origin = new Point(Vector3.Zero, 1);

    public static Point Meet(Plane plane, Line line)
    
        return new Point(
            Vector3.Cross(line.Moment, plane.Vector)+plane.Scalar*line.Vector,
            -Vector3.Dot(plane.Vector, line.Vector));
    
    public static Point Meet(Plane plane_1, Plane plane_2, Plane plane_3)
    
        return Meet(plane_1, Line.Meet(plane_2, plane_3));
    

    public float Magnitude  get => Math.Abs(Scalar); 
    public Vector3 Position  get => Vector/Scalar; 
    public float DistanceTo(Point point)
        => (Scalar*point.Vector - point.Scalar*Vector).Length()/(Scalar*point.Scalar);
    public float DistanceTo(Plane plane)
        => (Vector3.Dot(plane.Vector, Vector) + Scalar*plane.Scalar)/(Scalar*plane.Vector.Length());
    public float DistanceTo(Line line)
        => (Vector3.Cross(line.Vector, Vector) + Scalar * line.Moment).Length()/(Scalar*line.Vector.Length());
    public static Point operator +(Point point, Vector3 delta)
        => new Point(point.Vector + point.Scalar*delta, point.Scalar);
    public static Vector3 operator -(Point point, Point @base)
        => point.Position - @base.Position;


public class Plane 

    public Vector3 Vector  get; 
    public float Scalar  get; 

    public Plane(Vector3 vector, float scalar)
    
        this.Vector=vector;
        this.Scalar=scalar;
    
    public Plane(Vector4 coordinates)
        : this(new Vector3(coordinates.X, coordinates.Y, coordinates.Z), coordinates.W)
     
    public Plane(Point point, Vector3 normal)
        : this(normal, -Vector3.Dot(point.Position, normal))
     

    public Plane(Point point)
        : this(-point.Scalar*point.Vector, point.Vector.LengthSquared())
     
    public Plane(Line line)
        : this(Vector3.Cross(line.Moment, line.Vector), line.Moment.LengthSquared())
     
    public static implicit operator Plane(Vector4 coordinates)
        => new Plane(coordinates);

    public static Plane Join(Point point, Line line)
    
        return new Plane(
            Vector3.Cross(line.Vector, point.Position) + line.Moment,
            -Vector3.Dot(point.Position, line.Moment));
    
    public static Plane Join(Point point_1, Point point_2, Point point_3)
    
        return Join(point_1, Line.Join(point_2, point_3));
    
    public float Magnitude  get => Vector.Length(); 
    public Vector3 Normal  get => Vector3.Normalize(Vector); 
    public float Offset  get => -Scalar/Magnitude; 
    public Vector3 Position
    
        get => Normal*Offset;
    
    public float DistanceTo(Point point)
        => point.DistanceTo(this);

    public Point Project(Point point)
    
        float t = Vector3.Dot(Normal, point.Position)-Offset;
        return point.Position - Normal*t;
    

public class Line 

    public Vector3 Vector  get; 
    public Vector3 Moment  get; 

    public Line(Vector3 vector, Vector3 moment)
    
        this.Vector=vector;
        this.Moment=moment;
    

    public static Line Ray(Point point, Vector3 direction)
    
        return new Line(direction,
            Vector3.Cross(point.Position, direction));
    

    public static Line Join(Point point_1, Point point_2)
    
        return new Line(
            point_2.Position-point_1.Position,
            Vector3.Cross(point_1.Position, point_2.Position));
    
    public static Line Meet(Plane plane_1, Plane plane_2)
    
        return new Line(
            Vector3.Cross(plane_1.Vector, plane_2.Vector),
            plane_2.Vector*plane_1.Scalar-plane_1.Vector*plane_2.Scalar);
    
    public Point Along(float travel)
        => Position.Position + Direction * travel;
    public float Magnitude  get => Vector.Length(); 
    public Vector3 Direction  get => Vector3.Normalize(Vector); 
    public Point Position
    
        get => new Point(this);
    
    public float DistanceTo(Point point)
        => point.DistanceTo(this);
    public float DistanceTo(Line line)
        => (Vector3.Dot(Vector, line.Moment) + Vector3.Dot(line.Vector, Moment))/Vector3.Cross(Vector, line.Vector).Length();

    public Point Project(Point point)
    
        return Along(Vector3.Dot(Direction, point.Position-Position.Position));
    

以上数学基于Foundations of Game Engine Development, by Eric Lengyel。

更新

为磁盘属性添加了厚度,并使用它来检查一个点是否包含在磁盘中。 链接到MATALB代码Class definitions for Point3.m, Plane3.m and Line3.m我很久以前写的。可能缺少一些东西,但它可能会给你一个很好的起点。磁盘没有什么特别的,只是具有齐次坐标的几何框架。

【讨论】:

以上是关于空间中两个 3D 磁盘之间的碰撞检测的主要内容,如果未能解决你的问题,请参考以下文章

unity碰撞检测(碰撞器,触发器)

深入理解Unity的碰撞检测机制

Unity3d碰撞检测中碰撞器与触发器的区别

如何在游戏循环中只检测一次碰撞而不是连续检测碰撞?

物理AABB物理碰撞检测

Web3D编程总结——3D碰撞检测初探