Skip to content

Instantly share code, notes, and snippets.

@jocafa
Created August 11, 2021 21:39
Show Gist options
  • Save jocafa/906ab3dbe3af96377e92a4bb5f61972b to your computer and use it in GitHub Desktop.
Save jocafa/906ab3dbe3af96377e92a4bb5f61972b to your computer and use it in GitHub Desktop.
Teardown Graph Experiment
#include "Scale.lua"
local function identity (n)
return function () return n end
end
Graph = {}
Graph.__index = Graph
function Graph.init()
Graph.squareSprite = LoadSprite('gfx/square.png')
end
function Graph.new()
local self = setmetatable({}, Graph)
self.data = {}
self.xScale = ScaleLinear.new()
self.yScale = ScaleLinear.new()
self._xTicks = 10
self._yTicks = 10
self._xTickColor = function () return 1, 1, 1, 1 end
self._yTickColor = function () return 1, 1, 1, 1 end
self._depth = -10
self._position = Vec(0, 0, -10)
self._color = function () return 1, 1, 1, 1 end
self._drawLineFunction = DebugLine
self._bgColor = function () return 1, 1, 1, 1 end
self._bgDepthTest = false
self._bgAdditive = false
return self
end
function Graph:xTicks(n)
if type(n) == 'number' then
self._xTicks = n
return self
else
return self._xTicks
end
end
function Graph:yTicks(n)
if type(n) == 'number' then
self._yTicks = n
return self
else
return self._yTicks
end
end
function Graph:xTickColor(c)
if c then
if type(c) == 'table' then
local r, g, b = c[1], c[2], c[3]
local a = #c < 4 and 1.0 or c[4]
self._xTickColor = function () return r, g, b, a end
return self
elseif type(c) == 'function' then
self._xTickColor = c
return self
end
else
return self
end
end
function Graph:yTickColor(c)
if c then
if type(c) == 'table' then
local r, g, b = c[1], c[2], c[3]
local a = #c < 4 and 1.0 or c[4]
self._yTickColor = function () return r, g, b, a end
return self
elseif type(c) == 'function' then
self._xTickColor = c
return self
end
else
return self
end
end
function Graph:depth(d)
if type(d) == 'number' then
self._depth = d
return self
else
return self._depth
end
end
function Graph:color(c)
if c then
if type(c) == 'table' then
local r, g, b = c[1], c[2], c[3]
local a = #c < 4 and 1.0 or c[4]
self._color = function () return r, g, b, a end
return self
elseif type(c) == 'function' then
self._color = c
return self
end
else
return self
end
end
function Graph:drawLineFunction(f)
if f and type(f) == 'function' then
self._drawLineFunction = f
return self
else
return self._drawLineFunction
end
end
function Graph:bgColor(c)
if c then
if type(c) == 'table' then
local r, g, b = c[1], c[2], c[3]
local a = #c < 4 and 1.0 or c[4]
self._bgColor = function () return r, g, b, a end
return self
elseif type(c) == 'function' then
self._bgColor = c
return self
end
else
return
end
end
function Graph:bgDepthTest(b)
if type(b) == 'boolean' then
self._bgDepthTest = b
return self
else
return self._bgDepthTest
end
end
function Graph:bgAdditive(b)
if type(b) == 'boolean' then
self._bgAdditive = b
return self
else
return self._bgAdditive
end
end
function Graph:draw()
local cam = GetPlayerCameraTransform()
local lastpoint = nil
local lastvalue = 0
local xs = self.xScale
local ys = self.yScale
local xd = xs:domain()
local yd = ys:domain()
local xr = xs:range()
local yr = ys:range()
local xc = (xr[1] + xr[2]) / 2
local yc = (yr[1] + yr[2]) / 2
local centerTransform = TransformToParentTransform(cam, Transform(Vec(xc, yc, self._depth), QuatEuler(0, 0, 0)))
local topLeftTransform = TransformToParentTransform(cam, Transform(Vec(xr[1], yr[1], self._depth), QuatEuler(0, 0, 0)))
local bgr, bgg, bgb, bga = self._bgColor()
DrawSprite(
Graph.squareSprite,
centerTransform,
xr[2] - xr[1], yr[2] - yr[1],
bgr, bgg, bgb, bga,
self._bgDepthTest,
self._bgAdditive
)
if self._xTicks > 0 then
local xt = self.xScale:domainTicks(self._xTicks)
for i=1, #xt do
local p1 = TransformToParentPoint(cam, { xs:calc(xt[i]), ys:calc(yd[1]), self._depth })
local p2 = TransformToParentPoint(cam, { xs:calc(xt[i]), ys:calc(yd[2]), self._depth })
local r, g, b, a = self._xTickColor()
self._drawLineFunction(
p1, p2,
r, g, b, a
)
end
end
if self._yTicks > 0 then
local yt = self.yScale:domainTicks(self._yTicks)
for i=1, #yt do
local p1 = TransformToParentPoint(cam, { xs:calc(xd[1]), ys:calc(yt[i]), self._depth })
local p2 = TransformToParentPoint(cam, { xs:calc(xd[2]), ys:calc(yt[i]), self._depth })
local r, g, b, a = self._yTickColor()
self._drawLineFunction(
p1, p2,
r, g, b, a
)
end
end
for i=1, #self.data-1 do
local d1 = self.data[i]
local d2 = self.data[i+1]
local r, g, b, a = self._color(d1, d2)
local p1 = TransformToParentPoint(cam, { xs:calc(d1.x), ys:calc(d1.y), self._depth })
local p2 = TransformToParentPoint(cam, { xs:calc(d2.x), ys:calc(d2.y), self._depth })
self._drawLineFunction(
p1, p2,
r, g, b, a
)
lastpoint = p2
lastvalue = d2.y
end
if lastpoint then
UiPush()
UiFont('bold.ttf', 16)
UiColor(1, 1, 1, 1)
UiTextShadow(0,0,0,0.5)
UiPush()
UiAlign('right bottom')
local x, y, d = UiWorldToPixel(TransformToParentPoint(cam, { xr[2], yr[2], self._depth }))
UiTranslate(x, y)
UiText(string.format("%.2f m/s", yd[2]))
UiPop()
UiPush()
UiAlign('left middle')
local x, y, d = UiWorldToPixel(lastpoint, self._depth)
UiTranslate(x, y)
UiText(string.format("%.2f m/s", lastvalue))
UiPop()
UiPop()
end
end
#include "Graph.lua"
local graph = Graph.new()
local firstRun = true
local dumpDepth = 0
local dumpLength = 0
function dumpList(key)
print(string.rep(' ', dumpDepth)..key)
local list = ListKeys(key)
for i=1, #list do
dumpDepth = dumpDepth + 1
dumpList(key..'.'..list[i])
dumpDepth = dumpDepth - 1
end
dumpLength = dumpLength + 1
end
function init()
Graph.init()
--[[
for i=0, 1, 0.01 do
table.insert(graph.data, { x = i, y = math.random() })
end
]]
graph:depth(-1.25)
graph.xScale:range({-1.5, -0.5})
graph.yScale:range({-1.0, -0.5})
graph:xTicks(4)
graph:xTickColor({1.0, 1.0, 1.0, 0.5})
graph:yTicks(3)
graph:yTickColor({0.0, 0.0, 0.0, 0.5})
graph:drawLineFunction(DrawLine)
graph:color({0.0, 0.7, 1.0, 1.0})
graph:bgColor({0.0, 0.0, 0.0, 0.2})
graph:bgAdditive(false)
end
function tick(dt)
local now = GetTime()
local old = now - 4.0
local speed = VecLength(GetPlayerVelocity())
local maxSpeed = 1.0
local clean = {}
for i=1, #graph.data do
if graph.data[i].x > old then
if graph.data[i].y > maxSpeed then maxSpeed = graph.data[i].y end
table.insert(clean, { x = graph.data[i].x, y = graph.data[i].y })
end
end
table.insert(clean, { x = now, y = speed })
graph.data = clean
graph.xScale:domain({old, now})
graph.yScale:domain({0.0, maxSpeed})
end
function update(dt)
end
function draw(dt)
graph:draw()
if firstRun then
firstRun = false
print('--[ '..string.format('%4f', GetTime())..' ]'..string.rep('-', 60))
dumpList('options')
dumpList('game')
dumpList('savegame')
dumpList('level')
print(string.format('[ %d lines ]', dumpLength))
end
end
local function interpolate(a, b, t)
return a * (1 - t) + b * t
end
local function normalize(a, b, x)
return (x - a) / (b - a)
end
local function clamp(a, b, x)
if a > b then
a, b = b, a
end
return math.max(a, math.min(b, x))
end
local function round(n)
return tonumber(string.format("%.0f", n), 10)
end
local e10 = math.sqrt(50)
local e5 = math.sqrt(10)
local e2 = math.sqrt(2)
local function tickIncrement(start, stop, count)
local step = (stop - start) / math.max(0, count)
local power = math.floor(math.log10(step))
local err = step / math.pow(10, power)
--print(string.format("start: %.2f, stop: %.2f, count: %.2f", start, stop, count))
--print(string.format("step: %.2f, power: %.2f, err: %.2f", step, power, err))
if power >= 0 then
if err >= e10 then
return 10 * math.pow(10, power)
elseif err >= e5 then
return 5 * math.pow(10, power)
elseif err >= e2 then
return 2 * math.pow(10, power)
else
return math.pow(10, power)
end
else
if err >= e10 then
return -math.pow(10, -power) / 10
elseif err >= e5 then
return -math.pow(10, -power) / 5
elseif err >= e2 then
return -math.pow(10, -power) / 2
else
return -math.pow(10, -power)
end
end
end
local function ticks(start, stop, count)
local reverse = false
local t = {}
local n = 0
local step = 0
if start == stop and count > 0 then return {start} end
reverse = stop < start
if reverse then
n = start
start = stop
stop = n
end
step = tickIncrement(start, stop, count)
--print(string.format("ticks() step: %.2f", step))
if step == 0 then return {} end
if step > 0 then
local r0 = round(start / step)
local r1 = round(stop / step)
if r0 * step < start then r0 = r0 + 1 end
if r1 * step > stop then r1 = r1 - 1 end
local n = r1 - r0
for i=0, n do
table.insert(t, (r0 + i) * step)
end
else
step = -step
local r0 = round(start * step)
local r1 = round(stop * step)
if r0 / step < start then r0 = r0 + 1 end
if r1 / step > stop then r0 = r0 - 1 end
local n = r1 - r0
for i=0, n do
table.insert(t, (r0 + i) / step)
end
end
if reverse then
local rev = {}
for i=#t, 0, -1 do
table.insert(rev, t[i])
end
return rev
else
return t
end
end
-- ScaleLinear -----------------------------------------------------------------
ScaleLinear = {}
ScaleLinear.__index = ScaleLinear
function ScaleLinear.new()
local self = setmetatable({}, ScaleLinear)
self._domain = {0, 1}
self._range = {0, 1}
self._clamp = false
return self
end
function ScaleLinear:domain(d)
if d and type(d) == 'table' then
self._domain = d
return self
else
return self._domain
end
end
function ScaleLinear:range(r)
if r and type(r) == 'table' then
self._range = r
return self
else
return self._range
end
end
function ScaleLinear:domainTicks(t)
return ticks(self._domain[1], self._domain[2], t)
end
function ScaleLinear:rangeTicks(t)
return ticks(self._range[1], self._range[2], t)
end
function ScaleLinear:clamp(c)
if type(c) == 'boolean' then
self._clamp = c
return self
else
return self._clamp
end
end
function ScaleLinear:calc(x)
local n = normalize(self._domain[1], self._domain[2], x)
local i = interpolate(self._range[1], self._range[2], n)
if self._clamp == true then
i = clamp(self._range[1], self._range[2], i)
end
return i
end
-- Main ------------------------------------------------------------------------
local function main()
local s = ScaleLinear.new()
--print(table.concat(s:domain(), ', '))
s:domain({-10, 10})
s:range({100, -100})
--print(table.concat(s:domain(), ', '))
--print(normalize(0, 50, 10))
print("5: "..s:calc(5))
print("50: "..s:calc(50))
s:clamp(true)
print("50 c: "..s:calc(50))
print("ticks...")
local t = ticks(-80, 10, 12)
print(string.format("[%d] %s",#t,table.concat(t, ', ')))
end
if type(require) == 'function' then main() end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment