static BallState Detect(ProcessId pidA, BallState stateA, ProcessId pidB, BallState stateB, BallState lastStateB) { if (pidA == pidB) { return(stateB); } var a = new Point2(stateA.X, stateA.Y); var b = new Point2(stateB.X, stateB.Y); var avel = new Vector2(stateA.VelX.MetresPerSecond, stateA.VelY.MetresPerSecond); var bvel = new Vector2(stateB.VelX.MetresPerSecond, stateB.VelY.MetresPerSecond); Vector2 collision = a - b; double distance = collision.Length; if (distance == 0.0) { // hack to avoid div by zero collision = new Vector2(1.0, 0.0); distance = 1.0; } if (distance > stateA.BallSize && distance > stateB.BallSize) { return(stateB); } // Get the components of the velocity vectors which are parallel to the collision. // The perpendicular component remains the same for both fish collision = collision / distance; double aci = Vector2.Dot(avel, collision); double bci = Vector2.Dot(bvel, collision); // Solve for the new velocities using the 1-dimensional elastic collision equations. // Turns out it's really simple when the masses are the same. double acf = bci; double bcf = aci; // Replace the collision velocity components with the new ones var navel = avel + collision * (acf - aci); var nbvel = bvel + collision * (bcf - bci); SetPosAndVelMsg.Tell(pidA, a.X, a.Y, navel.DX * m / s, navel.DY * m / s); SetPosAndVelMsg.Tell(pidB, lastStateB.X, lastStateB.Y, nbvel.DX * m / s, nbvel.DY * m / s); return(lastStateB); }
static BallState Detect(ProcessId pidA, BallState stateA, ProcessId pidB, BallState stateB, BallState lastStateB) { if (pidA == pidB) return stateB; var a = new Point2(stateA.X, stateA.Y); var b = new Point2(stateB.X, stateB.Y); var avel = new Vector2(stateA.VelX.MetresPerSecond, stateA.VelY.MetresPerSecond); var bvel = new Vector2(stateB.VelX.MetresPerSecond, stateB.VelY.MetresPerSecond); Vector2 collision = a - b; double distance = collision.Length; if (distance == 0.0) { // hack to avoid div by zero collision = new Vector2(1.0, 0.0); distance = 1.0; } if (distance > stateA.BallSize && distance > stateB.BallSize) { return stateB; } // Get the components of the velocity vectors which are parallel to the collision. // The perpendicular component remains the same for both fish collision = collision / distance; double aci = Vector2.Dot(avel, collision); double bci = Vector2.Dot(bvel, collision); // Solve for the new velocities using the 1-dimensional elastic collision equations. // Turns out it's really simple when the masses are the same. double acf = bci; double bcf = aci; // Replace the collision velocity components with the new ones var navel = avel + collision * (acf - aci); var nbvel = bvel + collision * (bcf - bci); SetPosAndVelMsg.Tell(pidA, a.X, a.Y, navel.DX*m/s, navel.DY*m/s); SetPosAndVelMsg.Tell(pidB, lastStateB.X, lastStateB.Y, nbvel.DX*m/s, nbvel.DY*m/s); return lastStateB; }
/// <summary> /// Spawn a ball process and observe its state /// </summary> void SpawnBall(int id) { var element = CreateBallUI(); // Setup function that creates a random initial state for the // ball and passes on the bounds of the window Func <BallState> setup = () => BallState.CreateRandom() .With( BoxWidth: box.ActualWidth, BoxHeight: box.ActualHeight ); // Spawn the ball process kill(User["ball-" + id]); var pid = spawn <BallState, BallMsg>("ball-" + id, setup, BallProcess.Inbox); // Subscribe the ball process to the update process's state var sub = observeState <DateTime>(update).Subscribe(par(TimeMsg.Tell, pid)); // Subscribe to the state changes of the ball so we can update the view observeState <BallState>(pid) .ObserveOn(DispatcherScheduler.Current) .Subscribe(state => { element.Visibility = Visibility.Visible; Canvas.SetLeft(element, state.X); Canvas.SetTop(element, box.ActualHeight - state.Y); }, () => { box.Children.Remove(element); sub.Dispose(); } ); // The resolver subscribes to the state changes of the ball observeState <BallState>(pid).Subscribe(state => tell(resolver, Tuple(pid, state))); }
static BallState ImpulseRight(BallState state) => state.With( X: state.BoxWidth - state.BallSize, VelX: state.VelX * -state.SurfaceImpulse);
static bool HasCollidedWithRightWall(BallState state) => state.X > (state.BoxWidth - state.BallSize);
static BallState ImpulseLeft(BallState state) => state.With( X: 0, VelX: state.VelX * -state.SurfaceImpulse);
static bool HasCollidedWithCeiling(BallState state) => state.Y > state.BoxHeight;
static bool HasCollidedWithLeftWall(BallState state) => state.X < 0;
/// <summary> /// Ball inbox /// This is the entry point for messages sent to a ball. It looks for the message-type /// in the message-map; if found it invokes the message-inbox with the message and state. /// </summary> public static BallState Inbox(BallState state, BallMsg msg) => match(messageMap, msg.Tag, Some: fn => fn(msg)(state), None: () => state );
static BallState ImpulseCeiling(BallState state) => state.With( Y: state.BoxHeight, VelX: state.VelX * state.SurfaceImpulse, VelY: state.VelY * -state.SurfaceImpulse);
/// <summary> /// Find the relative time-step /// </summary> static Time DeltaTime(DateTime now, BallState state) => now - state.Time;
static BallState ImpulseGround(BallState state) => state.With( Y: state.BallSize, VelX: state.VelX * state.SurfaceImpulse, VelY: state.VelY * -state.SurfaceImpulse);
static bool HasCollidedWithGround(BallState state) => state.Y < state.BallSize;