# main.coffee |
--- | ---
¶ |
    sketch = (p) ->

      sfx = (() ->

        files = [
          "sfx/boost2.wav",
          "sfx/jump1.wav",
          "sfx/death.wav",
          "sfx/drone.mp3",
          "sfx/track1v2.mp3"
          ]

        objects = (new Audio( file ) for file in files)

        play = (n) ->
          if objects[n].currentTime != 0
            objects[n].currentTime = 0;
          objects[n].play();
          return

        pause = (n) ->
          objects[n].pause()

        set_looping = (n) ->
          objects[n].addEventListener 'ended', (() ->
            play n)

        preload = (obj) ->
          obj.addEventListener 'loaded', (() ->
            console.log "loaded", obj)

        preload obj for obj in objects

        crash = -> play 2
        jump = -> play 1
        boost = -> play 0
        set_looping 3
        drone = -> play 3
        drone_stop = -> pause 3
        set_looping 4
        track = -> play 4
        track_stop = -> pause 4

        crash: crash
        boost: boost
        jump: jump
        drone: drone
        drone_stop: drone_stop
        track: track
        track_stop: track_stop
        )()

      gfx = (() ->

        files = [
          "gfx/test-street2.png", # 0
          "gfx/test-warn.png",    # 1
          "gfx/test-boost2.png",  # 2
          "gfx/car2x32.png",      # 3
          "gfx/shadow3.png",      # 4
          "gfx/logo320b.png",     # 5
          "gfx/boost-empty.png",  # 6
          "gfx/boost-full.png"    # 7
          ]

        objects = (p.loadImage imgname for imgname in files)

        preview = () ->
          previewsize = 32
          p.stroke 0, 0, 100
          p.fill 0, 0, 100
          p.image img, x*previewsize, 0, previewsize, previewsize for img, x in objects
          return

        get = (n) ->
          objects[n]

        car = -> get 3
        car_shadow = -> get 4
        logo = -> get 5
        boost_full = -> get 7
        boost_empty = -> get 6
        space = -> get 8
        streets = [ 0, 1, 2 ]
        street = (n) -> get streets[n]

        preview: preview
        car: car
        car_shadow: car_shadow
        logo: logo
        boost_full: boost_full
        boost_empty: boost_empty
        space: space
        street: street
        )()
¶ # Time calculates a time delta to gain framerate-independence |
      time = (() ->

        now = previous = delta = 0

        update = () ->
          previous = now
          now = p.millis()
          delta = (now - previous)/ 1000
          return

        get_delta = () -> delta

        update: update
        delta: get_delta
        )()
¶ # Track core track function that delivers the street |
      track = (() ->

        track_depth = 3000
        street_count = 32
        street_gap = track_depth/street_count
¶ depth iterator, for looping the z-translation |
        depth_iter = (i, offset) ->
          if offset == undefined then offset = 0
          offset+(i*street_gap)

        track_scale = 1200
¶ standard noise |
        src_perlin = (scale=track_scale, offset=0, size=100) ->
          ((step) ->
            [(p.noise(step+offset)-0.3)*scale,
              (p.noise(step+211+offset)-0.3)*scale,
              size,
              0])
¶ straight line |
        src_straight =  ->
          (() ->
            [ 0, 0, 100, 0 ])
¶ lerped line |
        src_line = (seeda, seedb, len) ->
          ((step) ->
            x = step / len
            [ p.lerp( p.noise( seeda ), p.noise( seedb ), x ),
              p.lerp( p.noise( seeda + 211 ), p.noise( seedb + 211 ), x ),
              100,
              0 ])
¶ make a track approach 0 around range |
        zerofy = (range,track) ->
          ((step)->
            t = track(step)
            t[0] *= p.min(1,step/range)
            t[1] *= p.min(1,step/range)
            t)
¶ scale positions for a track |
        track_scale = (scale, track) ->
          ((step) ->
            t = track step
            t[0] *= scale
            t[1] *= scale
            t)
¶ scale steps for a track |
        step_scale = (scale,track) ->
          ((step) ->
            track step * scale)
¶ modify street diameter |
        diam_scale = (scale, track) ->
          ((step) ->
            t = track step
            t[2] *= scale
            t)
¶ add walls to track |
        wallify = (mod,offset,track) ->
          ((step) ->
            t = track step
            if (( step + offset )% mod ) < street_gap * 3
              t[2] = 220
              t[3] = 1
            t)
¶ add boosters to track |
        boostify = (mod,offset,track) ->
          ((step) ->
            t = track step
            if (( step + offset )% mod ) < street_gap * 3
              t[3] = 2
            t)
¶ move a track |
        move = (x, y, track) ->
          ((step) ->
            t = track step
            t[0] += x
            t[1] += y
            t)
¶ delay a tracks steps |
        delay = (d, track) ->
          ((step) ->
            track(step-d))
¶ modulo a tracks steps |
        mod = (m, track) ->
          ((step) ->
            track step % m)
¶ switch from track a to track b |
        shunt = (dist, tracka, trackb) ->
          ((step) ->
            if step < dist
              tracka step
            else
¶ t = tracka dist move( t[0], t[1], delay( dist, trackb ))( step )) |
              trackb step)

        comp_three_straight = ->
          ((step) ->
            length = 7200
            wallify( length, length / 2.3,
              boostify( length, length / 12,
                mod( length / 3,
                  shunt( length / 6,
                    move( 0, 9000, src_straight()),
                    src_straight()))))(step))

        comp_fast_perlin_snake = (offset=0,size=100)->
          ((stepo) ->
            length = 6000
            step = stepo + ( offset * length )
            sscale = 0.0004
            tscale = 1300
            src = step_scale( sscale, src_perlin( tscale, offset * 120000, size ))
            offlen = offset * length
            if step % length < length / 2
              boostify( length / 2, 0, src )(step)
            else
              wallify( length / 2, 0, src)(step))

        street_at = (step) ->
          [ comp_fast_perlin_snake(0)(step),
            comp_fast_perlin_snake(0.3)(step),
            comp_fast_perlin_snake(0.6)(step)
            ]
