Created
August 11, 2021 21:39
-
-
Save jocafa/906ab3dbe3af96377e92a4bb5f61972b to your computer and use it in GitHub Desktop.
Teardown Graph Experiment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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