Added WebGL paths.

This commit is contained in:
Scott Erickson 2014-11-18 11:46:42 -08:00
parent 6ebc9e8c28
commit aab51178e2
4 changed files with 122 additions and 316 deletions

View file

@ -1,5 +1,5 @@
CocoClass = require 'lib/CocoClass'
path = require './path'
TrailMaster = require './TrailMaster'
Dropper = require './Dropper'
AudioPlayer = require 'lib/AudioPlayer'
{me} = require 'lib/auth'
@ -181,7 +181,6 @@ module.exports = Surface = class Surface extends CocoClass
framesDropped = 0
while true
Dropper.tick()
@trailmaster.tick() if @trailmaster
# Skip some frame updates unless we're playing and not at end (or we haven't drawn much yet)
frameAdvanced = (@playing and @currentFrame < lastFrame) or @totalFramesDrawn < 2
if frameAdvanced and @playing
@ -210,7 +209,6 @@ module.exports = Surface = class Surface extends CocoClass
@updateState @currentFrame isnt oldFrame
@drawCurrentFrame e
@onFrameChanged()
@updatePaths() if (@totalFramesDrawn % 4) is 0 or createjs.Ticker.getMeasuredFPS() > createjs.Ticker.getFPS() - 5
Backbone.Mediator.publish('surface:ticked', {dt: 1 / @options.frameRate})
mib = @webGLStage.mouseInBounds
if @mouseInBounds isnt mib
@ -456,7 +454,8 @@ module.exports = Surface = class Surface extends CocoClass
@fastForwardingSpeed = lag / intendedLag
else
@fastForwardingToFrame = @fastForwardingSpeed = null
#console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt
# console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt
@updatePaths()
onIdleChanged: (e) ->
@setPaused e.idle unless @ended
@ -540,10 +539,12 @@ module.exports = Surface = class Surface extends CocoClass
#- Camera focus on hero
focusOnHero: ->
hadHero = @heroLank
@heroLank = @lankBoss.lankFor 'Hero Placeholder'
if me.team is 'ogres'
# TODO: do this for real
@heroLank = @lankBoss.lankFor 'Hero Placeholder 1'
@updatePaths() if not hadHero
#- Real-time playback
@ -576,22 +577,16 @@ module.exports = Surface = class Surface extends CocoClass
#- Paths - TODO: move to LankBoss? but only update on frame drawing instead of on every frame update?
updatePaths: ->
return # TODO: Get paths working again with WebGL
return unless @options.paths
return if @casting
return unless @options.paths and @heroLank
return unless me.isAdmin() # TODO: Fix world thang points, targets, then remove this
@hidePaths()
selectedThang = @lankBoss.selectedLank?.thang
return if @world.showPaths is 'never'
return if @world.showPaths is 'paused' and @playing
return if @world.showPaths is 'selected' and not selectedThang
@trailmaster ?= new path.Trailmaster @camera
selectedOnly = @playing and @world.showPaths is 'selected'
@paths = @trailmaster.generatePaths @world, @getCurrentFrame(), selectedThang, @lankBoss.lanks, selectedOnly
layerAdapter = @lankBoss.layerAdapters['Path']
@trailmaster ?= new TrailMaster @camera, layerAdapter
@paths = @trailmaster.generatePaths @world, @heroLank.thang
@paths.name = 'paths'
@lankBoss.layerAdapters['Path'].addChild @paths
layerAdapter.addChild @paths
hidePaths: ->
return if not @paths
@ -699,6 +694,7 @@ module.exports = Surface = class Surface extends CocoClass
@normalStage.clear()
@webGLStage.clear()
@musicPlayer?.destroy()
@trailmaster?.destroy()
@normalStage.removeAllChildren()
@webGLStage.removeAllChildren()
@webGLStage.removeEventListener 'stagemousemove', @onMouseMove

View file

