From f4f3b06935019bab9b0a76c1e8192520d04a5b99 Mon Sep 17 00:00:00 2001 From: Wesley Kerfoot Date: Tue, 5 Oct 2021 23:54:47 -0400 Subject: [PATCH] refactoring --- src/lsystem.nim | 205 +++++++++++++++++++++++++------------- src/lsystempkg/raylib.nim | 2 +- 2 files changed, 135 insertions(+), 72 deletions(-) diff --git a/src/lsystem.nim b/src/lsystem.nim index 033f7fd..bda706a 100644 --- a/src/lsystem.nim +++ b/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() diff --git a/src/lsystempkg/raylib.nim b/src/lsystempkg/raylib.nim index a7c772a..524f6bd 100644 --- a/src/lsystempkg/raylib.nim +++ b/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 \ No newline at end of file +const KEY_MENU* = 82