Add ObjectID property to returned replace object

    ObjectID  = require('mongodb').ObjectID

Import event emitter stuff.

    EventEmitter = require('events').EventEmitter
    inherits = require('util').inherits

Import data storage modules.

    mongo   = require '../db/mongo'
    redis   = require '../db/redis'

    parse   = require '../helper/parse'
    cache   = require '../helper/cache'

## Class: Doc

### Params

* `string` type - document type
* `string` id - document id

### Return

New `Doc`.

    module.exports = Doc = (type, id) ->
      throw new Error('Missing Doc type param') if not type
      throw new Error('Missing or invalid ID param') if typeof id isnt 'string' and id isnt null

      EventEmitter.call @

      @db   = mongo[type]
      @id   = if typeof id is 'string' then new ObjectID(id) else null
      @type = type
      @data = {}
      @chit = false

      if @id
        redis.hgetall "#{@type}:#{@id.toString()}", (err, data) =>
          return @emit 'error', err if err

          if data
            @data = cache.arrayify @type, data
            @chit = true
            return @emit 'ready'

          @db.findOne _id: @id, cache.getFilter(@type), (err, data) =>
            return @emit 'error', err if err

            @data = data or status: 'Slettet'
            redis.hmset "#{@type}:#{@id}", @data, (err) =>
              return @emit 'ready'
      else
        process.nextTick => @emit 'ready'

      return @

    inherits Doc, EventEmitter

## Doc.exists

Check if document exists.

### Return

Returns `true` if document exists; otherwise `false`.

    Doc.prototype.exists = ->
      return @data.status? and @data.status isnt 'Slettet'

## Doc.getId

Get document id (ObjectID) for current document.

### Return

Returns an `ObjectID` if the document has an id; otherwise `null`.

    Doc.prototype.getId = ->
      @id

## Doc.wasCacheHit

Check if document request was a cache hit.

### Return

Returns `true` if document request was a cache hit; otherwise `false`.

    Doc.prototype.wasCacheHit = ->
      @chit

## Doc.isNotModifiedSince

Check if document has not been modified since a given timestamp.

### Params

* `string` time - timestamp to check against.

### Return

Returns `true` if document has not been modified since given tmestamp; otherwise
`false`.

    Doc.prototype.isModifiedSince = (time) -> @modifiedSince time, false
    Doc.prototype.isNotModifiedSince = (time) -> @modifiedSince time, true

    Doc.prototype.modifiedSince = (time, negate) ->
      return false if not time or not @data.endret

      if not isNaN time
        # Make unix timestamp into milliseconds
        time = time + '000' if (time + '').length is 10
        time = parseInt time

      org = new Date(@data.endret)
      chk = new Date(time)

      # HTTP-date's don't have milliseconds
      org.setMilliseconds(0)

      return false if chk.toString() is 'Invalid Date'

      return chk >= org if negate
      return chk <  org

## doc.isMatch

Check if checksum, or Etag, matche current document checksum.

### Params

* `string` checksum - checksum to check against.

### Return

Returns `true` if checksum matches the current checksum; otherwise `false`.

    Doc.prototype.isMatch = (checksum) ->
      return false if not checksum or not @data.checksum
      return (checksum is "\"#{@data.checksum}\"" or checksum is '*')

## doc.isNoneMatch

Check if checksum, or Etag, doesnt match current document checksum.

### Params

* `string` checksum - checksum to check against.

### Return

Return `true` if checksum does not match or document has no checksum; otherwise
`false`.

    Doc.prototype.isNoneMatch = (checksum) ->
      return false if not checksum
      return true if not @data.checksum
      return checksum isnt "\"#{@data.checksum}\""

## Doc.get

Get cached data for current document.

### Params

* `string` key - get specific object property

### Return

Returns an `object` if `key` is `undefined`; otherwise `string`. Will retur
`undefined` if the given key does not exist.

    Doc.prototype.get = (key) ->
      return @data if not key
      return @data[key]

## doc.getFull

### Params

* `object` filter - control which object properties are returned
* `function` cb - callback function (`Error` err, `object` data)

### Return

Returns `undefined`.

    Doc.prototype.getFull = (filter, cb) ->
      if not cb
        throw new Error('Document doesnt exists') if not @exists()
        return @db.find _id: @id, filter, limit: 1
      else
        return cb new Error('Document doesnt exists') if not @exists()
        @db.findOne _id: @id, filter, cb

## Doc.insert

Inserts a document into databse.

### Params

* `object` data - data to insert for document.
* `function` cb - callback function (`Error` err, `Array` warn, `object` data).

### Return

Returns `undefined`.

    Doc.prototype.insert = (data, cb) ->
      return cb new Error('Document already exists') if @exists()
      return cb new Error('Document is deleted') if @data.status is 'Slettet'

      parse.docInsert @type, data, (err, warn, data) =>
        return cb err if err

        @db.insertOne data, w: 1, (err) =>
          return cb err if err

          @id   = new ObjectID(data._id)
          @data = cache.filterData @type, data

          redis.hmset "#{@type}:#{@id.toString()}", @data

          cb err, warn, data

## Doc.replace

Replaces all document data in database.

### Params

* `object` data - replacement data for document.
* `function` cb - callback function (`Error` err, `Array` warn, `object` data).

### Return

Returns `undefined`.

    Doc.prototype.replace = (data, cb) ->
      return cb new Error('Document doesnt exists') if not @exists()

      parse.docReplace @type, data, (err, warn, data) =>
        return cb err if err

        @db.replaceOne _id: @id, data, w: 1, (err) =>
          return cb err if err

          @data = cache.filterData @type, data

          redis.del "#{@type}:#{@id.toString()}"
          redis.hmset "#{@type}:#{@id.toString()}", @data

          data._id = @id
          cb err, warn, data

## Doc.update

Partially update the document-data in database. This is some times refered to as
PATCH in a HTTP / REST context.

### Params

* `object` data - replacement object for document.
* `function` cb - callback function (`Error` err, `Array` warn, `object` data).

### Return

Returns `undefined`.

    Doc.prototype.update = (query, cb) ->
      return cb new Error('Document doesnt exists') if not @exists()

      parse.docPatch @type, query, (err, warn, query) =>
        return cb err if err

        @db.updateOne _id: @id, query, w: 1, (err) =>
          return cb err if err

          @getFull {}, (err, doc) =>
            @data = cache.filterData @type, doc

            redis.del "#{@type}:#{@id.toString()}"
            redis.hmset "#{@type}:#{@id.toString()}", @data

            cb err, warn, doc

## Doc.delete

Delete a document from the database by removing all document properties and
setting the `status` property to `Slettet`.

### Params

* `function`  cb - callback function (`Error` err).

### Return

