如何与 Netwire (5.0.1) 实现冲突

How to implement a collision with Netwire (5.0.1)

我正在尝试使用 Netwire 为移动物体建模,并且想知道推荐的方法来实现类似球从墙上弹起的效果。我遇到了几种可能的方法来执行此操作,我需要一些帮助才能使它们正常工作。

运动代码如下所示:

type Pos = Float
type Vel = Float

data Collision = Collision | NoCollision
           deriving (Show)

motion :: (HasTime t s, MonadFix m) => Pos -> Vel -> Wire s Collision m a Pos
motion x0 v0 = proc _ -> do
             rec
                 v <- vel <<< delay 0 -< x
                 x <- pos x0 -< v
             returnA -< x

pos :: (HasTime t s, MonadFix m) => Pos -> Wire s Collision m Vel Pos
pos x0 = integral x0

main :: IO ()
main = testWire clockSession_ (motion 0 5)

制作在特定位置引起反弹的速度箭头的推荐方法是什么,例如 x=20?

我见过三种不同的方法可以做到这一点:

前两个似乎是最好的选择,但我不知道如何实现它们。

我看到过其他类似的问题,但不同版本的 netwire 之间的不兼容性使答案对我没有用。

免责声明:我无法评论什么是 "recommended",但我可以展示一种方法来实现您想要做的事情。

我想描述两种方法:
第一种是使用有状态线路,与 this a bit outdated tutorial from 2013 非常相似,但基于 Netwire 5.0.2。
第二种是使用无状态线路。因为它们是无状态的,所以需要反馈它们以前的值,这使得连线的输入类型和连线的最终组合有点复杂。否则它们非常相似。

两个例子中涉及到的基本类型是

type Collision = Bool
type Velocity = Float
type Position = Float

有状态

您可以使用两条(有状态的)线路然后组合起来对您的问题进行建模。

一根线对速度进行建模,速度是恒定的,并在发生碰撞时改变方向。它的(简化)类型是 Wire s e m Collision Velocity,即它的输入是碰撞是否发生,输出是当前速度。

另一个模拟位置,并处理碰撞。这个的(简化)类型是 Wire s e m Velocity (Position, Collision),即它需要一个速度,计算新的位置和 returns,如果发生碰撞。

最后将速度输入到位置线,碰撞结果反馈到速度线。

我们来看看速度线的细节:

-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
  let nextVel = if collision then negate vel else vel
  in (Right nextVel, velocity nextVel)

mkPureN 创建一个状态线,它只依赖于输入和它自己的状态(不依赖于 Monad 或时间)。状态是当前速度,如果 Collision=True 作为输入传递,则下一个速度被取反。 return 值是一对速度值和具有新状态的新线。

对于位置,直接使用integral线已经不够用了。我们想要一个增强的 integral "bounded" 版本,它确保该值保持低于上限并大于 0,并且 return 如果发生此类冲突,则提供信息。

-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound nextx)

mkPure 类似于 mkPureN,但允许电线依赖于时间。
dt是时差。
nextx' 是新位置,因为它将由 integral 编辑 return。
以下几行检查边界和 return 新位置,如果发生碰撞,则新线具有新状态。

最后,您使用 recdelay 将它们相互喂食。完整示例:

{-# LANGUAGE Arrows #-}

module Main where

import Control.Monad.Fix
import Control.Wire
import FRP.Netwire

type Collision = Bool
type Velocity = Float
type Position = Float

-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound nextx)

-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
  let nextVel = if collision then negate vel else vel
  in (Right nextVel, velocity nextVel)

run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
  rec
    v <- velocity vel <<< delay False -< collision
    (p, collision) <- pos bound start -< v
  returnA -< p

main :: IO ()
main = testWire clockSession_ (run 0 5 20)

无国籍

无状态变体与有状态变体非常相似,只是状态会漫游到连线的输入类型,而不是作为创建连线的函数的参数。

因此,速度线将元组 (Velocity, Collision) 作为其输入,我们只需提升一个函数即可创建它:

-- pure stateless independent from time
-- output velocity is input velocity potentially negated depending on collision
velocity :: Monad m => Wire s e m (Velocity, Collision) Velocity
velocity = arr $ \(vel, collision) -> if collision then -vel else vel

你也可以使用Control.Wire.Core中的函数mkSF_(然后摆脱对Monad m的限制)。

pos 变为

-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Position -> Wire s e m (Position, Velocity) (Position, Collision)
pos bound = mkPure $ \ds (x,dx) ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound)

这里还是需要使用mkPure,因为没有专门针对依赖时间.

的无状态wire的函数

为了连接这两条线,我们现在必须将速度的输出馈送到自身和位置,然后从 pos 线将位置馈入自身,将碰撞信息馈入速度线。

但实际上,对于无状态连线,您还可以将 pos 连线的 "integrating" 和 "bounds checking" 部分分开。 pos 线是 Wire s e m (Position, Velocity) Position 直接 return 上面的 nextx' ,而 boundedPos 线是 Wire s e m (Position, Velocity) (Position, Collision) 得到来自 pos 的新位置和速度,并应用边界检查。这样不同的逻辑部分就可以很好地分开。

完整示例:

{-# LANGUAGE Arrows #-}

module Main where

import Control.Monad.Fix
import Control.Wire
import FRP.Netwire

type Collision = Bool
type Velocity = Float
type Position = Float

-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Wire s e m (Position, Velocity) Position
pos = mkPure $ \ds (x,dx) ->
  let dt = realToFrac (dtime ds)
  in (Right (x + dt*dx), pos)

-- pure stateless independent from time
-- input position is bounced off the bounds
boundedPos :: Monad m => Position -> Wire s e m (Position, Velocity) (Position, Collision)
boundedPos bound = arr $ \(x, dx) ->
  let (nextx, collision)
        | x <= 0 && dx < 0 = (-x, True)
        | x >= bound && dx > 0 = (bound - (x - bound), True)
        | otherwise          = (x, False)
  in (nextx, collision)

-- pure stateless independent from time
-- output velocity is input velocity potentially negated depending on collision
velocity :: Monad m => Wire s e m (Velocity, Collision) Velocity
velocity = arr $ \(vel, collision) -> if collision then -vel else vel

-- plug the wires into each other
run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
  rec
    v <- velocity <<< delay (vel, False) -< (v, collision)
    lastPos <- delay start -< p'
    p <- pos -< (lastPos, v)
    (p', collision) <- boundedPos bound -< (p, v)
  returnA -< p'

main :: IO ()
main = testWire clockSession_ (run 0 5 20)