Re: Groverburger's 3D Engine (g3d) v1.5.2 Release
Posted: Thu Jan 26, 2023 9:38 pm
It's working with anim9?
Code: Select all
-- written by groverbuger for g3d
-- september 2021
-- MIT license
----------------------------------------------------------------------------------------------------
-- simple obj loader
----------------------------------------------------------------------------------------------------
-- give path of file
-- returns a lua table representation
return function (path, uFlip, vFlip)
local positions, uvs, normals = {}, {}, {}
local result = {}
-- go line by line through the file
for line in love.filesystem.lines(path) do
local words = {}
-- split the line into words
for word in line:gmatch "([^%s]+)" do
table.insert(words, word)
end
local firstWord = words[1]
if firstWord == "v" then
-- if the first word in this line is a "v", then this defines a vertex's position
table.insert(positions, {tonumber(words[2]), tonumber(words[3]), tonumber(words[4])})
elseif firstWord == "vt" then
-- if the first word in this line is a "vt", then this defines a texture coordinate
local u, v = tonumber(words[2]), tonumber(words[3])
-- optionally flip these texture coordinates
if uFlip then u = 1 - u end
if vFlip then v = 1 - v end
table.insert(uvs, {u, v})
elseif firstWord == "vn" then
-- if the first word in this line is a "vn", then this defines a vertex normal
table.insert(normals, {tonumber(words[2]), tonumber(words[3]), tonumber(words[4])})
elseif firstWord == "f" then
-- if the first word in this line is a "f", then this is a face
-- a face takes three point definitions
-- the arguments a point definition takes are vertex, vertex texture, vertex normal in that order
local vertices = {}
for i = 2, #words do
local v, vt, vn = words[i]:match "(%d*)/(%d*)/(%d*)"
v, vt, vn = tonumber(v), tonumber(vt), tonumber(vn)
table.insert(vertices, {
v and positions[v][1] or 0,
v and positions[v][2] or 0,
v and positions[v][3] or 0,
vt and uvs[vt][1] or 0,
vt and uvs[vt][2] or 0,
vn and normals[vn][1] or 0,
vn and normals[vn][2] or 0,
vn and normals[vn][3] or 0,
})
end
-- triangulate the face if it's not already a triangle
if #vertices > 3 then
-- choose a central vertex
local centralVertex = vertices[1]
-- connect the central vertex to each of the other vertices to create triangles
for i = 2, #vertices - 1 do
table.insert(result, centralVertex)
table.insert(result, vertices[i])
table.insert(result, vertices[i + 1])
end
else
for i = 1, #vertices do
table.insert(result, vertices[i])
end
end
end
end
return result
end
By generating a cylinder? Here is a function I had some time ago:Rigachupe wrote: ↑Mon Jan 30, 2023 6:43 pm Can you draw a 3D line in this engine? I tried it by defining a vertex array as a 3D line with two points.
This is the desired format
local vertexFormatLines={
{"VertexPosition", "float", 3},
{"VertexColor", "byte", 4},
}
local vertices={{0,0,0,1,1,1,1},{0,0,100,1,1,1,1}}
model.geometry=love.graphics.newMesh(vertexFormatLines,vertices,"lines")
Then to draw it like this:
love.graphics.draw(model.geometry)
The error blue screen shows:
Invalid mesh draw mode 'lines', expected one of the 'triangles', 'fan', 'points'.
Any idea how to do a 3D line without the need to simulate some kind of 3D triangle strip?
Code: Select all
function generate_cylinder(height, radius, nb_segments)
local vertices = {}
local angle_step = 2 * math.pi / nb_segments
local angle = 0
local base_z = -height / 2
local tip_z = height / 2
for i = 1, nb_segments do
local x1 = radius * math.cos(angle)
local y1 = radius * math.sin(angle)
local x2 = radius * math.cos(angle + angle_step)
local y2 = radius * math.sin(angle + angle_step)
-- Base triangle
table.insert(vertices, {0,0,base_z})
table.insert(vertices, {x1,y1,base_z})
table.insert(vertices, {x2,y2,base_z})
-- Tip triangle
table.insert(vertices, {0,0,tip_z})
table.insert(vertices, {x1,y1,tip_z})
table.insert(vertices, {x2,y2,tip_z})
-- Side triangles
table.insert(vertices, {x1,y1,base_z})
table.insert(vertices, {x1,y1,tip_z})
table.insert(vertices, {x2,y2,base_z})
table.insert(vertices, {x2,y2,tip_z})
table.insert(vertices, {x2,y2,base_z})
table.insert(vertices, {x1,y1,tip_z})
angle = angle + angle_step
end
return vertices
end
Code: Select all
function generate_cylinder(height, radius, nb_segments)
local vertices = {}
local angle_step = 2 * math.pi / nb_segments
local angle = 0
local base_z = -height / 2
local tip_z = height / 2
for i = 1, nb_segments do
local x1 = radius * math.cos(angle)
local y1 = radius * math.sin(angle)
local x2 = radius * math.cos(angle + angle_step)
local y2 = radius * math.sin(angle + angle_step)
-- Base triangle
table.insert(vertices, {0,0,base_z, 0.5,0.5, 0,0,-1})
table.insert(vertices, {x1,y1,base_z, 0,1, 0,0,-1})
table.insert(vertices, {x2,y2,base_z, 1,1, 0,0,-1})
-- Tip triangle
table.insert(vertices, {0,0,tip_z, 0.5,0.5, 0,0,1})
table.insert(vertices, {x1,y1,tip_z, 0,0, 0,0,1})
table.insert(vertices, {x2,y2,tip_z, 1,0, 0,0,1})
-- Side triangles
local edge1_x, edge1_y, edge1_z = x1 - x2, y1 - y2, base_z - tip_z
local edge2_x, edge2_y, edge2_z = x2 - x1, y2 - y1, base_z - tip_z
local normal_x = edge1_y * edge2_z - edge1_z * edge2_y
local normal_y = edge1_z * edge2_x - edge1_x * edge2_z
local normal_z = edge1_x * edge2_y - edge1_y * edge2_x
local normal_length = math.sqrt(normal_x * normal_x + normal_y * normal_y + normal_z * normal_z)
local nx, ny, nz = normal_x / normal_length, normal_y / normal_length, normal_z / normal_length
table.insert(vertices, {x1,y1,base_z, 0, 0.5 - 0.5*y1/radius, nx,ny,nz})
table.insert(vertices, {x1,y1,tip_z, 1, 0.5 - 0.5*y1/radius, nx,ny,nz})
table.insert(vertices, {x2,y2,base_z, 0, 0.5 - 0.5*y2/radius, nx,ny,nz})
table.insert(vertices, {x2,y2,tip_z, 1, 0.5 - 0.5*y2/radius, nx,ny,nz})
table.insert(vertices, {x2,y2,base_z, 0, 0.5 - 0.5*y2/radius, nx,ny,nz})
table.insert(vertices, {x1,y1,tip_z, 1, 0.5 - 0.5*y1/radius, nx,ny,nz})
angle = angle + angle_step
end
return vertices
end
This looks great ! I really like how you handled the light coming off of lava and the sky.Jasoco wrote: ↑Sun Jan 29, 2023 7:01 am I do wish I could do true lighting though. With shadows and such. Right now it's pretty simplistic. A mixture of calculated point lights and pre-baked lighting. Also every texture can have an associated "bright pixel" atlas for textures that need to have parts that are always full brightness. (For example, the green lights on the doors, the fluorescent lights in the last screenshot and the soda machine facades are all bright pixels.)
Also, everything is pretty much coded tables instead of .obj files. I haven't been able to figure out how to use 3D modeling software and the ones that I like don't export the files with the proper axis "up" that g3d expects. So I find it easier to just construct everything from triangles manually. I feel that uses a lot of memory though. I had a lot of memory problems with some of my earlier experiments where I was trying to make too big of a world with way too many polygons. So I borrowed the method used by Leadhaul for building my maps and creating my sprites.
Much amazing! Care to share your googlings?Jasoco wrote: ↑Sun Jan 29, 2023 7:01 am It's amazing what you can do with shaders when you spend a little time Googling and experimenting.
Screenshot 2023-01-29 at 1.35.47 AM.png
Screenshot 2023-01-29 at 1.33.44 AM.png
Screenshot 2023-01-29 at 1.32.39 AM.png
Screenshot 2023-01-29 at 1.33.22 AM.png
Screenshot 2023-01-29 at 1.36.58 AM.png
Screenshot 2023-01-29 at 1.36.23 AM.png
I do wish I could do true lighting though. With shadows and such. Right now it's pretty simplistic. A mixture of calculated point lights and pre-baked lighting. Also every texture can have an associated "bright pixel" atlas for textures that need to have parts that are always full brightness. (For example, the green lights on the doors, the fluorescent lights in the last screenshot and the soda machine facades are all bright pixels.)
Also, everything is pretty much coded tables instead of .obj files. I haven't been able to figure out how to use 3D modeling software and the ones that I like don't export the files with the proper axis "up" that g3d expects. So I find it easier to just construct everything from triangles manually. I feel that uses a lot of memory though. I had a lot of memory problems with some of my earlier experiments where I was trying to make too big of a world with way too many polygons. So I borrowed the method used by Leadhaul for building my maps and creating my sprites.
Code: Select all
function love.conf(t)
t.window.width = 848
t.window.height = 480
end
Code: Select all
-- Codado por Vico
-- Primeira edição: 12/01/2023
-- À definir a licença
-- Biblioteca g3d
local g3d = require "g3d"
-- Modelo de Cubo
local cube_verts = {
-- top
{0,1,1, 0,0},
{0,0,1, 0,1},
{1,1,1, 1,0},
{0,0,1, 0,1},
{1,0,1, 1,1},
{1,1,1, 1,0},
-- bottom
{0,0,0, 1,0},
{0,1,0, 1,1},
{1,1,0, 0,1},
{1,0,0, 0,0},
{0,0,0, 1,0},
{1,1,0, 0,1},
-- side 1
{0,0,1, 0,0},
{0,0,0, 0,1},
{1,0,1, 1,0},
{0,0,0, 0,1},
{1,0,0, 1,1},
{1,0,1, 1,0},
-- side 2
{0,1,0, 1,1},
{0,1,1, 1,0},
{1,1,1, 0,0},
{1,1,0, 0,1},
{0,1,0, 1,1},
{1,1,1, 0,0},
-- side 3
{0,0,0, 1,1},
{0,0,1, 1,0},
{0,1,1, 0,0},
{0,1,0, 0,1},
{0,0,0, 1,1},
{0,1,1, 0,0},
-- side 4
{1,0,1, 0,0},
{1,0,0, 0,1},
{1,1,1, 1,0},
{1,0,0, 0,1},
{1,1,0, 1,1},
{1,1,1, 1,0},
};
-- Modelo 3D
local dirt = g3d.newModel(cube_verts, "assets/textures/blocks/dirt.png");
local timer = 0;
-- debug
function love.draw()
love.graphics.print("Hello World", 400, 300)
dirt:draw();
end
function love.update(dt)
timer = timer + dt;
g3d.camera.firstPersonMovement(dt);
if love.keyboard.isDown "escape" then
love.event.push "quit"
end
end
function love.mousemoved(x,y, dx,dy)
g3d.camera.firstPersonLook(dx,dy);
end
Rendering the scene to a canvas seems to solve it. I still have no idea why this is the case but the below code should work as intended.
Code: Select all
-- Codado por Vico
-- Primeira edição: 12/01/2023
-- À definir a licença
-- Biblioteca g3d
local g3d = require "g3d"
-- Modelo de Cubo
local cube_verts = {
-- top
{0,1,1, 0,0},
{0,0,1, 0,1},
{1,1,1, 1,0},
{0,0,1, 0,1},
{1,0,1, 1,1},
{1,1,1, 1,0},
-- bottom
{0,0,0, 1,0},
{0,1,0, 1,1},
{1,1,0, 0,1},
{1,0,0, 0,0},
{0,0,0, 1,0},
{1,1,0, 0,1},
-- side 1
{0,0,1, 0,0},
{0,0,0, 0,1},
{1,0,1, 1,0},
{0,0,0, 0,1},
{1,0,0, 1,1},
{1,0,1, 1,0},
-- side 2
{0,1,0, 1,1},
{0,1,1, 1,0},
{1,1,1, 0,0},
{1,1,0, 0,1},
{0,1,0, 1,1},
{1,1,1, 0,0},
-- side 3
{0,0,0, 1,1},
{0,0,1, 1,0},
{0,1,1, 0,0},
{0,1,0, 0,1},
{0,0,0, 1,1},
{0,1,1, 0,0},
-- side 4
{1,0,1, 0,0},
{1,0,0, 0,1},
{1,1,1, 1,0},
{1,0,0, 0,1},
{1,1,0, 1,1},
{1,1,1, 1,0},
};
-- Modelo 3D
local dirt = g3d.newModel(cube_verts, "assets/textures/blocks/dirt.png");
local timer = 0;
-- debug
local canvas = love.graphics.newCanvas(love.graphics.getDimensions())
function love.draw()
love.graphics.setCanvas({canvas,depth=true})
love.graphics.clear()
love.graphics.print("Hello World", 400, 300)
dirt:draw();
love.graphics.setCanvas()
love.graphics.draw(canvas)
end
function love.update(dt)
timer = timer + dt;
g3d.camera.firstPersonMovement(dt);
if love.keyboard.isDown "escape" then
love.event.push "quit"
end
end
function love.mousemoved(x,y, dx,dy)
g3d.camera.firstPersonLook(dx,dy);
end
I threw this together in paint, maybe it will help you understand how the models are built.
Thank you so much for this explanation. It really helped me to visualize how it works.Hydrogen Maniac wrote: ↑Mon Feb 06, 2023 8:34 pm I threw this together in paint, maybe it will help you understand how the models are built.
Verts.png
Code: Select all
{
"credit": "Made with Blockbench",
"elements": [
{
"from": [1, 1, 1],
"to": [2, 2, 2],
"color": 5,
"faces": {
"north": {"uv": [0, 0, 1, 1], "texture": "#missing"},
"east": {"uv": [0, 0, 1, 1], "texture": "#missing"},
"south": {"uv": [0, 0, 1, 1], "texture": "#missing"},
"west": {"uv": [0, 0, 1, 1], "texture": "#missing"},
"up": {"uv": [0, 0, 1, 1], "texture": "#missing"},
"down": {"uv": [0, 0, 1, 1], "texture": "#missing"}
}
}
]
}