使用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
我可以使用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.Trace和Diagrams.Core.Trace的文档,但也许我找错了地方。
答案 0 :(得分:2)
通常没有办法;这取决于你到底打了什么。有一个模块Diagrams.Tangent用于计算路径的切线,但是要计算给定点处的切线,您必须知道其相对于路径的参数。目前我们缺少的一件事是一种将给定点转换为给定路段/路径/路径上最近点的参数的方法(它已经存在于待办事项列表中了一段时间)。
做得更大,也许痕迹本身应该返回更多信息---不仅参数告诉您击中的光线沿多远,而且还包含有关您击中的信息(从中可以更轻松地执行类似操作计算法线向量)。
您要计算什么样的痕迹?可能有一种方法可以利用您用例的特定细节,以一种不太可怕的方式获得所需的法线。
答案 1 :(得分:0)
Brent Yorgey的答案指出了Diagrams.Tangent模块,尤其是normalAtParam
模块,该模块适用于Parameteric
函数,包括路径,但不是所有图表。
幸运的是,许多2D图功能,例如circle
,square
,rect
,~~
等,实际上可以返回任何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)
,circle
,square
,rect
等返回的值。
~~
使用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 Trail
和circle
之类的函数返回的值,但不适用于组合图。因此,只要需要这种碰撞光线跟踪,就必须将这些图的构建块(作为线索)分开。
使用法向矢量来反射光线(出射光线与法向矢量成相等角度)看起来像这样: