fieldkit
Version:
Basic building blocks for computational design projects. Written in CoffeeScript for browser and server environments.
180 lines (131 loc) • 4.17 kB
text/coffeescript
###
Force Directed Graph
###
class GNode
children: []
particle: null
constructor: (@name) ->
@children = []
addNode: (name) ->
node = new GNode @name + "/" + name
@children.push node
node
getWeight: ->
weight = @children.length
for child in @children
weight += child.getWeight()
weight
init: (graph, center, radius, parentSize = 0) ->
physics = graph.physics
weight = @getWeight()
@particle = physics.emitter.create()
@particle.size = Math.min weight / 2 + 3, 30
@particle.position.set(center)
alpha = Math.random() * Math.PI * 2
jitter = (parentSize + @particle.size) * 2 + Math.random() * 10
@particle.position.x += Math.sin(alpha) * jitter
@particle.position.y += Math.cos(alpha) * jitter
@particle.clearVelocity()
@particle.color = new fk.Color(1, 1, 0)
@particle.color.randomize()
for child in @children
radius = weight * 0.5 + @particle.size * 4
child.init graph, @particle.position, radius, @particle.size
spring = graph.addLink @particle, child.particle
class Graph
root: null
constructor: (springIterations) ->
@physics = new fk.physics.Physics()
# use 2D particles
@physics.emitter.type = fk.physics.Particle2
@physics.emitter.rate = 0
@physics.springIterations = springIterations
# Behaviours
# @physics.add new fk.physics.Force new fk.math.Vec2(0, 1), 0.01
# Constraints
collision = new fk.physics.Collision(@physics)
@physics.add collision
@root = new GNode this, "root"
addLink: (parent, child) ->
spring = new fk.physics.Spring parent, child
@physics.addSpring spring
spring
# call this after all nodes are created
init: (center) ->
@root.init this, center, 0
@root.particle.isLocked = true
update: -> @physics.update()
getBounds: ->
rect = new fk.math.Rect(Number.MAX_VALUE, Number.MAX_VALUE, Number.MIN_VALUE, Number.MIN_VALUE)
for particle in @physics.particles
p = particle.position
rect.x1 = p.x if p.x < rect.x1
rect.y1 = p.y if p.y < rect.y1
rect.x2 = p.x if p.x > rect.x2
rect.y2 = p.y if p.y > rect.y2
rect
centerGraph: (screenCenter) ->
bounds = @getBounds()
offset = bounds.center().sub(screenCenter).scale(1)
# offset = screenCenter.sub().div(2)
console.log "screenCenter: #{screenCenter} bounds: #{bounds.center()} offset: #{offset}"
for particle in @physics.particles
particle.position.sub offset
particle.clearVelocity()
class Example extends fk.client.Sketch
State = fk.physics.State
rng = new fk.math.Random()
# Settings
maxDepth = 3
springIterations = maxDepth * 16
warmupIterations = 16
setup: ->
# create graph structure
@graph = new Graph(springIterations)
createNodes = (parent, depth=0) ->
numChildren = rng.randi 0, (depth * 5)
for i in [0..numChildren]
child = parent.addNode "c#{i}"
if depth < maxDepth
createNodes child, depth + 1
createNodes @graph.root
# create drawing structure
center = new fk.math.Vec2(@width / 2, @height / 2)
@graph.init center
# run simulation
for i in [0..warmupIterations]
@graph.update()
# find min / max
@graph.centerGraph center
console.log "created graph with #{@graph.root.getWeight()} nodes"
draw: ->
# update
@graph.update()
# draw
physics = @graph.physics
@background(0)
# draw bounds
r = @graph.getBounds()
@noFill()
@stroke(64)
@rect r.x1, r.y1, r.width(), r.height()
@fill(64)
@rect r.center().x, r.center().y, 6, 6
# draw springs
@noFill()
@stroke 128
@lineWidth 1
for spring in physics.springs
@line spring.a.position, spring.b.position
# draw particles
@noStroke()
@lineWidth 2
for particle in physics.particles
if particle.state == State.ALIVE
if particle == @graph.root.particle
@stroke 255
@fill 255,0,0
else
@noStroke()
@fill 255
@circle particle.position.x, particle.position.y, particle.size * 0.8