@ -0,0 +1,108 @@
PAST_PATH_ALPHA = 0.75
PAST_PATH_WIDTH = 5
FUTURE_PATH_ALPHA = 0.4
FUTURE_PATH_WIDTH = 2
Camera = require './Camera'
CocoClass = require 'lib/CocoClass'
module.exports = class TrailMaster extends CocoClass
paths: null # dictionary of thang ids to containers for their paths
pathDisplayObject: null
world: null
constructor: (@camera, @layerAdapter) ->
super()
@tweenedSprites = []
@listenTo @layerAdapter, 'new-spritesheet', -> @generatePaths(@world, @thang)
generatePaths: (@world, @thang) ->
return if @generatingPaths
@generatingPaths = true
@cleanUp()
@createGraphics()
@pathDisplayObject = new createjs.SpriteContainer(@layerAdapter.spriteSheet)
@pathDisplayObject.mouseEnabled = @pathDisplayObject.mouseChildren = false
@pathDisplayObject.addChild @createFuturePath()
# @pathDisplayObject.addChild @createPastPath() # Just made the animated path the full path... do we want to have past and future look different again?
@pathDisplayObject.addChild @createTargets()
@generatingPaths = false
return @pathDisplayObject
cleanUp: ->
createjs.Tween.removeTweens(sprite) for sprite in @tweenedSprites
@tweenedSprites = []
createGraphics: ->
color = @colorForThang(@thang.team, PAST_PATH_ALPHA)
@targetDotKey = @cachePathDot(10, color)
@pastDotKey = @cachePathDot(PAST_PATH_WIDTH, color)
@futureDotKey = @cachePathDot(FUTURE_PATH_WIDTH, @colorForThang(@thang.team, FUTURE_PATH_ALPHA))
cachePathDot: (width, color) ->
key = "path-dot-#{width}-#{color}"
color = createjs.Graphics.getRGB(color...)
unless key in @layerAdapter.spriteSheet.getAnimations()
circle = new createjs.Shape()
radius = width/2
circle.graphics.setStrokeStyle(1).beginFill(color).beginStroke('#000000').drawCircle(0, 0, radius)
@layerAdapter.addCustomGraphic(key, circle, [-radius*1.5, -radius*1.5, radius*3, radius*3])
return key
colorForThang: (team, alpha=1.0) ->
rgb = [0, 255, 0]
rgb = [255, 0, 0] if team is 'humans'
rgb = [0, 0, 255] if team is 'ogres'
rgb.push(alpha)
return rgb
createPastPath: ->
return unless points = @world.pointsForThang @thang.id, @camera
params = { interval: 8, frameKey: @pastDotKey }
return @createPath(points, params)
createFuturePath: ->
return unless points = @world.pointsForThang @thang.id, @camera
interval = Math.max(1, parseInt(@world.frameRate / 4))
params = { interval: interval, animate: true, frameKey: @futureDotKey }
return @createPath(points, params)
createTargets: ->
return unless @thang.allTargets
container = new createjs.SpriteContainer(@layerAdapter.spriteSheet)
for x, i in @thang.allTargets by 2
y = @thang.allTargets[i + 1]
sup = @camera.worldToSurface x: x, y: y
sprite = new createjs.Sprite(@layerAdapter.spriteSheet)
sprite.scaleX = sprite.scaleY = 1 / @layerAdapter.resolutionFactor
sprite.gotoAndStop(@targetDotKey)
sprite.x = sup.x
sprite.y = sup.y
container.addChild(sprite)
return container
createPath: (points, options={}) ->
options = options or {}
interval = options.interval or 8
key = options.frameKey or @pastDotKey
container = new createjs.SpriteContainer(@layerAdapter.spriteSheet)
for x, i in points by interval * 2
y = points[i + 1]
sprite = new createjs.Sprite(@layerAdapter.spriteSheet)
sprite.scaleX = sprite.scaleY = 1 / @layerAdapter.resolutionFactor
sprite.gotoAndStop(key)
sprite.x = x
sprite.y = y
container.addChild(sprite)
if lastSprite and options.animate
createjs.Tween.get(lastSprite, {loop: true}).to({x:x, y:y}, 1000)
@tweenedSprites.push lastSprite
lastSprite = sprite
@logged = true
container
destroy: ->
@cleanUp()
super()

