Netwire - how to construct a wire that produces positions, bouncing of walls?

261 views Asked by At

Using netwire-4.0.7

As the question title says, I'm trying to create a wire that produces positions (moving the position with a certain velocity in each step), "bouncing" off other objects. The simplest example I could think of was moving within a box, like in some screen savers.

I wrote this function as an attempt to do just that (for one axis only):

import Control.Wire
bouncyWire :: Double -> Double -> Double -> Double -> Wire () IO a Double
bouncyWire startPosition startVelocity lowerBound upperBound = proc _ -> do
  rec
     v <- delay startVelocity -< if p < lowerBound || p > upperBound
                                    then -v else v
     p <- integral_ startPosition -< v
  returnA -< p

(I actually use it with a different monad, but this code doesn't actually use it and it would overcomplicate the issue here).

Stepping it with a counterSession $ 1/60 however, I get a problem - instead of "bouncing" off the wall, it gets stuck there. I think what happens is that it keeps flipping the velocity v over and over, but I'm not sure why. I think I might be using delay wrong or something.

2

There are 2 answers

0
Petr On BEST ANSWER

As Richard Huxton explained, your wire is getting stuck behind the border. One solution is to check that you don't just reverse velocities, you always point them to the right direction. This way, the wire gets always from behind the border. Another solution is to change the position back inside the borders if it gets out. This way, the wire is never perceived to get behind (which is something you usually want to do in games). Combined together it could look like

import Prelude hiding ((.), id)
import Control.Category
import Control.Wire
import Control.Wire.Wire
import Control.Wire.Prefab.Move

bouncyWire :: (Monad m)
           => Double -> Double -> Double -> Double -> Wire () m a Double
bouncyWire startPosition startVelocity lowerBound upperBound =
    objPosition <$> object_ update (ObjectState startPosition startVelocity)
        . constant (Accelerate 0, ())
  where
    update _ s@(ObjectState pos vel)
        | (pos > upperBound)    = ObjectState (2*upperBound - pos) (- abs vel)
        | (pos < lowerBound)    = ObjectState (2*lowerBound - pos) (abs vel)
        | otherwise             = s

I'm not very familiar with the arrow notation so I used Category and Applicative notation (composition of wires using .). For this task, object_ is particularly handy. It integrates the velocity and loops internally and all we need to do is to give it a modifying function and extract position from the output.

0
Richard Huxton On

Ah, a chance to recyle knowledge from my days messing around writing 8-bit games in a mix of BASIC and assembler!

What's probably happening is that the "ball" is getting stuck just the wrong side of the wall due to rounding errors. So - if your wall is at 10.0, the ball oscillates between 11.0001 and 10.0001 with v=-1.0,+1.0,-1.0 etc. Not sure how you'd print the position to check with wires.

From (ancient) memory, you either want to calculate the bounce including any "leftover" vector from the previous step, or just place the ball exactly on the wall before bouncing it.