如何与 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 -->
函数。我有一个使用这个函数的原型,但我不知道如何根据碰撞时的速度制作一个新的速度箭头,我只能使用一个固定值,如果物体可以加速,这个值就没有用了。
vel :: (HasTime t s, MonadFix m) => Wire s Collision m Pos Vel
vel = pure 5 . unless (>20) --> pure (-5)
在 Netwire 中使用 Event
和 switch
。我不明白这个怎么用
一般使用箭头可用的(|||)
函数。
前两个似乎是最好的选择,但我不知道如何实现它们。
我看到过其他类似的问题,但不同版本的 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 新位置,如果发生碰撞,则新线具有新状态。
最后,您使用 rec
和 delay
将它们相互喂食。完整示例:
{-# 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)
我正在尝试使用 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
-->
函数。我有一个使用这个函数的原型,但我不知道如何根据碰撞时的速度制作一个新的速度箭头,我只能使用一个固定值,如果物体可以加速,这个值就没有用了。vel :: (HasTime t s, MonadFix m) => Wire s Collision m Pos Vel vel = pure 5 . unless (>20) --> pure (-5)
在 Netwire 中使用
Event
和switch
。我不明白这个怎么用一般使用箭头可用的
(|||)
函数。
前两个似乎是最好的选择,但我不知道如何实现它们。
我看到过其他类似的问题,但不同版本的 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 新位置,如果发生碰撞,则新线具有新状态。
最后,您使用 rec
和 delay
将它们相互喂食。完整示例:
{-# 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)