add obj loader

This commit is contained in:
2025-02-21 02:03:50 +03:00
parent 82da17287e
commit db4809d385
4 changed files with 177 additions and 5 deletions

View File

@ -0,0 +1,125 @@
---@class Obj
---@field model string
---@field texture string
---@field texture_size integer
---@field position Vector3
---@field triangles table
local Obj = {}
local Vector3 = require "types.vector3"
Obj.__index = Obj
local function string_split(str, delimiter)
local res = {}
local i = 0
local f
local match = '(.-)' .. delimiter .. '()'
if string.find(str, delimiter) == nil then
return {str}
end
for sub, j in string.gmatch(str, match) do
i = i + 1
res[i] = sub
f = j
end
if i ~= 0 then
res[i+1] = string.sub(str, f)
end
return res
end
---parses the obj file and triangulates it for drawing
---@param model string path to obj model
---@param texture {file: string, size: number}
---@param position Vector3?
---@return Obj
function Obj.create(model, texture, position)
local pos
if position ~= nil then
pos = position
else
pos = Vector3()
end
local obj = {
model = model,
texture = texture.file,
texture_size = texture.size,
position = pos:copy(),
triangles = {}
}
obj.position = obj.position:copy()
setmetatable(obj, Obj)
-- adapted from https://github.com/karai17/lua-obj/blob/master/obj_loader.lua
-- Copyright (c) 2014 Landon Manning - LManning17@gmail.com - LandonManning.com
-- MIT
local file = file_read{file = obj.model}
local lines = string_split(file, "\r?\n")
local obj_data = {
v = {},
vt = {},
f = {},
}
for _, line in ipairs(lines) do
local l = string_split(line, "%s+")
if l[1] == "v" then
local v = {
x = tonumber(l[2]),
y = tonumber(l[3]),
z = tonumber(l[4]),
}
table.insert(obj_data.v, v)
elseif l[1] == "vt" then
local vt = {
x = tonumber(l[2]) * obj.texture_size,
y = tonumber(l[3]) * obj.texture_size,
}
table.insert(obj_data.vt, vt)
elseif l[1] == "f" then
local f = {}
for i = 2, #l do
local split = string_split(l[i], "/")
local v = {}
v.v = tonumber(split[1])
if split[2] ~= "" then v.vt = tonumber(split[2]) end
-- v.vn = tonumber(split[3])
table.insert(f, v)
end
table.insert(obj_data.f, f)
end
end
for _, face in ipairs(obj_data.f) do
for i = 2, #face - 1 do
local triangle = {
v0 = obj_data.v[face[1].v],
v1 = obj_data.v[face[i].v],
v2 = obj_data.v[face[i + 1].v],
uv0 = obj_data.vt[face[1].vt],
uv1 = obj_data.vt[face[i].vt],
uv2 = obj_data.vt[face[i + 1].vt],
texture = obj.texture
}
table.insert(obj.triangles, triangle)
end
end
return obj
end
function Obj:draw()
for _, triangle in ipairs(self.triangles) do
local newt = util.shallow_copy(triangle)
newt.v0 = Vector3(triangle.v0) + self.position
newt.v1 = Vector3(triangle.v1) + self.position
newt.v2 = Vector3(triangle.v2) + self.position
draw_triangle(newt)
end
end
return Obj

View File

@ -3,6 +3,10 @@ local player = require "classes.player"
local Vector3 = require "types.vector3"
local List = require "types.list"
local Obj = require "classes.obj"
local cube = Obj.create("models/unit_cube.obj", {file = "images/measure002a.png", size = 512}, Vector3(0, 1, 0))
local Feed = require "classes.feed"
---@type List
local feed = List()
@ -41,10 +45,6 @@ function game_tick()
-- ctx.initialization_needed is true first frame and every time dynamic reload is performed
if ctx.initialization_needed then
audio_play{audio = "music/bg1.xm", loops = true, channel = "music"}
-- ctx.udata persists on reload
ctx.udata = {
capture = false,
}
player.ThrowPressed:connect(create_feed)
-- spawn some ducks
@ -55,7 +55,13 @@ function game_tick()
duck.SeekFeed:connect(duck_seek_feed)
duck.index = i
end
end
-- ctx.udata persists on reload
if ctx.udata == nil then
ctx.udata = {
capture = false,
}
end
ctx.mouse_capture = ctx.udata.capture
@ -80,4 +86,7 @@ function game_tick()
texture_region = { x = 0, y = 0, w = 512, h = 512 },
}
draw_quad(util.merge(q, params))
cube.position.x = math.sin(ctx.frame_number * 0.01)
cube.position.z = math.cos(ctx.frame_number * 0.01)
cube:draw()
end