光线追踪并在相交点找到曲面的法线向量

时间:2018-08-26 17:01:58

标签: haskell haskell-diagrams

使用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)

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

end <- rayTraceP start vel dia

我可以使用startend之间的距离找到碰撞时间:

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的文档,但也许我找错了地方。

2 个答案:

答案 0 :(得分:2)

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

做得更大,也许痕迹本身应该返回更多信息---不仅参数告诉您击中的光线沿多远,而且还包含有关您击中的信息(从中可以更轻松地执行类似操作计算法线向量)。

您要计算什么样的痕迹?可能有一种方法可以利用您用例的特定细节,以一种不太可怕的方式获得所需的法线。

答案 1 :(得分:0)

Brent Yorgey的答案指出了Diagrams.Tangent模块,尤其是normalAtParam模块,该模块适用于Parameteric函数,包括路径,但不是所有图表。

幸运的是,许多2D图功能,例如circlesquarerect~~等,实际上可以返回任何TrailLike类型,包括{ {1}}。所以类型为

的函数
Trail V2 n

如果可以定义,实际上可以使用rayTraceTrailC :: forall n . (RealFloat n, Epsilon n) => Point V2 n -> V2 n -> Located (Trail V2 n) -> Maybe (Collision V2 n) circlesquarerect等返回的值。

~~

使用fixTrail函数,可以通过将轨迹分成线性或贝塞尔曲线的固定段列表来定义此函数。这样可以将问题简化为更简单的> rayTraceTrailC (p2 (0, 0)) (r2 (1, 0)) (circle 1 # moveTo (p2 (2,0))) Just (Collision 1 (p2 (1, 0)) (r2 (-1, 0)))

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的点。之后,我们可以按照Brent Yorgey的建议致电normalAtParam

pt

通过这种方式,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 s,仅适用于Diagram s。

它可以处理Located Trailcircle之类的函数返回的值,但不适用于组合图。因此,只要需要这种碰撞光线跟踪,就必须将这些图的构建块(作为线索)分开。

使用法向矢量来反射光线(出射光线与法向矢量成相等角度)看起来像这样:

enter image description here