├── .gitattributes ├── .gitignore ├── barrier_bend.vox ├── boat ├── flags.lua └── texture.png ├── conf.lua ├── cube ├── 1 │ ├── flags.lua │ └── texture.png ├── 2 │ ├── flags.lua │ └── texture.png └── 3 │ ├── flags.lua │ └── texture.png ├── lovox ├── animModel.lua ├── camera.lua ├── init.lua ├── model.lua ├── modelData.lua ├── vox2png │ ├── vox_model.lua │ └── vox_texture.lua └── zlist.lua └── main.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | zzStartzz.bat 49 | -------------------------------------------------------------------------------- /barrier_bend.vox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keyslam/Lovox/73678fc76b01a0e182db7983d05c36c2827acaca/barrier_bend.vox -------------------------------------------------------------------------------- /boat/flags.lua: -------------------------------------------------------------------------------- 1 | return { 2 | frames = 16, 3 | width = 64, 4 | height = 32, 5 | } 6 | -------------------------------------------------------------------------------- /boat/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keyslam/Lovox/73678fc76b01a0e182db7983d05c36c2827acaca/boat/texture.png -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf(t) 2 | t.identity = "Lovox demo" 3 | t.version = "11.0" 4 | t.console = false 5 | 6 | t.window.title = "Lovox demo" 7 | t.window.width = 640 8 | t.window.height = 640 9 | t.window.resizable = true 10 | 11 | t.console = true 12 | end 13 | -------------------------------------------------------------------------------- /cube/1/flags.lua: -------------------------------------------------------------------------------- 1 | return { 2 | frames = 4, 3 | width = 4, 4 | height = 4, 5 | } 6 | -------------------------------------------------------------------------------- /cube/1/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keyslam/Lovox/73678fc76b01a0e182db7983d05c36c2827acaca/cube/1/texture.png -------------------------------------------------------------------------------- /cube/2/flags.lua: -------------------------------------------------------------------------------- 1 | return { 2 | frames = 4, 3 | width = 4, 4 | height = 4, 5 | } 6 | -------------------------------------------------------------------------------- /cube/2/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keyslam/Lovox/73678fc76b01a0e182db7983d05c36c2827acaca/cube/2/texture.png -------------------------------------------------------------------------------- /cube/3/flags.lua: -------------------------------------------------------------------------------- 1 | return { 2 | frames = 4, 3 | width = 4, 4 | height = 4, 5 | } 6 | -------------------------------------------------------------------------------- /cube/3/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keyslam/Lovox/73678fc76b01a0e182db7983d05c36c2827acaca/cube/3/texture.png -------------------------------------------------------------------------------- /lovox/animModel.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2016 Justin van der Leij 'Tjakka5' 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | ]] 25 | 26 | local _PATH = (...):gsub('%.[^%.]+$', '') 27 | local Model = require(_PATH..".model") 28 | 29 | local function setLooping(self, v) self.looping = v end 30 | local function isLooping(self) return self.looping end 31 | 32 | local function setPlaybackSpeed(self, s) self.playbackSpeed = #self.frames / s end 33 | local function getPlaybackSpeed(self) return self.playbackSpeed * #self.frames end 34 | 35 | local function isPlaying() return self.playing end 36 | local function isPaused() return self.paused end 37 | 38 | local function play(self) 39 | self.playing = true 40 | self.paused = false 41 | end 42 | 43 | local function stop(self) 44 | self.playing = false 45 | self:rewind() 46 | end 47 | 48 | local function pause(self) 49 | self.playing = false 50 | self.paused = true 51 | end 52 | 53 | local function resume(self) 54 | self.playing = true 55 | self.paused = false 56 | end 57 | 58 | local function rewind(self) 59 | self.currentTime = 0 60 | self.currentFrame = 1 61 | end 62 | 63 | local function update(self, dt) 64 | if self.playing and not self.paused then 65 | self.currentTime = self.currentTime + dt 66 | if self.currentTime >= self.playbackSpeed then 67 | self.currentTime = self.currentTime - self.playbackSpeed 68 | self.currentFrame = self.currentFrame + 1 69 | if self.currentFrame > #self.frames then 70 | if self.looping then 71 | self.currentFrame = 1 72 | else 73 | self:stop() 74 | end 75 | end 76 | end 77 | end 78 | end 79 | 80 | local function draw(self, ...) 81 | self.frames[self.currentFrame]:draw(...) 82 | end 83 | 84 | 85 | local function new(frames, playbackSpeed) 86 | local animModel = { 87 | frames = {}, 88 | playbackSpeed = playbackSpeed / #frames, 89 | 90 | playing = false, 91 | paused = false, 92 | looping = false, 93 | currentFrame = 1, 94 | currentTime = 0, 95 | 96 | setLooping = setLooping, 97 | isLooping = isLooping, 98 | 99 | setPlaybackSpeed = setPlaybackSpeed, 100 | getPlaybackSpeed = getPlaybackSpeed, 101 | 102 | isPlaying = isPlaying, 103 | isPaused = isPaused, 104 | 105 | play = play, 106 | pause = pause, 107 | stop = stop, 108 | rewind = rewind, 109 | resume = resume, 110 | 111 | update = update, 112 | draw = draw, 113 | } 114 | 115 | for i = 1, #frames do 116 | animModel.frames[i] = Model(frames[i]) 117 | end 118 | 119 | return animModel 120 | end 121 | 122 | return setmetatable({ 123 | new = new, 124 | 125 | setLooping = setLooping, 126 | isLooping = isLooping, 127 | 128 | setPlaybackSpeed = setPlaybackSpeed, 129 | getPlaybackSpeed = getPlaybackSpeed, 130 | 131 | isPlaying = isPlaying, 132 | isPaused = isPaused, 133 | 134 | play = play, 135 | pause = pause, 136 | stop = stop, 137 | rewind = rewind, 138 | resume = resume, 139 | 140 | update = update, 141 | draw = draw, 142 | }, { 143 | __call = function(_, ...) return new(...) end, 144 | }) 145 | -------------------------------------------------------------------------------- /lovox/camera.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2016 Justin van der Leij 'Tjakka5' 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | ]] 25 | 26 | local _PATH = (...):gsub('%.[^%.]+$', '') 27 | local Zlist = require(_PATH..".zlist") 28 | 29 | -- Internally used to render a model, can be used to directly draw a model without using the z-buffer 30 | local function renderModel(model, self) 31 | love.graphics.draw(model.spritebatch, model.x, model.y, nil, self.scale, self.scale) 32 | end 33 | 34 | 35 | -- Setters for position 36 | local function move(self, dx, dy) self.x, self.y = self.x + dx, self.y + dy end 37 | local function moveTo(self, x, y) self.x, self.y = x, y end 38 | 39 | -- Setters for rotation 40 | local function rotate(self, r) 41 | self.rotation = self.rotation + r 42 | self:updateRotations() 43 | end 44 | local function rotateTo(self, r) 45 | self.rotation = r 46 | self:updateRotations() 47 | end 48 | 49 | -- Setters for scale 50 | local function zoom(self, s) self.scale = self.scale * s end 51 | local function zoomTo(self, s) self.scale = s end 52 | 53 | 54 | -- Updates the camera importance values 55 | local function updateRotations(self) 56 | self.psin, self.nsin = math.sin(self.rotation), math.sin(-self.rotation) 57 | self.pcos, self.ncos = math.cos(self.rotation), math.cos(-self.rotation) 58 | end 59 | 60 | -- Returns x, y, w, h of the camera as a bounding box 61 | local function getBoundingBox(self) 62 | local s, c = self.psin, self.pcos 63 | if s < 0 then s = -s end 64 | if c < 0 then c = -c end 65 | local w, h = self.w / self.scale, self.h / self.scale 66 | local sw, sh = h * s + w * c, h * c + w * s 67 | return self.x - sw / 2, self.y - sh / 2, sw, sh 68 | end 69 | 70 | -- Translates x, y, z in the world into x, y on the screen. 71 | -- !! Code taken from Hump's camera. Credit goes to vrld !! 72 | local function worldToScreen(self, x, y, z) 73 | local c, s = self.pcos, self.psin 74 | x, y = x - self.x, y - self.y 75 | x, y = c * x - s * y, s * x + c * y 76 | return x * self.scale + self.w / 2, y * self.scale + self.h / 2 - z 77 | end 78 | 79 | -- Translates x, y on the screen into x, y, z in the world. z is 0 80 | -- !! Code taken from Hump's camera. Credit goes to vrld !! 81 | local function screenToWorld(self, x, y) 82 | local c, s = self.ncos, self.nsin 83 | x, y = (x - self.w / 2) / self.scale, (y - self.h / 2) / self.scale 84 | x, y = c * x - s * y, s * x + c * y 85 | return x + self.x, y + self.y, 0 86 | end 87 | 88 | 89 | -- Adds a model to the z-buffer 90 | local function draw(self, model) 91 | self.buffer:add(model, model.y) 92 | end 93 | 94 | -- Renders all the models and clears the z-buffer 95 | local function render(self) 96 | local r, g, b, a = love.graphics.getColor() 97 | love.graphics.setColor(255, 255, 255) 98 | 99 | self.buffer:forEach(renderModel, self) 100 | self.buffer:clear() 101 | 102 | love.graphics.setColor(r, g, b, a) 103 | end 104 | 105 | -- Resizes the camera size to the screen size 106 | local function resize(self, w, h) 107 | w, h = w or love.graphics.getWidth(), h or love.graphics.getHeight() 108 | self.w = w 109 | self.h = h 110 | end 111 | 112 | -- Constructor 113 | local function new(x, y, rotation, scale) 114 | return { 115 | x = x or 0, 116 | y = y or 0, 117 | w = love.graphics.getWidth() * (scale or 1), 118 | h = love.graphics.getHeight() * (scale or 1), 119 | rotation = rotation or 0, 120 | scale = scale or 1, 121 | psin = math.sin(rotation or 0), nsin = math.sin(rotation or 0), 122 | pcos = math.cos(rotation or 0), ncos = math.cos(rotation or 0), 123 | buffer = Zlist(), 124 | 125 | move = move, 126 | moveTo = moveTo, 127 | rotate = rotate, 128 | rotateTo = rotateTo, 129 | zoom = zoom, 130 | zoomTo = zoomTo, 131 | 132 | updateRotations = updateRotations, 133 | getBoundingBox = getBoundingBox, 134 | worldToScreen = worldToScreen, 135 | screenToWorld = screenToWorld, 136 | 137 | draw = draw, 138 | update = update, 139 | render = render, 140 | resize = resize, 141 | 142 | renderModel = renderModel, 143 | } 144 | end 145 | 146 | -- Return a camera 147 | local Module = { 148 | current = new(), 149 | 150 | new = new, 151 | move = move, 152 | moveTo = moveTo, 153 | rotate = rotate, 154 | rotateTo = rotateTo, 155 | zoom = zoom, 156 | zoomTo = zoomTo, 157 | 158 | updateRotations = updateRotations, 159 | getBoundingBox = getBoundingBox, 160 | worldToScreen = worldToScreen, 161 | screenToWorld = screenToWorld, 162 | 163 | draw = draw, 164 | update = update, 165 | render = render, 166 | resize = resize, 167 | 168 | renderModel = renderModel, 169 | } 170 | 171 | return setmetatable(Module, { 172 | __index = Module.current, 173 | __call = function(_, ...) return new(...) end, 174 | }) 175 | -------------------------------------------------------------------------------- /lovox/init.lua: -------------------------------------------------------------------------------- 1 | local _PATH = (...):gsub('%.[^%.]+$', '') 2 | local Vox_model = require(_PATH..".vox2png.vox_model") 3 | local Vox_texture = require(_PATH..".vox2png.vox_texture") 4 | 5 | local flagFormat = [[ 6 | return { 7 | frames = %1.f, 8 | width = %1.f, 9 | height = %1.f, 10 | } 11 | ]] 12 | local function vox2png(file) 13 | file:open("r") 14 | local model = Vox_model.new(file:read()) 15 | file:close() 16 | 17 | local texture = vox_texture.new(model) 18 | 19 | return texture.canvas:newImageData():encode("png"), string.format(flagFormat, texture.canvas:getWidth() / texture.sizeX, texture.sizeX, texture.sizeY) 20 | end 21 | 22 | return { 23 | _VERSION = "Voxol 1.0.0", 24 | _DESCRIPTION = "A set of modules to load and render voxels in Love2d", 25 | _URL = "https://github.com/tjakka5", 26 | _LICENSE = [[ 27 | Copyright (c) 2016 Justin van der Leij 'Tjakka5' 28 | 29 | Permission is hereby granted, free of charge, to any person 30 | obtaining a copy of this software and associated documentation 31 | files (the "Software"), to deal in the Software without 32 | restriction, including without limitation the rights to use, 33 | copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | copies of the Software, and to permit persons to whom the 35 | Software is furnished to do so, subject to the following 36 | conditions: 37 | 38 | The above copyright notice and this permission notice shall be 39 | included in all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 42 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 43 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 44 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 45 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 46 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 47 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 48 | OTHER DEALINGS IN THE SOFTWARE. 49 | ]], 50 | 51 | zlist = require(_PATH..".zlist"), 52 | camera = require(_PATH..".camera"), 53 | modelData = require(_PATH..".modelData"), 54 | animModel = require(_PATH..".animModel"), 55 | model = require(_PATH..".model"), 56 | vox2png = vox2png, 57 | } 58 | -------------------------------------------------------------------------------- /lovox/model.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2016 Justin van der Leij 'Tjakka5' 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | ]] 25 | 26 | local _PATH = (...):gsub('%.[^%.]+$', '') 27 | local ModelData = require(_PATH..".modelData") 28 | local Camera = require(_PATH..".camera") 29 | 30 | -- Fill a model's spritebatch 31 | local function setup(self) 32 | self.spritebatch = love.graphics.newSpriteBatch(self.texture, self.frames) 33 | 34 | for frame = 1, self.frames do 35 | self.ids[frame] = self.spritebatch:add(self.quads[frame]) 36 | end 37 | end 38 | 39 | -- Draw a model and add it to the zbuffer 40 | local function draw(self, x, y, z, rotation, xScale, yScale) 41 | rotation, xScale, yScale = rotation or 0, xScale or 0, yScale or 0 42 | 43 | self.x, self.y = Camera:worldToScreen(x, y, z) 44 | 45 | -- Only re-render when needed 46 | local rot = rotation + Camera.rotation 47 | if rot ~= self.rotation or xScale ~= self.xScale or yScale ~= self.yScale then 48 | -- Loop trough all textures and draw them with a offset 49 | for frame = 1, self.frames do 50 | self.spritebatch:set( 51 | self.ids[frame], 52 | self.quads[frame], 53 | 0, 0 - frame * yScale, 54 | rot, 55 | xScale, yScale, 56 | self.width / 2, 57 | self.height / 2 58 | ) 59 | end 60 | 61 | -- Log the rotation and scale 62 | self.rotation = rot 63 | self.xScale = xScale 64 | self.yScale = yScale 65 | end 66 | 67 | -- Tell the camera to draw this model 68 | Camera:draw(self) 69 | end 70 | 71 | -- Create a new model object 72 | local function new(modelData) 73 | local model = setmetatable({ 74 | x = 0, y = 0, 75 | depth = 0, 76 | 77 | spritebatch = nil, 78 | ids = {}, 79 | 80 | rotation = math.huge, 81 | xScale = math.huge, 82 | yScale = math.huge, 83 | 84 | setup = setup, 85 | draw = draw, 86 | }, { 87 | __index = modelData 88 | }) 89 | 90 | model:setup() 91 | return model 92 | end 93 | 94 | -- Return module 95 | return setmetatable({ 96 | new = new, 97 | draw = draw, 98 | }, { 99 | __call = function(_, ...) return new(...) end 100 | }) 101 | -------------------------------------------------------------------------------- /lovox/modelData.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (c) 2016 Justin van der Leij 'Tjakka5' 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | ]] 25 | 26 | local _PATH = (...):gsub('%.[^%.]+$', '') 27 | local Vox_model = require(_PATH..".vox2png.vox_model") 28 | local Vox_texture = require(_PATH..".vox2png.vox_texture") 29 | 30 | -- Generates new modelData from a path 31 | local function newFromPath(path) 32 | local modelData = require(path.."/flags") 33 | modelData.texture = love.graphics.newImage(path.."/texture.png") 34 | modelData.quads = {} 35 | 36 | -- Setup quads 37 | local w, h = modelData.texture:getDimensions() 38 | for i = 0, modelData.frames - 1 do 39 | modelData.quads[i + 1] = love.graphics.newQuad(i * modelData.width, 0, modelData.width, modelData.height, w, h) 40 | end 41 | 42 | return modelData 43 | end 44 | 45 | -- Generates new modelData from an image 46 | local function newFromImage(imageData, w, h) 47 | local w, h = w or imageData:getWidth(), h or imageData:getHeight() 48 | local size = w * h 49 | 50 | -- Map texture from source 51 | local texture = love.image.newImageData(size, 1) 52 | for x = 0, w - 1 do 53 | for y = 0, h - 1 do 54 | local r, g, b, a = imageData:getPixel(x, y) 55 | texture:setPixel(size - 1 - (x + (y * w)), 0, r, g, b, a) 56 | end 57 | end 58 | 59 | texture = love.graphics.newImage(texture) 60 | 61 | -- Create new modelData 62 | local modelData = { 63 | width = w, 64 | height = 1, 65 | frames = h, 66 | quads = {}, 67 | texture = texture, 68 | } 69 | modelData.spritebatch = love.graphics.newSpriteBatch(modelData.texture, modelData.frames) 70 | 71 | -- Setup quads 72 | for i = 0, modelData.frames - 1 do 73 | modelData.quads[i + 1] = love.graphics.newQuad(i * modelData.width, 0, modelData.width, modelData.height, w * h, 1) 74 | end 75 | 76 | -- Return new modelData 77 | return modelData 78 | end 79 | 80 | -- Generates new modelData from a function 81 | local function newFromFunc(func, w, h) 82 | local c = love.graphics.newCanvas(w, h) 83 | love.graphics.setCanvas(c) 84 | func() 85 | love.graphics.setCanvas() 86 | love.graphics.setColor(255, 255, 255) 87 | 88 | return newFromImage(c:newImageData()) 89 | end 90 | 91 | -- Generates new ModelData from a .vox 92 | -- !! This function is very slow. Only use it for testing !! 93 | local function newFromVox(path) 94 | local file = love.filesystem.newFile(path) 95 | file:open("r") 96 | local model = Vox_model.new(file:read()) 97 | file:close() 98 | 99 | local texture = Vox_texture.new(model) 100 | 101 | -- Create new modelData 102 | local modelData = { 103 | width = texture.sizeX, 104 | height = texture.sizeY, 105 | frames = texture.canvas:getWidth() / texture.sizeX, 106 | quads = {}, 107 | texture = texture.canvas, 108 | } 109 | modelData.spritebatch = love.graphics.newSpriteBatch(modelData.texture, modelData.frames) 110 | 111 | -- Setup quads 112 | local w, h = modelData.texture:getDimensions() 113 | for i = 0, modelData.frames - 1 do 114 | modelData.quads[i + 1] = love.graphics.newQuad(i * modelData.width, 0, modelData.width, modelData.height, w, h) 115 | end 116 | 117 | -- Return new modelData 118 | return modelData 119 | end 120 | 121 | -- Super constructor 122 | local function new(source, w, h) 123 | local t = type(source) 124 | 125 | if t == "string" then 126 | if love.filesystem.getInfo(source).type == 'directory' then 127 | return newFromPath(source) 128 | elseif love.filesystem.getInfo(source).type == 'file' then 129 | return newFromVox(source) 130 | end 131 | elseif t == "userdata" then 132 | return newFromImage(source, w, h) 133 | elseif t == "function" then 134 | return newFromFunc(source, w, h) 135 | end 136 | 137 | error("Wrong source type. Expected 'string' or 'userdata' or 'function'. Got '"..t.."'.") 138 | end 139 | 140 | -- Return module 141 | return setmetatable({ 142 | new = new, 143 | newFromPath = newFromPath, 144 | newFromImage = newFromImage, 145 | newFromFunc = newFromFunc, 146 | newFromVox = newFromVox, 147 | }, { 148 | __call = function(_, ...) return new(...) end 149 | }) 150 | -------------------------------------------------------------------------------- /lovox/vox2png/vox_model.lua: -------------------------------------------------------------------------------- 1 | local vox_model = {} 2 | 3 | local bit = require 'bit' 4 | local band, bor, lshift, rshift = bit.band, bit.bor, bit.lshift, bit.rshift 5 | 6 | --- 7 | 8 | local function concatBytes(a, b, c, d) 9 | return bor(a or 0, lshift(b or 0, 8), lshift(c or 0, 16), lshift(d or 0, 24)) 10 | end 11 | 12 | local function splitBytes(number) 13 | return band(number, 255), 14 | band(rshift(number, 8), 255), 15 | band(rshift(number, 16), 255), 16 | band(rshift(number, 24), 255) 17 | end 18 | 19 | local function createId(a,b,c,d) 20 | return concatBytes(string.byte(a), string.byte(b), string.byte(c), string.byte(d)) 21 | end 22 | 23 | --- 24 | 25 | local VERSION = 150 26 | local ID_VOX = createId('V', 'O', 'X', ' ') 27 | local ID_MAIN = createId('M', 'A', 'I', 'N') 28 | local ID_SIZE = createId('S', 'I', 'Z', 'E') 29 | local ID_XYZI = createId('X', 'Y', 'Z', 'I') 30 | local ID_RGBA = createId('R', 'G', 'B', 'A') 31 | 32 | local DEFAULT_PALETTE_NUMBERS = { 33 | 0xffffffff, 0xffccffff, 0xff99ffff, 0xff66ffff, 0xff33ffff, 0xff00ffff, 0xffffccff, 0xffccccff, 0xff99ccff, 0xff66ccff, 0xff33ccff, 0xff00ccff, 0xffff99ff, 0xffcc99ff, 0xff9999ff, 34 | 0xff6699ff, 0xff3399ff, 0xff0099ff, 0xffff66ff, 0xffcc66ff, 0xff9966ff, 0xff6666ff, 0xff3366ff, 0xff0066ff, 0xffff33ff, 0xffcc33ff, 0xff9933ff, 0xff6633ff, 0xff3333ff, 0xff0033ff, 0xffff00ff, 35 | 0xffcc00ff, 0xff9900ff, 0xff6600ff, 0xff3300ff, 0xff0000ff, 0xffffffcc, 0xffccffcc, 0xff99ffcc, 0xff66ffcc, 0xff33ffcc, 0xff00ffcc, 0xffffcccc, 0xffcccccc, 0xff99cccc, 0xff66cccc, 0xff33cccc, 36 | 0xff00cccc, 0xffff99cc, 0xffcc99cc, 0xff9999cc, 0xff6699cc, 0xff3399cc, 0xff0099cc, 0xffff66cc, 0xffcc66cc, 0xff9966cc, 0xff6666cc, 0xff3366cc, 0xff0066cc, 0xffff33cc, 0xffcc33cc, 0xff9933cc, 37 | 0xff6633cc, 0xff3333cc, 0xff0033cc, 0xffff00cc, 0xffcc00cc, 0xff9900cc, 0xff6600cc, 0xff3300cc, 0xff0000cc, 0xffffff99, 0xffccff99, 0xff99ff99, 0xff66ff99, 0xff33ff99, 0xff00ff99, 0xffffcc99, 38 | 0xffcccc99, 0xff99cc99, 0xff66cc99, 0xff33cc99, 0xff00cc99, 0xffff9999, 0xffcc9999, 0xff999999, 0xff669999, 0xff339999, 0xff009999, 0xffff6699, 0xffcc6699, 0xff996699, 0xff666699, 0xff336699, 39 | 0xff006699, 0xffff3399, 0xffcc3399, 0xff993399, 0xff663399, 0xff333399, 0xff003399, 0xffff0099, 0xffcc0099, 0xff990099, 0xff660099, 0xff330099, 0xff000099, 0xffffff66, 0xffccff66, 0xff99ff66, 40 | 0xff66ff66, 0xff33ff66, 0xff00ff66, 0xffffcc66, 0xffcccc66, 0xff99cc66, 0xff66cc66, 0xff33cc66, 0xff00cc66, 0xffff9966, 0xffcc9966, 0xff999966, 0xff669966, 0xff339966, 0xff009966, 0xffff6666, 41 | 0xffcc6666, 0xff996666, 0xff666666, 0xff336666, 0xff006666, 0xffff3366, 0xffcc3366, 0xff993366, 0xff663366, 0xff333366, 0xff003366, 0xffff0066, 0xffcc0066, 0xff990066, 0xff660066, 0xff330066, 42 | 0xff000066, 0xffffff33, 0xffccff33, 0xff99ff33, 0xff66ff33, 0xff33ff33, 0xff00ff33, 0xffffcc33, 0xffcccc33, 0xff99cc33, 0xff66cc33, 0xff33cc33, 0xff00cc33, 0xffff9933, 0xffcc9933, 0xff999933, 43 | 0xff669933, 0xff339933, 0xff009933, 0xffff6633, 0xffcc6633, 0xff996633, 0xff666633, 0xff336633, 0xff006633, 0xffff3333, 0xffcc3333, 0xff993333, 0xff663333, 0xff333333, 0xff003333, 0xffff0033, 44 | 0xffcc0033, 0xff990033, 0xff660033, 0xff330033, 0xff000033, 0xffffff00, 0xffccff00, 0xff99ff00, 0xff66ff00, 0xff33ff00, 0xff00ff00, 0xffffcc00, 0xffcccc00, 0xff99cc00, 0xff66cc00, 0xff33cc00, 45 | 0xff00cc00, 0xffff9900, 0xffcc9900, 0xff999900, 0xff669900, 0xff339900, 0xff009900, 0xffff6600, 0xffcc6600, 0xff996600, 0xff666600, 0xff336600, 0xff006600, 0xffff3300, 0xffcc3300, 0xff993300, 46 | 0xff663300, 0xff333300, 0xff003300, 0xffff0000, 0xffcc0000, 0xff990000, 0xff660000, 0xff330000, 0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088, 0xff000077, 0xff000055, 0xff000044, 47 | 0xff000022, 0xff000011, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700, 0xff005500, 0xff004400, 0xff002200, 0xff001100, 0xffee0000, 0xffdd0000, 0xffbb0000, 0xffaa0000, 48 | 0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000, 0xff110000, 0xffeeeeee, 0xffdddddd, 0xffbbbbbb, 0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111, 49 | [0] = 0x00000000 50 | } 51 | 52 | local function createDefaultPalette() 53 | local palette = {} 54 | for i=0, #DEFAULT_PALETTE_NUMBERS do palette[i] = { splitBytes(DEFAULT_PALETTE_NUMBERS[i]) } end 55 | return palette 56 | end 57 | 58 | --- 59 | 60 | local Stream = {} 61 | local Streammt = {__index = Stream} 62 | 63 | local function newStream(str) 64 | return setmetatable({str=str,index=1}, Streammt) 65 | end 66 | 67 | function Stream:readChar() 68 | self.index = self.index + 1 69 | if self.index > self.str:len()+1 then return 0 end 70 | return (self.str:byte(self.index - 1, self.index - 1)) 71 | end 72 | 73 | function Stream:readInteger() 74 | return concatBytes(self:readChar(), self:readChar(), self:readChar(), self:readChar()) 75 | end 76 | 77 | function Stream:readChunk(expectedId) 78 | local chunk = { id = self:readInteger() } 79 | 80 | if expectedId ~= nil and chunk.id ~= expectedId then 81 | error(('unexpected chunk id: found %d, expected %d'):format(chunk.id, expectedId)) 82 | end 83 | 84 | local a,b,c,d = splitBytes(chunk.id) 85 | chunk.name = string.char(a) .. string.char(b) .. string.char(c) .. string.char(d) 86 | chunk.contentSize = self:readInteger() 87 | chunk.childrenSize = self:readInteger() 88 | chunk.lastBytePos = self.index + chunk.contentSize + chunk.childrenSize 89 | 90 | return chunk 91 | end 92 | 93 | function Stream:readId(expectedId, description) 94 | local id = self:readInteger() 95 | 96 | if id ~= expectedId then 97 | error(('%s does not match: found %d, expected %d'):format(description, id, expectedId)) 98 | end 99 | 100 | return id 101 | end 102 | 103 | function Stream:readPositiveInteger(description) 104 | local n = self:readInteger() 105 | if n <= 0 then 106 | error(("Invalid %s: %d (expected positive integer)"):format(description, n)) 107 | end 108 | return n 109 | end 110 | 111 | function Stream:skip(numberOfBytes) 112 | self.index = self.index + numberOfBytes 113 | end 114 | 115 | function Stream:moveTo(position) 116 | self.index = position 117 | end 118 | 119 | --- 120 | 121 | -- binaryString is the contents of a Magicka Voxel file, read as binary 122 | -- vox_model.new returns a table with the following attributes: 123 | -- { 124 | -- magic = , 125 | -- version = 126 | -- palette = { {0,0,0,0}, ... } (a series of colors encoded as r,g,b,a) 127 | -- voxels = { {x,y,z, colorIndex} } -- the voxel coordinates followed by the index of a color in the palette 128 | -- } 129 | 130 | function vox_model.new(binaryString) 131 | 132 | local stream = newStream(binaryString) 133 | 134 | local model = { 135 | voxels = {}, 136 | palette = createDefaultPalette(), 137 | magic = stream:readId(ID_VOX, "magic number"), 138 | version = stream:readId(VERSION, "version") 139 | } 140 | 141 | local mainChunk = stream:readChunk(ID_MAIN) 142 | 143 | stream:skip(mainChunk.contentSize) 144 | 145 | while stream.index < mainChunk.lastBytePos do 146 | local chunk = stream:readChunk() 147 | if chunk.id == ID_SIZE then 148 | 149 | model.sizeX = stream:readPositiveInteger('sizeX') 150 | model.sizeY = stream:readPositiveInteger('sizeY') 151 | model.sizeZ = stream:readPositiveInteger('sizeZ') 152 | 153 | elseif chunk.id == ID_XYZI then 154 | 155 | local numVoxels = stream:readPositiveInteger('number of voxels') 156 | 157 | for _=1, numVoxels do 158 | model.voxels[#model.voxels + 1] = { stream:readChar(), stream:readChar(), stream:readChar(), stream:readChar() } -- x,y,z,colorIndex 159 | end 160 | 161 | elseif chunk.id == ID_RGBA then 162 | 163 | for i=1, 255 do 164 | model.palette[i] = { stream:readChar(), stream:readChar(), stream:readChar(), stream:readChar() } -- r,g,b,a 165 | end 166 | 167 | -- skip the last unused color (4 chars) 168 | stream:skip(16) 169 | end 170 | -- skip unread bytes of current chunk or the whole unused chunk 171 | stream:moveTo(chunk.lastBytePos) 172 | end 173 | 174 | return model 175 | end 176 | 177 | return vox_model 178 | -------------------------------------------------------------------------------- /lovox/vox2png/vox_texture.lua: -------------------------------------------------------------------------------- 1 | local vox_texture = {} 2 | 3 | local function getPoints(model) 4 | local points, len = {}, 0 5 | for _,voxel in ipairs(model.voxels) do 6 | len = len + 1 7 | local color8 = model.palette[voxel[4]] 8 | local color = {color8[1]/255, color8[2]/255, color8[3]/255, color8[4]/255} 9 | local x,y,z = voxel[1], voxel[2], voxel[3] 10 | points[len] = { z * model.sizeX + x + 0.5, 11 | y + 0.5, 12 | color[1], color[2], color[3], color[4]>0 and color[4] or 255} -- r,g,b,a 13 | end 14 | return points 15 | end 16 | 17 | 18 | -- model is a vox model parsed by vox_model.new(binaryString) 19 | -- vox_texture.new returns a table with the following attributes: 20 | -- { 21 | -- sizeX = , 22 | -- sizeY = , 23 | -- sizeZ = , 24 | -- canvas =