码迷,mamicode.com
首页 > 其他好文 > 详细

裁剪出碰撞点(manifold) (for GJK的通用改)

时间:2015-04-24 01:02:46      阅读:168      评论:0      收藏:0      [点我收藏+]

标签:

EPA得到图形的嵌入方向之后, 就可以裁出碰撞点(边)了.

首先需要找到图形在嵌入方向上最远的一条边.

多边形:

method getFarthestEdgeInDirection*(self: Polygon, direction: Vector2D): Line2D =
    var
        bestIndex: int = 0
        bestProjection: float32 = self[0] * direction
        projection: float32
    
    #先找出在direction上最远的一个顶点
    for i in 1..self.len-1:
        projection = self[i] * direction

        if projection > bestProjection:
            bestIndex = i
            bestProjection = projection

    #选出与该顶点相邻的两条边, 取在direction上投影最小的那条返回
    let
        left = self[(bestIndex + self.len - 1) mod self.len]
        right = self[(bestIndex + 1) mod self.len]
        mid = self[bestIndex]
        leftProj = abs(left * direction)
        rightProj = abs(right * direction)

    if leftProj > rightProj:
        return newLine2D(left, mid)
    else:
        return newLine2D(mid, right)

圆形只能得到一个点, 不过为了统一还是返回一条长度是0的线段:

method getFarthestEdgeInDirection*(self: Circle, direction: Vector2D): Line2D =
    let p = direction.norm() * self.radius
    return newLine2D(p, p)

还可以为扇形等各种凸图形写一个getFarthestEdgeInDirection函数, 这样就能处理所有的凸图形. 对直线边都返回一条线段, 对弧线边都返回一个点.

 

于是对所有的凸图形碰撞都分为三种情况, 点与点(两个弧边相碰撞), 点与线段(一个弧形边与一条直线边相碰撞), 线段与线段(两条直线边)

proc getManifold*(self: Contact): Manifold =
    let
        edge1 = self.collisionEdge1
        edge2 = self.collisionEdge2
        isPoint1 = edge1.isPoint()
        isPoint2 = edge2.isPoint()

    if isPoint1:
        if isPoint2: 
            result = getPointsManifold(edge1.a, edge2.a)
        else: 
            result = getPointLineManifold(edge1.a, edge2)
    else:
        if isPoint2: 
            result = getPointLineManifold(edge2.a, edge1)
        else: 
            result = getLinesManifold(edge1, edge2, self.penetration)

    result.penetration = self.penetration
    result.rigidA = self.rigidA
    result.rigidB = self.rigidB

 

实际上的刚体碰撞不会陷入到另一个物体内部, 裁剪出来的碰撞点(边)只要在两个图形刚好接触或者小程度的重叠时看上去合理就没问题.

对于点与点的情况, 可以简单的取两点中点:

技术分享

proc getPointsManifold(p1, p2: Vector2D): Manifold =
    new(result)
    let p = (p1 + p2) * 0.5
    result.add(p)

 

点与线段的情况:

技术分享

如果弧上的点夹在线段两点之间, 那么说明是圆形去碰多边形上的一条边, 可以直接返回P点, 也可返回P与线段的中点.

否则, 就是多边形的一个角去碰圆弧, 返回线段上离P最近的那个顶点.

proc getPointLineManifold(point: Vector2D, line: Line2D): Manifold =
    new(result)
    let
        a = line.a
        b = line.b
    if (point - a) * (b - a) < 0: #cos < 0, 夹角大于90°, P在A的外侧
        result.add(line.a)
    elif (point - b) * (a - b) < 0:
        result.add(line.b)
    else:
        result.add(point)

 

两条线段的情况:

技术分享

把与嵌入方向更垂直的那一条边找出来当作"参考边", 把另一条当作"事件边".

用参考边去裁剪事件边, 把事件边在参考多边形外侧的部分全部裁掉, 再把事件边在参考边左侧与右侧的部分也裁掉.

借助下面这个函数, 把edge和start都投影在dir上, 把edge投影比start小的部分都裁掉.

proc clipEdge(edge: Manifold, dir: Vector2D, start: Vector2D): Manifold =
    new(result)
    let
        min = start * dir
        proj1 = edge.a * dir - min
        proj2 = edge.b * dir - min
    if proj1 >= 0:
        result.add(edge.a)
    if proj2 >= 0:
        result.add(edge.b)
    if proj1 * proj2 < 0:
        result.add((edge.b - edge.a) * (proj1 / (proj1 - proj2)) + edge.a)

 

proc getLinesManifold(l1, l2: Line2D, dir: Vector2D): Manifold =
    var
        refVector: Vector2D
        refEdge: Line2D
        incEdge: Manifold
        normal: Vector2D

    let
        v1 = l1.b - l1.a
        v2 = l2.b - l2.a
        proj1 = abs(v1 * dir)
        proj2 = abs(v2 * dir)

    if proj1 < proj2: #投影较小的边更垂直, 作为参考边
        refEdge = l1
        refVector = v1
        incEdge = newManifold(l2)
        normal = tripleProduct(v1, dir.negate(), v1) #得到一个指向(-dir)方向的垂直于v1的向量
    else:
        refEdge = l2
        refVector = v2
        incEdge = newManifold(l1)
        normal = tripleProduct(v2, dir, v2)

    incEdge = clipEdge(incEdge, normal, refEdge.a) #把在参考多边形外部的部分裁剪掉
    if incEdge.length < 2: return incEdge

    normal = tripleProduct(normal, refEdge.b - refEdge.a, normal) #把在a外的部分裁剪掉
    incEdge = clipEdge(incEdge, normal, refEdge.a)
    if incEdge.length < 2: return incEdge
    
    incEdge = clipEdge(incEdge, normal.negate(), refEdge.b)
    return incEdge

 

效果如下:

技术分享

裁剪出碰撞点(manifold) (for GJK的通用改)

标签:

原文地址:http://www.cnblogs.com/pngx/p/4452221.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!