Returns `undefined`.

    Doc.prototype.delete = (cb) ->
      return cb new Error('Document doesnt exists') if not @exists()

      @data = status: 'Slettet'
      @db.replaceOne _id: @id, @data, w: 1, (err) =>
        return cb err if err

        redis.del "#{@type}:#{@id.toString()}"
        redis.hmset "#{@type}:#{@id.toString()}", @data

        cb null

    ObjectID  = require('mongodb').ObjectID

Import event emitter stuff.

    EventEmitter = require('events').EventEmitter
    inherits = require('util').inherits

Import data storage modules.

    mongo   = require '../db/mongo'
    redis   = require '../db/redis'

    parse   = require '../helper/parse'
    cache   = require '../helper/cache'

## Class: Doc

### Params

* `string` type - document type
* `string` id - document id

### Return

New `Doc`.

    module.exports = Doc = (type, id) ->
      throw new Error('Missing Doc type param') if not type
      throw new Error('Missing or invalid ID param') if typeof id isnt 'string' and id isnt null

      EventEmitter.call @

      @db   = mongo[type]
      @id   = if typeof id is 'string' then new ObjectID(id) else null
      @type = type
      @data = {}
      @chit = false

      if @id
        redis.hgetall "#{@type}:#{@id.toString()}", (err, data) =>
          return @emit 'error', err if err

          if data
            @data = cache.arrayify @type, data
            @chit = true
            return @emit 'ready'

          @db.findOne _id: @id, cache.getFilter(@type), (err, data) =>
            return @emit 'error', err if err

            @data = data or status: 'Slettet'
            redis.hmset "#{@type}:#{@id}", @data, (err) =>
              return @emit 'ready'
      else
        process.nextTick => @emit 'ready'

      return @

    inherits Doc, EventEmitter

## Doc.exists

Check if document exists.

### Return

Returns `true` if document exists; otherwise `false`.

    Doc.prototype.exists = ->
      return @data.status? and @data.status isnt 'Slettet'

## Doc.getId

Get document id (ObjectID) for current document.

### Return

Returns an `ObjectID` if the document has an id; otherwise `null`.

    Doc.prototype.getId = ->
      @id

## Doc.wasCacheHit

Check if document request was a cache hit.

### Return

Returns `true` if document request was a cache hit; otherwise `false`.

    Doc.prototype.wasCacheHit = ->
      @chit

## Doc.isNotModifiedSince

Check if document has not been modified since a given timestamp.

### Params

* `string` time - timestamp to check against.

### Return

Returns `true` if document has not been modified since given tmestamp; otherwise
`false`.

    Doc.prototype.isModifiedSince = (time) -> @modifiedSince time, false
    Doc.prototype.isNotModifiedSince = (time) -> @modifiedSince time, true

    Doc.prototype.modifiedSince = (time, negate) ->
      return false if not time or not @data.endret

      if not isNaN time
        # Make unix timestamp into milliseconds
        time = time + '000' if (time + '').length is 10
        time = parseInt time

      org = new Date(@data.endret)
      chk = new Date(time)

      # HTTP-date's don't have milliseconds
      org.setMilliseconds(0)

      return false if chk.toString() is 'Invalid Date'

      return chk >= org if negate
      return chk <  org

## doc.isMatch

Check if checksum, or Etag, matche current document checksum.

### Params

* `string` checksum - checksum to check against.

### Return

Returns `true` if checksum matches the current checksum; otherwise `false`.

    Doc.prototype.isMatch = (checksum) ->
      return false if not checksum or not @data.checksum
      return (checksum is "\"#{@data.checksum}\"" or checksum is '*')

## doc.isNoneMatch

Check if checksum, or Etag, doesnt match current document checksum.

### Params

* `string` checksum - checksum to check against.

### Return

Return `true` if checksum does not match or document has no checksum; otherwise
`false`.

    Doc.prototype.isNoneMatch = (checksum) ->
      return false if not checksum
      return true if not @data.checksum
      return checksum isnt "\"#{@data.checksum}\""

## Doc.get

Get cached data for current document.

### Params

* `string` key - get specific object property

### Return

Returns an `object` if `key` is `undefined`; otherwise `string`. Will retur
`undefined` if the given key does not exist.

    Doc.prototype.get = (key) ->
      return @data if not key
      return @data[key]

## doc.getFull

### Params

* `object` filter - control which object properties are returned
* `function` cb - callback function (`Error` err, `object` data)

### Return

Returns `undefined`.

    Doc.prototype.getFull = (filter, cb) ->
      if not cb
        throw new Error('Document doesnt exists') if not @exists()
        return @db.find _id: @id, filter, limit: 1
      else
        return cb new Error('Document doesnt exists') if not @exists()
        @db.findOne _id: @id, filter, cb

## Doc.insert

Inserts a document into databse.

### Params

* `object` data - data to insert for document.
* `function` cb - callback function (`Error` err, `Array` warn, `object` data).

### Return

Returns `undefined`.

    Doc.prototype.insert = (data, cb) ->
      return cb new Error('Document already exists') if @exists()
      return cb new Error('Document is deleted') if @data.status is 'Slettet'

      parse.docInsert @type, data, (err, warn, data) =>
        return cb err if err

        @db.insertOne data, w: 1, (err) =>
          return cb err if err

          @id   = new ObjectID(data._id)
          @data = cache.filterData @type, data

          redis.hmset "#{@type}:#{@id.toString()}", @data

          cb err, warn, data

## Doc.replace

Replaces all document data in database.

### Params

* `object` data - replacement data for document.
* `function` cb - callback function (`Error` err, `Array` warn, `object` data).

### Return

Returns `undefined`.

    Doc.prototype.replace = (data, cb) ->
      return cb new Error('Document doesnt exists') if not @exists()

      parse.docReplace @type, data, (err, warn, data) =>
        return cb err if err

        @db.replaceOne _id: @id, data, w: 1, (err) =>
          return cb err if err

          @data = cache.filterData @type, data

          redis.del "#{@type}:#{@id.toString()}"
          redis.hmset "#{@type}:#{@id.toString()}", @data

          cb err, warn, data

## Doc.update

Partially update the document-data in database. This is some times refered to as
PATCH in a HTTP / REST context.

### Params

* `object` data - replacement object for document.
* `function` cb - callback function (`Error` err, `Array` warn, `object` data).

### Return

Returns `undefined`.

    Doc.prototype.update = (query, cb) ->
      return cb new Error('Document doesnt exists') if not @exists()

      parse.docPatch @type, query, (err, warn, query) =>
        return cb err if err

        @db.updateOne _id: @id, query, w: 1, (err) =>
          return cb err if err

          @getFull {}, (err, doc) =>
            @data = cache.filterData @type, doc

            redis.del "#{@type}:#{@id.toString()}"
            redis.hmset "#{@type}:#{@id.toString()}", @data

            cb err, warn, doc

## Doc.delete

Delete a document from the database by removing all document properties and
setting the `status` property to `Slettet`.

### Params

* `function`  cb - callback function (`Error` err).

### Return

Returns `undefined`.

    Doc.prototype.delete = (cb) ->
      return cb new Error('Document doesnt exists') if not @exists()

      @data = status: 'Slettet'
      @db.replaceOne _id: @id, @data, w: 1, (err) =>
        return cb err if err

        redis.del "#{@type}:#{@id.toString()}"
        redis.hmset "#{@type}:#{@id.toString()}", @data

        cb null


add nef and rw2 into default file types

add nef and rw2 into default file types

This is a internal lib file.  It will be included at the head of [cleaner.litcoffee](cleaner.html).

Arguments
-----

`"use strict";` always first :)

    'use strict'

Let the help message more colorful~

    require 'colors'

Use [Yargs](https://github.com/chevex/yargs) to build the argv function and help display.

    yargs = require('yargs')

Print the common descriptions.

    .usage '''
           This application will append extra extname to all filtered out JPG files.
           Then you can check the result by your OS\'s search function easily.
           In case there is anything wrong, you can rollback.

           The default destination is here.

           P.S. The priority among -l, -d & -r is -l > -d > -r

           ''' +
           'Usage: clean-jpg [--options] [destination]'.cyan

Set the extname(s) of your RAW files.
You can give a comma-separated list of extnames (e.g. `-x nef,cr2,dng`).

Besides, this is case-**in**sensitive.

My default is "_ARW,RW2,NEF_",
because I own 2 Sony, 1 Panasonic, and my Dad owns 2 Nikon :)

    .options 'x',
      alias: 'extname'
      describe: """
                the extname(s) of your RAW files, case-#{'IN'.yellow}sensitive
                a comma-seperated list is acceptable
                #{'e.g. "-x nef,cr2,dng"'.cyan}

                """
      default: 'arw,rw2,nef'

The extname can be overridden if you want.
But we still suggest you can use a outstanding name to prevent confusion with others.
Usually a name will be as safe as long.

    .options 'r',
      alias: 'rename'
      default: 'extra-jpg-cleaner-result'
      describe: 'the extname used when renaming\n'

Delete the files **DIRECTLY**.

The default behavior of this application is rename the target files then you can check it manually.
It's for safety purpose.

This will require a complex confirmation.

    .options 'd',
      alias: 'delete'
      boolean: true
      describe: '!!!CAUTION!!!\n'.yellow.bold +
                """
                Delete DIRECTLY w/ a single confirmation
                instead of confirming one by one.
                It WON'T move your files to the recycle bin
                or trash can.
                Generally this operation will be unrecoverable.
                Try this only if you think your luck is overwhelming.
                Incase something goes wrong,
                go to buy an EasyRecovery or something similar.
                Then test your overwhelming luck again...
                """

Delete the files **DIRECTLY** with only a simple confirmation.

Same as `-d` but will just ask a simple `Y/N`.

    .options 'D',
      alias: 'delete-directly'
      boolean: true
      describe: '!!!CAUTION!!!\n'.red.bold +
                """
                Same as "-d" but with
                only even simpler "Y/N" confirmation.
                """

For such a application (who do the cleanup jobs), allow user to dry run is a good idea.

    .options 'l',
      alias: 'list'
      describe: 'just print a list of result, will NOT delete anything'
      boolean: true

Give more messages...
Actually will print every file when renaming / deleting.

    .options 'v',
      alias: 'verbose'
      describe: 'print when renaming / deleting'
      boolean: true

Print the help text.

    .options 'h',
      alias: 'help'
      describe: 'print this help message'
      boolean: true

Change a new line at the 80th character.

    .wrap 80

Exports the whole yargs instance.

As a result, all properties & results of that are available.

    module.exports = yargs

This is a internal lib file.  It will be included at the head of [cleaner.litcoffee](cleaner.html).

Arguments
-----

`"use strict";` always first :)

    'use strict'

Let the help message more colorful~

    require 'colors'

Use [Yargs](https://github.com/chevex/yargs) to build the argv function and help display.

    yargs = require('yargs')

Print the common descriptions.

    .usage '''
           This application will append extra extname to all filtered out JPG files.
           Then you can check the result by your OS\'s search function easily.
           In case there is anything wrong, you can rollback.

           The default destination is here.

           P.S. The priority among -l, -d & -r is -l > -d > -r

           ''' +
           'Usage: clean-jpg [--options] [destination]'.cyan

Set the extname(s) of your RAW files.
You can give a comma-separated list of extnames (e.g. `-x nef,cr2,dng`).

Besides, this is case-**in**sensitive.

My default is "_ARW_" because I'm a NEX user :)

    .options 'x',
      alias: 'extname'
      describe: """
                the extname(s) of your RAW files, case-#{'IN'.yellow}sensitive
                a comma-seperated list is acceptable
                #{'e.g. "-x nef,cr2,dng"'.cyan}

                """
      default: 'arw'

The extname can be overridden if you want.
But we still suggest you can use a outstanding name to prevent confusion with others.
Usually a name will be as safe as long.

    .options 'r',
      alias: 'rename'
      default: 'extra-jpg-cleaner-result'
      describe: 'the extname used when renaming\n'

Delete the files **DIRECTLY**.

The default behavior of this application is rename the target files then you can check it manually.
It's for safety purpose.

This will require a complex confirmation.

    .options 'd',
      alias: 'delete'
      boolean: true
      describe: '!!!CAUTION!!!\n'.yellow.bold +
                """
                Delete DIRECTLY w/ a single confirmation
                instead of confirming one by one.
                It WON'T move your files to the recycle bin
                or trash can.
                Generally this operation will be unrecoverable.
                Try this only if you think your luck is overwhelming.
                Incase something goes wrong,
                go to buy an EasyRecovery or something similar.
                Then test your overwhelming luck again...
                """

Delete the files **DIRECTLY** with only a simple confirmation.

Same as `-d` but will just ask a simple `Y/N`.

    .options 'D',
      alias: 'delete-directly'
      boolean: true
      describe: '!!!CAUTION!!!\n'.red.bold +
                """
                Same as "-d" but with
                only even simpler "Y/N" confirmation.
                """

For such a application (who do the cleanup jobs), allow user to dry run is a good idea.

    .options 'l',
      alias: 'list'
      describe: 'just print a list of result, will NOT delete anything'
      boolean: true

Give more messages...
Actually will print every file when renaming / deleting.

    .options 'v',
      alias: 'verbose'
      describe: 'print when renaming / deleting'
      boolean: true

Print the help text.

    .options 'h',
      alias: 'help'
      describe: 'print this help message'
      boolean: true

Change a new line at the 80th character.

    .wrap 80

Exports the whole yargs instance.

As a result, all properties & results of that are available.

    module.exports = yargs


Move square thumbnail higher up in versions array

    Upload = require 's3-uploader'

Configure [s3-uploader](https://github.com/Turistforeningen/node-s3-uploader) to
store the following versions:

| Suffix    | Max Height | Max Width | Aspect Ratio |
|-----------|------------|-----------|--------------|
| `-full`   | `1200`     | `1200`    |              |
| `-thumb`  | `250`      | `250`     | `1:1`        |
| `-800`    | `800`      | `800`     | `3:2`        |
| `-500`    | `500`      | `500`     | `3:2`        |
| `-260`    | `260`      | `260`     | `3:2`        |
| `-150`    | `150`      | `150`     | `3:2`        |

    module.exports = new Upload process.env.AWS_BUCKET_NAME,
      aws:
        region: process.env.AWS_BUCKET_REGION
        path: process.env.AWS_BUCKET_PATH
        acl: 'public-read'
        httpOptions: timeout: 60000

      returnExif: true

      cleanup:
        original: true
        versions: true

      original:
        awsImageAcl: 'private'

      versions: [
        suffix: '-full'
        quality: 80
        maxHeight: 1200
        maxWidth: 1200
      ,
        suffix: '-thumb'
        maxHeight: 250
        maxWidth: 250
        aspect: '1:1'
      ,
        suffix: '-800'
        maxHeight: 800
        maxWidth: 800
        aspect: '3:2'
      ,
        suffix: '-500'
        maxHeight: 500
        maxWidth: 500
        aspect: '3:2'
      ,
        suffix: '-260'
        maxHeight: 260
        maxWidth: 260
        aspect: '3:2'
      ,
        suffix: '-150'
        maxHeight: 150
        maxWidth: 150
        aspect: '3:2'
      ]

    Upload = require 's3-uploader'

Configure [s3-uploader](https://github.com/Turistforeningen/node-s3-uploader) to
store the following versions:

| Suffix    | Max Height | Max Width | Aspect Ratio |
|-----------|------------|-----------|--------------|
| `-full`   | `1200`     | `1200`    |              |
| `-800`    | `800`      | `800`     | `3:2`        |
| `-500`    | `500`      | `500`     | `3:2`        |
| `-260`    | `260`      | `260`     | `3:2`        |
| `-150`    | `150`      | `150`     | `3:2`        |
| `-thumb1` | `250`      | `250`     | `1:1`        |

    module.exports = new Upload process.env.AWS_BUCKET_NAME,
      aws:
        region: process.env.AWS_BUCKET_REGION
        path: process.env.AWS_BUCKET_PATH
        acl: 'public-read'
        httpOptions: timeout: 60000

      returnExif: true

      cleanup:
        original: true
        versions: true

      original:
        awsImageAcl: 'private'

      versions: [
        suffix: '-full'
        quality: 80
        maxHeight: 1200
        maxWidth: 1200
      ,
        suffix: '-800'
        maxHeight: 800
        maxWidth: 800
        aspect: '3:2'
      ,
        suffix: '-500'
        maxHeight: 500
        maxWidth: 500
        aspect: '3:2'
      ,
        suffix: '-260'
        maxHeight: 260
        maxWidth: 260
        aspect: '3:2'
      ,
        suffix: '-150'
        maxHeight: 150
        maxWidth: 150
        aspect: '3:2'
      ,
        suffix: '-thumb1'
        maxHeight: 250
        maxWidth: 250
        aspect: '1:1'
      ]


Modify prototype, not instance

Modify prototype, not instance

    Array.prototype.last = -> this[this.length - 1]

    q     = require 'q'
    fs    = require 'fs'
    async = require 'async'
    uuid  = require 'uuid-1345'

    microtime = require 'microtime'

    EventEmitter2 = require('eventemitter2').EventEmitter2
    emitter = new EventEmitter2 wildcard: true

    available   = { }
    initialized = { }

    code_root = process.env.MADUL_ROOT || 'dist'

    underscore = (value) -> value.replace /\W+/g, '_'

    WRAPPED = { }

    _INITIALIZERS = { }

    INITIALIZERS = (cls, method) ->
      _INITIALIZERS[cls] = [ ] unless _INITIALIZERS[cls]?

      _INITIALIZERS[cls].push method

    WRAP = (cls, method) ->
      unless WRAPPED[cls]?
        WRAPPED[cls] = [
          'constructor'
          'listen'
          'fire'
          'warn'
          'report'
          'do_wrap'
          'wrap_methods'
          'strip'
          'make_available'
          'hydrate'
          'finish_up'
          'initialize'
        ]

      WRAPPED[cls].indexOf(method) < 0

    class Madul

      @LISTEN: (event, callback) => emitter.on event, callback

      @FIRE: (event, args) => emitter.emit event, args

      listen: (event, callback) => Madul.LISTEN "*.#{@clazz}.#{event}", callback

      fire: (event, args) => Madul.FIRE "@.#{@clazz}.#{event}", args

      warn: (event, details) => Madul.FIRE "!.#{@clazz}.#{event}", details

      report: (method, state, id, args) =>
        @fire "#{method}.#{state}",
          uuid:      id
          args:      args
          timestamp: microtime.now()

      do_wrap: (method) =>
        proto  = @constructor.prototype
        callMe = proto[method]

        Madul.FIRE "$.#{@clazz}.#{method}.wrap"

        proto["_#{method}"] = @[method]

        respond = (id, deferred) =>
          (state, data) =>
            process.nextTick =>
              deferred[state] data

              @report method, state, id, data

        WRAPPED[@clazz].push method

        proto[method] = =>
          id   = uuid.v4fast()
          def  = q.defer()
          args = if arguments[0]? then Array.prototype.slice.call arguments else [ ]
          res  = respond id, def

          args.push (out) => res 'resolve', out
          args.push (err) => res 'reject',  err
          args.push (out) => res 'notify',  out

          @report method, 'invoke', id, args

          proto["_#{method}"].apply @, args

          def.promise

      wrap_methods: =>
        props = Object.keys @constructor.prototype
        proto = @constructor.prototype

        for prop in props
          if prop[0] != '_'                          &&
             typeof proto[prop] == 'function'        &&
             typeof proto["_#{prop}"] == 'undefined' &&
             WRAP(@clazz, prop) == true

            @do_wrap prop

            INITIALIZERS @clazz, prop if prop[0] == '$'

      strip: (name) => name.toLowerCase().replace /\W+/g, ''

      make_available: (name, mod) =>
        Madul.FIRE "$.#{name}.available"

        available[@strip name] = mod

      hydrate: (deps, done) =>
        load = (name, path, loaded) =>
          mod = require path

          if typeof mod == 'function'
            new mod name
              .initialize @ctor_params?[name]
              .then (mod) =>
                @make_available name, mod
                loaded()
          else
            @make_available name, mod
            loaded()

        check = (path, dep, finished) =>
          fs.readdir path, (err, files) =>
            if err?
              @warn 'file-not-found', err

              done type: 'ERROR', info: err
            else
              async.each files, (f, next) =>
                depth = "#{path}/#{f}"

                fs.stat depth, (e, s) =>
                  if s.isDirectory() && f[0] != '.'
                    check depth, dep, next
                  else if s.isFile() && f.substring(0, f.length - 3) == dep
                    load f.substring(0, f.length - 3), depth, => do_add dep, next
                  else
                    next()
              , finished

        add_dependency = (name, mod) =>
          Madul.FIRE "$.#{@clazz}.dependency.register", name
          @[underscore name] = mod

        do_add = (name, next) =>
          add_dependency name, available[@strip name]
          next()

        add = (name, resource, next) =>
          @make_available name, require resource
          do_add name, next

        async.each deps, (d, next) =>
          if @[underscore d]? == false
            if available[@strip d]? == true
              do_add d, next
            else
              try
                add d, d, next
              catch e
                cwd = process.cwd()
                pkg = "#{cwd}/node_modules/#{d}/package.json"

                fs.stat pkg, (err, stat) =>
                  if stat?.isFile()
                    fs.readFile pkg, 'utf8', (err, data) =>
                      json = JSON.parse data
                      main = json.main || json._main || 'index.js'

                      add d, "#{cwd}/node_modules/#{d}/#{main}", next
                  else
                    check "#{cwd}/#{code_root}", d, next
          else
            next()
        , done

      finish_up: (resolve, args) =>
        async.each _INITIALIZERS[@clazz], (initializer, next) =>
          @[initializer] args
            .then next
        , =>
          @make_available @clazz, @
          resolve @
          Madul.FIRE "$.#{@clazz}.initialized"

      initialize: =>
        deferred = q.defer()
        args     = if arguments[0]? then Array.prototype.slice.call arguments else undefined

        @clazz = @constructor.name unless @clazz?

        Madul.FIRE "$.#{@clazz}.request_instance", args

        if initialized[@clazz] != true
          Madul.FIRE "$.#{@clazz}.initialize", args

          initialized[@clazz] = true

          @wrap_methods()

          if @deps?
            @hydrate @deps, => @finish_up deferred.resolve, args
          else
            @finish_up deferred.resolve, args
        else
          process.nextTick => deferred.resolve available[@strip @clazz]

        deferred.promise

    module.exports = Madul

    Array.prototype.last = -> this[this.length - 1]

    q     = require 'q'
    fs    = require 'fs'
    async = require 'async'
    uuid  = require 'uuid-1345'

    microtime = require 'microtime'

    EventEmitter2 = require('eventemitter2').EventEmitter2
    emitter = new EventEmitter2 wildcard: true

    available   = { }
    initialized = { }

    code_root = process.env.MADUL_ROOT || 'dist'

    underscore = (value) -> value.replace /\W+/g, '_'

    WRAPPED = { }

    _INITIALIZERS = { }

    INITIALIZERS = (cls, method) ->
      _INITIALIZERS[cls] = [ ] unless _INITIALIZERS[cls]?

      _INITIALIZERS[cls].push method

    WRAP = (cls, method) ->
      unless WRAPPED[cls]?
        WRAPPED[cls] = [
          'constructor'
          'listen'
          'fire'
          'warn'
          'report'
          'do_wrap'
          'wrap_methods'
          'strip'
          'make_available'
          'hydrate'
          'finish_up'
          'initialize'
        ]

      WRAPPED[cls].indexOf(method) < 0

    class Madul

      @LISTEN: (event, callback) => emitter.on event, callback

      @FIRE: (event, args) => emitter.emit event, args

      listen: (event, callback) => Madul.LISTEN "*.#{@clazz}.#{event}", callback

      fire: (event, args) => Madul.FIRE "@.#{@clazz}.#{event}", args

      warn: (event, details) => Madul.FIRE "!.#{@clazz}.#{event}", details

      report: (method, state, id, args) =>
        @fire "#{method}.#{state}",
          uuid:      id
          args:      args
          timestamp: microtime.now()

      do_wrap: (method) =>
        callMe = @[method]

        Madul.FIRE "$.#{@clazz}.#{method}.wrap"

        @["_#{method}"] = @[method]

        respond = (id, deferred) =>
          (state, data) =>
            process.nextTick =>
              deferred[state] data

              @report method, state, id, data

        WRAPPED[@clazz].push method

        @[method] = =>
          id   = uuid.v4fast()
          def  = q.defer()
          args = if arguments[0]? then Array.prototype.slice.call arguments else [ ]
          res  = respond id, def

          args.push (out) => res 'resolve', out
          args.push (err) => res 'reject',  err
          args.push (out) => res 'notify',  out

          @report method, 'invoke', id, args

          @["_#{method}"].apply @, args

          def.promise

      wrap_methods: =>
        props = Object.keys @constructor.prototype

        for prop in props
          if prop[0] != '_'                      &&
             typeof @[prop] == 'function'        &&
             typeof @["_#{prop}"] == 'undefined' &&
             WRAP(@clazz, prop) == true

            @do_wrap prop

            INITIALIZERS @clazz, prop if prop[0] == '$'

      strip: (name) => name.toLowerCase().replace /\W+/g, ''

      make_available: (name, mod) =>
        Madul.FIRE "$.#{name}.available"

        available[@strip name] = mod

      hydrate: (deps, done) =>
        load = (name, path, loaded) =>
          mod = require path

          if typeof mod == 'function'
            new mod name
              .initialize @ctor_params?[name]
              .then (mod) =>
                @make_available name, mod
                loaded()
          else
            @make_available name, mod
            loaded()

        check = (path, dep, finished) =>
          fs.readdir path, (err, files) =>
            if err?
              @warn 'file-not-found', err

              done type: 'ERROR', info: err
            else
              async.each files, (f, next) =>
                depth = "#{path}/#{f}"

                fs.stat depth, (e, s) =>
                  if s.isDirectory() && f[0] != '.'
                    check depth, dep, next
                  else if s.isFile() && f.substring(0, f.length - 3) == dep
                    load f.substring(0, f.length - 3), depth, => do_add dep, next
                  else
                    next()
              , finished

        add_dependency = (name, mod) =>
          Madul.FIRE "$.#{@clazz}.dependency.register", name
          @[underscore name] = mod

        do_add = (name, next) =>
          add_dependency name, available[@strip name]
          next()

        add = (name, resource, next) =>
          @make_available name, require resource
          do_add name, next

        async.each deps, (d, next) =>
          if @[underscore d]? == false
            if available[@strip d]? == true
              do_add d, next
            else
              try
                add d, d, next
              catch e
                cwd = process.cwd()
                pkg = "#{cwd}/node_modules/#{d}/package.json"

                fs.stat pkg, (err, stat) =>
                  if stat?.isFile()
                    fs.readFile pkg, 'utf8', (err, data) =>
                      json = JSON.parse data
                      main = json.main || json._main || 'index.js'

                      add d, "#{cwd}/node_modules/#{d}/#{main}", next
                  else
                    check "#{cwd}/#{code_root}", d, next
          else
            next()
        , done

      finish_up: (resolve, args) =>
        async.each _INITIALIZERS[@clazz], (initializer, next) =>
          @[initializer] args
            .then next
        , =>
          @make_available @clazz, @
          resolve @
          Madul.FIRE "$.#{@clazz}.initialized"

      initialize: =>
        deferred = q.defer()
        args     = if arguments[0]? then Array.prototype.slice.call arguments else undefined

        @clazz = @constructor.name unless @clazz?

        Madul.FIRE "$.#{@clazz}.request_instance", args

        if initialized[@clazz] != true
          Madul.FIRE "$.#{@clazz}.initialize", args

          initialized[@clazz] = true

          @wrap_methods()

          if @deps?
            @hydrate @deps, => @finish_up deferred.resolve, args
          else
            @finish_up deferred.resolve, args
        else
          process.nextTick => deferred.resolve available[@strip @clazz]

        deferred.promise

    module.exports = Madul


Navmesh.constrain, as documented, no longer pins the location to the navmesh surface, only following the walls.

# navmesh

Navmeshes are stored as "MSN" (MasSplat Navmesh) and are used to define where
entities (including the player) can travel.  By default, they are unable to
leave the surface, "crawling" over it.  Each tile is a 3D triangle.  A light
Any two triangles sharing two vertices are joined and the edge formed may be
crossed from one to the other, otherwise, the edge is treated as a form of wall.

MSN is a binary format.  It is comprised of:

| Type    | Num.              | Description                                                |
| ------- | ----------------- | ---------------------------------------------------------- |
| uint16  | 1                 | The number of vertices defined.                            |
| uint16  | 1                 | The number of triangles defined.                           |
| float32 | 3x num. vertices  | The X, Y and Z of each vertex.                             |
| uint16  | 3x num. triangles | The first, second and third vertex index of each triangle. |

    file = require "./file.litcoffee"
    vector = require "./vector.litcoffee"
    plane = require "./plane.litcoffee"

## load

- filename: The "real" location of the MSN file to load.  (i.e. already
            "require"-d)
- callback: A callback to execute with the array of objects representing the
            loaded objects on success, containing:

+ plane: A plane object for the surface of the triangle itself.
+ vertices: An array of vectors specifying the locations of the vertices which
            make up the triangle.
+ edges: An array of three objects representing the edges of the triangle,
       containing:

* neighbor: When null, there is no other triangle over this edge.  Otherwise
            a reference to the triangle object which can be found over it.
* plane: A plane facing inwards from the edge.  If this edge borders another
         triangle, this will be aligned to both this triangle and the
         neighbor.

    load = (path, callback) ->
        file.arrayBuffer path, (response) ->
            header = new Uint16Array response, 0, 2
            vertexData = new Float32Array response, 4, header[0] * 3
            indices = new Uint16Array response, 4 + header[0] * 4 * 3, header[1] * 3

            vertices = for i in [0...vertexData.length] by 3
                [
                    vertexData[i]
                    vertexData[i + 1]
                    vertexData[i + 2]
                ]

            triangles = for i in [0...indices.length] by 3
                plane: plane.fromTriangle vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]
                indices: [
                    indices[i]
                    indices[i + 1]
                    indices[i + 2]
                ]
                vertices: [
                    vertices[indices[i]]
                    vertices[indices[i + 1]]
                    vertices[indices[i + 2]]
                ]

            for triangle in triangles
                triangle.edges = for edge in [0...3]
                    vertexA = triangle.indices[edge]
                    vertexB = triangle.indices[(edge + 1) % 3]
                    vertexC = []
                    vector.add.vector vertices[vertexA], triangle.plane.normal, vertexC
                    neighbor = null
                    for other in triangles
                        if triangle is other then continue
                        if vertexA not in other.indices then continue
                        if vertexB not in other.indices then continue
                        neighbor = other
                        vector.add.vector vertexC, other.plane.normal, vertexC
                        break
                    neighbor: neighbor
                    plane: plane.fromTriangle vertices[vertexA], vertexC, vertices[vertexB]

            callback triangles
            return
        return

## constrain

- A vector specifying the location moved to.
- A reference to the navmesh triangle which contained the location moved from.

Updates the location vector to take into account collision with navmesh triangle
walls and returns the new containing navmesh triangle.

    constrain = (location, triangle) ->

        for iterations in [0...25]
            distanceA = plane.distance triangle.edges[0].plane, location
            distanceB = plane.distance triangle.edges[1].plane, location
            distanceC = plane.distance triangle.edges[2].plane, location
            if distanceA >= 0 and distanceB >= 0 and distanceC >= 0
                break

            if distanceA < 0 and triangle.edges[0].neighbor
                triangle = triangle.edges[0].neighbor
                continue

            if distanceB < 0 and triangle.edges[1].neighbor
                triangle = triangle.edges[1].neighbor
                continue

            if distanceC < 0 and triangle.edges[2].neighbor
                triangle = triangle.edges[2].neighbor
                continue

            if distanceA < 0 and distanceB < 0
                vector.copy triangle.vertices[1], location
                continue

            if distanceB < 0 and distanceC < 0
                vector.copy triangle.vertices[2], location
                continue

            if distanceC < 0 and distanceA < 0
                vector.copy triangle.vertices[0], location
                continue

            if distanceA < 0
                plane.project triangle.edges[0].plane, location, location
                continue

            if distanceB < 0
                plane.project triangle.edges[1].plane, location, location
                continue

            if distanceC < 0
                plane.project triangle.edges[2].plane, location, location
                continue

        triangle

    module.exports = { load, constrain }

# navmesh

Navmeshes are stored as "MSN" (MasSplat Navmesh) and are used to define where
entities (including the player) can travel.  By default, they are unable to
leave the surface, "crawling" over it.  Each tile is a 3D triangle.  A light
Any two triangles sharing two vertices are joined and the edge formed may be
crossed from one to the other, otherwise, the edge is treated as a form of wall.

MSN is a binary format.  It is comprised of:

| Type    | Num.              | Description                                                |
| ------- | ----------------- | ---------------------------------------------------------- |
| uint16  | 1                 | The number of vertices defined.                            |
| uint16  | 1                 | The number of triangles defined.                           |
| float32 | 3x num. vertices  | The X, Y and Z of each vertex.                             |
| uint16  | 3x num. triangles | The first, second and third vertex index of each triangle. |

    file = require "./file.litcoffee"
    vector = require "./vector.litcoffee"
    plane = require "./plane.litcoffee"

## load

- filename: The "real" location of the MSN file to load.  (i.e. already
            "require"-d)
- callback: A callback to execute with the array of objects representing the
            loaded objects on success, containing:

+ plane: A plane object for the surface of the triangle itself.
+ vertices: An array of vectors specifying the locations of the vertices which
            make up the triangle.
+ edges: An array of three objects representing the edges of the triangle,
       containing:

* neighbor: When null, there is no other triangle over this edge.  Otherwise
            a reference to the triangle object which can be found over it.
* plane: A plane facing inwards from the edge.  If this edge borders another
         triangle, this will be aligned to both this triangle and the
         neighbor.

    load = (path, callback) ->
        file.arrayBuffer path, (response) ->
            header = new Uint16Array response, 0, 2
            vertexData = new Float32Array response, 4, header[0] * 3
            indices = new Uint16Array response, 4 + header[0] * 4 * 3, header[1] * 3

            vertices = for i in [0...vertexData.length] by 3
                [
                    vertexData[i]
                    vertexData[i + 1]
                    vertexData[i + 2]
                ]

            triangles = for i in [0...indices.length] by 3
                plane: plane.fromTriangle vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]
                indices: [
                    indices[i]
                    indices[i + 1]
                    indices[i + 2]
                ]
                vertices: [
                    vertices[indices[i]]
                    vertices[indices[i + 1]]
                    vertices[indices[i + 2]]
                ]

            for triangle in triangles
                triangle.edges = for edge in [0...3]
                    vertexA = triangle.indices[edge]
                    vertexB = triangle.indices[(edge + 1) % 3]
                    vertexC = []
                    vector.add.vector vertices[vertexA], triangle.plane.normal, vertexC
                    neighbor = null
                    for other in triangles
                        if triangle is other then continue
                        if vertexA not in other.indices then continue
                        if vertexB not in other.indices then continue
                        neighbor = other
                        vector.add.vector vertexC, other.plane.normal, vertexC
                        break
                    neighbor: neighbor
                    plane: plane.fromTriangle vertices[vertexA], vertexC, vertices[vertexB]

            callback triangles
            return
        return

## constrain

- A vector specifying the location moved to.
- A reference to the navmesh triangle which contained the location moved from.

Updates the location vector to take into account collision with navmesh triangle
walls and returns the new containing navmesh triangle.

    constrain = (location, triangle) ->

        for iterations in [0...25]
            distanceA = plane.distance triangle.edges[0].plane, location
            distanceB = plane.distance triangle.edges[1].plane, location
            distanceC = plane.distance triangle.edges[2].plane, location
            if distanceA >= 0 and distanceB >= 0 and distanceC >= 0
                break

            if distanceA < 0 and triangle.edges[0].neighbor
                triangle = triangle.edges[0].neighbor
                continue

            if distanceB < 0 and triangle.edges[1].neighbor
                triangle = triangle.edges[1].neighbor
                continue

            if distanceC < 0 and triangle.edges[2].neighbor
                triangle = triangle.edges[2].neighbor
                continue

            if distanceA < 0 and distanceB < 0
                vector.copy triangle.vertices[1], location
                continue

            if distanceB < 0 and distanceC < 0
                vector.copy triangle.vertices[2], location
                continue

            if distanceC < 0 and distanceA < 0
                vector.copy triangle.vertices[0], location
                continue

            if distanceA < 0
                plane.project triangle.edges[0].plane, location, location
                continue

            if distanceB < 0
                plane.project triangle.edges[1].plane, location, location
                continue

            if distanceC < 0
                plane.project triangle.edges[2].plane, location, location
                continue

        plane.project triangle.plane, location, location
        triangle

    module.exports = { load, constrain }


Fixed crash when entering the corner of a navmesh triangle.

# navmesh

Navmeshes are stored as "MSN" (MasSplat Navmesh) and are used to define where
entities (including the player) can travel.  By default, they are unable to
leave the surface, "crawling" over it.  Each tile is a 3D triangle.  A light
Any two triangles sharing two vertices are joined and the edge formed may be
crossed from one to the other, otherwise, the edge is treated as a form of wall.

MSN is a binary format.  It is comprised of:

| Type    | Num.              | Description                                                |
| ------- | ----------------- | ---------------------------------------------------------- |
| uint16  | 1                 | The number of vertices defined.                            |
| uint16  | 1                 | The number of triangles defined.                           |
| float32 | 3x num. vertices  | The X, Y and Z of each vertex.                             |
| uint16  | 3x num. triangles | The first, second and third vertex index of each triangle. |

    file = require "./file.litcoffee"
    vector = require "./vector.litcoffee"
    plane = require "./plane.litcoffee"

## load

- filename: The "real" location of the MSN file to load.  (i.e. already
            "require"-d)
- callback: A callback to execute with the array of objects representing the
            loaded objects on success, containing:

+ plane: A plane object for the surface of the triangle itself.
+ vertices: An array of vectors specifying the locations of the vertices which
            make up the triangle.
+ edges: An array of three objects representing the edges of the triangle,
       containing:

* neighbor: When null, there is no other triangle over this edge.  Otherwise
            a reference to the triangle object which can be found over it.
* plane: A plane facing inwards from the edge.  If this edge borders another
         triangle, this will be aligned to both this triangle and the
         neighbor.

    load = (path, callback) ->
        file.arrayBuffer path, (response) ->
            header = new Uint16Array response, 0, 2
            vertexData = new Float32Array response, 4, header[0] * 3
            indices = new Uint16Array response, 4 + header[0] * 4 * 3, header[1] * 3

            vertices = for i in [0...vertexData.length] by 3
                [
                    vertexData[i]
                    vertexData[i + 1]
                    vertexData[i + 2]
                ]

            triangles = for i in [0...indices.length] by 3
                plane: plane.fromTriangle vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]
                indices: [
                    indices[i]
                    indices[i + 1]
                    indices[i + 2]
                ]
                vertices: [
                    vertices[indices[i]]
                    vertices[indices[i + 1]]
                    vertices[indices[i + 2]]
                ]

            for triangle in triangles
                triangle.edges = for edge in [0...3]
                    vertexA = triangle.indices[edge]
                    vertexB = triangle.indices[(edge + 1) % 3]
                    vertexC = []
                    vector.add.vector vertices[vertexA], triangle.plane.normal, vertexC
                    neighbor = null
                    for other in triangles
                        if triangle is other then continue
                        if vertexA not in other.indices then continue
                        if vertexB not in other.indices then continue
                        neighbor = other
                        vector.add.vector vertexC, other.plane.normal, vertexC
                        break
                    neighbor: neighbor
                    plane: plane.fromTriangle vertices[vertexA], vertexC, vertices[vertexB]

            callback triangles
            return
        return

## constrain

Call with an object containing:

- location: An array of three numbers specifying the X, Y and Z moved to.
- triangle: A reference to the triangle the entity is currently in.

This will modify these properties to constrain the entity to the surface of the
navmesh.

    constrain = (obj) ->

        for iterations in [0...25]
            distanceA = plane.distance obj.triangle.edges[0].plane, obj.location
            distanceB = plane.distance obj.triangle.edges[1].plane, obj.location
            distanceC = plane.distance obj.triangle.edges[2].plane, obj.location
            if distanceA >= 0 and distanceB >= 0 and distanceC >= 0
                break

            if distanceA < 0 and obj.triangle.edges[0].neighbor
                obj.triangle = obj.triangle.edges[0].neighbor
                continue

            if distanceB < 0 and obj.triangle.edges[1].neighbor
                obj.triangle = obj.triangle.edges[1].neighbor
                continue

            if distanceC < 0 and obj.triangle.edges[2].neighbor
                obj.triangle = obj.triangle.edges[2].neighbor
                continue

            if distanceA < 0 and distanceB < 0
                vector.copy obj.triangle.vertices[1], obj.location
                continue

            if distanceB < 0 and distanceC < 0
                vector.copy obj.triangle.vertices[2], obj.location
                continue

            if distanceC < 0 and distanceA < 0
                vector.copy obj.triangle.vertices[0], obj.location
                continue

            if distanceA < 0
                plane.project obj.triangle.edges[0].plane, obj.location, obj.location
                continue

            if distanceB < 0
                plane.project obj.triangle.edges[1].plane, obj.location, obj.location
                continue

            if distanceC < 0
                plane.project obj.triangle.edges[2].plane, obj.location, obj.location
                continue

        plane.project obj.triangle.plane, obj.location, obj.location
        return

    module.exports = { load, constrain }

