Browse Source

add camera controls and multiple placements

master
Wesley Kerfoot 4 years ago
parent
commit
19744532a4
  1. 155
      src/lsystem.nim

155
src/lsystem.nim

@ -11,20 +11,31 @@ type Terms = seq[Term]
proc rewrite(terms: Terms, maxIterations: int) : 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: for _ in repeat(0, maxIterations):
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:
case term: case term:
of LeafTerm: newTerms &= @[LineTerm, LineTerm, PushTerm, GoRight, LeafTerm, PopTerm, GoLeft, LeafTerm] of LeafTerm:
case sample(toSeq(0..15)):
of 0..10: newTerms &= @[LineTerm, LineTerm, PushTerm, GoRight, LeafTerm, PopTerm, GoLeft, LeafTerm]
of 11..12: newTerms &= @[LineTerm, LineTerm, PushTerm, GoRight, PopTerm, GoLeft, LeafTerm]
of 13..15: newTerms &= @[LineTerm, LineTerm, PushTerm, GoRight, LeafTerm, PopTerm, GoLeft]
else:
continue
else: newTerms &= @[term] else: newTerms &= @[term]
currentTerms = newTerms currentTerms = newTerms
if n == maxIterations:
# Add a trunk proportional to the number of iterations
# Maybe should be proportional to the total magnitude of the entire thing somehow?
for _ in repeat(0, maxIterations):
currentTerms = @[LineTerm] & currentTerms
return currentTerms 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)
@ -46,25 +57,30 @@ 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, magnitude: float64, angle: float64) : Instruction = iterator axiomToInstructions(maxIterations: int, magnitude: float64, angle: float64, leafColor: Color = DARKGREEN) : Instruction =
var currentLeafColor = leafColor
let axiom = @[LeafTerm] let axiom = @[LeafTerm]
let termsToConvert = rewrite(axiom, maxIterations) let termsToConvert = rewrite(axiom, maxIterations)
var angle_delta: float64 = angle var angle_delta: float64 = angle
var magnitudes: seq[float64] = @[magnitude] var magnitudes: seq[float64] = @[magnitude]
var widths: seq[float64] = @[maxIterations.float64 + 3] var widths: seq[float64] = @[maxIterations.float64]
var current_magnitude = magnitude var current_magnitude = magnitude
var current_width: float64 = widths[0] var current_width: float64 = widths[0]
# axiom # axiom
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: 180, magnitude: magnitude)) yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(angle_change: 180, magnitude: magnitude))
for term in termsToConvert: for term in termsToConvert:
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]) let angle_delta = angle_delta * sample(@[1.0, 1.0, 0.9])
case term: case term:
of LeafTerm: of LeafTerm:
# when there's a leaf we want to make the magnitude smaller # when there's a leaf we want to make the magnitude smaller
let leaf_width = (10 * sample(@[0.50, 0.10, 0.25])) let leaf_width = (16 * sample(@[1.2, 1.0, 0.50]))
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 currentLeafColor.r += (sample(@[1, 2, -1, -2, 3]).uint8)
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKGREEN, width: leaf_width, angle_change: -(angle_delta*2), magnitude: magnitudes[0]))
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: currentLeafColor, width: leaf_width, angle_change: angle_delta, magnitude: magnitudes[0]))
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: currentLeafColor, width: leaf_width, angle_change: 0, magnitude: -magnitudes[0])) # hack
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: currentLeafColor, width: leaf_width, angle_change: -(angle_delta*2), magnitude: magnitudes[0]))
of LineTerm: of LineTerm:
# Draw without changing direction # Draw without changing direction
yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKBROWN, width: current_width, angle_change: 0, magnitude: magnitudes[0])) yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKBROWN, width: current_width, angle_change: 0, magnitude: magnitudes[0]))
@ -72,7 +88,7 @@ iterator axiomToInstructions(maxIterations: int, magnitude: float64, angle: floa
# L-systems don't go "backwards" # L-systems don't go "backwards"
# So you can go left or right on the x-axis at a given angle delta # So you can go left or right on the x-axis at a given angle delta
of GoLeft: of GoLeft:
current_magnitude = current_magnitude - (current_magnitude * sample(@[0.05, 0.01])) current_magnitude = current_magnitude - (current_magnitude * sample(@[0.05, 0.10]))
current_width = current_width - (current_width * sample(@[0.15, 0.10])) 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)) yield Instruction(kind: pkDraw, drawInstruction: DrawInstruction(color: DARKBROWN, width: current_width, angle_change: angle_delta, magnitude: current_magnitude))
of GoRight: of GoRight:
@ -99,6 +115,7 @@ iterator axiomToInstructions(maxIterations: int, magnitude: float64, angle: floa
type Position = object type Position = object
x: float64 x: float64
y: float64 y: float64
mid: Vector2
angle: float64 angle: float64
proc `$` (p: Position): string = proc `$` (p: Position): string =
@ -107,6 +124,7 @@ proc `$` (p: Position): string =
# Line (along with the angle relative to origin # Line (along with the angle relative to origin
type DrawLine = object type DrawLine = object
start_pos: Vector2 start_pos: Vector2
mid_pos: Vector2
end_pos: Vector2 end_pos: Vector2
width: float64 width: float64
angle: float64 angle: float64
@ -130,6 +148,8 @@ 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.mid_pos = Vector2(x: result.start_pos.x, y: result.end_pos.y)
result.width = inst.width result.width = inst.width
result.color = inst.color result.color = inst.color
result.angle = new_angle result.angle = new_angle
@ -162,6 +182,7 @@ proc executeProgram(instructions: seq[Instruction], starting_pos: Position) : se
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,
mid: nextLine.mid_pos,
angle: nextLine.angle) angle: nextLine.angle)
# leave the stack alone, set the current position however # leave the stack alone, set the current position however
@ -187,41 +208,50 @@ proc guiLoop*() =
SetWindowTitle("L-Systems") SetWindowTitle("L-Systems")
MaximizeWindow() MaximizeWindow()
#GuiLoadStyle("styles/terminal/terminal.rgs")
var mousePos = Vector2(x: 0, y: 0) var mousePos = Vector2(x: 0, y: 0)
var windowPos = Vector2(x: screenWidth.float64, y: screenHeight.float64) var windowPos = Vector2(x: screenWidth.float64, y: screenHeight.float64)
var panOffset = mousePos var panOffset = mousePos
SetTargetFPS(60)
# Control variables
var dragWindow = false var dragWindow = false
var restartSimulation = false var restartSimulation = false
var clearForest = false
var restartButton = false var restartButton = false
var magnitude: float64 = 10 var magnitude: float64 = 10
var angle: float64 = 30 var angle: float64 = 30
var iterations = 2
var startingPosition_x: float32 = screenWidth/2
var startingPosition_y: float32 = screenHeight.float32
var startingPositions: seq[Position] = @[]
var instructionLists: seq[seq[Instruction]] = @[]
var drawLinesList: seq[seq[DrawLine]] = @[]
var zoom: float32 = 1
var rotation: float32 = 0
var camera_x_offset = screenWidth/2
var camera_y_offset = screenHeight/2
SetTargetFPS(60) var camera: Camera2D
var iterations = 2 camera.offset = Vector2(x: camera_x_offset, y: camera_y_offset)
camera.target = Vector2(x: screenWidth/2, y: screenHeight/2)
camera.rotation = rotation
camera.zoom = zoom
# "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(): while not WindowShouldClose():
BeginDrawing() BeginDrawing()
restartSimulation = GuiButton(Rectangle(x: 0.float32, y: 0.float32, width: 100.float32, height: 20.float32), "Restart".cstring) restartSimulation = GuiButton(Rectangle(x: 0.float32, y: 20.float32, width: 100.float32, height: 20.float32), "Restart".cstring)
clearForest = GuiButton(Rectangle(x: 0.float32, y: 40.float32, width: 100.float32, height: 20.float32), "Clear".cstring)
let fewerIterations = GuiButton(Rectangle(x: 0.float32, y: 20.float32, width: 100.float32, height: 20.float32), "Fewer".cstring) let fewerIterations = GuiButton(Rectangle(x: 0.float32, y: 60.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) let moreIterations = GuiButton(Rectangle(x: 0.float32, y: 80.float32, width: 100.float32, height: 20.float32), "More".cstring)
magnitude = GuiSliderBar(Rectangle( magnitude = GuiSliderBar(Rectangle(
x: 0.float32, x: 0.float32,
y: 60.float32, y: 100.float32,
width: 80.float32, width: 80.float32,
height: 20.float32), height: 20.float32),
"Smaller", "Smaller",
@ -229,15 +259,43 @@ proc guiLoop*() =
magnitude, magnitude,
10, 100) 10, 100)
angle = GuiSliderBar(Rectangle( angle = GuiSliderBar(Rectangle(x: 0.float32,
x: 0.float32, y: 120.float32,
y: 90.float32,
width: 80.float32, width: 80.float32,
height: 20.float32), height: 20.float32),
"Narrower", "Narrower",
"Wider", "Wider",
angle, angle,
1, 45) 1, 360)
if IsKeyDown(KEY_DOWN) and IsKeyDown(KEY_LEFT_CONTROL):
zoom -= 0.01
if IsKeyDown(KEY_UP) and IsKeyDown(KEY_LEFT_CONTROL):
zoom += 0.01
if IsKeyDown(KEY_LEFT) and IsKeyDown(KEY_LEFT_CONTROL) and rotation < 360:
rotation += 1
if IsKeyDown(KEY_RIGHT) and IsKeyDown(KEY_LEFT_CONTROL) and rotation > -360:
rotation -= 1
camera.zoom = zoom
camera.rotation = rotation
if GetMouseX() <= 0:
camera_x_offset += 5
if GetMouseX() >= (GetScreenWidth() - 10):
camera_x_offset -= 5
if GetMouseY() <= 10:
camera_y_offset += 5
if GetMouseY() >= (GetScreenHeight() - 10):
camera_y_offset -= 5
camera.offset = Vector2(x: camera_x_offset, y: camera_y_offset)
if fewerIterations: if fewerIterations:
if iterations > 1: if iterations > 1:
iterations -= 1 iterations -= 1
@ -246,19 +304,44 @@ proc guiLoop*() =
restartSimulation = true restartSimulation = true
iterations += 1 iterations += 1
if IsKeyDown(KEY_LEFT_CONTROL) and IsMouseButtonPressed(MOUSE_LEFT_BUTTON):
startingPosition_x = GetMouseX().float32
startingPosition_y = GetMouseY().float32
let newPositionVector = GetScreenToWorld2D(Vector2(x: startingPosition_x, y: startingPosition_y), camera)
let newPosition = Position(x: newPositionVector.x, y: newPositionVector.y, angle: 90)
startingPositions &= @[newPosition]
let newInstructions = toSeq(axiomToInstructions(iterations, magnitude, angle))
drawLinesList &= @[executeProgram(newInstructions, newPosition)]
if restartSimulation: if restartSimulation:
echo "Re-executing" echo "Re-executing"
instructions = toSeq(axiomToInstructions(iterations, magnitude, angle)) drawLinesList = @[]
drawLines = executeProgram(instructions, startingPosition) for startingPosition in startingPositions:
let instructions = toSeq(axiomToInstructions(iterations, magnitude, angle))
drawLinesList &= @[executeProgram(instructions, startingPosition)]
if clearForest:
drawLinesList = @[]
instructionLists = @[]
startingPositions = @[]
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!
# Make sure to clear the background before drawing
ClearBackground(BLACK) ClearBackground(BLACK)
# Only want the camera to apply to drawn stuff, not controls
BeginMode2D(camera)
for drawLines in drawLinesList:
for line in drawLines: for line in drawLines:
DrawLineEx(line.start_pos, line.end_pos, line.width, line.color) DrawLineEx(line.start_pos, line.end_pos, line.width, line.color)
EndMode2D()
EndDrawing() EndDrawing()
CloseWindow() CloseWindow()
when isMainModule: when isMainModule:

Loading…
Cancel
Save