Browse Source

refactoring

master
Wesley Kerfoot 3 years ago
parent
commit
f4f3b06935
  1. 205
      src/lsystem.nim
  2. 2
      src/lsystempkg/raylib.nim

205
src/lsystem.nim

@ -8,11 +8,13 @@ randomize()
type Term = enum LeafTerm, LineTerm, GoLeft, GoRight, PushTerm, PopTerm
type Terms = seq[Term]
iterator rewrite(terms: Terms) : Terms =
proc rewrite(terms: Terms, maxIterations: int) : Terms =
var currentTerms: Terms = terms
var newTerms: Terms
var n = 0
while true:
n += 1
# Reset this each iteration to gather new expansions
newTerms = @[]
for term in currentTerms:
@ -20,14 +22,17 @@ iterator rewrite(terms: Terms) : Terms =
of LeafTerm: newTerms &= @[LineTerm, LineTerm, PushTerm, GoRight, LeafTerm, PopTerm, GoLeft, LeafTerm]
else: newTerms &= @[term]
currentTerms = newTerms
yield currentTerms
if n == maxIterations:
return currentTerms
type StackControl = enum Push, Pop
# An intruction along with a change in angle and magnitude (i.e. a vector)
type DrawInstruction = object
angle_change: float64
width: float64
magnitude: float64
color: Color
type
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 pkStack: return "direction = " & $i.stackInstruction
iterator axiomToInstructions(maxIterations: int) : Instruction =
iterator axiomToInstructions(maxIterations: int, magnitude: float64, angle: float64) : Instruction =
let axiom = @[LeafTerm]
var n = 0
var termsToConvert: Terms
for terms in rewrite(axiom):
n += 1
if n == maxIterations:
termsToConvert = terms
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
let termsToConvert = rewrite(axiom, maxIterations)
var angle_delta: float64 = angle
var magnitudes: seq[float64] = @[magnitude]
var widths: seq[float64] = @[maxIterations.float64 + 3]
var current_magnitude = magnitude
var current_width: float64 = widths[0]
# 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:
angle_delta = sample(angles, angle_cdf)
magnitude = sample(magnitudes, magnitude_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])
case term:
of LeafTerm:
magnitude = magnitude / 1.5
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: angle_delta, magnitude: magnitude))
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: 0, magnitude: -magnitude)) # hack
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: -(angle_delta*2), magnitude: magnitude))
# when there's a leaf we want to make the magnitude smaller
let leaf_width = (10 * sample(@[0.50, 0.10, 0.25]))
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKGREEN, width: leaf_width, angle_change: angle_delta, magnitude: magnitudes[0]))
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:
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:
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:
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:
# Save current location
magnitudes = @[current_magnitude] & magnitudes
widths = @[current_width] & widths
yield Instruction(kind: pkStack, stackInstruction: Push)
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)
# A Position along with its angle
@ -100,7 +108,9 @@ proc `$` (p: Position): string =
type DrawLine = object
start_pos: Vector2
end_pos: Vector2
width: float64
angle: float64
color: Color
proc `$` (d: DrawLine): string =
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
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
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
if instructions.len <= 0:
echo "Returning"
return @[]
let inst = instructions[0]
var nextLine: DrawLine
case inst.kind:
of pkStack:
if inst.stackInstruction == Push:
return executeProgram(instructions[1..^1], current_pos & positions, current_pos)
elif inst.stackInstruction == Pop:
let newCurrent = positions[0]
return executeProgram(instructions[1..^1], positions[1..^1], newCurrent)
else:
return
of pkDraw:
nextLine = calculateNextLine(inst.drawInstruction, current_pos)
let new_position = Position(x: nextLine.end_pos.x,
y: nextLine.end_pos.y,
angle: nextLine.angle)
# leave the stack alone, set the current position however
return @[nextLine] & executeProgram(instructions[1..^1], positions, new_position)
proc guiLoop*(instructions: seq[Instruction]) =
var insts = instructions
var positions = @[starting_pos]
var current_pos = starting_pos
var draw_lines : seq[DrawLine] = @[]
while insts.len > 0:
let inst = insts[0]
var nextLine: DrawLine
case inst.kind:
of pkStack:
if inst.stackInstruction == Push:
insts = insts[1..^1]
positions = current_pos & positions
elif inst.stackInstruction == Pop:
current_pos = positions[0]
insts = insts[1..^1]
positions = positions[1..^1]
else:
continue
of pkDraw:
nextLine = calculateNextLine(inst.drawInstruction, current_pos)
let new_position = Position(x: nextLine.end_pos.x,
y: nextLine.end_pos.y,
angle: nextLine.angle)
# leave the stack alone, set the current position however
draw_lines = draw_lines & @[nextLine]
insts = insts[1..^1]
current_pos = new_position
return draw_lines
proc guiLoop*() =
# TODO get from xlib
var screenWidth: int = 100
var screenHeight: int = 100
@ -174,29 +194,72 @@ proc guiLoop*(instructions: seq[Instruction]) =
var panOffset = mousePos
var dragWindow = false
var exitWindow = false
var restartSimulation = false
var restartButton = false
var magnitude: float64 = 10
var angle: float64 = 30
SetTargetFPS(60)
# "axiom"
let startingPosition = Position(x: screenWidth/2, y: screenHeight.float64-100, angle: 90)
let drawLines = executeProgram(instructions, @[startingPosition], startingPosition)
var iterations = 2
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()
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
screenHeight = (monitor.GetMonitorHeight() / 2).int
# This must come before anything else!
ClearBackground(BLACK)
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()
CloseWindow()
when isMainModule:
#guiLoop()
guiLoop(toSeq(axiomToInstructions(7)))
guiLoop()

2
src/lsystempkg/raylib.nim

@ -1288,4 +1288,4 @@ proc SetAudioStreamVolume*(stream: AudioStream; volume: float32) {.RLAPI, import
proc SetAudioStreamPitch*(stream: AudioStream; pitch: float32) {.RLAPI, importc: "SetAudioStreamPitch".} # Set pitch for audio stream (1.0 is base level)
proc SetAudioStreamBufferSizeDefault*(size: int32) {.RLAPI, importc: "SetAudioStreamBufferSizeDefault".} # Default size for new audio streams
const GetImageData* = LoadImageColors
const KEY_MENU* = 82
const KEY_MENU* = 82

Loading…
Cancel
Save