I am trying to model moving objects using Netwire and would like to know the recommended way to implement something like the bouncing of a ball off a wall. I have encountered several possible ways to do this and I need some help actually getting them to work.
The motion code looks like this:
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)
What is the recommended way to make a velocity arrow that causes a bounce at a certain position, eg x=20?
I have seen three different ways that I might be able to do this:
The netwire
-->function which seems the simplest. I have a prototype using this function but I don't know how to make a new velocity arrow based on the velocity at the time of the collision, I can only use a fixed value which is not useful if the object can accelerate.vel :: (HasTime t s, MonadFix m) => Wire s Collision m Pos Vel vel = pure 5 . unless (>20) --> pure (-5)Using the
Eventandswitchin Netwire. I don't understand how to use this.Using the
(|||)function available to arrows in general.
The first two seem like the best options but I don't know how to implement them.
I have seen other similar questions to this but the incompatibility between different versions of netwire have made the answers not useful for me.
Disclaimer: I cannot comment on what is "recommended", but I can show a way that does what you want to do.
I want to describe two methods:
The first is using stateful wires, and is pretty similar to this a bit outdated tutorial from 2013, but based on Netwire 5.0.2.
The second is using stateless wires. Because they are stateless they need to be fed back their previous values, which makes the wire's input types and the final combination of the wires a bit more convoluted. Otherwise they are pretty similar.
The basic types that are involved in both examples are
Stateful
You can model your problem with two (stateful) wires that are then combined.
One wire models the velocity, which is constant, and changes direction when a collision happens. The (simplified) type of this is
Wire s e m Collision Velocity, i.e. it's input is if a collision happened and the output is the current velocity.The other one models the position, and handles collisions. The (simplified) type of this is
Wire s e m Velocity (Position, Collision), i.e. it takes a velocity, calculates the new position and returns that and if a collision happened.Finally the velocity is fed into the position wire, and the collision result from that is fed back into the velocity wire.
Let's have a look at the details of the velocity wire:
mkPureNcreates a stateful wire that only depends on the input and its own state (not on a Monad, or time). The state is the current velocity, and the next velocity is negated ifCollision=Trueis passed as input. The return value is a pair of the velocity value and the new wire with the new state.For the position it is no longer sufficient to use the
integralwire directly. We want an enhanced, "bounded" version ofintegralwhich makes sure that the value stays lower than an upper bound and greater than 0, and returns the information if such a collision has happened.mkPureis similar tomkPureN, but allows the wire to depend on time.dtis the time difference.nextx'is the new position, as it would be returned byintegral.The following lines check the bounds and return the new position, if a collision has occurred and the new wire with the new state.
Finally you feed them into each other using
recanddelay. Full example:Stateless
The stateless variant is very similar to the stateful one, except that the state wanders to the input type of the wires instead of being a parameter to the functions that create the wire.
The velocity wire therefore takes a tuple
(Velocity, Collision)as its input, and we can just lift a function to create it:You can also use the function
mkSF_fromControl.Wire.Core(and then get rid of the restriction toMonad m).posbecomesHere we still need to use mkPure, because there is no function that specifically can be used for stateless wires that depend on time.
To connect the two wire we now must feed the output of velocity into itself and the position, and from the
poswire the position into itself and the collision information into the velocity wire.But actually with stateless wires you can also separate the "integrating" and the "bounds checking" parts of the
poswire. Theposwire then is aWire s e m (Position, Velocity) Positionthat directly returns what isnextx'above, and theboundedPoswire is aWire s e m (Position, Velocity) (Position, Collision)that gets the new position fromposand the velocity, and applies the bound check. That way the different logical parts become nicely separated.Full example: