|
@ -8,11 +8,13 @@ randomize() |
|
|
type Term = enum LeafTerm, LineTerm, GoLeft, GoRight, PushTerm, PopTerm |
|
|
type Term = enum LeafTerm, LineTerm, GoLeft, GoRight, PushTerm, PopTerm |
|
|
type Terms = seq[Term] |
|
|
type Terms = seq[Term] |
|
|
|
|
|
|
|
|
iterator rewrite(terms: Terms) : Terms = |
|
|
proc rewrite(terms: Terms, maxIterations: int) : Terms = |
|
|
var currentTerms: Terms = terms |
|
|
var currentTerms: Terms = terms |
|
|
var newTerms: Terms |
|
|
var newTerms: Terms |
|
|
|
|
|
var n = 0 |
|
|
|
|
|
|
|
|
while true: |
|
|
while true: |
|
|
|
|
|
n += 1 |
|
|
# Reset this each iteration to gather new expansions |
|
|
# Reset this each iteration to gather new expansions |
|
|
newTerms = @[] |
|
|
newTerms = @[] |
|
|
for term in currentTerms: |
|
|
for term in currentTerms: |
|
@ -20,14 +22,17 @@ iterator rewrite(terms: Terms) : Terms = |
|
|
of LeafTerm: newTerms &= @[LineTerm, LineTerm, PushTerm, GoRight, LeafTerm, PopTerm, GoLeft, LeafTerm] |
|
|
of LeafTerm: newTerms &= @[LineTerm, LineTerm, PushTerm, GoRight, LeafTerm, PopTerm, GoLeft, LeafTerm] |
|
|
else: newTerms &= @[term] |
|
|
else: newTerms &= @[term] |
|
|
currentTerms = newTerms |
|
|
currentTerms = newTerms |
|
|
yield currentTerms |
|
|
if n == maxIterations: |
|
|
|
|
|
return currentTerms |
|
|
|
|
|
|
|
|
type StackControl = enum Push, Pop |
|
|
type StackControl = enum Push, Pop |
|
|
|
|
|
|
|
|
# An intruction along with a change in angle and magnitude (i.e. a vector) |
|
|
# An intruction along with a change in angle and magnitude (i.e. a vector) |
|
|
type DrawInstruction = object |
|
|
type DrawInstruction = object |
|
|
angle_change: float64 |
|
|
angle_change: float64 |
|
|
|
|
|
width: float64 |
|
|
magnitude: float64 |
|
|
magnitude: float64 |
|
|
|
|
|
color: Color |
|
|
|
|
|
|
|
|
type |
|
|
type |
|
|
InstructionKind = enum pkDraw, pkStack |
|
|
InstructionKind = enum pkDraw, pkStack |
|
@ -41,50 +46,53 @@ proc `$` (i: Instruction): string = |
|
|
of pkDraw: return "angle_change = " & $i.drawInstruction.angle_change & ", magnitude = " & $i.drawInstruction.magnitude |
|
|
of pkDraw: return "angle_change = " & $i.drawInstruction.angle_change & ", magnitude = " & $i.drawInstruction.magnitude |
|
|
of pkStack: return "direction = " & $i.stackInstruction |
|
|
of pkStack: return "direction = " & $i.stackInstruction |
|
|
|
|
|
|
|
|
iterator axiomToInstructions(maxIterations: int) : Instruction = |
|
|
iterator axiomToInstructions(maxIterations: int, magnitude: float64, angle: float64) : Instruction = |
|
|
let axiom = @[LeafTerm] |
|
|
let axiom = @[LeafTerm] |
|
|
var n = 0 |
|
|
let termsToConvert = rewrite(axiom, maxIterations) |
|
|
var termsToConvert: Terms |
|
|
var angle_delta: float64 = angle |
|
|
for terms in rewrite(axiom): |
|
|
var magnitudes: seq[float64] = @[magnitude] |
|
|
n += 1 |
|
|
var widths: seq[float64] = @[maxIterations.float64 + 3] |
|
|
if n == maxIterations: |
|
|
var current_magnitude = magnitude |
|
|
termsToConvert = terms |
|
|
var current_width: float64 = widths[0] |
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
var magnitudes: seq[float64] = @[18.float64, 34.float64, 50.float64] |
|
|
|
|
|
let magnitude_weights = [3, 6, 1] |
|
|
|
|
|
let magnitude_cdf = math.cumsummed(magnitude_weights) |
|
|
|
|
|
|
|
|
|
|
|
let angles: seq[float64] = @[10 * 1.618, 20 * 1.618, 30 * 1.618] |
|
|
|
|
|
let angle_weights = [3, 5, 2] |
|
|
|
|
|
let angle_cdf = math.cumsummed(angle_weights) |
|
|
|
|
|
|
|
|
|
|
|
var angle_delta: float64 |
|
|
|
|
|
var magnitude: float64 |
|
|
|
|
|
# every time you encounter a push divide, and a pop multiply |
|
|
|
|
|
# type Term = enum LeafTerm, LineTerm, GoLeft, GoRight, PushTerm, PopTerm |
|
|
|
|
|
|
|
|
|
|
|
# axiom |
|
|
# axiom |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: 180, magnitude: 180)) |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: 180, magnitude: magnitude)) |
|
|
for term in termsToConvert: |
|
|
for term in termsToConvert: |
|
|
angle_delta = sample(angles, angle_cdf) |
|
|
let angle_delta = angle_delta * sample(@[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]) |
|
|
magnitude = sample(magnitudes, magnitude_cdf) |
|
|
|
|
|
case term: |
|
|
case term: |
|
|
of LeafTerm: |
|
|
of LeafTerm: |
|
|
magnitude = magnitude / 1.5 |
|
|
# when there's a leaf we want to make the magnitude smaller |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: angle_delta, magnitude: magnitude)) |
|
|
let leaf_width = (10 * sample(@[0.50, 0.10, 0.25])) |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: 0, magnitude: -magnitude)) # hack |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKGREEN, width: leaf_width, angle_change: angle_delta, magnitude: magnitudes[0])) |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: -(angle_delta*2), magnitude: magnitude)) |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKGREEN, width: leaf_width, angle_change: 0, magnitude: -magnitudes[0])) # hack |
|
|
|
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKGREEN, width: leaf_width, angle_change: -(angle_delta*2), magnitude: magnitudes[0])) |
|
|
of LineTerm: |
|
|
of LineTerm: |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: 0, magnitude: magnitude)) # don't change direction |
|
|
# Draw without changing direction |
|
|
|
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKBROWN, width: current_width, angle_change: 0, magnitude: magnitudes[0])) |
|
|
|
|
|
|
|
|
|
|
|
# L-systems don't go "backwards" |
|
|
|
|
|
# So you can go left or right on the x-axis at a given angle delta |
|
|
of GoLeft: |
|
|
of GoLeft: |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: angle_delta, magnitude: magnitude)) # change direction to left 45 degrees |
|
|
current_magnitude = current_magnitude - (current_magnitude * sample(@[0.05, 0.01])) |
|
|
|
|
|
current_width = current_width - (current_width * sample(@[0.15, 0.10])) |
|
|
|
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKBROWN, width: current_width, angle_change: angle_delta, magnitude: current_magnitude)) |
|
|
of GoRight: |
|
|
of GoRight: |
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: -angle_delta, magnitude: magnitude)) # change direction to right 45 degrees |
|
|
current_magnitude = current_magnitude - (current_magnitude * sample(@[0.05, 0.01])) |
|
|
|
|
|
current_width = current_width - (current_width * sample(@[0.15, 0.10])) |
|
|
|
|
|
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKBROWN, width: current_width, angle_change: -angle_delta, magnitude: current_magnitude)) |
|
|
|
|
|
|
|
|
|
|
|
# Control the stack of saved positions |
|
|
of PushTerm: |
|
|
of PushTerm: |
|
|
|
|
|
# Save current location |
|
|
|
|
|
magnitudes = @[current_magnitude] & magnitudes |
|
|
|
|
|
widths = @[current_width] & widths |
|
|
yield Instruction(kind: pkStack, stackInstruction: Push) |
|
|
yield Instruction(kind: pkStack, stackInstruction: Push) |
|
|
of PopTerm: |
|
|
of PopTerm: |
|
|
magnitude = magnitude * 1.5 |
|
|
current_magnitude = magnitudes[0] |
|
|
|
|
|
current_width = widths[0] |
|
|
|
|
|
magnitudes = magnitudes[1..^1] |
|
|
|
|
|
widths = widths[1..^1] |
|
|
|
|
|
# Pop location stack and set current location to it |
|
|
|
|
|
# reset magnitude |
|
|
yield Instruction(kind: pkStack, stackInstruction: Pop) |
|
|
yield Instruction(kind: pkStack, stackInstruction: Pop) |
|
|
|
|
|
|
|
|
# A Position along with its angle |
|
|
# A Position along with its angle |
|
@ -100,7 +108,9 @@ proc `$` (p: Position): string = |
|
|
type DrawLine = object |
|
|
type DrawLine = object |
|
|
start_pos: Vector2 |
|
|
start_pos: Vector2 |
|
|
end_pos: Vector2 |
|
|
end_pos: Vector2 |
|
|
|
|
|
width: float64 |
|
|
angle: float64 |
|
|
angle: float64 |
|
|
|
|
|
color: Color |
|
|
|
|
|
|
|
|
proc `$` (d: DrawLine): string = |
|
|
proc `$` (d: DrawLine): string = |
|
|
return "start_pos = " & $d.start_pos & ", " & "end_pos = " & $d.end_pos |
|
|
return "start_pos = " & $d.start_pos & ", " & "end_pos = " & $d.end_pos |
|
@ -120,37 +130,47 @@ proc calculateNextLine(inst: DrawInstruction, pos: Position) : DrawLine = |
|
|
|
|
|
|
|
|
# Ending position is relative to the starting position, so add the coordinates |
|
|
# Ending position is relative to the starting position, so add the coordinates |
|
|
result.end_pos = Vector2(x: result.start_pos.x+new_x, y: result.start_pos.y+new_y) |
|
|
result.end_pos = Vector2(x: result.start_pos.x+new_x, y: result.start_pos.y+new_y) |
|
|
|
|
|
result.width = inst.width |
|
|
|
|
|
result.color = inst.color |
|
|
result.angle = new_angle |
|
|
result.angle = new_angle |
|
|
|
|
|
|
|
|
proc executeProgram(instructions: seq[Instruction], positions: seq[Position], current_pos: Position) : seq[DrawLine] = |
|
|
proc executeProgram(instructions: seq[Instruction], starting_pos: Position) : seq[DrawLine] = |
|
|
# each instruction will be followed by a stack control instruction |
|
|
# each instruction will be followed by a stack control instruction |
|
|
if instructions.len <= 0: |
|
|
var insts = instructions |
|
|
echo "Returning" |
|
|
var positions = @[starting_pos] |
|
|
return @[] |
|
|
var current_pos = starting_pos |
|
|
|
|
|
|
|
|
let inst = instructions[0] |
|
|
var draw_lines : seq[DrawLine] = @[] |
|
|
|
|
|
|
|
|
|
|
|
while insts.len > 0: |
|
|
|
|
|
let inst = insts[0] |
|
|
|
|
|
|
|
|
var nextLine: DrawLine |
|
|
var nextLine: DrawLine |
|
|
|
|
|
|
|
|
case inst.kind: |
|
|
case inst.kind: |
|
|
of pkStack: |
|
|
of pkStack: |
|
|
if inst.stackInstruction == Push: |
|
|
if inst.stackInstruction == Push: |
|
|
return executeProgram(instructions[1..^1], current_pos & positions, current_pos) |
|
|
insts = insts[1..^1] |
|
|
|
|
|
positions = current_pos & positions |
|
|
elif inst.stackInstruction == Pop: |
|
|
elif inst.stackInstruction == Pop: |
|
|
let newCurrent = positions[0] |
|
|
current_pos = positions[0] |
|
|
return executeProgram(instructions[1..^1], positions[1..^1], newCurrent) |
|
|
insts = insts[1..^1] |
|
|
|
|
|
positions = positions[1..^1] |
|
|
else: |
|
|
else: |
|
|
return |
|
|
continue |
|
|
of pkDraw: |
|
|
of pkDraw: |
|
|
nextLine = calculateNextLine(inst.drawInstruction, current_pos) |
|
|
nextLine = calculateNextLine(inst.drawInstruction, current_pos) |
|
|
let new_position = Position(x: nextLine.end_pos.x, |
|
|
let new_position = Position(x: nextLine.end_pos.x, |
|
|
y: nextLine.end_pos.y, |
|
|
y: nextLine.end_pos.y, |
|
|
angle: nextLine.angle) |
|
|
angle: nextLine.angle) |
|
|
# leave the stack alone, set the current position however |
|
|
# leave the stack alone, set the current position however |
|
|
return @[nextLine] & executeProgram(instructions[1..^1], positions, new_position) |
|
|
|
|
|
|
|
|
|
|
|
proc guiLoop*(instructions: seq[Instruction]) = |
|
|
draw_lines = draw_lines & @[nextLine] |
|
|
|
|
|
insts = insts[1..^1] |
|
|
|
|
|
current_pos = new_position |
|
|
|
|
|
return draw_lines |
|
|
|
|
|
|
|
|
|
|
|
proc guiLoop*() = |
|
|
# TODO get from xlib |
|
|
# TODO get from xlib |
|
|
var screenWidth: int = 100 |
|
|
var screenWidth: int = 100 |
|
|
var screenHeight: int = 100 |
|
|
var screenHeight: int = 100 |
|
@ -174,29 +194,72 @@ proc guiLoop*(instructions: seq[Instruction]) = |
|
|
var panOffset = mousePos |
|
|
var panOffset = mousePos |
|
|
|
|
|
|
|
|
var dragWindow = false |
|
|
var dragWindow = false |
|
|
var exitWindow = false |
|
|
var restartSimulation = false |
|
|
|
|
|
|
|
|
var restartButton = false |
|
|
var restartButton = false |
|
|
|
|
|
|
|
|
|
|
|
var magnitude: float64 = 10 |
|
|
|
|
|
var angle: float64 = 30 |
|
|
|
|
|
|
|
|
SetTargetFPS(60) |
|
|
SetTargetFPS(60) |
|
|
|
|
|
|
|
|
# "axiom" |
|
|
var iterations = 2 |
|
|
let startingPosition = Position(x: screenWidth/2, y: screenHeight.float64-100, angle: 90) |
|
|
|
|
|
let drawLines = executeProgram(instructions, @[startingPosition], startingPosition) |
|
|
|
|
|
|
|
|
|
|
|
while not exitWindow and not WindowShouldClose(): |
|
|
# "axiom" |
|
|
|
|
|
let startingPosition = Position(x: screenWidth/2, y: screenHeight.float64, angle: 90) |
|
|
|
|
|
var instructions = toSeq(axiomToInstructions(iterations, magnitude, angle)) |
|
|
|
|
|
for inst in instructions: |
|
|
|
|
|
echo inst |
|
|
|
|
|
var drawLines = executeProgram(instructions, startingPosition) |
|
|
|
|
|
while not WindowShouldClose(): |
|
|
BeginDrawing() |
|
|
BeginDrawing() |
|
|
|
|
|
|
|
|
|
|
|
restartSimulation = GuiButton(Rectangle(x: 0.float32, y: 0.float32, width: 100.float32, height: 20.float32), "Restart".cstring) |
|
|
|
|
|
|
|
|
|
|
|
let fewerIterations = GuiButton(Rectangle(x: 0.float32, y: 20.float32, width: 100.float32, height: 20.float32), "Fewer".cstring) |
|
|
|
|
|
let moreIterations = GuiButton(Rectangle(x: 0.float32, y: 40.float32, width: 100.float32, height: 20.float32), "More".cstring) |
|
|
|
|
|
|
|
|
|
|
|
magnitude = GuiSliderBar(Rectangle( |
|
|
|
|
|
x: 0.float32, |
|
|
|
|
|
y: 60.float32, |
|
|
|
|
|
width: 80.float32, |
|
|
|
|
|
height: 20.float32), |
|
|
|
|
|
"Smaller", |
|
|
|
|
|
"Larger", |
|
|
|
|
|
magnitude, |
|
|
|
|
|
10, 100) |
|
|
|
|
|
|
|
|
|
|
|
angle = GuiSliderBar(Rectangle( |
|
|
|
|
|
x: 0.float32, |
|
|
|
|
|
y: 90.float32, |
|
|
|
|
|
width: 80.float32, |
|
|
|
|
|
height: 20.float32), |
|
|
|
|
|
"Narrower", |
|
|
|
|
|
"Wider", |
|
|
|
|
|
angle, |
|
|
|
|
|
1, 45) |
|
|
|
|
|
if fewerIterations: |
|
|
|
|
|
if iterations > 1: |
|
|
|
|
|
iterations -= 1 |
|
|
|
|
|
restartSimulation = true |
|
|
|
|
|
if moreIterations: |
|
|
|
|
|
restartSimulation = true |
|
|
|
|
|
iterations += 1 |
|
|
|
|
|
|
|
|
|
|
|
if restartSimulation: |
|
|
|
|
|
echo "Re-executing" |
|
|
|
|
|
instructions = toSeq(axiomToInstructions(iterations, magnitude, angle)) |
|
|
|
|
|
drawLines = executeProgram(instructions, startingPosition) |
|
|
|
|
|
|
|
|
screenWidth = (monitor.GetMonitorWidth() / 2).int |
|
|
screenWidth = (monitor.GetMonitorWidth() / 2).int |
|
|
screenHeight = (monitor.GetMonitorHeight() / 2).int |
|
|
screenHeight = (monitor.GetMonitorHeight() / 2).int |
|
|
# This must come before anything else! |
|
|
# This must come before anything else! |
|
|
ClearBackground(BLACK) |
|
|
ClearBackground(BLACK) |
|
|
for line in drawLines: |
|
|
for line in drawLines: |
|
|
DrawLineEx(line.start_pos, line.end_pos, 3, WHITE) |
|
|
DrawLineEx(line.start_pos, line.end_pos, line.width, line.color) |
|
|
|
|
|
|
|
|
EndDrawing() |
|
|
EndDrawing() |
|
|
CloseWindow() |
|
|
CloseWindow() |
|
|
|
|
|
|
|
|
when isMainModule: |
|
|
when isMainModule: |
|
|
#guiLoop() |
|
|
guiLoop() |
|
|
guiLoop(toSeq(axiomToInstructions(7))) |
|
|
|
|
|