View file

@ -1,289 +0,0 @@
# paths before the current state taper out,
# and have a different color than the future
PAST_PATH_TAIL_BRIGHTNESS = 150
PAST_PATH_TAIL_ALPHA = 0.3
PAST_PATH_HEAD_BRIGHTNESS = 200
PAST_PATH_HEAD_ALPHA = 0.75
PAST_PATH_HEAD_LENGTH = 50
PAST_PATH_TAIL_WIDTH = 2
PAST_PATH_HEAD_WIDTH = 2
PAST_PATH_MAX_LENGTH = 200
# paths in the future are single color dotted lines
FUT_PATH_BRIGHTNESS = 153
FUT_PATH_ALPHA = 0.8
FUT_PATH_HEAD_LENGTH = 0
FUT_PATH_WIDTH = 1
FUT_PATH_MAX_LENGTH = 2000
# selected paths are single color, and larger, more prominent
# most other properties are the same as non-selected
SELECTED_PATH_TAIL_BRIGHTNESS = 146
SELECTED_PATH_TAIL_ALPHA = 0.5
SELECTED_PATH_HEAD_BRIGHTNESS = 200
SELECTED_PATH_HEAD_ALPHA = 1.0
SELECTED_PAST_PATH_MAX_LENGTH = 2000
FUT_SELECTED_PATH_WIDTH = 3
# for sprites along the path
CLONE_INTERVAL = 250 # distance between them, ignored for new actions
CLONE_SCALE = 1.0
CLONE_ALPHA = 0.4
# path defaults
PATH_DOT_LENGTH = 3
PATH_SEGMENT_LENGTH = 15 # should be > PATH_DOT_LENGTH
Camera = require './Camera'
module.exports.Trailmaster = class Trailmaster
paths: null # dictionary of thang ids to containers for their paths
selectedPath: null # container of path selected
pathDisplayObject: null
world: null
clock: 0
constructor: (@camera) ->
tick: ->
@clock += 1
generatePaths: (@world, @currentFrame, @selectedThang, @sprites, @selectedOnly) ->
@paths = {}
@pathDisplayObject = new createjs.Container()
@pathDisplayObject.mouseEnabled = @pathDisplayObject.mouseChildren = false
for thang in world.thangs
continue unless thang.isSelectable
continue unless thang.isMovable
continue if @selectedOnly and thang isnt @selectedThang
path = @createPathForThang thang
continue if not path
@pathDisplayObject.addChild path
@paths[thang.id] = path
@pathDisplayObject
createPathForThang: (thang) ->
container = new createjs.Container()
path = @createPastPathForThang(thang)
container.addChild(path) if path
path = @createFuturePathForThang(thang)
container.addChild(path) if path
targets = @createTargetsForThang(thang)
container.addChild(targets) if targets
if thang is @selectedThang
sprites = @spritesForThang(thang)
for sprite in sprites
container.addChild(sprite)
container
createPastPathForThang: (thang) ->
maxLength = if thang is @selectedThang then SELECTED_PAST_PATH_MAX_LENGTH else PAST_PATH_MAX_LENGTH
start = Math.max(@currentFrame - maxLength, 0)
start = 0 if thang isnt @selectedThang
resolution = if thang is @selectedThang then 4 else 12
return unless points = @world.pointsForThang thang.id, start, @currentFrame, @camera, resolution
params =
tailWidth: PAST_PATH_TAIL_WIDTH
headWidth: PAST_PATH_HEAD_WIDTH
headLength: PAST_PATH_HEAD_LENGTH
if thang is @selectedThang
params['tailColor'] = colorForThang(thang.team, SELECTED_PATH_TAIL_BRIGHTNESS, SELECTED_PATH_TAIL_ALPHA)
params['headColor'] = colorForThang(thang.team, SELECTED_PATH_HEAD_BRIGHTNESS, SELECTED_PATH_HEAD_ALPHA)
else
params['tailColor'] = colorForThang(thang.team, PAST_PATH_TAIL_BRIGHTNESS, PAST_PATH_TAIL_ALPHA)
params['headColor'] = colorForThang(thang.team, PAST_PATH_HEAD_BRIGHTNESS, PAST_PATH_HEAD_ALPHA)
return createPath(points, params)
createFuturePathForThang: (thang) ->
resolution = 8
return unless points = @world.pointsForThang thang.id, @currentFrame, @currentFrame + FUT_PATH_MAX_LENGTH, @camera, resolution
if thang is @selectedThang
color = colorForThang(thang.team, SELECTED_PATH_HEAD_BRIGHTNESS, SELECTED_PATH_HEAD_ALPHA)
else
color = colorForThang(thang.team, FUT_PATH_BRIGHTNESS, FUT_PATH_ALPHA)
return createPath(points,
tailColor: color
tailWidth: if thang is @selectedThang then FUT_SELECTED_PATH_WIDTH else FUT_PATH_WIDTH
headLength: FUT_PATH_HEAD_LENGTH
dotted: true
dotOffset: @clock
)
createTargetsForThang: (thang) ->
return unless thang.allTargets
g = new createjs.Graphics()
g.setStrokeStyle(0.5)
g.beginStroke(createjs.Graphics.getRGB(0, 0, 0))
color = colorForThang(thang.team)
i = 0
while i < thang.allTargets.length
g.beginStroke(createjs.Graphics.getRGB(0, 0, 0))
g.beginFill(createjs.Graphics.getRGB(color...))
sup = @camera.worldToSurface x: thang.allTargets[i], y: thang.allTargets[i + 1]
g.drawEllipse(sup.x - 5, sup.y - 3, 10, 6)
g.endStroke()
i += 2
s = new createjs.Shape(g)
s.x = 0
s.y = 0
s
spritesForThang: (thang) ->
i = 0
sprites = []
sprite = @sprites[thang.id]
return sprites unless sprite?.actions
lastPos = @camera.surfaceToWorld x: sprite.sprite.x, y: sprite.sprite.y
minDistance = Math.pow(CLONE_INTERVAL * Camera.MPP, 2)
actions = @world.actionsForThang(thang.id)
lastAction = null
for action in actions
continue if action.name in ['idle', 'move']
frame = @world.frames[action.frame]
frame.restoreStateForThang(thang)
if lastPos
diff = Math.pow(lastPos.x - thang.pos.x, 2)
diff += Math.pow(lastPos.y - thang.pos.y, 2)
continue if diff < minDistance and action.name is lastAction
clone = sprite.sprite.clone()
clonePos = @camera.worldToSurface thang.pos
clone.x = clonePos.x
clone.y = clonePos.y
clone.alpha = CLONE_ALPHA
clone.scaleX *= CLONE_SCALE
clone.scaleY *= CLONE_SCALE
if sprite.expandActions # old Sprite
sprite.updateRotation(clone, sprite.data)
animActions = sprite.expandActions(if thang.acts then thang.getActionName() else 'idle')
sprite.applyActionsToSprites(animActions, [clone], true)
animation = clone.spriteSheet.getAnimation(clone.currentAnimation)
clone.currentAnimationFrame = Math.min(@clock % (animation.frames.length * 3), animation.frames.length - 1)
else
continue unless animation = sprite.actions[action.name]
sprite.updateRotation clone
animation = sprite.getActionDirection(animation) ? animation # no idea if this ever works
clone.gotoAndStop animation.name
# TODO: use action-specific framerate here?
# clone.currentAnimationFrame = Math.min(@clock % (animation.frames.length * 3), animation.frames.length - 1)
sprites.push(clone)
lastPos = x: thang.pos.x, y: thang.pos.y
lastAction = action.name
@world.frames[@currentFrame].restoreStateForThang(thang)
sprites
createPath = (points, options={}, g=null) ->
options = options or {}
tailColor = options.tailColor ? options.headColor
headColor = options.headColor ? options.tailColor
oneColor = true
oneColor = oneColor and headColor[i] is tailColor[i] for i in [0..4]
maxLength = options.maxLength or 0
tailWidth = options.tailWidth
headWidth = options.headWidth
oneWidth = headWidth is tailWidth
headLength = options.headLength
dotted = options.dotted
dotOffset = if options.dotOffset? then options.dotOffset else 0
points = points.slice(-maxLength * 2) if maxLength isnt 0
points = points.slice(((points.length / 2 + dotOffset) % PATH_SEGMENT_LENGTH) * 2) if dotOffset
g = new createjs.Graphics() unless g
return new createjs.Shape(g) if not points
g.setStrokeStyle(tailWidth)
g.beginStroke(createjs.Graphics.getRGB(tailColor...))
g.moveTo(points[0], points[1])
headStart = points.length - headLength
[lastX, lastY] = [points[0], points[1]]
for x, i in points by 2
continue if i is 0
y = points[i + 1]
if i >= headStart and not (oneColor and oneWidth)
diff = (i - headStart) / headLength
style = transition(tailWidth, headWidth, diff)
color = colorTransition(tailColor, headColor, diff)
g.setStrokeStyle(style)
g.beginStroke(createjs.Graphics.getRGB(color...))
g.moveTo(lastX, lastY) if lastX?
else if dotted
if false and i < 2
# Test: footprints
g.beginFill(createjs.Graphics.getRGB(tailColor...))
xofs = x - lastX
yofs = y - lastY
theta = Math.atan2(yofs, xofs)
[fdist, fwidth] = [4, 2]
fside = if (i + dotOffset) % 4 is 0 then -1 else 1
fx = [lastX + fside * fdist * (Math.cos(theta) * xofs - Math.sin(theta) * yofs)]
fy = [lastY + fside * fdist * (Math.sin(theta) * xofs - Math.cos(theta) * yofs)]
g.drawCircle(fx, fy, 2)
offset = ((i / 2) % PATH_SEGMENT_LENGTH)
if offset >= PATH_DOT_LENGTH
if offset is PATH_DOT_LENGTH
g.endStroke()
lastX = x
lastY = y
continue
else
if offset is 0
g.beginStroke(createjs.Graphics.getRGB(tailColor...))
g.moveTo(lastX, lastY) if lastX?
g.lineTo(x, y)
lastX = x
lastY = y
g.endStroke()
s = new createjs.Shape(g)
return s
colorTransition = (color1, color2, pct) ->
return color1 if pct <= 0
return color2 if pct >= 1
i = 0
color = []
while i < 4
val = transition(color1[i], color2[i], pct)
val = Math.floor(val) if i isnt 3
color.push(val)
i += 1
color
transition = (num1, num2, pct) ->
return num1 if pct <= 0
return num2 if pct >= 1
num1 + (num2 - num1) * pct
colorForThang = (team, brightness=100, alpha=1.0) =>
# multipliers should sum to 3.0
multipliers = [2.0, 0.5, 0.5] if team is 'humans'
multipliers = [0.5, 0.5, 2.0] if team is 'ogres'
multipliers = [2.0, 0.5, 0.5] if not multipliers
color = _.map(multipliers, (m) -> return parseInt(m * brightness))
color.push(alpha)
return color
module.exports.createPath = createPath

View file

@ -551,7 +551,7 @@ module.exports = class World
freeMemoryAfterEachSerialization: ->
@frames[i] = null for frame, i in @frames when i < @frames.length - 1
pointsForThang: (thangID, frameStart=0, frameEnd=null, camera=null, resolution=4) ->
pointsForThang: (thangID, camera=null) ->
# Optimized
@pointsForThangCache ?= {}
cacheKey = thangID
@ -570,16 +570,7 @@ module.exports = class World
allPoints.reverse()
@pointsForThangCache[cacheKey] = allPoints
points = []
[lastX, lastY] = [null, null]
for frameIndex in [Math.floor(frameStart / resolution) ... Math.ceil(frameEnd / resolution)]
x = allPoints[frameIndex * 2 * resolution]
y = allPoints[frameIndex * 2 * resolution + 1]
continue if x is lastX and y is lastY
lastX = x
lastY = y
points.push x, y
points
return allPoints
actionsForThang: (thangID, keepIdle=false) ->
# Optimized