Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.*
*~
\#*
node_modules
test.css
.idea
Expand Down
27 changes: 21 additions & 6 deletions coffee/compilation.coffee
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
NullSystem = new LSystem({},{},{},"",1,"no system")
DefaultSystem = new LSystem({
size: {value:12.27}
angle: {value:4187.5}
},{},{ size: {value:9} } ,"L : SS\nS : F->[F-Y[S(L]]\nY : [-|F-F+)Y]\n" ,12 ,"click-and-drag-me!" )
NullSystem = new LSystem({},{},{},0,"","",1,"no system")
DefaultSystem = new LSystem(
{ size: {value: 20}, angle: {value: 9840} },
{ x: 0, y: 50, rot: 0},
null,
"L : SS\nS : F-[F-Y[S>(L]]\nY : [-|F-F+)Y]"
12,
0.218,
[ "black", "white", "cyan", "#e8cc00", "#007272", "#ff4c00" ],
false,
"return {\n
angle: t/100,\n
angleG: t/100,\n
size: null,\n
sizeG: null,\n
offsetX: null,\n
offsetY: null,\n
rotation: null\n
}",
"click-and-drag-me!"
)

# =========================================
class CompiledSystem
Expand Down Expand Up @@ -92,4 +108,3 @@ class SystemManager
)

getInstructions: -> @compiledElements

59 changes: 59 additions & 0 deletions coffee/controls.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,65 @@ class Joystick

# ===============================

class Animation
active: false
now: 0
f: (t) -> {}

constructor: (f) ->
@setF(f)

setF: (f, el) ->
if el
$(el).removeClass('error')
$(el).attr('title', '')
try
f = Function ['t'], f
r = f.call {}, 0
unless r instanceof Object
throw 'Return value is not Object'
@f = f
catch err
if el
$(el).addClass 'error'
$(el).attr 'title', err.toString()

enable: ->
@active = true
@onActivate()
disable: ->
@active = false
@onRelease()
toggle: (active = not @active) ->
if active
@enable()
else
@disable()

onActivate: -> # noop unless overriden
onRelease: -> # noop unless overriden

state: () ->
d =
try
@f(@now)
catch e
{}
if d instanceof Object
d
else
{}

clear: -> #noop for now
draw: ->
if @active
@now++

center: ->
@now = 0

# ===============================

# ui binding for a single system variable
class Control
constructor: (@controlkey) ->
Expand Down
57 changes: 53 additions & 4 deletions coffee/lsystem.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,44 @@ class Defaults
@_sensitivites: ->
size: {value: 7.7, growth:7.53}
angle: {value: 7.6, growth:4}
@lineWidth: 0.218
@colors: [ "black", "white", "cyan", "#e8cc00", "#007272", "#ff4c00" ]
@play: 0
@animation: "
return {\n
angle: t/100,\n
angleG: t/100,\n
size: null,\n
sizeG: null,\n
offsetX: null,\n
offsetY: null,\n
rotation: null\n
}"

# =========================================
class LSystem
constructor: (params, offsets, sensitivities, @rules, @iterations, @name) ->
constructor: (params, offsets, sensitivities, @rules, @iterations, lineWidth, colors, play, animation, @name) ->
@params = Util.map(Defaults.params(params), (c) -> Param.fromJson(c))
@offsets = Defaults.offsets(offsets)
@sensitivities = Util.map(Defaults.sensitivities(sensitivities), (s) -> Sensitivity.fromJson(s))
@play =
if (typeof play == 'number' && Number.isFinite play) || (typeof play == 'boolean')
if play then 1 else 0
else
Defaults.play
@animation =
if typeof animation == 'string' and 0 < animation.length
animation
else
Defaults.animation
@lineWidth =
if typeof lineWidth == 'number' && Number.isFinite lineWidth
then lineWidth
else Defaults.lineWidth
@colors =
if typeof colors == 'object'
then colors
else Defaults.colors

# this is not the most efficient of methods...
clone: -> return LSystem.fromUrl(@toUrl())
Expand All @@ -51,10 +82,14 @@ class LSystem
base = "#?i=#{@iterations}&r=#{encodeURIComponent(@rules)}"
mkQueryString = (params) -> _.reduce(params, ((acc,v) -> "#{acc}&#{v.toUrlComponent()}"), "")
params = mkQueryString(@params)
sensitivities = mkQueryString(@sensitivities)
offsets = "&offsets=#{@offsets.x},#{@offsets.y},#{@offsets.rot}"
sensitivities = mkQueryString(@sensitivities)
lineWidth = "&l="+@lineWidth
colors = "&c="+@colors.join(',')
play = "&play="+@play
animation = "&anim="+encodeURIComponent(@animation)
name = "&name=#{encodeURIComponent(@name)}"
return base+params+sensitivities+offsets+name
return base+params+offsets+sensitivities+lineWidth+colors+play+animation+name

