• patches.coffee

  • ¶

    Patches

  • ¶

    Class Patches is a singleton 2D matrix of Patch instances, each patch representing a 1x1 square in patch coordinates (via 2D coord transforms).

    From @model.world, set in Model:

    • size: pixel h/w of each patch.
    • minX/maxX: min/max x coord in patch coords
    • minY/maxY: min/max y coord in patch coords
    • numX/numY: width/height of grid.
    • isTorus: true if coord system wraps around at edges
    • hasNeighbors: true if each patch caches its neighbors
    class Patches extends AgentSet
      drawWithPixels: true # Experimental class variable
  • ¶

    Constructor: super creates the empty AgentSet instance and installs the agentClass (breed) variable shared by all the Patches in this set. Patches are created from top-left to bottom-right to match data sets.

      constructor: -> # model, agentClass, name, mainSet
        super # call super with all the args I was called with
        @monochrome = false # set to true to optimize patches all default color
        @[k] = v for own k,v of @model.world # add world items to patches
        @populate() unless @mainSet?
  • ¶

    Setup patch world from world parameters. Note that this is done as separate method so like other agentsets, patches are started up empty and filled by “create” calls.

      populate: -> # TopLeft to BottomRight, exactly as canvas imagedata
        for y in [@maxY..@minY] by -1
          for x in [@minX..@maxX] by 1
            @add new @agentClass x, y
        @setNeighbors() if @hasNeighbors
        @setPixels() if @model.div? # setup off-page canvas for pixel ops
  • ¶

    Have patches cache the turtles currently on them. Optimizes p.turtlesHere method. Call before first turtle is created.

      cacheTurtlesHere: -> p.turtles = [] for p in @; null
  • ¶

    Draw patches using scaled image of colors. Note anti-aliasing may occur if browser does not support smoothing flags.

      usePixels: () ->
        u.deprecated "Patches.usePixels: pixels always used (color.pixel)"
  • ¶

    Optimization: Cache a single set by modeler for use by patchRect, inCone, inRect, inRadius. Ex: flock demo model’s vision rect.

      cacheRect: (radius, meToo=true) ->
        for p in @
          p.pRect = @patchRect p, radius, radius, meToo
          p.pRect.radius = radius
        null # avoid CS returning huge array!
  • ¶

    Install neighborhoods in patches

      setNeighbors: ->
        for p in @
          p.n =  @patchRect p, 1, 1, false
          p.n4 = @asSet (n for n in p.n when n.x is p.x or n.y is p.y)
        null # radius # avoid CS returning huge array!
  • ¶

    Setup pixels used for drawScaledPixels and importColors

      setPixels: ->
        ctx = @model.contexts.patches
        u.setCtxSmoothing ctx, false # crisp rendering
        if @size is 1
        then @pixelsCtx = ctx
        else @pixelsCtx = u.createCtx @numX, @numY
        @pixelsImageData = @pixelsCtx.getImageData(0, 0, @numX, @numY)
        @pixelsData = @pixelsImageData.data
        @pixelsData32 = new Uint32Array @pixelsData.buffer if @drawWithPixels
  • ¶

    Draw patches. Three cases:

    • Pixels: use pixel manipulation rather than canvas draws
    • Monochrome: just fill canvas w/ patch default
    • Otherwise: just draw each patch individually
      draw: (ctx) ->
        if @monochrome
        then u.fillCtx ctx, @agentClass::color
        else @drawScaledPixels ctx
  • ¶

    Patch grid coord system utilities:

  • ¶

    Return the patch id/index given integer x,y in patch coords

      patchIndex: (x,y) -> x-@minX + @numX*(@maxY-y)
  • ¶

    Return the patch at matrix position x,y where x & y are both valid integer patch coordinates.

      patchXY: (x,y) -> @[@patchIndex x,y]
  • ¶

    Return x,y float values to be between min/max patch coord values

      clamp: (x,y) -> [u.clamp(x, @minXcor, @maxXcor), u.clamp(y, @minYcor, @maxYcor)]
  • ¶

    Return x,y float values to be modulo min/max patch coord values.

      wrap: (x,y) -> [u.wrap(x, @minXcor, @maxXcor), u.wrap(y, @minYcor, @maxYcor)]
  • ¶

    Return x,y float values to be between min/max patch values using either clamp/wrap above according to isTorus topology.

      coord: (x,y) -> #returns a valid world coord (real, not int)
        if @isTorus then @wrap x,y else @clamp x,y
  • ¶

    Return true if on world or torus, false if non-torus and off-world

      isOnWorld: (x,y) -> @isTorus or (@minXcor<=x<=@maxXcor and @minYcor<=y<=@maxYcor)
  • ¶

    Return patch at x,y float values according to topology.

      patch: (x,y) ->
        [x,y]=@coord x,y
        x = u.clamp Math.round(x), @minX, @maxX
        y = u.clamp Math.round(y), @minY, @maxY
        @patchXY x, y
  • ¶

    Return a random valid float x,y point in patch space

      randomPt: -> [u.randomFloat2(@minXcor,@maxXcor), u.randomFloat2(@minYcor,@maxYcor)]
  • ¶

    Patch metrics

  • ¶

    Convert patch measure to pixels

      toBits: (p) -> p*@size
  • ¶

    Convert bit measure to patches

      fromBits: (b) -> b/@size
  • ¶

    Patch utilities

      patchRect: (p, dx, dy=dx, meToo=true) ->
        return p.pRect if p.pRect? and (p.pRect.radius is dx) and (dx is dy)
        rect = []; # REMIND: optimize if no wrapping, rect inside patch boundaries
        for y in [p.y-dy..p.y+dy] by 1 # by 1: perf: avoid bidir JS for loop
          for x in [p.x-dx..p.x+dx] by 1
            if @isTorus or (@minX<=x<=@maxX and @minY<=y<=@maxY)
              if @isTorus
                if x < @minX then x += @numX else if x > @maxX then x -= @numX
                if y < @minY then y += @numY else if y > @maxY then y -= @numY
              pnext = @patchXY x, y # much faster than coord()
              rect.push pnext if (meToo or p isnt pnext)
        @asSet rect
  • ¶

    Return patches within the patch square.

      inRect: (agent, radius) -> #agentSet.inRadius @, radius
        @patchRect( (agent.p ? agent), Math.ceil(radius) )
  • ¶

    Return patches within radius of the given agent (patch or turtle)

      inRadius: (agent, radius) -> #agentSet.inRadius @, radius
        pset = @patchRect( (agent.p ? agent), Math.ceil(radius) )
        pset.inRadius agent, radius
      inCone: (agent, radius, angle, heading) ->
        pset = @patchRect( (agent.p ? agent), Math.ceil(radius) )
        pset.inRadius agent, radius, angle, heading
  • ¶

    Return all the turtles contained in the patchRect.

      turtlesOnRect: (p, dx, dy=dx) ->
        @turtlesOnPatches @patchRect(p, dx, dy, true)
      turtlesOnPatches: (patches) ->
        array = []
        if patches.length isnt 0
          u.error "turtlesOnPatches: no cached turtles." if not patches[0].turtles?
  • ¶

    Use push.apply, not concat, see: jsPerf

          Array.prototype.push.apply(array, p.turtles) for p in patches
        @asSet array
  • ¶

    Return all the unique patches the agentset or turtle is on.

      patchesOf: (aset) ->
        return @asSet([aset.p ? aset]) unless aset.length?
        @asSet( ((a.p ? a) for a in aset) ).sortById().uniq()
      turtlesOf: (aset) -> @turtlesOnPatches(@patchesOf(aset))
  • ¶

    Draws, or “imports” an image URL into the drawing layer. The image is scaled to fit the drawing layer.

    This is an async load, see this [new Image()] (http://javascript.mfields.org/2011/creating-an-image-in-javascript/) tutorial. We draw the image into the drawing layer as soon as the onload callback executes.

      importDrawing: (imageSrc, f) ->
        u.importImage imageSrc, (img) => # fat arrow, this context
          @installDrawing img
          f() if f?
  • ¶

    Direct install image into the given context, not async.

      installDrawing: (img, ctx=@model.contexts.drawing) ->
        u.setIdentity ctx
        ctx.drawImage img, 0, 0, ctx.canvas.width, ctx.canvas.height
        ctx.restore() # restore patch transform
  • ¶

    Utility function for pixel manipulation. Given a patch, returns the native canvas index i into the pixel data. The top-left order simplifies finding pixels in data sets

      pixelByteIndex: (p) -> 4*p.id # Uint8
      pixelWordIndex: (p) -> p.id   # Uint32
  • ¶

    Convert pixel location (top/left offset i.e. mouse) to patch coords (float)

      pixelXYtoPatchXY: (x,y) -> [@minXcor+(x / @size), @maxYcor-(y / @size)]
  • ¶

    Convert patch coords (float) to pixel location (top/left offset i.e. mouse)

      patchXYtoPixelXY: (x,y) -> [(x-@minXcor)*@size, (@maxYcor-y)*@size]
  • ¶

    Draws, or “imports” an image URL into the patches as their color property. The drawing is scaled to the number of x,y patches, thus one pixel per patch. The colors are then transferred to the patches. Map is a color map, only for gray for now

      importColors: (imageSrc, f, map) ->
        u.importImage imageSrc, (img) => # fat arrow, this context
          @installColors(img, map)
          f() if f?
  • ¶

    Direct install image into the patch colors, not async.

      installColors: (img, map) ->
        u.setIdentity @pixelsCtx
        @pixelsCtx.drawImage img, 0, 0, @numX, @numY # scale if needed
        data = @pixelsCtx.getImageData(0, 0, @numX, @numY).data
        for p in @
          i = @pixelByteIndex p
  • ¶

    promote initial default

          p.color = if map? then map[i] else [data[i++],data[i++],data[i]]
        @pixelsCtx.restore() # restore patch transform
  • ¶

    Draw the patches via pixel manipulation rather than 2D drawRect. See Mozilla pixel manipulation article

      drawScaledPixels: (ctx) ->
  • ¶

    u.setIdentity ctx & ctx.restore() only needed if patch size not 1, pixel ops don’t use transform but @size>1 uses a drawimage, using a transform

        u.setIdentity ctx if @size isnt 1
        data = @pixelsData32 ? @pixelsData
    
        if @pixelsData32?
        then data[p.id] = p.color.pixel for p in @
        else data.set(p.color, p.id*4) for p in @
    
        @pixelsCtx.putImageData @pixelsImageData, 0, 0
        return if @size is 1
        ctx.drawImage @pixelsCtx.canvas, 0, 0, @pxWidth, @pxHeight
        ctx.restore() if @size isnt 1
  • ¶

    Diffuse the value of patch variable p.v by distributing rate percent of each patch’s value of v to its neighbors. If a color map cMap is given, scale the patch color via variable’s value If the patch has less than 8 neighbors, return the extra to the patch.

      diffuse: (v, rate, color) -> # variable name, diffusion rate, cMap (optional)
  • ¶

    zero temp variable if not yet set

        unless @[0]._diffuseNext?
          p._diffuseNext = 0 for p in @
  • ¶

    pass 1: calculate contribution of all patches to themselves and neighbors

        for p in @
          dv = p[v]*rate; dv8 = dv/8; nn = p.n.length
          p._diffuseNext += p[v] - dv + (8-nn)*dv8
          n._diffuseNext += dv8 for n in p.n
  • ¶

    pass 2: set new value for all patches, zero temp, modify color if c given

        for p in @
          p[v] = p._diffuseNext
          p._diffuseNext = 0
          p.color = ColorMaps.scaleColor color, p[v] if color?
        null # avoid returning copy of @