# navmesh

Navmeshes are stored as "MSN" (MasSplat Navmesh) and are used to define where
entities (including the player) can travel.  By default, they are unable to
leave the surface, "crawling" over it.  Each tile is a 3D triangle.  A light
Any two triangles sharing two vertices are joined and the edge formed may be
crossed from one to the other, otherwise, the edge is treated as a form of wall.

MSN is a binary format.  It is comprised of:

| Type    | Num.              | Description                                                |
| ------- | ----------------- | ---------------------------------------------------------- |
| uint16  | 1                 | The number of vertices defined.                            |
| uint16  | 1                 | The number of triangles defined.                           |
| float32 | 3x num. vertices  | The X, Y and Z of each vertex.                             |
| uint16  | 3x num. triangles | The first, second and third vertex index of each triangle. |

    file = require "./file.litcoffee"
    vector = require "./vector.litcoffee"
    plane = require "./plane.litcoffee"

## load

- filename: The "real" location of the MSN file to load.  (i.e. already
            "require"-d)
- callback: A callback to execute with the array of objects representing the
            loaded objects on success, containing:

+ plane: A plane object for the surface of the triangle itself.
+ edges: An array of three objects representing the edges of the triangle,
       containing:

* neighbor: When null, there is no other triangle over this edge.  Otherwise
            a reference to the triangle object which can be found over it.