¶ draw helper: z-translate a drawing function |
        put_at_depth = (depth, draw_fn) ->
          p.pushMatrix()
          p.translate 0, 0, depth
          p.fill 0, 0, 100-p.abs(depth/track_depth*100)
          draw_fn()
          p.popMatrix()
          return

        plot_sprite = (street) ->
          p.image gfx.street(street[3]), street[0]-(street[2]/2), street[1]-(street[2]/2), street[2], street[2]
          p.translate 0, 0, 1

        plot_sprites = (streets) ->
          (() -> plot_sprite street for street in streets)

        draw = () ->
          offset = player.depth() % street_gap
          rest = player.depth() - offset
          p.noStroke()
          p.fill 0, 0, 100
          put_at_depth depth_iter(-i, offset), plot_sprites track.street_at depth_iter(i, rest) for i in [street_count-1..0] by -1

        street_at: street_at
        draw: draw
        )()
¶ Player player entity, also controls camera |
      player = (() ->

        alive = false

        turnSpeed = 600
        driveSpeed = 1000

        camY = 60
        camZ = 80 #120
        camAhead = 100

        boost = (() ->
          count = 1
          hold = false

          add = () ->
            if count < 3
              count += 1
              sfx.boost()
            hold = true
            return

          held = () -> hold
          release = () ->
            hold = false

          draw = ->
            ww = p.width / 8
            p.image gfx.boost_empty(), x, 0, ww, ww for x in [ww*2.5..ww*4.5] by ww
            if count > 0
              p.image gfx.boost_full(), x, 0, ww, ww for x in [ww*2.5..ww*(2.5+count-1)] by ww
            return

          get_count = -> count

          spend = ->
            if count > 0
              count -= 1
              true
            else
              false

          reset = ->
            count = 1

          add: add
          spend: spend
          held: held
          release: release
          draw: draw
          count: get_count
          reset: reset
          )()

        position = new p.PVector 0, -200
        shadow = 0
        force = new p.PVector 0, 0
        upvector = new p.PVector 0, 1
        upforce = new p.PVector 0, 1
        depth = 0

        jumpvector = new p.PVector 0, 0
        jumppotencial = 30
        jumptime = 0.4
        jumpforce = 0

        sinkpotential = 100
        sinktime = 2
        sinkforce = 0

        floor_contact = false

        hover_escape = 50
        hover_downto = 10 #10
        hover_upto = 10 #5
        crash_tolerance = 20

        get_position = -> position

        get_depth = -> depth

        crash = ->
          if alive
            sfx.crash()
            sfx.drone()
            sfx.track_stop()
            boost.reset()
            alive = false

        start = ->
          alive = true
          sfx.drone_stop()
          sfx.track()

        has_alive = -> alive

        closest_street = (streets) ->
          dists = ((p.dist position.x, position.y, street[0], street[1])-(street[2]/2) for street in streets)
          min = Math.min dists...
          (streets[i] for d,i in dists when d is min)[0]

        find_shadow = (street) ->
          shadow_vector = new p.PVector street[0]-position.x, street[1]-position.y
          length = shadow_vector.mag()
          surface = street[2]/2
          shadow = p.PVector.limit( shadow_vector, length - surface ).mag()
¶ use the closest street to determine an appropriate up-vector for the camera as well as any pushing/pulling gravity forces |
        adjust_forces_to = (street, dt) ->
          vector = new p.PVector street[0]-position.x, street[1]-position.y
          upforce.set vector
          upforce.normalize()
          length = vector.mag()
          surface = street[2]/2
          force.set 0, 0, 0
          if jumpforce > 0
            if floor_contact == true then jumpvector.set upforce
            force.set p.PVector.mult jumpvector, -jumpforce
            jumpforce = p.max jumpforce - ( jumppotencial / jumptime * dt ), 0
            floor_contact = false
          else if length > ( surface + hover_escape )
            force.set p.PVector.mult upforce, sinkforce
            force.limit length - ( surface + hover_downto )
            sinkforce = p.min sinkforce + ( sinkpotential / sinktime * dt ), sinkpotential
            floor_contact = false
          else if length > ( surface + hover_downto )
            vector.limit length - ( surface + hover_downto )
            force.set vector
            floor_contact = true
          else if length < (surface+hover_upto)
            vector.normalize()
            vector.mult ((surface+hover_upto)-length)*-1
            force.set vector
            floor_contact = true
          if length < (surface-crash_tolerance)
            crash()
          if floor_contact == true
            sinkforce = 0
          return

        apply_forces = (dt) ->
          upvector.add p.PVector.mult upforce, dt*5
          upvector.normalize()
          position.add force
          return
¶ speed & direction changes |
        drive = (x,y,dt) ->
          position.add( upvector.y * x * turnSpeed * dt, upvector.x * -x * turnSpeed * dt,0)
          depth += if alive then y * driveSpeed * dt else y * driveSpeed * dt / 10
          return

        jump = () ->
          if floor_contact and boost.spend()
            jumpforce = jumppotencial
            sfx.jump()
          return
¶ update player position & up-vector |
        update = (dt) ->
          closest = closest_street track.street_at depth
          if closest[3] == 2 and floor_contact and alive
            if not boost.held()
              boost.add()
          else
            boost.release()
          adjust_forces_to closest, dt
          apply_forces dt
          find_shadow closest
          return

        plot_circle = (circle) ->
          p.ellipse circle[0], circle[1], circle[2], circle[2]
          return
¶ draw the player |
        draw = () ->
          p.pushMatrix()
          p.translate position.x, position.y
          p.rotateZ (p.atan2 upforce.y, upforce.x)-p.HALF_PI

          if alive
            p.noStroke()
            p.fill 0, 0, 100
            p.scale 0.5
            p.image gfx.car_shadow(), -16, -8+shadow, 32, 32
            p.scale 0.6
            p.image gfx.car(), -64, -64, 128, 128
            p.popMatrix()

          p.camera()
          p.hint p.DISABLE_DEPTH_TEST
          if alive then boost.draw() else p.image gfx.logo(), 0, 0
          return

        cam = ->
          p.camera position.x-(upvector.x*camY), position.y-(upvector.y*camY), camZ,
            position.x, position.y, -camAhead,
            upvector.x, upvector.y, 0
          p.perspective(p.PI/3.0, p.width/p.height, 10, 3000)
          return

        position: get_position
        depth: get_depth
        drive: drive
        jump: jump
        update: update
        draw: draw
        cam: cam
        alive: has_alive
        start: start
        )()
