Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on May 15th, 2012  |  syntax: None  |  size: 21.39 KB  |  hits: 12  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. (window or exports).limber = limber =
  2.   version: "0.0.2"
  3.   geometry: {}
  4.   component: {}
  5.   entity: {}
  6.   trait: {}
  7.   engine: {}
  8.  
  9.   #convenience methods
  10.   vector: (x, y) -> new limber.geometry.Vector(x, y)
  11.   box: (x0, y0, x1, y1) -> new limber.geometry.Box(x0, y0, x1, y1)
  12.  
  13. class limber.EventEmitter
  14.   on: (event, cb) ->
  15.     @listeners ||= {}
  16.     @listeners[event] ||= []
  17.     @listeners[event].push(cb)
  18.     @
  19.  
  20.   echo: (emitter, event, asEvent) ->
  21.     emitter.on event, @trigger(asEvent or event)
  22.  
  23.   emit: (event, args...) ->
  24.     @listeners ||= {}
  25.     cb.apply(this, args) for cb in @listeners[event] when cb if @listeners[event]
  26.     @
  27.  
  28.   removeListener: (event, cb) ->
  29.     @listeners ||= {}
  30.     if listeners = @listeners[event]
  31.       index = listeners.indexOf(cb)
  32.       unless index == -1
  33.         listeners[index] = null
  34.         listeners.splice(index, 1)
  35.     @
  36.  
  37.   removeAllListeners: ->
  38.     @listeners[i] = null for i in @listeners if @listeners
  39.     @
  40.  
  41.   trigger: (event) ->
  42.     self = this
  43.     (args...) -> self.emit(event, args...)
  44.  
  45. class limber.Timer
  46.   constructor: ->
  47.     @lastTick = @currTick = @+ new Date
  48.  
  49.   tick: ->
  50.     [@currTick, @lastTick] = [+ new Date, @currTick]
  51.     @delta = @currTick - @lastTick
  52.  
  53. #-----------#
  54. # COMPONENT #
  55. #-----------#
  56.  
  57.  
  58. # limber.Component is the basic component used in the engine
  59. class limber.component.Component extends limber.EventEmitter    
  60.   attach: (component) ->
  61.     @children ||= []
  62.     @children.push(component)
  63.     @emit "attach", component
  64.     component.emit("attached", this)
  65.     @
  66.  
  67.   attachTo: (component) ->
  68.     component.attach(this)
  69.     @
  70.    
  71.   detach: (component) ->
  72.     @children ||= []
  73.     unless -1 == (index = @children.indexOf(component))
  74.       @emit "detach", component.emit("detached", this)
  75.       @children[index] = null
  76.       @children.splice(index, 1)
  77.     @
  78.  
  79. class limber.component.FPS extends limber.component.Component
  80.   constructor: (id) ->
  81.     @el = document.getElementById(id)
  82.     @size = 20
  83.     @index = 0
  84.     @sum = 0
  85.     @sample = []
  86.    
  87.     @sample[i] = 0 for i in [0 ... @size]
  88.    
  89.     @on "attached", (component) ->
  90.       component.on "update", @update
  91.       component.on "render", @render
  92.  
  93.   update: (engine) =>
  94.     @sum -= @sample[@index]
  95.     @sum += engine.timer.delta
  96.    
  97.     @sample[@index] = engine.timer.delta
  98.    
  99.     @index += 1
  100.     @index = 0 if @index == @size
  101.  
  102.   render: (engine) =>
  103.     @el.innerHTML = Math.floor(1000 / (@sum / @size)) + " FPS"
  104.  
  105.  
  106. #--------- #
  107. # GEOMETRY #
  108. #----------#
  109.  
  110. class limber.geometry.Vector
  111.   constructor: (x, y) ->
  112.     if x instanceof limber.geometry.Vector
  113.       @x = x.x
  114.       @y = x.y
  115.     else if Array.isArray(x)
  116.       @x = x[0]
  117.       @y = x[1]
  118.     else
  119.       @x = x or 0
  120.       @y = y or 0
  121.    
  122.   clone: -> new limber.geometry.Vector(this)
  123.   reset: ->
  124.     @x = 0
  125.     @y = 0
  126.     @
  127.  
  128.   scale: (scalar) ->
  129.     @x *= scalar
  130.     @y *= scalar
  131.     @
  132.  
  133.   shrink: (scalar) ->
  134.     @x /= scalar
  135.     @y /= scalar
  136.     @
  137.  
  138.   product: (x, y) ->
  139.     vector = new limber.geometry.Vector(x, y)
  140.    
  141.     @x *= vector.x
  142.     @y *= vector.y
  143.     @
  144.  
  145.   add: (x, y) ->
  146.     vector = new limber.geometry.Vector(x, y)
  147.    
  148.     @x += vector.x
  149.     @y += vector.y
  150.     @
  151.  
  152.   subtract: (x, y) ->
  153.     vector = new limber.geometry.Vector(x, y)
  154.    
  155.     @x -= vector.x
  156.     @y -= vector.y    
  157.     @
  158.  
  159.   normalize: ->
  160.     if @x == 0 and @y
  161.       @y = if @y > 0 then 1 else -1
  162.     else if @y == 0 and @y
  163.       @x = if @x > 0 then 1 else -1
  164.     else if (magnitude = @magnitude())
  165.    
  166.       @x = @x / magnitude
  167.       @y = @y / magnitude
  168.     @
  169.  
  170.   reflect: (x, y) ->
  171.     normal = new limber.geometry.Vector(x, y).normalize().rotateRight()
  172.    
  173.     @add(normal.scale(-2 * @dot(normal)))  
  174.     @
  175.    
  176.    
  177.   dot: (x, y) ->
  178.     if x instanceof limber.geometry.Vector then return @x * x.x + @y * x.y
  179.     else return @x * x + @y * y
  180.  
  181.  
  182.   magnitude: -> Math.sqrt(@x * @x + @y * @y)
  183.   magnitudeSq: -> @x * @x + @y * @y
  184.  
  185.   flipX: ->
  186.     @x = - @x
  187.     @
  188.   flipY: ->
  189.     @y = - @y
  190.     @
  191.   flip: ->
  192.     @x = - @x
  193.     @y = - @y
  194.     @
  195.  
  196.   rotateRight: ->
  197.     [@x, @y] = [@y, - @x]
  198.     @
  199.  
  200.   rotateLeft: ->
  201.     [@x, @y] = [- @y, @x]
  202.     @
  203.    
  204. class limber.geometry.Projection
  205.   constructor: (min, max) ->
  206.     if min instanceof limber.geometry.Projection
  207.       @min = min.min
  208.       @max = min.max
  209.     else
  210.       @min = min
  211.       @max = max
  212.  
  213.   overlap: (x, y) ->
  214.     proj = new limber.geometry.Projection(x, y)
  215.     overlap = 0
  216.    
  217.     unless @max < proj.min or @min > proj.max
  218.       if @min > proj.min and @min < proj.max then overlap = proj.max - @min
  219.       else if @max < proj.max and @max > proj.min then overlap = proj.min - @max
  220.       else
  221.         test = proj.max - proj.min
  222.         overlap = @max - @min
  223.         overlap = test if test < overlap
  224.    
  225.     overlap = Number.MAX_VALUE if overlap is Number.POSITIVE_INFINITY
  226.     overlap = -Number.MAX_VALUE if overlap is Number.NEGATIVE_INFINITY
  227.  
  228.     #proj = null
  229.     overlap
  230.  
  231.    
  232. class limber.geometry.MTV
  233.   constructor: (axis, @overlap) ->
  234.     @axis = limber.vector(axis)
  235.  
  236.   flip: ->
  237.     @axis.flip()
  238.     @
  239.  
  240. class limber.geometry.ConvexHull
  241.   constructor: (vertices...) ->
  242.     @vertices = for vertex in vertices then limber.vector(vertex)
  243.    
  244.   add: (x, y) ->
  245.     vertex.add(x, y) for vertex in @vertices
  246.     @
  247.   subtract: (x, y) ->
  248.     vertex.subtract(x, y) for vertex in @vertices
  249.     @
  250.    
  251.   testCollision: (convex) ->
  252.     return @testPolygonPolygon(convex)
  253.     test = "test"
  254.     test +=
  255.       if this instanceof limber.geometry.Polygon then "Polygon"
  256.       else if this instanceof limber.geometry.Circle then "Circle"
  257.     test +=
  258.       if convex instanceof limber.geometry.Polygon then "Polygon"
  259.       else if convex instanceof limber.geometry.Circle then "Circle"
  260.    
  261.     throw new Error("Collisions method not implemented: #{test}") unless this[test]
  262.    
  263.     method = this[test]
  264.    
  265.     method.call(this, convex)
  266.    
  267.    
  268.   testPolygonPolygon: (convex) ->
  269.    
  270.     minOverlap = Number.POSITIVE_INFINITY
  271.     minAxis = null
  272.     multAxes = false
  273.    
  274.     axes = @getFaceNormals()
  275.     axes.concat(convex.getFaceNormals()) unless this instanceof limber.geometry.AABB and convex instanceof limber.geometry.AABB
  276.    
  277.     for axis in axes
  278.       proj1 = @projectOnto(axis)
  279.       proj2 = convex.projectOnto(axis)
  280.       overlap = proj1.overlap(proj2)
  281.      
  282.       return unless overlap
  283.       if overlap == minOverlap
  284.         minAxis.add(axis)
  285.         multAxes = true
  286.       else if overlap < minOverlap
  287.         multAxes = false
  288.         minOverlap = overlap
  289.         minAxis = axis.clone()
  290.    
  291.     minAxis.normalize() if multAxes
  292.    
  293.     new limber.geometry.MTV(minAxis, minOverlap)
  294.  
  295.   projectOnto: (x, y) ->
  296.     return new limber.geometry.Projection(0, 0) unless @vertices and @vertices.length
  297.    
  298.     axis = limber.vector(x, y)
  299.     min = Number.POSITIVE_INFINITY
  300.     max = Number.NEGATIVE_INFINITY
  301.    
  302.     for vertex in @vertices
  303.       d = axis.dot(vertex)
  304.       min = if d < min then d else min
  305.       max = if d > max then d else max
  306.    
  307.     return new limber.geometry.Projection(min, max)
  308.  
  309.   getFaceNormals: ->
  310.     @normals or @normals = for vertex, i in @vertices
  311.       @vertices[(i + 1) % @vertices.length].clone().subtract(vertex).rotateRight().normalize()
  312.  
  313.   addNormal: (x, y) ->
  314.     @normals ||= []
  315.     @normals.push(limber.vector(x, y))
  316.     @
  317.   addVertex: (x, y) ->
  318.     @vertices ||= []
  319.     @vertices.push(limber.vector(x, y))
  320.     @
  321.  
  322.   getVertices: -> @vertices or []
  323.  
  324. class limber.geometry.Polygon extends limber.geometry.ConvexHull
  325.  
  326. class limber.geometry.AABB extends limber.geometry.Polygon
  327.   constructor: (x0, y0, x1, y1) ->
  328.     if x0 instanceof limber.geometry.AABB
  329.       @bl = x0.bl.clone()
  330.       @tr = x0.tr.clone()
  331.     else if arguments.length == 2
  332.       @bl = new limber.geometry.Vector(x0)
  333.       @tr = new limber.geometry.Vector(y0)
  334.     else if arguments.length == 4
  335.       @bl = new limber.geometry.Vector(x0, y0)
  336.       @tr = new limber.geometry.Vector(x1, y1)
  337.    
  338.     @addVertex(@bl.x, @bl.y)
  339.     @addVertex(@bl.x, @tr.y)
  340.     @addVertex(@tr.x, @tr.y)
  341.     @addVertex(@tr.x, @bl.y)
  342.    
  343.     @normals = [limber.vector(1, 0), limber.vector(0, 1)]
  344.  
  345.   clone: -> new limber.geometry.AABB(@bl, @tr)
  346.   add: (x, y) ->
  347.     @bl.add(x, y)
  348.     @tr.add(x, y)
  349.     super(x, y)
  350.   subtract: (x, y) ->
  351.     @bl.subtract(x, y)
  352.     @tr.subtract(x, y)
  353.     super(x, y)
  354.  
  355.   getFaceNormals: -> @normals
  356.  
  357.   toVector: -> @tr.clone().subtract(@bl)
  358.  
  359.  
  360.  
  361. class limber.geometry.Wall extends limber.geometry.AABB      
  362.   constructor: (x, y, offset, yoff) ->
  363.     #NOTE: ONLY WORKS FOR AABB WALLS FOR NOW
  364.    
  365.     if x
  366.       @addVertex(offset, -Number.MAX_VALUE)
  367.       @addVertex(offset, +Number.MAX_VALUE)
  368.       @addVertex(-x * Number.MAX_VALUE, yoff)
  369.     else
  370.       @addVertex(-Number.MAX_VALUE, offset)
  371.       @addVertex(+Number.MAX_VALUE, offset)
  372.    
  373.       @addVertex(yoff, -y * Number.MAX_VALUE)
  374.     @addNormal(x, y)
  375.    
  376.     normal = null
  377.  
  378.   # Special case for Walls
  379.   testPolygonCircle: (circle) ->
  380.  
  381.  
  382. class limber.geometry.Circle extends limber.geometry.ConvexHull
  383.  
  384.  
  385. #--------#
  386. # ENGINE #
  387. #--------#
  388.  
  389. class limber.engine.Canvas2D extends limber.component.Component
  390.   constructor: (id, width, height) ->
  391.     @canvas = document.getElementById(id)
  392.     @canvas.addEventListener("mousemove", @onMouseMove, false)
  393.     #WebGL2D.enable(@canvas)
  394.  
  395.     @canvas.width = width
  396.     @canvas.height = height
  397.    
  398.     @context = @canvas.getContext("2d")
  399.     @timer = new limber.Timer
  400.     @mousePosition = limber.vector()
  401.    
  402.     @context.translate(0, height)
  403.     @context.scale(1, -1)
  404.  
  405.   animate: ->
  406.     renderer = this
  407.     canvas = @canvas
  408.    
  409.     nextFrame = window.requestAnimationFrame or
  410.       window.webkitRequestAnimationFrame or
  411.       window.mozRequestAnimationFrame or
  412.       window.oRequestAnimationFrame or
  413.       window.msRequestAnimationFrame or
  414.       null
  415.    
  416.     @timer.tick()
  417.    
  418.     innerFrame = ->
  419.       renderer.timer.tick()
  420.       renderer.emit("update", renderer)
  421.       renderer.context.clearRect(0, 0, canvas.width, canvas.height)
  422.       renderer.emit("render", renderer)
  423.      
  424.     if nextFrame != null
  425.       recursiveFrame = ->
  426.         innerFrame()
  427.         nextFrame(recursiveFrame)
  428.      
  429.       nextFrame(recursiveFrame)
  430.     else
  431.       ONE_FRAME_TIME = 1000.0 / 60.0
  432.       setInterval(innerFrame, ONE_FRAME_TIME)
  433.  
  434.   onMouseMove: (e) =>
  435.     @mousePosition =
  436.       if e.pageX or e.pageY then limber.vector(e.pageX, e.pageY)
  437.       else limber.vector(e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft, e.clientY + document.body.scrollTop + document.documentElement.scrollTop)
  438.    
  439.     @mousePosition
  440.       .flipY().add(0, @canvas.height) # To switch to our coordinate scale
  441.       .subtract(@canvas.offsetLeft, @canvas.offsetTop)
  442.  
  443.  
  444. #--------#
  445. # ENTITY #
  446. #--------#
  447.  
  448. class limber.entity.Entity extends limber.component.Component
  449.   constructor: (args...) ->
  450.     self = this
  451.     update = (args...) -> self.emit "update", args...
  452.     render = (args...) -> self.emit "render", args...
  453.    
  454.     @components = []
  455.    
  456.     @on "update", @update
  457.     @on "render", @render
  458.     @on "attached", (component) ->
  459.       component.on "update", update
  460.       component.on "render", render
  461.      
  462.       self.components.push(component)
  463.     @on "detached", (component) ->
  464.       component.removeListener "update", update
  465.       component.removeListener "render", render
  466.     @initialize(args...)
  467.  
  468.   destroy: ->
  469.     @emit "detached", component for component in @components
  470.     @emit "destroy", this
  471.     #@mixout(trait) for provide, trait of @traits if @traits
  472.     @removeAllListeners()
  473.    
  474.   mixin: (trait) ->
  475.     provides = if Array.isArray(trait.provides) then trait.provides else [trait.provides]
  476.     requires = if Array.isArray(trait.requires) then trait.requires else [trait.requires]
  477.    
  478.     @traits ||= {}
  479.     @requireTraits(requires)
  480.    
  481.     trait.emit "mixin", this
  482.    
  483.     trait.augment(this)
  484.    
  485.     @traits[name] = trait for name in provides
  486.     @
  487.  
  488.   mixout: (trait) ->
  489.     provides = if Array.isArray(trait.provides) then trait.provides else [trait.provides]
  490.    
  491.     trait.emit "mixout", this
  492.  
  493.     @traits ||= {}
  494.    
  495.     for provide in provides
  496.       @traits[provide] = null
  497.       delete @traits[provide]
  498.  
  499.     @
  500.    
  501.  
  502.   requireTraits: (requires...) ->
  503.     requires = requires[0] if Array.isArray(requires[0])
  504.     for name in requires
  505.       throw new Error("Missing required trait: #{name}") unless @traits[name]
  506.     @
  507.    
  508.   attached: -> @
  509.   detached: -> @
  510.   initialize: -> @
  511.   update: (component) -> @
  512.   render: (component) -> @
  513.  
  514. #-------#
  515. # TRAIT #
  516. #-------#
  517.  
  518. class limber.trait.Trait extends limber.EventEmitter
  519.   provides: []
  520.   requires: []
  521.  
  522.   constructor: (args...) ->
  523.     self = this
  524.     @initialize(args...)
  525.  
  526.   destroy: ->
  527.     @removeAllListeners()
  528.    
  529.   initialize: -> @
  530.   augment: -> @
  531.  
  532. class limber.trait.Position extends limber.trait.Trait
  533.   provides: "position"
  534.   initialize: (x, y) -> @position = limber.vector(x, y)
  535.    
  536.   augment: (entity) -> entity.position = @position
  537.  
  538. class limber.trait.Velocity extends limber.trait.Trait
  539.   provides: "velocity"
  540.   requires: "position"
  541.   initialize: (x, y) -> @velocity = limber.vector(x, y)
  542.   augment: (entity) ->
  543.     entity.velocity = @velocity
  544.     entity.on "update", (engine) ->
  545.       @position.add @velocity.clone().scale(engine.timer.delta / 1000)
  546.  
  547.  
  548. class limber.trait.Acceleration extends limber.trait.Trait
  549.   provides: "acceleration"
  550.   requires: "velocity"
  551.   initialize: (x, y) -> @acceleration = limber.vector(x, y)
  552.   augment: (entity) ->
  553.     entity.acceleration = @acceleration
  554.     entity.on "update", (engine) ->
  555.       @velocity.add @acceleration
  556.       @acceleration.reset()
  557.  
  558. class limber.trait.AABBBody extends limber.trait.Trait
  559.   provides: "body"
  560.   initialize: (x0, y0, x1, y1) -> @aabb = new limber.geometry.AABB(x0, y0, x1, y1)
  561.   augment: (entity) -> entity.body = @aabb
  562.  
  563. class limber.trait.Bounded extends limber.trait.Trait
  564.   requires: ["position", "body"]
  565.  
  566.   constructor: (x0, y0, x1, y1) ->
  567.     boundaries = [
  568.       new limber.geometry.Wall(1, 0, x0, y1)
  569.       new limber.geometry.Wall(0, 1, y0, x1)
  570.       new limber.geometry.Wall(-1, 0, x1, y0)
  571.       new limber.geometry.Wall(0, -1, y1, x0)
  572.     ]
  573.    
  574.     self = this
  575.     @on "augment", (entity) ->
  576.       entity.on "update", (engine) ->
  577.         entity = @
  578.         bounds = entity.body.clone().add(entity.position)
  579.        
  580.         for wall in boundaries
  581.           if mtv = bounds.testCollision(wall)
  582.             @emit "collision", engine, mtv, wall
  583.  
  584. class limber.trait.Wrapped extends limber.trait.Trait
  585.   requires: ["position", "body"]
  586.   initialize: (@x0, @y0, @x1, @y1) ->  
  587.   augment: (entity) ->
  588.     self = this
  589.     entity.on "update", (engine) ->
  590.       if @position.x < self.x0 then @position.x = self.x1 - self.x0 - entity.position.x
  591.       else if @position.x > self.x1 then @position.x = self.x0 - self.x1 + entity.position.x
  592.       if @position.y < self.y0 then @position.y = self.y1 - self.y0 - entity.position.y
  593.       else if @position.y > self.y1 then @position.y = self.y0 - self.y1 + entity.position.x
  594.       @
  595.  
  596. class limber.trait.Comparator extends limber.trait.Trait
  597.   provides: "comparator"
  598.   initialize: -> @setup = false
  599.   augment: (entity) ->
  600.     self = this
  601.  
  602.     entity.on "render", (engine) ->
  603.       self.seenInFrame = []
  604.       entity.emit "comparator:teardown", engine, this
  605.     entity.on "update", (engine) ->
  606.       self.seenInFrame or self.seenInFrame = []
  607.       entity.emit "comparator:setup", engine, this
  608.       entity.emit "comparator:compare", engine, this, other for other in self.seenInFrame
  609.       self.seenInFrame.push(this)
  610.    
  611.  
  612.  
  613. class limber.trait.CollisionDetection extends limber.trait.Trait
  614.   provides: "area"
  615.   requires: ["position", "body", "comparator"]
  616.  
  617.   augment: (entity) ->
  618.    
  619.     entity.on "comparator:setup", @setup
  620.     entity.on "comparator:compare", @compare
  621.  
  622.   setup: (engine, entity) ->
  623.     entity.area = entity.body.clone().add(entity.position)
  624.  
  625.   compare: (engine, entity, other) ->
  626.     if mtv = entity.area.testCollision(other.area)
  627.       entity.emit "collision", engine, mtv, other
  628.       other.emit "collision", engine, mtv.flip(), entity    
  629.  
  630. class limber.trait.CollisionAvoidance extends limber.trait.Trait
  631.   requires: ["position", "velocity", "acceleration", "body", "comparator"]
  632.   provides: "avoidance"
  633.  
  634.   initialize: (@short, @far, @closeWidth, @farWidth) ->
  635.    
  636.   augment: (entity) ->
  637.     entity.on "comparator:setup", @setup
  638.     entity.on "comparator:compare", @compare
  639.  
  640.   setup: (engine, entity) =>
  641.     #console.log "Avoid.setup", arguments...
  642.     accel = new limber.geometry.Vector
  643.    
  644.     vel = entity.velocity.clone()
  645.     dir = vel.clone().normalize()
  646.     pos = entity.position.clone().add(vel.clone().scale(@short))
  647.     normal = dir.clone().rotateRight()
  648.     ahead = @far - @short
  649.    
  650.     w = (@farWidth - @closeWidth) / 2
  651.    
  652.     entity.avoidance = new limber.geometry.Polygon
  653.     entity.avoidance.addVertex(pos.add(normal.clone().scale(@closeWidth / 2))) #side, right
  654.     entity.avoidance.addVertex(pos.add(vel.clone().scale(ahead).add(normal.clone().scale(w)))) #front, right
  655.     entity.avoidance.addVertex(pos.add(normal.clone().scale(- @farWidth))) #front, left
  656.     entity.avoidance.addVertex(pos.add(vel.clone().scale(- ahead).add(normal.clone().scale(w)))) #side, left
  657.    
  658.     entity.avoidance.avoid = false
  659.  
  660.   compare: (engine, entity, other) =>
  661.     #console.log "Avoid.compare", arguments...
  662.     if mtv = entity.avoidance.testCollision(other.avoidance)
  663.       entity.avoidance.avoid = true
  664.       entity.acceleration.add(mtv.axis.scale(mtv.overlap))
  665.      
  666.       other.avoidance.avoid = true
  667.       other.acceleration.subtract(mtv.axis) #Already scaled above
  668.  
  669.  
  670. class limber.trait.Flocking extends limber.trait.Trait
  671.   requires: ["position", "velocity", "acceleration"]
  672.  
  673.   initialize: ->
  674.     @flock = []
  675.    
  676.   augment: (entity) ->
  677.     self = this
  678.     self.flock.push(entity)
  679.     entity.on "update", -> self.steer(entity)
  680.  
  681.   steer: (entity) ->
  682.     approach = new limber.geometry.Vector(0, 0)
  683.     avoid = new limber.geometry.Vector(0, 0)
  684.     match = new limber.geometry.Vector(0, 0)
  685.     accel = new limber.geometry.Vector(0, 0)
  686.    
  687.     approach.n = avoid.n = match.n = 0
  688.    
  689.     for boid in @flock when boid != entity
  690.       dist = boid.position.clone().subtract(entity.position)
  691.       dist2 = dist.magnitudeSq()
  692.      
  693.       if dist2 < 10000
  694.         approach.add(dist)
  695.         approach.n++
  696.       if dist2 < 1000
  697.         avoid.subtract(dist)
  698.         avoid.n++
  699.       if dist2 < 10000
  700.         match.add(boid.velocity.clone())
  701.         match.n++
  702.  
  703.     approach.shrink(approach.n) if approach.n
  704.     avoid.shrink(avoid.n) if avoid.n
  705.     match.shrink(match.n) if match.n
  706.  
  707.  
  708.     accel
  709.       .add(approach).shrink(20)
  710.       .add(avoid)
  711.       .add(match).shrink(10)
  712.       .normalize()
  713.       .scale(20)
  714.      
  715.     #unless not @once and accel.magnitude()
  716.     #  @once = true
  717.     #  console.log "WTF", accel
  718.  
  719.     entity.acceleration.add(accel)
  720.  
  721.    
  722.  
  723.  
  724. class limber.trait.CollisionResponse extends limber.trait.Trait
  725.   requires: "velocity"
  726.   constructor: ->
  727.     @on "augment", (entity) ->
  728.       entity.on "render", -> @collided = false
  729.       entity.on "collision", (engine, mtv, other) ->
  730.         @collided = true
  731.         @position.add(mtv.axis.clone().scale(mtv.overlap / 2))
  732.  
  733. class limber.trait.RandomAcceleration extends limber.trait.Trait
  734.   requires: "acceleration"
  735.  
  736.   constructor: (@speed = 20) ->
  737.   augment: (entity) ->
  738.     speed = @speed
  739.     twiceSpeed = speed * 2
  740.     entity.on "update", (engine) ->
  741.       @acceleration.add(Math.random() * twiceSpeed - speed, Math.random() * twiceSpeed - speed)
  742.  
  743. class limber.trait.ConstrainVelocity extends limber.trait.Trait
  744.   requires: "velocity"
  745.  
  746.   constructor: (@min = 20, @max = 200) ->
  747.    
  748.   augment: (entity) ->
  749.     min = @min
  750.     max = @max
  751.     minSq = min * min
  752.     maxSq = max * max
  753.     entity.on "update", (engine) ->
  754.       v2 = @velocity.magnitudeSq()
  755.       if v2 > maxSq then @velocity.scale(Math.sqrt(v2) / maxSq)
  756.       else if v2 < minSq then @velocity.scale(min / Math.sqrt(v2))
  757.  
  758. class limber.trait.ConstrainAcceleration extends limber.trait.Trait
  759.   requires: "velocity"
  760.  
  761.   constructor: (@max = 20) ->
  762.    
  763.   augment: (entity) ->
  764.     max = @max
  765.     maxSq = max * max
  766.     entity.on "update", (engine) ->
  767.       v2 = @acceleration.magnitudeSq()
  768.       if v2 > maxSq then @acceleration.normalize().scale(max)
  769.  
  770. class limber.trait.AnimatedSprite extends limber.trait.Trait
  771.   @imgCache = []
  772.  
  773.   requires: ["position", "velocity"]
  774.   provides: "sprite"
  775.  
  776.   initialize: (src, @frameW, @frameH, @w, @h, @framesPerRow = 1, @frameCount = 1, @frameTime = 100) ->
  777.     cache = limber.trait.AnimatedSprite.imgCache
  778.     unless cache[src]
  779.       cache[src] = new Image
  780.       cache[src].src = src
  781.    
  782.     @image = cache[src]
  783.     @frame = 0
  784.     @xOff = 0
  785.     @yOff = 0
  786.     @nextFrame = @frameTime
  787.    
  788.   augment: (entity) ->
  789.     self = this
  790.    
  791.     render = (engine) ->
  792.       engine.context.save()
  793.       engine.context.translate(entity.position.x, entity.position.y)
  794.       engine.context.rotate(Math.atan2(entity.velocity.y, entity.velocity.x) + Math.PI / 2)
  795.       engine.context.drawImage(self.image, self.frameW * self.xOff, self.frameH * self.yOff, self.frameW, self.frameH, - self.w / 2, - self.h / 2, self.w, self.h)
  796.       engine.context.restore()
  797.     update = (engine) ->
  798.       self.nextFrame -= engine.timer.delta
  799.      
  800.       if self.nextFrame <= 0
  801.         self.frame += 1
  802.         if self.frame == self.frameCount
  803.           self.frame = 0
  804.           @emit "sprite:reset", this
  805.         self.nextFrame = self.frameTime + self.nextFrame
  806.         self.xOff = self.frame % self.framesPerRow
  807.         self.yOff = Math.floor(self.frame / self.framesPerRow)
  808.  
  809.     entity.on "render", render
  810.     entity.on "update", update
  811.    
  812.     entity.on "destroy", (entity) ->
  813.       entity.removeListener "render", render
  814.       entity.removeListener "update", update