* plane: A plane facing inwards from the edge.  If this edge borders another
         triangle, this will be aligned to both this triangle and the
         neighbor.

    load = (path, callback) ->
        file.arrayBuffer path, (response) ->
            header = new Uint16Array response, 0, 2
            vertexData = new Float32Array response, 4, header[0] * 3
            indices = new Uint16Array response, 4 + header[0] * 4 * 3, header[1] * 3

            vertices = for i in [0...vertexData.length] by 3
                [
                    vertexData[i]
                    vertexData[i + 1]
                    vertexData[i + 2]
                ]

            triangles = for i in [0...indices.length] by 3
                plane: plane.fromTriangle vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]
                indices: [
                    indices[i]
                    indices[i + 1]
                    indices[i + 2]
                ]

            for triangle in triangles
                triangle.edges = for edge in [0...3]
                    vertexA = triangle.indices[edge]
                    vertexB = triangle.indices[(edge + 1) % 3]
                    vertexC = []
                    vector.add.vector vertices[vertexA], triangle.plane.normal, vertexC
                    neighbor = null
                    for other in triangles
                        if triangle is other then continue
                        if vertexA not in other.indices then continue
                        if vertexB not in other.indices then continue
                        neighbor = other
                        vector.add.vector vertexC, other.plane.normal, vertexC
                        break
                    neighbor: neighbor
                    plane: plane.fromTriangle vertices[vertexA], vertexC, vertices[vertexB]

            callback triangles
            return
        return