merge: (system) ->
_.extend(@, system) if system
Expand All @@ -81,7 +116,21 @@ class LSystem
y: parseFloat(o[1])
rot: parseFloat(o[2])

return new LSystem(params, offsets, sensitivities, decodeURIComponent(config.r), config.i, decodeURIComponent(config.name) or "unnamed")
anim =
if 'anim' of config
decodeURIComponent(config.anim)
else
null

colors = undefined
if (config.c)
colors = config.c.split(',')

return new LSystem(params, offsets, sensitivities,
decodeURIComponent(config.r), parseInt(config.i),
parseFloat(config.l), colors,
parseInt(config.play), anim,
decodeURIComponent(config.name) or "unnamed")

isIsomorphicTo: (system) -> if (!system) then false else @rules == system.rules and @iterations == system.iterations

Expand Down
74 changes: 68 additions & 6 deletions coffee/manager.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,51 @@ class InputHandler
system.params.angle.value = Util.round(system.params.angle.value + @joystick.dx(system.sensitivities.angle.value), 4)
system.params.angle.growth = Util.round(system.params.angle.growth + @joystick.dy(system.sensitivities.angle.growth),9)

class AnimationHandler
snapshot: null # lsystem params as they were was when joystick activated
constructor: (@animation) ->

sensitivity: (value) ->
if (value) then Math.pow(10,value-10) else 1

update: (system) =>
return if not @animation.active
d = @animation.state()
if (typeof d.angle == 'number' && Number.isFinite d.angle)
system.params.angle.value = Util.round(system.params.angle.value + d.angle * @sensitivity(system.sensitivities.angle.value), 4)
if (typeof d.angleG == 'number' && Number.isFinite d.angleG)
system.params.angle.growth = Util.round(system.params.angle.growth + d.angleG * @sensitivity(system.sensitivities.angle.growth), 9)
if (typeof d.size == 'number' && Number.isFinite d.size)
system.params.size.value = Util.round(@snapshot.params.size.value + d.size * @sensitivity(system.sensitivities.size.value), 2)
if (typeof d.sizeG == 'number' && Number.isFinite d.sizeG)
system.params.size.growth = Util.round(@snapshot.params.size.growth + d.sizeG * @sensitivity(system.sensitivities.size.growth), 6)
if (typeof d.offsetX == 'number' && Number.isFinite d.offsetX)
system.offsets.x = @snapshot.offsets.x + d.offsetX
if (typeof d.offsetY == 'number' && Number.isFinite d.offsetY)
system.offsets.y = @snapshot.offsets.y + d.offsetY
if (typeof d.rotation == 'number' && Number.isFinite d.rotation)
system.offsets.rot = @snapshot.offsets.rot + d.rotation

class AppManager
joystick:null
animation:null
keystate: null
inputHandler: null
renderer:null
systemManager: null

constructor: (@container, @controls) ->
@joystick = new Joystick(container)
constructor: (@container, @controls, @animation) ->
@joystick = new Joystick(@container)
@keystate = new KeyState
@inputHandler = new InputHandler(@keystate, @joystick)
@animationHandler = new AnimationHandler(@animation)

@joystick.onRelease = => @syncLocationQuiet()
@joystick.onActivate = => @inputHandler.snapshot = @systemManager.activeSystem.clone()

@animation.onRelease = => @syncLocationQuiet()
@animation.onActivate = => @animationHandler.snapshot = @systemManager.activeSystem.clone()

@renderer = new Renderer(@container)

