3

使用 进行光线追踪时rayTraceP,我可以找到光线与图表相交的点。

> rayTraceP (p2 (0, 0)) (r2 (1, 0)) ((p2 (1,-1) ~~ p2 (1,1))
Just (p2 (1.0, 0.0))

我想用它不仅找到“碰撞点”,还找到碰撞时间和该点表面的法线向量。

-- A Collision has a time, a contact point, and a normal vector.
-- The normal vector is perpendicular to the surface at the contact
-- point.
data Collision v n = Collision n (Point v n) (v n)
  deriving (Show)

给定射线的起点和沿射线的速度矢量,我可以end使用以下方法找到接触点rayTraceP

end <- rayTraceP start vel dia

我可以使用 和 之间的距离找到碰撞start时间end

time = distance start end / norm vel

但我坚持寻找法线向量。我在这个函数中工作:

rayTraceC :: (Metric v, OrderedField n)
             => Point v n -> v n -> QDiagram B v n Any -> Maybe (Collision v n)
-- Takes a starting position for the ray, a velocity vector for the
-- ray, and a diagram to trace the ray to. If the ray intersects with
-- the diagram, it returns a Collision containing:
--  * The amount of time it takes for a point along the ray going at
--    the given velocity to intersect with the diagram.
--  * The point at which it intersects with the diagram.
--  * The normal vector to the surface at that point (which will be
--    perpendicular to the surface there).
-- If the ray does not intersect with the diagram, it returns Nothing.
rayTraceC start vel dia =
  do
    end <- rayTraceP start vel dia
    let time = distance start end / norm vel
    -- This is where I'm getting stuck. 
    -- How do I find the normal vector?
    let normalV = ???
    return (Collision time end normalV)

我想要它做的一些例子:

> -- colliding straight on:
> rayTraceC (p2 (0, 0)) (r2 (1, 0)) (p2 (1,-1) ~~ p2 (1,1))
Just (Collision 1 (p2 (1, 0)) (r2 (-1, 0)))
> -- colliding from a diagonal:
> rayTraceC (p2 (0, 0)) (r2 (1, 1)) (p2 (1,0) ~~ p2 (1,2))
Just (Collision 1 (p2 (1, 1)) (r2 (-1, 0))
> -- colliding onto a diagonal:
> rayTraceC (p2 (0, 0)) (r2 (1, 0)) (p2 (0,-1) ~~ p2 (2,1))
Just (Collision 1 (p2 (1, 0)) (r2 (-√2/2, √2/2)))
> -- no collision
> rayTraceC (p2 (0, 0)) (r2 (1, 0)) (p2 (1,1) ~~ p2 (1,2))
Nothing

除了法线向量之外,这些示例中的所有内容都是正确的。

我查看了Diagrams.TraceDiagrams.Core.Trace的文档,但也许我找错了地方。

4

2 回答 2

2

一般没有办法做到这一点;这取决于你到底击中了什么。有一个模块Diagrams.Tangent用于计算轨迹的切线,但是要计算给定点的切线,您必须知道它相对于轨迹的参数;我们目前缺少的一件事是一种将给定点转换为给定段/路径/路径上最近点参数的方法(它已经在待办事项列表上一段时间了)。

梦想更大,也许轨迹本身应该返回更多信息——不仅仅是参数告诉你击中光线有多远,还有关于你击中什么的信息(从中可以更容易地做一些事情,比如计算一个法线向量)。

你在计算什么样的东西的踪迹?可能有一种方法可以利用用例的特定细节以不太糟糕的方式获得所需的法线。

于 2018-08-26T19:34:11.200 回答
0

Brent Yorgey 的回答指出了Diagrams.Tangent模块,特别是normalAtParam,它适用于Parameteric功能,包括轨迹,但不是所有的图表。

幸运的是,许多 2D 图表函数,如circlesquarerect~~等实际上可以返回任何TrailLike类型,包括Trail V2 n. 所以一个函数的类型

rayTraceTrailC :: forall n . (RealFloat n, Epsilon n)
                  => 
                  Point V2 n 
                  -> V2 n
                  -> Located (Trail V2 n)
                  -> Maybe (Collision V2 n)

如果可以定义,则实际上可以处理 , , , 等circle返回squarerect值:~~

> rayTraceTrailC 
    (p2 (0, 0))
    (r2 (1, 0))
    (circle 1 # moveTo (p2 (2,0)))
Just (Collision 1 (p2 (1, 0)) (r2 (-1, 0)))

并且可以通过使用该函数将轨迹分解为线性或贝塞尔曲线的固定段列表来定义此fixTrail函数。这将问题简化为更简单的问题rayTraceFixedSegmentC

rayTraceTrailC start vel trail =
  combine (mapMaybe (rayTraceFixedSegmentC start vel) (fixTrail trail))
  where
    combine [] = Nothing
    combine cs = Just (minimumBy (\(Collision a _ _) (Collision b _ _) -> compare a b) cs)

rayTraceFixedSegmentC可以用来计算rayTraceP接触点,但是我们不能马上找到法向量,因为我们不知道那个接触点的参数是什么。因此,进一步平底船并将fixedSegmentNormalV辅助功能添加到愿望清单:

rayTraceFixedSegmentC :: forall n . (RealFloat n, Epsilon n)
                         =>
                         Point V2 n
                         -> V2 n
                         -> FixedSegment V2 n
                         -> Maybe (Collision V2 n)
rayTraceFixedSegmentC start vel seg =
  do
    end <- rayTraceP start vel (unfixTrail [seg])
    let time = distance start end / norm vel
    let normalV = normalize (project (fixedSegmentNormalV seg end) (negated vel))
    return (Collision time end normalV)

这个fixedSegmentNormalV函数只需要为通过一个点的单个线段返回一个法线向量,而不用担心vel方向。它可以破坏FixedSegment类型,如果它是线性的,那很容易:

fixedSegmentNormalV :: forall n . (OrderedField n)
                       => 
                       FixedSegment V2 n -> Point V2 n -> V2 n
fixedSegmentNormalV seg pt =
  case seg of
    FLinear a b -> perp (b .-. a)
    FCubic a b c d ->
      ???

在这种FCubic情况下,要计算曲线通过的参数pt,我不确定该怎么做,但如果你不介意这里的近似值,我们可以沿着它取一堆点并找到最接近 的点pt。之后,我们可以normalAtParam按照 Brent Yorgey 的建议进行通话。

fixedSegmentNormalV seg pt =
  case seg of
    FLinear a b -> perp (b .-. a)
    FCubic a b c d ->
      -- APPROXIMATION: find the closest parameter value t
      let ts = map ((/100) . fromIntegral) [0..100]
          dist t = distance (seg `atParam` t) pt
          t = minimumBy (\a b -> compare (dist a) (dist b)) ts
      -- once we have that parameter value we can call a built-in function
      in normalAtParam seg t

有了这个,该rayTraceTrailC函数正在使用这个近似值。但是,它不适用于Diagrams,仅适用于Located Trails。

circle它可以处理由和之类的函数返回的值rect,但不能处理组合图。因此,只要您需要这种碰撞光线追踪,就必须将这些图表的构建块分开,作为轨迹。

使用法线向量反射光线(出射光线与法线向量的角度相等)如下所示:

在此处输入图像描述

于 2018-08-27T04:18:53.250 回答