2014-01-03 13:32:13 -05:00
CocoModel = require ( ' ./CocoModel ' )
SpriteBuilder = require ' lib/sprites/SpriteBuilder '
2014-03-13 16:25:03 -04:00
buildQueue = [ ]
2014-01-03 13:32:13 -05:00
module.exports = class ThangType extends CocoModel
@className: " ThangType "
2014-04-22 14:11:08 -04:00
@schema: require ' schemas/models/thang_type '
2014-01-03 13:32:13 -05:00
urlRoot: " /db/thang.type "
2014-01-14 16:16:30 -05:00
building: { }
2014-01-03 13:32:13 -05:00
initialize: ->
super ( )
2014-01-14 16:16:30 -05:00
@building = { }
2014-01-03 13:32:13 -05:00
@ setDefaults ( )
@ on ' sync ' , @ setDefaults
@spriteSheets = { }
setDefaults: ->
@ resetRawData ( ) unless @ get ( ' raw ' )
resetRawData: ->
@ set ( ' raw ' , { shapes : { } , containers : { } , animations : { } } )
resetSpriteSheetCache: ->
@ buildActions ( )
@spriteSheets = { }
2014-01-21 02:45:27 -05:00
@building = { }
2014-05-13 13:26:33 -04:00
isFullyLoaded: ->
# TODO: Come up with a better way to identify when the model doesn't have everything needed to build the sprite. ie when it's a projection without all the required data.
return @ get ( ' actions ' ) or @ get ( ' raster ' ) # needs one of these two things
2014-01-03 13:32:13 -05:00
getActions: ->
2014-05-13 13:26:33 -04:00
return { } unless @ isFullyLoaded ( )
2014-01-03 13:32:13 -05:00
return @ actions or @ buildActions ( )
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
buildActions: ->
2014-05-13 13:26:33 -04:00
return null unless @ isFullyLoaded ( )
@actions = $ . extend ( true , { } , @ get ( ' actions ' ) )
2014-01-03 13:32:13 -05:00
for name , action of @ actions
action.name = name
for relatedName , relatedAction of action . relatedActions ? { }
relatedAction.name = action . name + " _ " + relatedName
@ actions [ relatedAction . name ] = relatedAction
@ actions
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
getSpriteSheet: (options) ->
options = @ fillOptions options
key = @ spriteSheetKey ( options )
return @ spriteSheets [ key ] or @ buildSpriteSheet ( options )
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
fillOptions: (options) ->
options ? = { }
options = _ . clone options
options . resolutionFactor ? = 4
options . async ? = false
options
buildSpriteSheet: (options) ->
2014-05-13 13:26:33 -04:00
return false unless @ isFullyLoaded ( )
2014-01-14 16:16:30 -05:00
@options = @ fillOptions options
key = @ spriteSheetKey ( @ options )
return if @ building [ key ]
2014-01-04 11:55:12 -05:00
@ initBuild ( options )
@ addGeneralFrames ( ) unless @ options . portraitOnly
@ addPortrait ( )
2014-01-14 16:16:30 -05:00
@ building [ key ] = true
result = @ finishBuild ( )
return result
2014-01-03 13:32:13 -05:00
2014-01-04 11:55:12 -05:00
initBuild: (options) ->
@ buildActions ( ) if not @ actions
2014-01-12 15:27:10 -05:00
@vectorParser = new SpriteBuilder ( @ , options )
2014-01-04 11:55:12 -05:00
@builder = new createjs . SpriteSheetBuilder ( )
@builder.padding = 2
@frames = { }
2014-01-15 16:04:48 -05:00
2014-01-04 11:55:12 -05:00
addPortrait: ->
# The portrait is built very differently than the other animations, so it gets a separate function.
2014-01-06 15:37:35 -05:00
return unless @ actions
2014-01-04 11:55:12 -05:00
portrait = @ actions . portrait
return unless portrait
scale = portrait . scale or 1
pt = portrait . positions ? . registration
rect = new createjs . Rectangle ( pt ? . x / scale or 0 , pt ? . y / scale or 0 , 100 / scale , 100 / scale )
if portrait . animation
mc = @ vectorParser . buildMovieClip portrait . animation
mc.nominalBounds = mc.frameBounds = null # override what the movie clip says on bounding
@ builder . addMovieClip ( mc , rect , scale )
frames = @ builder . _animations [ portrait . animation ] . frames
2014-01-09 01:30:00 -05:00
frames = @ mapFrames ( portrait . frames , frames [ 0 ] ) if portrait . frames ?
2014-01-04 11:55:12 -05:00
@ builder . addAnimation ' portrait ' , frames , true
else if portrait . container
s = @ vectorParser . buildContainerFromStore ( portrait . container )
frame = @ builder . addFrame ( s , rect , scale )
@ builder . addAnimation ' portrait ' , [ frame ] , false
2014-01-03 13:32:13 -05:00
2014-01-04 11:55:12 -05:00
addGeneralFrames: ->
2014-01-03 13:32:13 -05:00
framesMap = { }
for animation in @ requiredRawAnimations ( )
name = animation . animation
2014-01-04 11:55:12 -05:00
mc = @ vectorParser . buildMovieClip name
2014-01-30 19:17:41 -05:00
continue unless mc
2014-01-04 11:55:12 -05:00
@ builder . addMovieClip mc , null , animation . scale * @ options . resolutionFactor
framesMap [ animation . scale + " _ " + name ] = @ builder . _animations [ name ] . frames
2014-01-03 13:32:13 -05:00
for name , action of @ actions when action . animation
2014-01-04 11:55:12 -05:00
continue if name is ' portrait '
scale = action . scale ? @ get ( ' scale ' ) ? 1
frames = framesMap [ scale + " _ " + action . animation ]
2014-01-30 19:17:41 -05:00
continue unless frames
2014-01-04 11:55:12 -05:00
frames = @ mapFrames ( action . frames , frames [ 0 ] ) if action . frames ?
2014-01-03 13:32:13 -05:00
next = true
2014-01-04 11:55:12 -05:00
next = action . goesTo if action . goesTo
next = false if action . loops is false
@ builder . addAnimation name , frames , next
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
for name , action of @ actions when action . container and not action . animation
2014-01-04 11:55:12 -05:00
continue if name is ' portrait '
scale = @ options . resolutionFactor * ( action . scale or @ get ( ' scale ' ) or 1 )
s = @ vectorParser . buildContainerFromStore ( action . container )
2014-01-30 19:17:41 -05:00
continue unless s
2014-01-04 11:55:12 -05:00
frame = @ builder . addFrame ( s , s . bounds , scale )
@ builder . addAnimation name , [ frame ] , false
2014-01-09 01:30:00 -05:00
requiredRawAnimations: ->
required = [ ]
for name , action of @ get ( ' actions ' )
continue if name is ' portrait '
allActions = [ action ] . concat ( _ . values ( action . relatedActions ? { } ) )
for a in allActions when a . animation
scale = if name is ' portrait ' then a . scale or 1 else a . scale or @ get ( ' scale ' ) or 1
animation = { animation: a . animation , scale: scale }
animation.portrait = name is ' portrait '
unless _ . find ( required , (r) -> _ . isEqual r , animation )
required . push animation
required
2014-01-04 11:55:12 -05:00
mapFrames: (frames, frameOffset) ->
return frames unless _ . isString ( frames ) # don't accidentally do this again
( parseInt ( f , 10 ) + frameOffset for f in frames . split ( ' , ' ) )
2014-01-03 13:32:13 -05:00
2014-01-04 11:55:12 -05:00
finishBuild: ->
return if _ . isEmpty ( @ builder . _animations )
key = @ spriteSheetKey ( @ options )
2014-01-03 13:32:13 -05:00
spriteSheet = null
2014-01-04 11:55:12 -05:00
if @ options . async
2014-03-13 18:45:24 -04:00
buildQueue . push @ builder
2014-03-19 20:11:45 -04:00
@builder.t0 = new Date ( ) . getTime ( )
2014-03-13 16:25:03 -04:00
@ builder . buildAsync ( ) unless buildQueue . length > 1
2014-01-04 11:55:12 -05:00
@ builder . on ' complete ' , @ onBuildSpriteSheetComplete , @ , true , key
2014-01-03 13:32:13 -05:00
return true
2014-03-23 19:48:30 -04:00
t0 = new Date ( )
2014-01-04 11:55:12 -05:00
spriteSheet = @ builder . build ( )
2014-05-13 13:26:33 -04:00
console . debug " Built #{ @ get ( ' name ' ) } in #{ new Date ( ) - t0 } ms. "
2014-01-04 11:55:12 -05:00
@ spriteSheets [ key ] = spriteSheet
2014-01-21 02:45:27 -05:00
delete @ building [ key ]
2014-01-03 13:32:13 -05:00
spriteSheet
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
onBuildSpriteSheetComplete: (e, key) ->
2014-03-19 20:11:45 -04:00
console . log " Built #{ @ get ( ' name ' ) } async in #{ new Date ( ) . getTime ( ) - @ builder . t0 } ms. " if @ builder
2014-03-13 16:25:03 -04:00
buildQueue = buildQueue . slice ( 1 )
2014-03-19 20:11:45 -04:00
buildQueue [ 0 ] . t0 = new Date ( ) . getTime ( ) if buildQueue [ 0 ]
2014-03-13 18:45:24 -04:00
buildQueue [ 0 ] ? . buildAsync ( )
2014-01-03 13:32:13 -05:00
@ spriteSheets [ key ] = e . target . spriteSheet
2014-01-21 02:45:27 -05:00
delete @ building [ key ]
2014-01-03 13:32:13 -05:00
@ trigger ' build-complete '
2014-01-15 16:04:48 -05:00
@builder = null
@vectorParser = null
2014-01-03 13:32:13 -05:00
spriteSheetKey: (options) ->
2014-01-12 15:27:10 -05:00
colorConfigs = [ ]
for groupName , config of options . colorConfig or { }
colorConfigs . push " #{ groupName } : #{ config . hue } | #{ config . saturation } | #{ config . lightness } "
colorConfigs = colorConfigs . join ' , '
2014-03-05 15:53:48 -05:00
portraitOnly = ! ! options . portraitOnly
" #{ @ get ( ' name ' ) } - #{ options . resolutionFactor } - #{ colorConfigs } - #{ portraitOnly } "
2014-01-03 13:32:13 -05:00
2014-01-06 15:37:35 -05:00
getPortraitImage: (spriteOptionsOrKey, size=100) ->
src = @ getPortraitSource ( spriteOptionsOrKey , size )
2014-01-09 01:30:00 -05:00
return null unless src
2014-01-06 18:36:35 -05:00
$ ( ' <img /> ' ) . attr ( ' src ' , src )
2014-01-06 15:37:35 -05:00
getPortraitSource: (spriteOptionsOrKey, size=100) ->
2014-01-09 01:30:00 -05:00
stage = @ getPortraitStage ( spriteOptionsOrKey , size )
stage ? . toDataURL ( )
getPortraitStage: (spriteOptionsOrKey, size=100) ->
2014-05-13 13:26:33 -04:00
return unless @ isFullyLoaded ( )
2014-01-03 13:32:13 -05:00
key = spriteOptionsOrKey
2014-01-09 14:04:22 -05:00
key = if _ . isString ( key ) then key else @ spriteSheetKey ( @ fillOptions ( key ) )
2014-01-03 13:32:13 -05:00
spriteSheet = @ spriteSheets [ key ]
2014-03-03 13:21:51 -05:00
if not spriteSheet
options = if _ . isPlainObject spriteOptionsOrKey then spriteOptionsOrKey else { }
options.portraitOnly = true
spriteSheet = @ buildSpriteSheet ( options )
2014-01-03 13:32:13 -05:00
return unless spriteSheet
canvas = $ ( " <canvas width= ' #{ size } ' height= ' #{ size } ' ></canvas> " )
stage = new createjs . Stage ( canvas [ 0 ] )
sprite = new createjs . Sprite ( spriteSheet )
pt = @ actions . portrait ? . positions ? . registration
sprite.regX = pt ? . x or 0
sprite.regY = pt ? . y or 0
2014-02-17 20:38:49 -05:00
sprite.framerate = @ actions . portrait ? . framerate ? 20
2014-01-03 13:32:13 -05:00
sprite . gotoAndStop ' portrait '
stage . addChild ( sprite )
stage . update ( )
2014-01-09 01:30:00 -05:00
stage.startTalking = ->
sprite . gotoAndPlay ' portrait '
2014-01-09 14:04:22 -05:00
return if @ tick
2014-02-17 20:38:49 -05:00
@tick = (e) => @ update ( e )
2014-01-09 01:30:00 -05:00
createjs . Ticker . addEventListener ' tick ' , @ tick
stage.stopTalking = ->
2014-01-09 14:04:22 -05:00
sprite . gotoAndStop ' portrait '
@ update ( )
2014-01-09 01:30:00 -05:00
createjs . Ticker . removeEventListener ' tick ' , @ tick
2014-01-09 14:04:22 -05:00
@tick = null
2014-01-09 01:30:00 -05:00
stage
2014-01-15 16:04:48 -05:00
2014-05-02 20:03:30 -04:00
uploadGenericPortrait: (callback, src) ->
src ? = @ getPortraitSource ( )
2014-01-09 14:04:22 -05:00
return callback ? ( ) unless src
2014-01-06 15:37:35 -05:00
src = src . replace ( ' data:image/png;base64, ' , ' ' ) . replace ( /\ /g , ' + ' )
body =
filename: ' portrait.png '
mimetype: ' image/png '
path: " db/thang.type/ #{ @ get ( ' original ' ) } "
b64png: src
force: ' true '
2014-01-06 18:36:35 -05:00
$ . ajax ( ' /file ' , { type: ' POST ' , data: body , success: callback or @ onFileUploaded } )
2014-01-06 15:37:35 -05:00
onFileUploaded: =>
console . log ' Image uploaded '
2014-03-03 12:03:44 -05:00
2014-03-03 13:42:11 -05:00
@loadUniversalWizard: ->
return @ wizardType if @ wizardType
wizOriginal = " 52a00d55cf1818f2be00000b "
2014-03-11 21:30:25 -04:00
url = " /db/thang.type/ #{ wizOriginal } /version "
2014-03-03 13:42:11 -05:00
@wizardType = new module . exports ( )
@wizardType.url = -> url
@ wizardType . fetch ( )
2014-03-11 21:30:25 -04:00
@ wizardType