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