@systemManager = new SystemManager
Expand Down Expand Up @@ -56,6 +85,8 @@ class AppManager
@recalculationPromise = @systemManager.activate(system).progress(@onRecalculateProgress)
@recalculationPromise.done( =>
@joystick.enable()
@animation.setF(system.animation, @controls.animation)
@animation.toggle(system.play)
@renderer.prepare(system)
@syncAll();
@draw()
Expand All @@ -65,12 +96,20 @@ class AppManager
@recalculationPromise

lsystemFromControls: ->
play = $(@controls.play).hasClass('play')
animation = $(@controls.animation).val()
Defaults.play = play
Defaults.animation = animation
return new LSystem(
@paramControls.toJson(),
@offsetControls.toJson(),
@sensitivityControls.toJson(),
$(@controls.rules).val(),
parseInt($(@controls.iterations).val()),
parseFloat($(@controls.lineWidth).val()),
Util.mapArray(@controls.colors, (el) -> el.value),
play,
animation,
$(@controls.name).val()
)

Expand All @@ -94,7 +133,7 @@ class AppManager
r.context.state.y = (y-b.y1+15)

@draw(r)
filename = "lsys_"+system.name.replace(/[\ \/]/g,"_")
filename = "lsys_"+system.name.replace(/[\ \/]/g,"_")+".png"
rootCanvas = container.children[0]
rootContext = rootCanvas.getContext('2d')
[].slice.call(container.children, 1).forEach( (c) ->
Expand All @@ -110,12 +149,17 @@ class AppManager
.always(@run)

run: =>
requestAnimationFrame(@run, @container)
@inputHandler.update(@systemManager.activeSystem)
if @joystick.active and not @renderer.isDrawing
@draw()
@joystick.draw()
@inputHandler.update(@systemManager.activeSystem)
@syncControls()
@draw()
if @animation.active and not @renderer.isDrawing
@animation.draw()
@animationHandler.update(@systemManager.activeSystem)
@syncControls()
@draw()
requestAnimationFrame(@run, @container)

draw: (renderer = @renderer) =>
elems = @systemManager.getInstructions()
Expand All @@ -134,10 +178,28 @@ class AppManager
$(@controls.name).val(system.name)
@syncControls(system)
@syncRulesAndIterations(system)
@syncLineStyle(system)

syncRulesAndIterations: (system = @systemManager.activeSystem) ->
$(@controls.iterations).val(system.iterations)
$(@controls.rules).val(system.rules)
$(@controls.animation).val(system.animation)
if (system.play)
$(@controls.play).addClass('play')
else
$(@controls.play).removeClass('play')

syncFloat: (input, value) ->
if (parseFloat(input.val()) != value and not isNaN(parseFloat(value))) then input.val(value)

syncColor: (input, value) ->
$(input).val(value)
input.style.backgroundColor = value

syncLineStyle: (system = @systemManager.activeSystem) ->
@syncFloat($(@controls.lineWidth), system.lineWidth)
@syncColor(@controls.colors[i], col) for col, i in system.colors
@container.style.backgroundColor = @controls.colors[0].value

syncControls: (system = @systemManager.activeSystem) ->
@paramControls.sync(system.params)
Expand Down
13 changes: 10 additions & 3 deletions coffee/rendering.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ class Bounding
#================================================================

getG = (c) -> c.getContext('2d')
getColors = (system) -> ["#ffffff","#0044DD","#00DD44","#DD4400"]
getColors = (system) -> system.colors.slice(1)
getLineWidth = (system) -> system.lineWidth

class Renderer
context:null
g:null
stack:[]
isDrawing:false
constructor: (@container) ->
@context = new RenderingContext(container)
@context = new RenderingContext(@container)

prepare: (system) =>
colors = getColors(system)
Expand Down Expand Up @@ -78,7 +79,7 @@ class Renderer
this.reset(system)

@context.gs.forEach (g,i) =>
g.lineWidth = 0.218
g.lineWidth = getLineWidth(system)
g.beginPath()
g.moveTo(@context.state.x, @context.state.y)

Expand Down Expand Up @@ -147,5 +148,11 @@ class Renderer
"1": (state, params, context, a) -> state.color = a; if (context.g != context.gs[1]) then ( context.g = context.gs[1]; context.g.moveTo(state.x,state.y))
"2": (state, params, context, a) -> state.color = a; if (context.g != context.gs[2]) then ( context.g = context.gs[2]; context.g.moveTo(state.x,state.y))
"3": (state, params, context, a) -> state.color = a; if (context.g != context.gs[3]) then ( context.g = context.gs[3]; context.g.moveTo(state.x,state.y))
"4": (state, params, context, a) -> state.color = a; if (context.g != context.gs[4]) then ( context.g = context.gs[4]; context.g.moveTo(state.x,state.y))
"5": (state, params, context, a) -> state.color = a; if (context.g != context.gs[5]) then ( context.g = context.gs[5]; context.g.moveTo(state.x,state.y))
"6": (state, params, context, a) -> state.color = a; if (context.g != context.gs[6]) then ( context.g = context.gs[6]; context.g.moveTo(state.x,state.y))
"7": (state, params, context, a) -> state.color = a; if (context.g != context.gs[7]) then ( context.g = context.gs[7]; context.g.moveTo(state.x,state.y))
"8": (state, params, context, a) -> state.color = a; if (context.g != context.gs[8]) then ( context.g = context.gs[8]; context.g.moveTo(state.x,state.y))
"9": (state, params, context, a) -> state.color = a; if (context.g != context.gs[9]) then ( context.g = context.gs[9]; context.g.moveTo(state.x,state.y))
}
)()
Loading