¶ Key State |
      keys = (() ->
        state =
          ff: false
          left: false
          right: false
          jump: false

        set = (keyCode, nustate) ->
          switch keyCode
            when p.LEFT then state.left = nustate
            when p.RIGHT then state.right = nustate
            when p.UP then state.jump = nustate
            when p.DOWN then state.start = nustate
            when 8 then abort = true
          return

        get_state = ->
          state

        set: set
        state: get_state
        )()
¶ # Setup |
      p.setup = ->
        p.size 320, 240, p.OPENGL
        p.frameRate 30
        p.rectMode p.CENTER
        p.ellipseMode p.CENTER
        p.colorMode p.HSB, 100
        p.noiseDetail 2
        sfx.drone()
        return
¶ # Draw |
      draw = ->
        time.update()
¶ |
        if player.alive()
          player.drive (if keys.state().left then -1 else keys.state().right), 1, time.delta()
          if keys.state().jump then player.jump()
        else
          player.drive 0, 1, time.delta()
          if keys.state().start then player.start()
        player.update time.delta()
¶ |
        p.background 0
¶ |
        player.cam()
        track.draw()
        player.draw()
        return

      preload = ->
        p.background 0
        gfx.preview()
        p.draw = draw

      p.draw = preload
¶ # Events |
      p.keyPressed = ->
        keys.set p.keyCode, true

      p.keyReleased = ->
        keys.set p.keyCode, false
¶ end of sketch |
      return
¶ # Init |
    window.onload = ->
      canvas = document.getElementById("canvas")
      p5 = new Processing canvas, sketch
      canvas.style.width = "640px"
      return