## constrain

Call with an object containing:

- location: An array of three numbers specifying the X, Y and Z moved to.
- triangle: A reference to the triangle the entity is currently in.

This will modify these properties to constrain the entity to the surface of the
navmesh.

    constrain = (obj) ->

        for iterations in [0...25]
            distanceA = plane.distance obj.triangle.edges[0].plane, obj.location
            distanceB = plane.distance obj.triangle.edges[1].plane, obj.location
            distanceC = plane.distance obj.triangle.edges[2].plane, obj.location
            if distanceA >= 0 and distanceB >= 0 and distanceC >= 0
                break

            if distanceA < 0 and obj.triangle.edges[0].neighbor
                obj.triangle = obj.triangle.edges[0].neighbor
                continue

            if distanceB < 0 and obj.triangle.edges[1].neighbor
                obj.triangle = obj.triangle.edges[1].neighbor
                continue

            if distanceC < 0 and obj.triangle.edges[2].neighbor
                obj.triangle = obj.triangle.edges[2].neighbor
                continue

            if distanceA < 0 and distanceB < 0
                vector.copy vertices[obj.triangle.indices[1]], obj.location
                continue

            if distanceB < 0 and distanceC < 0
                vector.copy vertices[obj.triangle.indices[2]], obj.location
                continue

            if distanceC < 0 and distanceA < 0
                vector.copy vertices[obj.triangle.indices[0]], obj.location
                continue

            if distanceA < 0
                plane.project obj.triangle.edges[0].plane, obj.location, obj.location
                continue

            if distanceB < 0
                plane.project obj.triangle.edges[1].plane, obj.location, obj.location
                continue

            if distanceC < 0
                plane.project obj.triangle.edges[2].plane, obj.location, obj.location
                continue

        plane.project obj.triangle.plane, obj.location, obj.location
        return

    module.exports = { load, constrain }