From db61937809ca0dff18f286ec0b25ff18b7fcc9ae Mon Sep 17 00:00:00 2001 From: Devyn Freet Date: Sun, 11 Oct 2026 22:55:13 -0400 Subject: [PATCH] init: tool for graphing functions and slope fields in love2d --- .luarc.json | 7 ++ coordinates.lua | 56 ++++++++++++++++ eulerNumber.lua | 15 +++++ main.lua | 165 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 .luarc.json create mode 100644 coordinates.lua create mode 100644 eulerNumber.lua create mode 100644 main.lua diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..d4dbaa1 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,7 @@ +{ + "runtime.version": "LuaJIT", + "workspace.library": ["../love2d"], + "runtime.special": { + "love.filesystem.load": "loadfile" + } +} diff --git a/coordinates.lua b/coordinates.lua new file mode 100644 index 0000000..9e1e744 --- /dev/null +++ b/coordinates.lua @@ -0,0 +1,56 @@ +local function getPixelPosition(point, minPoint, maxPoint, drawBounds) + if + point[1] < minPoint[1] + or point[1] > maxPoint[1] + or point[2] < minPoint[2] + or point[2] > maxPoint[2] + then + error("Point ("..point[1]..", "..point[2]..") outside the bounds (" + ..minPoint[1]..", "..minPoint[2]..") to ("..maxPoint[1]..", "..maxPoint[2]..")") + end + local pointBound = {maxPoint[1] - minPoint[1], maxPoint[2] - minPoint[2]} + local pointOffset = {point[1] - minPoint[1], -point[2] - minPoint[2]} + local position = { + (pointOffset[1] / pointBound[1]) * drawBounds.width + drawBounds.x, + (pointOffset[2] / pointBound[2]) * drawBounds.height + drawBounds.y + } + return position[1], position[2] +end + +function GraphFunction(x, y, width, height, func) + local points = {} + local origin = {x = (width + x) / 2, y = (height + y) / 2} + local neg_x, neg_y = -math.floor(origin.x), -math.floor(origin.y) + local pos_x, pos_y = math.floor(origin.x), math.floor(origin.y) + for i = neg_x, pos_x, 1 do + local j = math.floor(func(i)) + if j > neg_y and j < pos_y then + local k, l = getPixelPosition({i,j}, {neg_x,neg_y}, {pos_x,pos_y}, + {x=x,y=x,width=width,height=height}) + table.insert(points, k) + table.insert(points, l) + end + end + return points +end + +function GetLineFromSlope(midpoint, slope, length, color) + color = color or {1, 1, 1} + local delta_x, delta_y + if slope == nil then + delta_x, delta_y = 0, length + else + delta_x = math.sqrt((length ^ 2) / (1 + slope ^ 2)) + delta_y = -(slope * delta_x) + end + local x1 = midpoint.x - delta_x / 2 + local y1 = midpoint.y - delta_y / 2 + local x2 = x1 + delta_x + local y2 = y1 + delta_y + return { + p1 = {x = x1, y = y1}, + p2 = {x = x2, y = y2}, + midpoint = midpoint, + color = color + } +end diff --git a/eulerNumber.lua b/eulerNumber.lua new file mode 100644 index 0000000..e82b12f --- /dev/null +++ b/eulerNumber.lua @@ -0,0 +1,15 @@ +local function factorial(n) + if n <= 2 then + return n + end + return n * factorial(n-1) +end + +local function calculate_e(terms) + local e = 1 + for i = 1, terms do + e = e + 1 / factorial(i) + end + return e +end +return calculate_e(10) diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..fe974b8 --- /dev/null +++ b/main.lua @@ -0,0 +1,165 @@ +local utf8 = require("utf8") + +require("coordinates") +-- to use with functions involving e +--local e = require("eulerNumber") + +local function differential(x, y) + return 2*y +end + +local function loadWindow() + love.window.setTitle("Slope Fields") + love.keyboard.setKeyRepeat(true) +end +love.load = loadWindow + +local function drawLine(line) + love.graphics.setColor(unpack(line.color)) + love.graphics.line(line.p1.x, line.p1.y, line.p2.x, line.p2.y) + love.graphics.circle("fill", line.midpoint.x, line.midpoint.y, 4) +end + +local width, height = love.window.getMode() + +local boxMargin = 10 +local boxHeight = math.max(height / 12, 20) +local textbox = { + x = boxMargin, + y = height - (boxHeight + boxMargin), + width = width - 2 * boxMargin, + height = boxHeight, + padding = 5, + text = 'x', + active = false, + colors = { + background = {0.2, 0.2, 0.2}, + text = {0.9, 0.9, 0.9} + } +} +local graphbox = { + x = boxMargin, + y = boxMargin, + width = width - 2 * boxMargin, + height = textbox.y - 2 * boxMargin, + colors = { + background = {0.2, 0.2, 0.2} + } +} + +local center = { + x = graphbox.x + graphbox.width / 2, + y = graphbox.y + graphbox.height / 2 +} +local scale = 5 +local xScale, yScale +local step +if graphbox.width <= graphbox.height then + step = graphbox.width / (2 * (scale+1)) + xScale = scale + yScale = math.floor(graphbox.height / (2 * step)) - 1 +else + step = graphbox.height / (2 * (scale+1)) + yScale = scale + xScale = math.floor(graphbox.width / (2 * step)) - 1 +end + +local lines = { + grid = {}, + axes = { + x = GetLineFromSlope(center, 0, graphbox.width), + y = GetLineFromSlope(center, nil, graphbox.height) + }, + field = {} +} +local xpos, ypos = graphbox.x, graphbox.y + graphbox.height +for y = -yScale, yScale, 1 do + ypos = ypos - step + lines.grid[{y=y}] = GetLineFromSlope({x = center.x, y = ypos}, 0, graphbox.width, {0.3, 0.3, 0.3}) +end +for x = -xScale, xScale, 1 do + xpos = xpos + step + lines.grid[{x=x}] = GetLineFromSlope({x = xpos, y = center.y}, nil, graphbox.height, {0.3, 0.3, 0.3}) + ypos = graphbox.y + graphbox.height + for y = -yScale, yScale, 1 do + ypos = ypos - step + lines.field[{x, y}] = GetLineFromSlope({x = xpos, y = ypos}, differential(x,y), 50, {0, 0.5, 1}) + end +end + +local function func(x) + return x^2 +end +local points = GraphFunction(graphbox.x, graphbox.y, graphbox.width, graphbox.height, func) + +local function drawScene() + love.graphics.setColor(unpack(graphbox.colors.background)) + love.graphics.rectangle('fill', + graphbox.x, graphbox.y, + graphbox.width, graphbox.height, + 4, 4) + + love.graphics.setColor(unpack(textbox.colors.background)) + love.graphics.rectangle('fill', + textbox.x, textbox.y, + textbox.width, textbox.height, + 4, 4) + + love.graphics.setColor(unpack(textbox.colors.text)) + love.graphics.printf(textbox.text, + textbox.x + textbox.padding, textbox.y + textbox.padding, + textbox.width, 'left', 0, 2, 2) + + for _, line in pairs(lines.grid) do + drawLine(line) + end + for _, line in pairs(lines.axes) do + drawLine(line) + end + for _, line in pairs(lines.field) do + drawLine(line) + end + + if points ~= nil then + love.graphics.setColor(0,1,0) + love.graphics.points(points) + end +end +love.draw = drawScene + +local function textboxinput(text) + if textbox.active and string.match(text,"[xy+%-%/*%^%d()]") then + textbox.text = textbox.text .. text + end +end +love.textinput = textboxinput + +local function keypress(key) + if key == "backspace" then + local byteoffset = utf8.offset(textbox.text, -1) + + if byteoffset then + textbox.text = string.sub(textbox.text, 1, byteoffset - 1) + end + return + end + if key == "return" then + textbox.active = false + end +end +love.keypressed = keypress + +local function mousepress(x, y) + if + x >= textbox.x and + x <= textbox.x + textbox.width and + y >= textbox.y and + y <= textbox.y + textbox.height + then + textbox.active = true + elseif textbox.active then + textbox.active = false + end +end +love.mousepressed = mousepress +