mdsim/vis.nim

254 lines
6.6 KiB
Nim

import sdl2
import sdl2/ttf
import strutils
import linalg
import sequtils
import strformat
import os
var filename = "output.txt"
if paramCount() > 0:
filename = paramStr(1)
let lines = filename.readFile().split('\n').filterIt(it.len() > 0)
const
windowWidth = 1000
windowHeight = 1000
ballRadius = 8
textHeight = 35
# assumed to be in ratio with the win width/height
xMin = -50.0
xMax = 50.0
yMin = -50.0
yMax = 50.0
type
Particle = object
pos: Vector
vel: Vector
name: string
var lineN = 1
proc parseLine(line: string, t: var float): seq[Particle] =
type
TokenType = enum
ttFloat, ttString
Token = object
lineN: int
case kind: TokenType:
of ttFloat:
floatVal: float
of ttString:
stringVal: string
var
c = 1
tokens: seq[Token]
proc advance: char =
c.inc
line[c-1]
proc peek: char =
if c > line.high():
'\0'
else:
line[c]
proc scanNum: float =
let start = c - 1
while peek() in {'0'..'9'}:
discard advance()
if peek() == '.':
discard advance()
while peek() in {'0'..'9'}:
discard advance()
if peek() == 'e':
discard advance()
if peek() == '-' or peek() == '+':
discard advance()
while peek() in {'0'..'9'}:
discard advance()
return parseFloat(line[start..c-1])
proc scanIdent: string =
let start = c - 1
while peek() in {'a' .. 'z'}:
discard advance()
return line[start..c-1]
t = scanNum()
while c < line.len():
case advance():
of '-':
tokens.add(Token(lineN: lineN, kind: ttFloat, floatVal: scanNum()))
of '0'..'9':
tokens.add(Token(lineN: lineN, kind: ttFloat, floatVal: scanNum()))
of 'a' .. 'z':
tokens.add(Token(lineN: lineN, kind: ttString, stringVal: scanIdent()))
else:
discard #whitespace or [ or ]
var i = 0
while i < tokens.len():
try:
let name = tokens[i].stringVal
let x = tokens[i+1].floatVal
let y = tokens[i+2].floatVal
let vx = tokens[i+3].floatVal
let vy = tokens[i+4].floatVal
i += 5
result.add(Particle(name: name, pos: vector(x, y), vel: vector(vx, vy)))
except:
echo "parsing crashed on line" & $tokens[i].lineN
raise newException(Defect, "Parser crash")
inc lineN
proc vec2scr(v: Vector, x, y: var cint) =
let xper = (v.x - xMin) / (xMax - xMin)
let yper = (v.y - yMin) / (yMax - yMin)
x = cint(xper * windowWidth)
y = cint(yper * windowHeight)
# traces
var trace: array[2000, Vector]
var traceIndex = 0
proc drawRectAt(v: Vector, renderer: RendererPtr) =
trace[traceIndex] = v
traceIndex.inc()
if traceIndex > trace.high():
traceIndex = 0
# how many % of the screen it is at
var x, y: cint
vec2scr(v, x, y)
if x < ballRadius or y < ballRadius:
return
if x + ballRadius > windowWidth or y + ballRadius > windowHeight:
return
x -= ballRadius
y -= ballRadius
let size = cint(2 * ballRadius)
var r = rect(x, y, size, size)
renderer.fillRect(r)
proc drawArrow(start: Vector, delta: Vector, renderer: RendererPtr) =
var x, y: cint
var ex, ey: cint
vec2scr(start, x, y)
vec2scr(start+delta, ex, ey)
renderer.drawLine(x, y, ex, ey)
proc drawTrace(renderer: RendererPtr) =
var x, y: cint
for v in trace:
vec2scr(v, x, y)
renderer.drawPoint(x, y)
proc draw(renderer: RendererPtr, font: FontPtr, frame: int, speed: int) =
let line = lines[frame]
var t = 0.0
let parts = parseLine(line, t)
renderer.setDrawColor(0, 0, 0, 255)
renderer.clear()
renderer.setDrawColor(255, 255, 255, 255)
for i in 0..parts.high:
let part = parts[i]
part.pos.drawRectAt(renderer)
#part.pos.drawArrow(part.vel, renderer)
renderer.setDrawColor(0, 0, 255, 255)
#renderer.drawTrace()
let color = color(255, 255, 255, 0)
let text = &"t = {t:>09.2f} frame = {frame} speed = {speed}"
let surface = ttf.renderTextSolid(font, text, color)
let texture = renderer.createTextureFromSurface(surface)
surface.freeSurface()
defer: texture.destroy()
let textWidth: int = text.len() * int(float(textHeight) * 0.6)
var r = rect(
cint((windowWidth - textWidth) div 2),
0,
cint(textWidth),
textHeight
)
renderer.copy(texture, nil, addr r)
renderer.present()
type SDLException = object of Defect
template sdlFailIf(cond: bool, reason: string) =
if cond:
raise newException(SDLException, reason & ", SDL error " & $getError())
proc main =
sdlFailIf(not sdl2.init(INIT_VIDEO or INIT_TIMER or INIT_EVENTS)):
"SDL2 init required"
defer: sdl2.quit()
let win = createWindow(
title = "vis",
x = SDL_WINDOWPOS_CENTERED,
y = SDL_WINDOWPOS_CENTERED,
w = windowWidth,
h = windowHeight,
flags = SDL_WINDOW_SHOWN
)
sdlFailIf (win.isNil): "window couldn't be created"
defer: win.destroy()
let renderer = createRenderer(
window = win,
index = -1,
flags = Renderer_Accelerated or Renderer_PresentVsync or Renderer_TargetTexture
)
sdlFailIf renderer.isNil: "renderer couldn't be created"
defer: renderer.destroy()
sdlFailIf(not ttfInit()): "TTF init failed"
defer: ttfQuit()
let font = ttf.openFont("FreeMono.ttf", textHeight)
sdlFailIf(font.isNil): "font couldn't be created"
var
running = true
frame = 0
speed = 1
while running:
var event = defaultEvent
while pollEvent(event):
case event.kind:
of QuitEvent:
running = false
break
of KeyDown:
if event.key.keysym.scancode == SDL_SCANCODE_UP:
speed *= 2
elif event.key.keysym.scancode == SDL_SCANCODE_DOWN:
if speed > 1 or speed < -1:
speed = speed div 2
elif event.key.keysym.scancode == SDL_SCANCODE_LEFT:
speed = -speed
else:
discard
frame += speed
if frame < 1:
frame = 1
if frame > lines.high():
frame = lines.high()
draw(renderer, font, frame, speed)
main()