diff --git a/README.md b/README.md index d86be27..53439ca 100644 --- a/README.md +++ b/README.md @@ -34,27 +34,27 @@ For more information, check out the [g3d wiki](https://github.com/groverburger/g The entire `main.lua` file for the Earth and Moon demo is under 30 lines, as shown here: ```lua -- written by groverbuger for g3d --- october 2020 +-- january 2021 -- MIT license -require "g3d" +g3d = require "g3d" function love.load() - Earth = Model:new("assets/sphere.obj", "assets/earth.png", {0,0,4}, nil, {-1,1,1}) - Moon = Model:new("assets/sphere.obj", "assets/moon.png", {5,0,4}, nil, {-0.5,0.5,0.5}) - Background = Model:new("assets/sphere.obj", "assets/starfield.png", {0,0,0}, nil, {500,500,500}) + Earth = g3d.newModel("assets/sphere.obj", "assets/earth.png", {0,0,4}, nil, {-1,1,1}) + Moon = g3d.newModel("assets/sphere.obj", "assets/moon.png", {5,0,4}, nil, {-0.5,0.5,0.5}) + Background = g3d.newModel("assets/sphere.obj", "assets/starfield.png", {0,0,0}, nil, {500,500,500}) Timer = 0 end function love.mousemoved(x,y, dx,dy) - FirstPersonCameraLook(dx,dy) + g3d.camera.firstPersonLook(dx,dy) end function love.update(dt) Timer = Timer + dt Moon:setTranslation(math.cos(Timer)*5, 0, math.sin(Timer)*5 +4) Moon:setRotation(0,-1*Timer,0) - FirstPersonCameraMovement(dt) + g3d.camera.firstPersonMovement(dt) end function love.draw() diff --git a/Thumbs.db b/Thumbs.db new file mode 100644 index 0000000..11cc24f Binary files /dev/null and b/Thumbs.db differ diff --git a/g3d/camera.lua b/g3d/camera.lua index 94b14ae..2a36612 100644 --- a/g3d/camera.lua +++ b/g3d/camera.lua @@ -1,17 +1,56 @@ -- written by groverbuger for g3d --- august 2020 +-- january 2021 -- MIT license +local shader = require(G3D_PATH .. "/shader") +local matrices = require(G3D_PATH .. "/matrices") + +---------------------------------------------------------------------------------------------------- +-- define the camera singleton +---------------------------------------------------------------------------------------------------- + +local camera = { + fov = math.pi/2, + nearClip = 0.01, + farClip = 1000, + aspectRatio = love.graphics.getWidth()/love.graphics.getHeight(), + position = {0,0,0}, + target = {0,0,0}, + down = {0,-1,0}, +} + +-- private variables used only for the first person camera functions +local fpsController = { + direction = 0, + pitch = 0 +} + +-- give the camera a point to look from and a point to look towards +function camera.lookAt(x,y,z, xAt,yAt,zAt) + camera.position[1] = x + camera.position[2] = y + camera.position[3] = z + camera.target[1] = xAt + camera.target[2] = yAt + camera.target[3] = zAt + + -- TODO: update fpsController's direction and pitch here + + -- update the camera in the shader + camera.updateViewMatrix() +end + -- move and rotate the camera, given a point and a direction and a pitch (vertical direction) -function SetCamera(x,y,z, direction,pitch) - Camera.position[1] = x - Camera.position[2] = y - Camera.position[3] = z - Camera.direction = direction or Camera.direction - Camera.pitch = pitch or Camera.pitch +function camera.lookInDirection(x,y,z, directionTowards,pitchTowards) + camera.position[1] = x + camera.position[2] = y + camera.position[3] = z + + fpsController.direction = directionTowards or fpsController.direction + fpsController.pitch = pitchTowards or fpsController.pitch -- convert the direction and pitch into a target point - local sign = math.cos(Camera.pitch) + local sign = math.cos(fpsController.pitch) if sign > 0 then sign = 1 elseif sign < 0 then @@ -19,79 +58,89 @@ function SetCamera(x,y,z, direction,pitch) else sign = 0 end - local cosPitch = sign*math.max(math.abs(math.cos(Camera.pitch)), 0.001) - Camera.target[1] = Camera.position[1]+math.sin(Camera.direction)*cosPitch - Camera.target[2] = Camera.position[2]-math.sin(Camera.pitch) - Camera.target[3] = Camera.position[3]+math.cos(Camera.direction)*cosPitch + local cosPitch = sign*math.max(math.abs(math.cos(fpsController.pitch)), 0.001) + camera.target[1] = camera.position[1]+math.sin(fpsController.direction)*cosPitch + camera.target[2] = camera.position[2]-math.sin(fpsController.pitch) + camera.target[3] = camera.position[3]+math.cos(fpsController.direction)*cosPitch -- update the camera in the shader - G3DShader:send("viewMatrix", GetViewMatrix(Camera.position, Camera.target, Camera.down)) + camera.updateViewMatrix() end --- give the camera a point to look from and a point to look towards -function SetCameraAndLookAt(x,y,z, xAt,yAt,zAt) - Camera.position[1] = x - Camera.position[2] = y - Camera.position[3] = z - Camera.target[1] = xAt - Camera.target[2] = yAt - Camera.target[3] = zAt +-- recreate the camera's view matrix from its current values +-- and send the matrix to the shader specified, or the default shader +function camera.updateViewMatrix(shaderGiven) + (shaderGiven or shader):send("viewMatrix", matrices.getViewMatrix(camera.position, camera.target, camera.down)) +end - -- update the camera in the shader - G3DShader:send("viewMatrix", GetViewMatrix(Camera.position, Camera.target, Camera.down)) +-- recreate the camera's projection matrix from its current values +-- and send the matrix to the shader specified, or the default shader +function camera.updateProjectionMatrix(shaderGiven) + (shaderGiven or shader):send("projectionMatrix", matrices.getProjectionMatrix(camera.fov, camera.nearClip, camera.farClip, camera.aspectRatio)) +end + +-- recreate the camera's orthographic projection matrix from its current values +-- and send the matrix to the shader specified, or the default shader +function camera.updateOrthographicMatrix(size, shaderGiven) + (shaderGiven or shader):send("projectionMatrix", matrices.getOrthographicMatrix(camera.fov, size or 5, camera.nearClip, camera.farClip, camera.aspectRatio)) end -- simple first person camera movement with WASD --- put this function in your love.update to use, passing in dt -function FirstPersonCameraMovement(dt) +-- put this local function in your love.update to use, passing in dt +function camera.firstPersonMovement(dt) -- collect inputs - local mx,my = 0,0 + local moveX,moveY = 0,0 local cameraMoved = false if love.keyboard.isDown("w") then - my = my - 1 + moveY = moveY - 1 end if love.keyboard.isDown("a") then - mx = mx - 1 + moveX = moveX - 1 end if love.keyboard.isDown("s") then - my = my + 1 + moveY = moveY + 1 end if love.keyboard.isDown("d") then - mx = mx + 1 + moveX = moveX + 1 end if love.keyboard.isDown("space") then - Camera.position[2] = Camera.position[2] - 0.15*dt*60 + camera.position[2] = camera.position[2] - 0.15*dt*60 cameraMoved = true end if love.keyboard.isDown("lshift") then - Camera.position[2] = Camera.position[2] + 0.15*dt*60 + camera.position[2] = camera.position[2] + 0.15*dt*60 cameraMoved = true end - -- add camera's direction and movement direction - -- then move in the resulting direction - if mx ~= 0 or my ~= 0 then - local angle = math.atan2(my,mx) - local speed = 0.15 - local dx,dz = math.cos(Camera.direction + angle)*speed*dt*60, math.sin(Camera.direction + angle + math.pi)*speed*dt*60 + -- do some trigonometry on the inputs to make movement relative to camera's direction + -- also to make the player not move faster in diagonal directions + if moveX ~= 0 or moveY ~= 0 then + local angle = math.atan2(moveY,moveX) + local speed = 9 + local directionX,directionZ = math.cos(fpsController.direction + angle)*speed*dt, math.sin(fpsController.direction + angle + math.pi)*speed*dt - Camera.position[1] = Camera.position[1] + dx - Camera.position[3] = Camera.position[3] + dz + camera.position[1] = camera.position[1] + directionX + camera.position[3] = camera.position[3] + directionZ cameraMoved = true end + -- update the camera's in the shader + -- only if the camera moved, for a slight performance benefit if cameraMoved then - SetCamera(Camera.position[1],Camera.position[2],Camera.position[3], Camera.direction,Camera.pitch) + camera.lookInDirection(camera.position[1],camera.position[2],camera.position[3], fpsController.direction,fpsController.pitch) end end --- best served with FirstPersonCameraMovement() -- use this in your love.mousemoved function, passing in the movements -function FirstPersonCameraLook(dx,dy) +function camera.firstPersonLook(dx,dy) + -- capture the mouse love.mouse.setRelativeMode(true) + local sensitivity = 1/300 - Camera.direction = Camera.direction + dx*sensitivity - Camera.pitch = math.max(math.min(Camera.pitch - dy*sensitivity, math.pi*0.5), math.pi*-0.5) + fpsController.direction = fpsController.direction + dx*sensitivity + fpsController.pitch = math.max(math.min(fpsController.pitch - dy*sensitivity, math.pi*0.5), math.pi*-0.5) - SetCamera(Camera.position[1],Camera.position[2],Camera.position[3], Camera.direction,Camera.pitch) + camera.lookInDirection(camera.position[1],camera.position[2],camera.position[3], fpsController.direction,fpsController.pitch) end + +return camera diff --git a/g3d/init.lua b/g3d/init.lua index d85c0c6..3a44455 100644 --- a/g3d/init.lua +++ b/g3d/init.lua @@ -1,5 +1,5 @@ -- written by groverbuger for g3d --- august 2020 +-- january 2021 -- MIT license --[[ @@ -14,63 +14,21 @@ \_/__/ --]] ----------------------------------------------------------------------------------------------------- --- set up the basic 3D shader ----------------------------------------------------------------------------------------------------- --- the shader that projects 3D meshes onto the screen +-- add the path to g3d to the global namespace +-- so submodules can know how to load their dependencies +G3D_PATH = ... -G3DShader = love.graphics.newShader [[ - uniform mat4 projectionMatrix; - uniform mat4 modelMatrix; - uniform mat4 viewMatrix; +local g3d = {} - varying vec4 vertexColor; - - #ifdef VERTEX - vec4 position(mat4 transform_projection, vec4 vertex_position) - { - vertexColor = VertexColor; - return projectionMatrix * viewMatrix * modelMatrix * vertex_position; - } - #endif - - #ifdef PIXEL - vec4 effect(vec4 color, Image tex, vec2 texcoord, vec2 pixcoord) - { - vec4 texcolor = Texel(tex, texcoord); - if (texcolor.a == 0.0) { discard; } - return vec4(texcolor)*color*vertexColor; - } - #endif -]] - ----------------------------------------------------------------------------------------------------- --- load in all the required files ----------------------------------------------------------------------------------------------------- - -require(... .. "/matrices") -require(... .. "/objloader") -require(... .. "/model") -require(... .. "/camera") - ----------------------------------------------------------------------------------------------------- --- set up the basic camera ----------------------------------------------------------------------------------------------------- - -Camera = { - fov = math.pi/2, - nearClip = 0.01, - farClip = 1000, - aspectRatio = love.graphics.getWidth()/love.graphics.getHeight(), - position = {0,0,0}, - target = {0,0,1}, - direction = 0, - pitch = 0, - down = {0,-1,0}, -} - --- create the projection matrix from the camera and send it to the shader -G3DShader:send("projectionMatrix", GetProjectionMatrix(Camera.fov, Camera.nearClip, Camera.farClip, Camera.aspectRatio)) +g3d.newModel = require(G3D_PATH .. "/model") +g3d.camera = require(G3D_PATH .. "/camera") +g3d.camera.updateProjectionMatrix() -- so that far polygons don't overlap near polygons love.graphics.setDepthMode("lequal", true) + +-- get rid of G3D_PATH from the global namespace +-- so the end user doesn't have to worry about any globals +G3D_PATH = nil + +return g3d diff --git a/g3d/matrices.lua b/g3d/matrices.lua index 98969a6..172fafc 100644 --- a/g3d/matrices.lua +++ b/g3d/matrices.lua @@ -1,17 +1,21 @@ -- written by groverbuger for g3d --- august 2020 +-- january 2021 -- MIT license +local vectors = require(G3D_PATH .. "/vectors") + ---------------------------------------------------------------------------------------------------- -- transformation, projection, and rotation matrices ---------------------------------------------------------------------------------------------------- -- the three most important matrices for 3d graphics -- these three matrices are all you need to write a simple 3d shader +local matrices = {} + -- returns a transformation matrix -- translation and rotation are 3d vectors -function GetTransformationMatrix(translation, rotation, scale) - local ret = IdentityMatrix() +function matrices.getTransformationMatrix(translation, rotation, scale) + local ret = matrices.getIdentityMatrix() -- translations ret[4] = translation[1] @@ -20,35 +24,35 @@ function GetTransformationMatrix(translation, rotation, scale) -- rotations -- x - local rx = IdentityMatrix() + local rx = matrices.getIdentityMatrix() rx[6] = math.cos(rotation[1]) rx[7] = -1*math.sin(rotation[1]) rx[10] = math.sin(rotation[1]) rx[11] = math.cos(rotation[1]) - ret = MatrixMult(ret, rx) + ret = matrices.multiply(ret, rx) -- y - local ry = IdentityMatrix() + local ry = matrices.getIdentityMatrix() ry[1] = math.cos(rotation[2]) ry[3] = math.sin(rotation[2]) - ry[9] = -math.sin(rotation[2]) + ry[9] = -1*math.sin(rotation[2]) ry[11] = math.cos(rotation[2]) - ret = MatrixMult(ret, ry) + ret = matrices.multiply(ret, ry) -- z - local rz = IdentityMatrix() + local rz = matrices.getIdentityMatrix() rz[1] = math.cos(rotation[3]) - rz[2] = -math.sin(rotation[3]) + rz[2] = -1*math.sin(rotation[3]) rz[5] = math.sin(rotation[3]) rz[6] = math.cos(rotation[3]) - ret = MatrixMult(ret, rz) + ret = matrices.multiply(ret, rz) -- scale - local sm = IdentityMatrix() + local sm = matrices.getIdentityMatrix() sm[1] = scale[1] sm[6] = scale[2] sm[11] = scale[3] - ret = MatrixMult(ret, sm) + ret = matrices.multiply(ret, sm) return ret end @@ -57,7 +61,7 @@ end -- (things farther away appear smaller) -- all arguments are scalars aka normal numbers -- aspectRatio is defined as window width divided by window height -function GetProjectionMatrix(fov, near, far, aspectRatio) +function matrices.getProjectionMatrix(fov, near, far, aspectRatio) local top = near * math.tan(fov/2) local bottom = -1*top local right = top * aspectRatio @@ -75,7 +79,7 @@ end -- (things farther away are the same size as things closer) -- all arguments are scalars aka normal numbers -- aspectRatio is defined as window width divided by window height -function GetOrthoMatrix(fov, size, near, far, aspectRatio) +function matrices.getOrthographicMatrix(fov, size, near, far, aspectRatio) local top = size * math.tan(fov/2) local bottom = -1*top local right = top * aspectRatio @@ -91,76 +95,47 @@ end -- returns a view matrix -- eye, target, and down are all 3d vectors -function GetViewMatrix(eye, target, down) - local z = NormalizeVector({eye[1] - target[1], eye[2] - target[2], eye[3] - target[3]}) - local x = NormalizeVector(CrossProduct(down, z)) - local y = CrossProduct(z, x) +function matrices.getViewMatrix(eye, target, down) + local z = vectors.normalizeVector({eye[1] - target[1], eye[2] - target[2], eye[3] - target[3]}) + local x = vectors.normalizeVector(vectors.crossProduct(down, z)) + local y = vectors.crossProduct(z, x) return { - x[1], x[2], x[3], -1*DotProduct(x, eye), - y[1], y[2], y[3], -1*DotProduct(y, eye), - z[1], z[2], z[3], -1*DotProduct(z, eye), + x[1], x[2], x[3], -1*vectors.dotProduct(x, eye), + y[1], y[2], y[3], -1*vectors.dotProduct(y, eye), + z[1], z[2], z[3], -1*vectors.dotProduct(z, eye), 0, 0, 0, 1, } end ----------------------------------------------------------------------------------------------------- --- basic vector functions ----------------------------------------------------------------------------------------------------- --- vectors are just 3 numbers in table, defined like {1,0,0} - -function NormalizeVector(vector) - local dist = math.sqrt(vector[1]^2 + vector[2]^2 + vector[3]^2) - return { - vector[1]/dist, - vector[2]/dist, - vector[3]/dist, - } -end - -function SubtractVector(v1, v2) - return {v1[1] - v2[1], v1[2] - v2[2], v1[3] - v2[3]} -end - -function DotProduct(a,b) - return a[1]*b[1] + a[2]*b[2] + a[3]*b[3] -end - -function CrossProduct(a,b) - return { - a[2]*b[3] - a[3]*b[2], - a[3]*b[1] - a[1]*b[3], - a[1]*b[2] - a[2]*b[1], - } -end - ---------------------------------------------------------------------------------------------------- -- basic matrix functions ---------------------------------------------------------------------------------------------------- -- matrices are just 16 numbers in table, representing a 4x4 matrix -- an identity matrix is defined as {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1} -function IdentityMatrix() +-- creates and returns a new identity matrix +function matrices.getIdentityMatrix() return {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1} end -- i find rows and columns confusing, so i use coordinate pairs instead -- this returns a value of a matrix at a specific coordinate -function GetMatrixXY(matrix, x,y) +function matrices.getMatrixValueAt(matrix, x,y) return matrix[x + (y-1)*4] end -- return the matrix that results from the two given matrices multiplied together -function MatrixMult(a,b) +function matrices.multiply(a,b) local ret = {0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0} local i = 1 for y=1, 4 do for x=1, 4 do - ret[i] = ret[i] + GetMatrixXY(a,1,y)*GetMatrixXY(b,x,1) - ret[i] = ret[i] + GetMatrixXY(a,2,y)*GetMatrixXY(b,x,2) - ret[i] = ret[i] + GetMatrixXY(a,3,y)*GetMatrixXY(b,x,3) - ret[i] = ret[i] + GetMatrixXY(a,4,y)*GetMatrixXY(b,x,4) + ret[i] = ret[i] + matrices.getMatrixValueAt(a,1,y)*matrices.getMatrixValueAt(b,x,1) + ret[i] = ret[i] + matrices.getMatrixValueAt(a,2,y)*matrices.getMatrixValueAt(b,x,2) + ret[i] = ret[i] + matrices.getMatrixValueAt(a,3,y)*matrices.getMatrixValueAt(b,x,3) + ret[i] = ret[i] + matrices.getMatrixValueAt(a,4,y)*matrices.getMatrixValueAt(b,x,4) i = i + 1 end end @@ -168,12 +143,4 @@ function MatrixMult(a,b) return ret end --- returns a transpose of the given matrix -function TransposeMatrix(m) - return { - GetMatrixXY(m, 1,1), GetMatrixXY(m, 1,2), GetMatrixXY(m, 1,3), GetMatrixXY(m, 1,4), - GetMatrixXY(m, 2,1), GetMatrixXY(m, 2,2), GetMatrixXY(m, 2,3), GetMatrixXY(m, 2,4), - GetMatrixXY(m, 3,1), GetMatrixXY(m, 3,2), GetMatrixXY(m, 3,3), GetMatrixXY(m, 3,4), - GetMatrixXY(m, 4,1), GetMatrixXY(m, 4,2), GetMatrixXY(m, 4,3), GetMatrixXY(m, 4,4), - } -end +return matrices diff --git a/g3d/model.lua b/g3d/model.lua index 48d0e00..fd67dec 100644 --- a/g3d/model.lua +++ b/g3d/model.lua @@ -1,31 +1,39 @@ -- written by groverbuger for g3d --- august 2020 +-- january 2021 -- MIT license +local vectors = require(G3D_PATH .. "/vectors") +local matrices = require(G3D_PATH .. "/matrices") +local loadObjFile = require(G3D_PATH .. "/objloader") + ---------------------------------------------------------------------------------------------------- -- define a model class ---------------------------------------------------------------------------------------------------- -Model = { +-- define some default properties that every model should inherit +-- that being the standard vertexFormat and basic 3D shader +local model = { vertexFormat = { {"VertexPosition", "float", 3}, {"VertexTexCoord", "float", 2}, {"VertexNormal", "float", 3}, {"VertexColor", "byte", 4}, }, + + shader = require(G3D_PATH .. "/shader"), } -Model.__index = Model +model.__index = model --- this returns a new instance of the Model class +-- this returns a new instance of the model class -- a model must be given a .obj file or equivalent lua table, and a texture -- translation, rotation, and scale are all 3d vectors and are all optional -function Model:new(given, texture, translation, rotation, scale) - local self = setmetatable({}, Model) +local function newModel(given, texture, translation, rotation, scale) + local self = setmetatable({}, model) -- if given is a string, use it as a path to a .obj file -- otherwise given is a table, use it as a model defintion if type(given) == "string" then - given = LoadObjFile(given) + given = loadObjFile(given) end -- if texture is a string, use it as a path to an image file @@ -35,18 +43,18 @@ function Model:new(given, texture, translation, rotation, scale) end -- initialize my variables - self.shader = G3DShader self.verts = given self.texture = texture self.mesh = love.graphics.newMesh(self.vertexFormat, self.verts, "triangles") self.mesh:setTexture(self.texture) - self:setTransform(translation or {0,0,0}, rotation or {0,0,0}, scale or {1,1,1}) + self:setTransform(translation, rotation, scale) return self end -- populate model's normals in model's mesh automatically -function Model:makeNormals() +-- if true is passed in, then the normals are all flipped +function model:makeNormals(isFlipped) for i=1, #self.verts, 3 do local vp = self.verts[i] local v = self.verts[i+1] @@ -54,23 +62,25 @@ function Model:makeNormals() local vec1 = {v[1]-vp[1], v[2]-vp[2], v[3]-vp[3]} local vec2 = {vn[1]-v[1], vn[2]-v[2], vn[3]-v[3]} - local normal = NormalizeVector(CrossProduct(vec1,vec2)) - vp[6] = normal[1] - vp[7] = normal[2] - vp[8] = normal[3] - - v[6] = normal[1] - v[7] = normal[2] - v[8] = normal[3] - - vn[6] = normal[1] - vn[7] = normal[2] - vn[8] = normal[3] + local normal = vectors.normalizeVector(vectors.crossProduct(vec1,vec2)) + local flippage = isFlipped and -1 or 1 + + vp[6] = normal[1] * flippage + vp[7] = normal[2] * flippage + vp[8] = normal[3] * flippage + + v[6] = normal[1] * flippage + v[7] = normal[2] * flippage + v[8] = normal[3] * flippage + + vn[6] = normal[1] * flippage + vn[7] = normal[2] * flippage + vn[8] = normal[3] * flippage end end -- move and rotate given two 3d vectors -function Model:setTransform(translation, rotation, scale) +function model:setTransform(translation, rotation, scale) self.translation = translation or {0,0,0} self.rotation = rotation or {0,0,0} self.scale = scale or {1,1,1} @@ -78,32 +88,40 @@ function Model:setTransform(translation, rotation, scale) end -- move given one 3d vector -function Model:setTranslation(tx,ty,tz) - self.translation = {tx,ty,tz} +function model:setTranslation(tx,ty,tz) + self.translation[1] = tx + self.translation[2] = ty + self.translation[3] = tz self:updateMatrix() end -- rotate given one 3d vector -function Model:setRotation(rx,ry,rz) - self.rotation = {rx,ry,rz} +function model:setRotation(rx,ry,rz) + self.rotation[1] = rx + self.rotation[2] = ry + self.rotation[3] = rz self:updateMatrix() end -- resize model's matrix based on a given 3d vector -function Model:setScale(sx,sy,sz) - self.scale = {sx,sy,sz} +function model:setScale(sx,sy,sz) + self.scale[1] = sx + self.scale[2] = sy or sx + self.scale[3] = sz or sx self:updateMatrix() end -- update the model's transformation matrix -function Model:updateMatrix() - self.matrix = GetTransformationMatrix(self.translation, self.rotation, self.scale) +function model:updateMatrix() + self.matrix = matrices.getTransformationMatrix(self.translation, self.rotation, self.scale) end -- draw the model -function Model:draw() +function model:draw() love.graphics.setShader(self.shader) self.shader:send("modelMatrix", self.matrix) love.graphics.draw(self.mesh) love.graphics.setShader() end + +return newModel diff --git a/g3d/objloader.lua b/g3d/objloader.lua index 9da56c6..fd76426 100644 --- a/g3d/objloader.lua +++ b/g3d/objloader.lua @@ -1,5 +1,5 @@ -- written by groverbuger for g3d --- august 2020 +-- january 2021 -- MIT license ---------------------------------------------------------------------------------------------------- @@ -7,7 +7,6 @@ ---------------------------------------------------------------------------------------------------- -- stitch two tables together and return the result --- useful for use in the LoadObjFile function local function concatTables(t1,t2,t3) local ret = {} @@ -26,7 +25,7 @@ end -- give path of file -- returns a lua table representation -function LoadObjFile(path) +local function objLoader(path) local verts = {} local faces = {} local uvs = {} @@ -61,7 +60,10 @@ function LoadObjFile(path) -- the arguments a point takes is v,vt,vn if words[1] == "f" then local store = {} + + -- TODO allow models with untriangulated faces assert(#words == 4, "Faces in "..path.." must be triangulated before they can be used in g3d!") + for i=2, #words do local num = "" local word = words[i] @@ -120,3 +122,5 @@ function LoadObjFile(path) return compiled end + +return objLoader diff --git a/g3d/shader.lua b/g3d/shader.lua new file mode 100644 index 0000000..a5f8b45 --- /dev/null +++ b/g3d/shader.lua @@ -0,0 +1,35 @@ +-- written by groverbuger for g3d +-- january 2021 +-- MIT license + +---------------------------------------------------------------------------------------------------- +-- define the 3d shader +---------------------------------------------------------------------------------------------------- +-- this is what projects 3d objects onto the 2d screen + +local shader = love.graphics.newShader [[ + uniform mat4 projectionMatrix; + uniform mat4 modelMatrix; + uniform mat4 viewMatrix; + + varying vec4 vertexColor; + + #ifdef VERTEX + vec4 position(mat4 transform_projection, vec4 vertex_position) + { + vertexColor = VertexColor; + return projectionMatrix * viewMatrix * modelMatrix * vertex_position; + } + #endif + + #ifdef PIXEL + vec4 effect(vec4 color, Image tex, vec2 texcoord, vec2 pixcoord) + { + vec4 texcolor = Texel(tex, texcoord); + if (texcolor.a == 0.0) { discard; } + return vec4(texcolor)*color*vertexColor; + } + #endif +]] + +return shader diff --git a/g3d/vectors.lua b/g3d/vectors.lua new file mode 100644 index 0000000..e668d84 --- /dev/null +++ b/g3d/vectors.lua @@ -0,0 +1,33 @@ +-- written by groverbuger for g3d +-- january 2021 +-- MIT license + +---------------------------------------------------------------------------------------------------- +-- basic vector functions +---------------------------------------------------------------------------------------------------- +-- vectors are just 3 numbers in table, defined like {1,0,0} + +local vectors = {} + +function vectors.normalizeVector(vector) + local dist = math.sqrt(vector[1]^2 + vector[2]^2 + vector[3]^2) + return { + vector[1]/dist, + vector[2]/dist, + vector[3]/dist, + } +end + +function vectors.dotProduct(a,b) + return a[1]*b[1] + a[2]*b[2] + a[3]*b[3] +end + +function vectors.crossProduct(a,b) + return { + a[2]*b[3] - a[3]*b[2], + a[3]*b[1] - a[1]*b[3], + a[1]*b[2] - a[2]*b[1], + } +end + +return vectors diff --git a/main.lua b/main.lua index 70f8eac..8ca5b99 100644 --- a/main.lua +++ b/main.lua @@ -1,25 +1,25 @@ -- written by groverbuger for g3d --- october 2020 +-- january 2021 -- MIT license -require "g3d" +g3d = require "g3d" function love.load() - Earth = Model:new("assets/sphere.obj", "assets/earth.png", {0,0,4}) - Moon = Model:new("assets/sphere.obj", "assets/moon.png", {5,0,4}, nil, {0.5,0.5,0.5}) - Background = Model:new("assets/sphere.obj", "assets/starfield.png", {0,0,0}, nil, {500,500,500}) + Earth = g3d.newModel("assets/sphere.obj", "assets/earth.png", {0,0,4}) + Moon = g3d.newModel("assets/sphere.obj", "assets/moon.png", {5,0,4}, nil, {0.5,0.5,0.5}) + Background = g3d.newModel("assets/sphere.obj", "assets/starfield.png", {0,0,0}, nil, {500,500,500}) Timer = 0 end function love.mousemoved(x,y, dx,dy) - FirstPersonCameraLook(dx,dy) + g3d.camera.firstPersonLook(dx,dy) end function love.update(dt) Timer = Timer + dt Moon:setTranslation(math.cos(Timer)*5, 0, math.sin(Timer)*5 +4) Moon:setRotation(0,-1*Timer,0) - FirstPersonCameraMovement(dt) + g3d.camera.firstPersonMovement(dt) end function love.draw()