class ModelClass Model is the control center for our AgentSets: Patches, Turtles and Links.
Creating new models is done by subclassing class Model and overriding two
virtual/abstract methods: setup() and step()
class ModelClass variable for layers parameters. Can be added to by programmer to modify/create layers, before starting your own model. Example:
v.z++ for k,v of Model::contextsInit # increase each z value by one
contextsInit: { # Experimental: image: {z:15, ctx:"img"}
patches: {z:10, ctx:"2d"}
drawing: {z:20, ctx:"2d"}
links: {z:30, ctx:"2d"}
turtles: {z:40, ctx:"2d"}
spotlight: {z:50, ctx:"2d"}
}Static method: return defaulted Model options object. Access by (ABM.)Model.defaultOptions() etc
@nonWorldOptions = ["div"]
@defaultOptions: ->
div: null
size: 13
minX: -16
maxX: 16
minY: -16
maxY: 16
isTorus: false
hasNeighbors: trueConstructor:
setup abstract method constructor: (args) ->Setup and error check args
options = Model.defaultOptions()
unless u.isObject args # remove after a while
console.log "option defaults:", options
u.error "Model constructor: use options object; see console for defaults."
u.deprecated "Model: isHeadless no longer used" if args.isHeadless?Merge args into options. Insures newer options/defaults included
for k,v of args # when k isnt "isHeadless" # Remove headless test shortly
u.error "Bad Model arg: #{k}: #{v}" if options[k] is undefined
options[k] = v
@setWorld options
u.mixin(@, new Evented())
@contexts = {}
if options.div?
@div=document.getElementById(options.div)el.setAttribute ‘style’ erases existing style, el.style.xx does not
s=@div.style
s.position="relative"; s.width=@world.pxWidth; s.height=@world.pxHeightNote: The transform is permanent .. there isn’t the usual ctx.restore(). To use the original canvas 2D transform temporarily:
u.setIdentity ctx
<draw in native coord system>
ctx.restore() # restore patch coord system
for own k,v of @contextsInit
@contexts[k] = ctx = u.createLayer @div, @world.pxWidth, @world.pxHeight, v.z, v.ctx
@setCtxTransform ctx if ctx.canvas?
if ctx.canvas? then ctx.canvas.style.pointerEvents = 'none'
u.elementTextParams ctx, "10px sans-serif", "center", "middle"One of the layers is used for drawing only, not an agentset:
@drawing = @contexts.drawing
@drawing.clear = => u.clearCtx @drawingSetup spotlight layer, also not an agentset:
@contexts.spotlight.globalCompositeOperation = "xor"
@anim = new Animator @Set drawing controls. Default to drawing each agentset. Optimization: If any of these is set to false, the associated agentset is drawn only once, remaining static after that.
@refreshLinks = @refreshTurtles = @refreshPatches = trueCreate model-local versions of AgentSets and their agent class. Clone the agent classes so that they can use “defaults” in isolation when multiple models run on a page.
@Patch = u.cloneClass(Patch)
@Turtle = u.cloneClass(Turtle)
@Link = u.cloneClass(Link)Initialize agentsets.
@patches = new Patches @, @Patch, "patches"
@turtles = new Turtles @, @Turtle, "turtles"
@links = new Links @, @Link, "links"Initialize model global resources
@debugging = false
@modelReady = false
@globalNames = null; @globalNames = u.ownKeys @
@globalNames.set = false
@startup()
u.waitOnFiles => @modelReady=true; @setupAndEmit(); @globals() unless @globalNames.setInitialize/reset world parameters by expanding options.
setWorld: (opts) ->
w = {}; w[k] = v for own k,v of opts when k not in Model.nonWorldOptions
w.numX = w.maxX-w.minX+1; w.numY = w.maxY-w.minY+1
w.pxWidth = w.numX*w.size; w.pxHeight= w.numY*w.size
w.minXcor = w.minX-.5; w.maxXcor = w.maxX+.5
w.minYcor = w.minY-.5; w.maxYcor = w.maxY+.5
@world = w
setCtxTransform: (ctx) ->
ctx.canvas.width = @world.pxWidth; ctx.canvas.height = @world.pxHeight
ctx.save()
ctx.scale @world.size, -@world.size
ctx.translate -(@world.minXcor), -(@world.maxYcor)
globals: (globalNames) ->
if globalNames?
then @globalNames = globalNames; @globalNames.set = true
else @globalNames = u.removeItems u.ownKeys(@), @globalNamesModelers “tune” their model by adjusting flags:
@refreshLinks, @refreshTurtles, @refreshPatches
and by the following helper methods:
Draw patches using scaled image of colors. Note anti-aliasing may occur if browser does not support imageSmoothingEnabled or equivalent.
setFastPatches: -> @patches.usePixels()Patches are all the same static default color, just “clear” entire canvas. Don’t use if patch breeds have different colors.
setMonochromePatches: -> @patches.monochrome = trueHave patches cache the turtles currently on them. Optimizes Patch p.turtlesHere method
setCacheTurtlesHere: -> @patches.cacheTurtlesHere()Have turtles cache the links with them as a node. Optimizes Turtle a.myLinks method
setCacheMyLinks: -> @turtles.cacheLinks()A user’s model is made by subclassing Model and over-riding these
two abstract methods. super need not be called.
Initialize model resources (images, files) here. Uses Util.waitOn so can be be async.
startup: -> # called by constructorInitialize your model variables and defaults here. If async used, make sure step/draw are aware of possible missing data.
setup: ->Update/step your model here
step: -> # called each step of the animationConvenience access to animator:
Start/stop the animation
start: -> u.waitOn (=> @modelReady), (=> @anim.start()); @
stop: -> @anim.stop()Animate once by step(); draw(). For UI and debugging from console.
Will advance the ticks/draws counters.
once: -> @stop() unless @anim.stopped; @anim.once()Stop and reset the model, restarting if restart is true
reset: (restart = false) ->
console.log "reset: anim"
@anim.reset() # stop & reset ticks/steps counters
console.log "reset: contexts" # clear/resize canvas xfms b4 agentsets
(v.restore(); @setCtxTransform v) for k,v of @contexts when v.canvas?
console.log "reset: patches"
@patches = new Patches @, @Patch, "patches"
console.log "reset: turtles"
@turtles = new Turtles @, @Turtle, "turtles"
console.log "reset: links"
@links = new Links @, @Link, "links"
Shapes.spriteSheets.length = 0 # possibly null out entries?
console.log "reset: setup"
@setupAndEmit()
@setRootVars() if @debugging
@start() if restartCall the agentset draw methods if either the first draw call or their “refresh” flags are set. The latter are simple optimizations to avoid redrawing the same static scene. Called by animator.
draw: (force = @anim.stopped or @anim.draws is 1) ->
if @debugging
console.log @anim.toString() if @anim.draws % 100 is 0
@showSpriteSheet() if (@anim.draws is 2)# and @turtles[0]?.useSprites
if @div?
@patches.draw @contexts.patches if force or @refreshPatches
@links.draw @contexts.links if force or @refreshLinks
@turtles.draw @contexts.turtles if force or @refreshTurtles
@drawSpotlight @spotlightTurtle, @contexts.spotlight if @spotlightTurtle?
@emit('draw')
toggleDrawing: ->
if @div?
@div0 = @div; @div = null
else
@div = @div0; @div0 = null
setupAndEmit: ->
@setup()
@emit('setup')
stepAndEmit: ->
@step()
@emit('step')Creates a spotlight effect on a turtle, so we can follow it throughout the model. Use:
@setSpotlight breed.oneOf()
to draw one of a random breed. Remove spotlight by passing null
setSpotlight: (@spotlightTurtle) ->
u.clearCtx @contexts.spotlight unless @spotlightTurtle?
drawSpotlight: (turtle, ctx) ->
u.clearCtx ctx
u.fillCtx ctx, Color.typedColor(0,0,0,.6*255)
ctx.beginPath()
ctx.arc turtle.x, turtle.y, 3, 0, 2*Math.PI, false
ctx.fill()Three versions of NL’s breed commands.
@patchBreeds "streets buildings"
@agentBreeds "embers fires"
@linkBreeds "spokes rims"
will create 6 agentSets:
@streets and @buildings
@embers and @fires
@spokes and @rims
These agentsets’ create methods create subclasses of Turtle/Link.
Use of
@embers.setDefault "color", [255,0,0]
..will set the default color for just the embers. Note: patch breeds are experimental, using setBreed, due to the patches being prebuilt.
createBreeds: (breedNames, baseClass, baseSet) ->
breeds = []; breeds.classes = {}; breeds.sets = {}
for breedName in breedNames.split(" ")
className = u.upperCamelCase breedName
breedClass = u.cloneClass baseClass, className
breed = @[breedName] =
new baseSet @, breedClass, breedName, baseClass::breed
breeds.push breed
breeds.sets[breedName] = breed
breeds.classes["#{breedName}Class"] = breedClass
breeds
patchBreeds: (breedNames) ->
@patches.breeds = @createBreeds breedNames, @Patch, Patches
agentBreeds: (breedNames) ->
@turtles.breeds = @createBreeds breedNames, @Turtle, Turtles
linkBreeds: (breedNames) ->
@links.breeds = @createBreeds breedNames, @Link, LinksUtility for models to create agentsets from arrays. Ex:
even = @asSet (a for a in @turtles when a.id % 2 is 0)
even.shuffle().getProp("id") # [6, 0, 4, 2, 8]
asSet: (a, setType = AgentSet) -> AgentSet.asSet a, setTypeA simple debug aid which places short names in the global name space. Note we avoid using the actual name, such as “patches” because this can cause our modules to mistakenly depend on a global name. See CoffeeConsole Chrome extension too.
debug: (@debugging=true)->u.waitOn (=>@modelReady),(=>@setRootVars()); @
setRootVars: ->
window.psc = Patches
window.tsc = Turtles
window.lsc = Links
window.pc = @Patch
window.tc = @Turtle
window.lc = @Link
window.ps = @patches
window.ts = @turtles
window.ls = @links
window.p0 = @patches[0]
window.t0 = @turtles[0]
window.l0 = @links[0]
window.dr = @drawing
window.u = Util
window.cx = @contexts
window.an = @anim
window.gl = @globals()
window.dv = @div
window.app = @Debug aid: Fill a named div with the (last) sprite sheet Typically the div should be before the model div, float right:
If no divName given, use spriteSheet itself w/ float:right style
showSpriteSheet: (divName) ->
if Shapes.spriteSheets.length isnt 0
sheet = Util.last(Shapes.spriteSheets)
if divName?
document.getElementById(divName).appendChild(sheet.canvas)
else
sheet.canvas.setAttribute "style", "float:right"
@div.parentElement.insertBefore(sheet.canvas, @div)
else
console.log "showSpriteSheet: not using sprites"Create the namespace ABM for our project.
Note here this or @ == window due to coffeescript wrapper call.
Thus @ABM is placed in the global scope.
@ABM = {
Util
Color
ColorMaps
colorMixin
Shapes
AgentSet
Patch
Patches
Turtle
Turtles
Link
Links
Animator
Evented
Model
}