├── LICENSE ├── README.md └── mesh ├── generator.lua └── init.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Vectorbyte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LÖVE3D-Mesh 2 | Generates primitive mesh objects for LÖVE3D 3 | 4 | ![alt tag](http://i.imgur.com/X8qLr2f.png) 5 | 6 | # Usage 7 | ```lua 8 | mesh = require "mesh" 9 | 10 | -- Do stuff -- 11 | 12 | -- Set texture default 13 | mesh.texture_default(default_texture) 14 | 15 | -- Create mesh objects 16 | skybox = mesh.cuboid_new( 17 | cpml.vec3(-500, -500, -500), -- Position 18 | cpml.vec3(1000, 1000, 1000), -- Size 19 | { 20 | {x = 0 , y = 0 , w = 512, h = 512}, -- UV map 21 | {x = 256, y = 256, w = 256, h = 256}, -- You can generate UVs for each face of the cuboid, 22 | -- If you don't provide more UVs, the next cuboid faces default to the last UVs provided. 23 | }, 24 | skybox_texture -- Assign skybox_texture as the texture batch for this mesh 25 | ) 26 | 27 | sphere = mesh.sphere_new( 28 | cpml.vec3(0, 0, 0), -- Position 29 | cpml.vec3(1, 1, 1), -- Size 30 | {x = 21, z = 21}, -- Horizontal/Vertical loops 31 | {x = 128, y = 128, radius = 32} -- UV map 32 | -- No texture (defaults) 33 | ) 34 | 35 | -- Do more stuff -- 36 | 37 | love.graphics.setShader(shader_skybox) 38 | mesh.draw(skybox) 39 | love.graphics.setShader() 40 | 41 | love.graphics.setShader(shader_3D) 42 | mesh.draw(sphere) 43 | love.graphics.setShader() 44 | 45 | ``` 46 | 47 | # TODO 48 | - General cleanup 49 | - Improve on sphere UVs 50 | - Add Pyramid-Cone mesh generator 51 | - Add Cyllinder mesh generator 52 | - Add Geosphere mesh generator 53 | - Add Subdivided Quad generator 54 | 55 | # Dependencies 56 | - LÖVE3D 57 | - CPML 58 | -------------------------------------------------------------------------------- /mesh/generator.lua: -------------------------------------------------------------------------------- 1 | ---------------------------- 2 | -- Generation Functions 3 | ---------------------------- 4 | local generator = {} 5 | 6 | -- Creates UVs from meshes generated by quads ---------------------------------------------- 7 | function generator.create_quad_uvs(texture, coords, vertices) 8 | -- UV table 9 | local uvs = {} 10 | 11 | -- UV parameters 12 | local x, y, w, h 13 | 14 | -- Maximum ammount of loops 15 | local max = vertices/12 16 | 17 | -- Create the UV quads for each face of the cuboid 18 | for i = 1, max do 19 | -- Defaults 20 | if not coords[i] then 21 | x, y = x or 1, y or 1 22 | w, h = w or 64, h or 64 23 | else 24 | x, y = coords[i].x or 1, coords[i].y or 1 25 | w, h = coords[i].w or 64, coords[i].h or 64 26 | end 27 | 28 | -- Calculate the normalized coordinates 29 | local min = cpml.vec2(x/texture:getWidth(), x/texture:getHeight()) 30 | local max = cpml.vec2(w/texture:getWidth(), h/texture:getHeight()) 31 | 32 | local add = (i-1)*8 33 | 34 | -- UV Quad 35 | -- Vertex 1 36 | uvs[add+1] = min.x 37 | uvs[add+2] = max.y 38 | -- Vertex 2 39 | uvs[add+3] = max.x 40 | uvs[add+4] = max.y 41 | -- Vertex 3 42 | uvs[add+5] = max.x 43 | uvs[add+6] = min.y 44 | -- Vertex 4 45 | uvs[add+7] = min.x 46 | uvs[add+8] = min.y 47 | end 48 | 49 | return uvs 50 | end 51 | 52 | -- Creates UVs from meshes generated by loops ---------------------------------------------- 53 | function generator.create_loop_uvs(texture, coords, loops) 54 | local add_x = math.pi*2/loops.x 55 | local center = cpml.vec2(coords.x, coords.y) 56 | 57 | -- Expansion midpoint 58 | local vertical_midpoint = loops.z/2 59 | 60 | local uvs = { 61 | center.x/texture:getWidth(), center.y/texture:getHeight() 62 | } 63 | 64 | -- Stack horizontal loops 65 | for z = 1, loops.z - 3 do 66 | -- Solve vertex offset 67 | local new_z = z*loops.z/(loops.z - 2) 68 | local mid_expand = -math.cos((new_z - vertical_midpoint)/loops.z*math.pi) 69 | 70 | -- Create horizontal loop 71 | for x = 0, math.pi*2 - (add_x - cpml.constants.FLT_EPSILON), add_x do 72 | local uv_x = math.cos(x)*(center.x*mid_expand) + center.x 73 | local uv_y = -math.sin(x)*(center.y*mid_expand) + center.y 74 | 75 | table.insert(uvs, uv_x/texture:getWidth()) 76 | table.insert(uvs, uv_y/texture:getHeight()) 77 | end 78 | end 79 | 80 | -- Add final vertex 81 | table.insert(uvs, center.x/texture:getWidth()) 82 | table.insert(uvs, center.y/texture:getHeight()) 83 | 84 | return uvs 85 | end 86 | 87 | -- Creates vertex indices from meshes generated by quads 88 | function generator.create_quad_index(vertices) 89 | local indices = {} 90 | local v = 0 91 | 92 | for i = 1, vertices/3, 1 do indices[#indices+1] = i end 93 | for i = 4, vertices/3, 4 do 94 | table.insert(indices, 4*(i/4)+v, i-3) 95 | v = v + 1 96 | table.insert(indices, 4*(i/4)+v, i-1) 97 | v = v + 1 98 | end 99 | 100 | return indices 101 | end 102 | 103 | -- Creates vertex indices from meshes generated by edge loops ------------------------------ 104 | function generator.create_loop_index(loops) 105 | -- Create vertex indices 106 | local indices = {} 107 | 108 | local min_val = 3 109 | local max_val = loops.x + 2 110 | 111 | -- Create indices for the bottom triangles 112 | for i = min_val, max_val, 1 do 113 | table.insert(indices, i - 1) 114 | table.insert(indices, 1) 115 | 116 | if i == max_val then 117 | table.insert(indices, min_val - 1) 118 | else 119 | table.insert(indices, i) 120 | end 121 | end 122 | 123 | min_val = 2 124 | max_val = loops.x + 1 125 | 126 | -- Create indices for mid triangles 127 | for z = 1, loops.z - 4 do 128 | for x = min_val, max_val, 1 do 129 | local val = x + loops.x 130 | 131 | -- Triangle index 1 132 | table.insert(indices, val) 133 | table.insert(indices, x) 134 | -- Wrap triangle index 1 135 | if x == max_val then 136 | table.insert(indices, min_val) 137 | else 138 | table.insert(indices, x+1) 139 | end 140 | 141 | -- Triangle index 2 142 | -- Wrap triangle index 2 143 | if x == max_val then 144 | table.insert(indices, min_val) 145 | table.insert(indices, val+1-loops.x) 146 | table.insert(indices, val) 147 | else 148 | table.insert(indices, x+1) 149 | table.insert(indices, val+1) 150 | table.insert(indices, val) 151 | end 152 | end 153 | 154 | min_val = min_val + loops.x 155 | max_val = max_val + loops.x 156 | end 157 | 158 | min_val = min_val + 1 159 | max_val = min_val - 1 + loops.x 160 | 161 | -- Create indices for the top triangles 162 | for i = min_val, max_val, 1 do 163 | table.insert(indices, max_val) 164 | table.insert(indices, i - 1) 165 | 166 | if i == max_val then 167 | table.insert(indices, min_val - 1) 168 | else 169 | table.insert(indices, i) 170 | end 171 | end 172 | 173 | return indices 174 | end 175 | 176 | -- UV/vertex table merger ------------------------------------------------------------------ 177 | function generator.uv_merge(a, b) 178 | local ret = {} 179 | 180 | for i = 1, #a do 181 | local j, k = i*3, i*2 182 | 183 | -- Merge Vertex and UV coordinates 184 | ret[i] = { 185 | a[j-2], -- Vertex x 186 | a[j-1], -- Vertex y 187 | a[j-0], -- Vertex z 188 | b[k-1], -- UV x 189 | b[k-0] -- UV y 190 | } 191 | end 192 | 193 | return ret 194 | end 195 | 196 | return generator 197 | -------------------------------------------------------------------------------- /mesh/init.lua: -------------------------------------------------------------------------------- 1 | ---------------------------- 2 | -- Primitive Meshes 3 | ---------------------------- 4 | local current_folder = (...):gsub('%.[^%.]+$', '') .. "." 5 | local generator = require(current_folder .. "generator") 6 | local mesh = {} 7 | 8 | -- Set Defaults ---------------------------------------------------------------------------- 9 | function mesh.texture_default(texture) 10 | mesh.default_texture = texture 11 | end 12 | 13 | ---------------------------- 14 | -- Constructors 15 | ---------------------------- 16 | -- Cuboid constructor ---------------------------------------------------------------------- 17 | function mesh.cuboid_new(pos, max, uv_map, texture) 18 | -- Our cuboid object 19 | local cuboid = {} 20 | 21 | -- Texturing defaults 22 | local texture = texture or mesh.default_texture 23 | local uv_map = uv_map or {} 24 | 25 | -- Default minimum vertex position to 0 26 | local min = cpml.vec3() 27 | 28 | -- Vertex coordinates 29 | local vertices = { 30 | -- Top 31 | min.x, min.y, max.z, max.x, min.y, max.z, 32 | max.x, max.y, max.z, min.x, max.y, max.z, 33 | 34 | -- Bottom 35 | max.x, min.y, min.z, min.x, min.y, min.z, 36 | min.x, max.y, min.z, max.x, max.y, min.z, 37 | 38 | -- Front 39 | min.x, min.y, min.z, max.x, min.y, min.z, 40 | max.x, min.y, max.z, min.x, min.y, max.z, 41 | 42 | -- Back 43 | max.x, max.y, min.z, min.x, max.y, min.z, 44 | min.x, max.y, max.z, max.x, max.y, max.z, 45 | 46 | -- Right 47 | max.x, min.y, min.z, max.x, max.y, min.z, 48 | max.x, max.y, max.z, max.x, min.y, max.z, 49 | 50 | -- Left 51 | min.x, max.y, min.z, min.x, min.y, min.z, 52 | min.x, min.y, max.z, min.x, max.y, max.z 53 | } 54 | 55 | -- Create UVs and indices 56 | local uvs = generator.create_quad_uvs(texture, uv_map, #vertices) 57 | local indices = generator.create_quad_index(#vertices) 58 | 59 | -- Merge tables for mesh creation 60 | vertices = generator.uv_merge(vertices, uvs) 61 | 62 | -- Create the mesh 63 | local layout = { 64 | { "VertexPosition", "float", 3 }, 65 | { "VertexTexCoord", "float", 2 } 66 | } 67 | cuboid.mesh = love.graphics.newMesh(layout, vertices, "triangles", "static") 68 | 69 | -- Assignments 70 | cuboid.mesh:setVertexMap(indices) 71 | cuboid.mesh:setTexture(texture) 72 | 73 | cuboid.type = "cuboid" 74 | cuboid.position = pos 75 | cuboid.orientation = cpml.quat(0, 0, 0, 1) 76 | 77 | return cuboid 78 | end 79 | 80 | -- Quad constructor ------------------------------------------------------------------------ 81 | function mesh.quad_new(pos, max, uv_map, texture) 82 | -- Our quad object 83 | local quad = {} 84 | 85 | -- Texturing defaults 86 | local texture = texture or mesh.default_texture 87 | local uv_map = uv_map or {} 88 | 89 | -- Default minimum vertex position to 0 90 | local min = cpml.vec3() 91 | 92 | -- Vertex coordinates 93 | local vertices = { 94 | min.x, min.y, max.z, max.x, min.y, max.z, 95 | max.x, max.y, max.z, min.x, max.y, max.z, 96 | } 97 | 98 | -- Create UVs and indices 99 | local uvs = generator.create_uvs(texture, uv_map, #vertices) 100 | local indices = generator.create_index(#vertices) 101 | 102 | -- Merge tables for mesh creation 103 | vertices = generator.uv_merge(vertices, uvs) 104 | 105 | -- Create the mesh 106 | local layout = { 107 | { "VertexPosition", "float", 3 }, 108 | { "VertexTexCoord", "float", 2 } 109 | } 110 | quad.mesh = love.graphics.newMesh(layout, vertices, "triangles", "static") 111 | 112 | -- Assignments 113 | quad.mesh:setVertexMap(indices) 114 | quad.mesh:setTexture(texture) 115 | 116 | quad.type = "quad" 117 | quad.position = pos 118 | quad.orientation = cpml.quat(0, 0, 0, 1) 119 | 120 | return quad 121 | end 122 | 123 | -- Sphere constructor ---------------------------------------------------------------------- 124 | function mesh.sphere_new(pos, max, loops, uv_map, texture) 125 | -- Our sphere object 126 | local sphere = {} 127 | 128 | -- Texturing defaults 129 | local texture = texture or mesh.default_texture 130 | local uv_map = uv_map or {} 131 | 132 | -- Default minimum vertex position to 0 133 | local min = cpml.vec3() 134 | 135 | -- Round values if needed 136 | loops.x = math.floor(loops.x) 137 | loops.z = math.floor(loops.z) + 3 138 | 139 | assert(loops.x > 2 and loops.z > 2, "Not enough loops to create sphere") 140 | 141 | -- Expansion midpoint 142 | local vertical_midpoint = loops.z/2 143 | 144 | -- Create vertex table and add first vertex 145 | local vertices = { 146 | max.x/2, max.y/2, min.z 147 | } 148 | 149 | local add_x = math.pi*2/loops.x 150 | 151 | -- Stack horizontal loops 152 | for z = 1, loops.z - 3 do 153 | -- Solve vertex height 154 | local new_z = z*loops.z/(loops.z - 2) 155 | 156 | local mid_expand = -math.cos((new_z - vertical_midpoint)/loops.z*math.pi) 157 | local mult_z = -math.cos(new_z/loops.z*math.pi)/2+0.5 158 | 159 | local vertex_z = max.z*mult_z 160 | 161 | -- Create horizontal loop 162 | for x = 0, math.pi*2 - (add_x - cpml.constants.FLT_EPSILON), add_x do 163 | local vertex_x = math.cos(x)*(max.x*mid_expand)/2 + max.x/2 164 | local vertex_y = math.sin(x)*(max.y*mid_expand)/2 + max.y/2 165 | 166 | table.insert(vertices, vertex_x) 167 | table.insert(vertices, vertex_y) 168 | table.insert(vertices, vertex_z) 169 | end 170 | end 171 | 172 | -- Add final vertex 173 | table.insert(vertices, max.x/2) 174 | table.insert(vertices, max.y/2) 175 | table.insert(vertices, max.z) 176 | 177 | -- Create UVs and indices 178 | local uvs = generator.create_loop_uvs(texture, uv_map, loops) 179 | local indices = generator.create_loop_index(loops) 180 | 181 | -- Merge tables for mesh creation 182 | vertices = generator.uv_merge(vertices, uvs) 183 | 184 | -- Create the mesh 185 | local layout = { 186 | { "VertexPosition", "float", 3 }, 187 | { "VertexTexCoord", "float", 2 } 188 | } 189 | sphere.mesh = love.graphics.newMesh(layout, vertices, "triangles", "static") 190 | 191 | -- Assignments 192 | sphere.mesh:setVertexMap(indices) 193 | sphere.mesh:setTexture(texture) 194 | 195 | sphere.type = "sphere" 196 | sphere.position = pos 197 | sphere.orientation = cpml.quat(0, 0, 0, 1) 198 | 199 | return sphere 200 | end 201 | 202 | ---------------------------- 203 | -- Drawing 204 | ---------------------------- 205 | function mesh.draw(object) 206 | local projection = cpml.mat4() 207 | :translate(object.position) 208 | :rotate(object.orientation) 209 | 210 | love.graphics.getShader():send("u_model", projection:to_vec4s()) 211 | 212 | -- draw the mesh 213 | love.graphics.draw(object.mesh) 214 | end 215 | 216 | return mesh 217 | --------------------------------------------------------------------------------