depends:
-$ sudo apt install lua5.3 libsqlite3
+
+$ sudo apt install build-essential make lua5.3 liblua5.3-dev libgl1-mesa-dev libglew-dev libglm-dev libglfw3-dev libassimp-dev libsqlite3-dev luarocks
$ luarocks install lsqlite3
$ luarocks install luasocket
$ luarocks install luafilesystem
-Build moongl, moonglfw, moonglmath, moonimage and moonassimp: Only needed for running the client, see build instructions in deps/*/README.md
+Build moongl, moonglfw, moonglmath, moonimage and moonassimp: See build instructions in deps/*/README.md
+
+Run:
+ ./init.lua Client
lsqlite3 = require("lsqlite3")
gl = require("moongl")
glfw = require("moonglfw")
-image = require("moonimage")
glm = require("moonglmath")
+image = require("moonimage")
+perlin = require("util/perlin")
string.split = require("util/string_split")
table.indexof = require("util/table_indexof")
table.assign = require("util/table_assign")
RenderEngine:init()
RenderEngine.bininear_filter = false
- RenderEngine.mipmap = false
+ RenderEngine.mipmap = true
RenderEngine.mouse_sensitivity = 0.7
- --RenderEngine.pitch_move = true
+ RenderEngine.pitch_move = false
RenderEngine.mesh_effect_grow_time = 0.25
- RenderEngine.mesh_effect_flyin_time = 0.5
- RenderEngine.mesh_effect_flyin_offset = 20
+ RenderEngine.mesh_effect_flyin_time = 0.25
+ RenderEngine.mesh_effect_flyin_offset = 10
RenderEngine.mesh_effect_rotate_speed = 1
RenderEngine:set_wireframe(false)
RenderEngine:set_sky("#87CEEB")
+ RenderEngine:toggle_fullscreen()
+
BlockSystem:init_textures()
end
-function graphics:create_chunk_meshes(chunk)
- local mesh = RenderEngine.ChunkMesh()
- mesh:set_pos(glm.vec3(0, 0, 0))
- mesh:set_size(glm.vec3(1, 1, 1))
- mesh:set_texture(BlockSystem:get_def("game:dirt").texture)
- mesh:create_vertices(chunk)
- mesh:set_effect(RenderEngine.Mesh.EFFECT_FLYIN)
- mesh:add_to_scene()
-end
-
return graphics
Client.map = WorldSystem.Map()
Client.player = PlayerSystem.LocalPlayer()
-Client.player:set_position(glm.vec3(8, 20, 8))
+Client.player:set_position(glm.vec3(8, 8, 8))
+
Dragonblocks:add_task(function()
- repeat
+ while true do
coroutine.yield("FPS:" .. math.floor(Dragonblocks.tps or 0))
- until false
+ end
end)
-RenderEngine:render_loop(true)
+--RenderEngine:render_loop(true)
local leaves = BlockSystem:get_def("game:leaves")
local tree = BlockSystem:get_def("game:tree")
-math.randomseed(os.time())
+local grass_layer_start, grass_layer_end = 0, 30
+local grass_layer_height = grass_layer_end - grass_layer_start
-function MapGen:generate(chunk)
- local grass_layer_table, old_grass_layer_table
- local grass_layer
- for x = 0, 15 do
- grass_layer_table, old_grass_layer_table = {}, grass_layer_table
- grass_layer = old_grass_layer_table and old_grass_layer_table[1] or 8 + math.random(5)
- for z = 0, 15 do
- local old_grass_layer = old_grass_layer_table and old_grass_layer_table[z] or grass_layer
- grass_layer = math.floor((grass_layer + old_grass_layer) / 2)
- if math.random(3) == 1 then
- grass_layer = grass_layer + math.random(3) - 2
- end
- grass_layer = glm.clamp(grass_layer, 0, 15)
- grass_layer_table[z] = grass_layer
- if math.random(25) == 1 then
- chunk:add_block(glm.vec3(x, grass_layer, z), dirt)
- self:add_tree(chunk, glm.vec3(x, grass_layer + 1, z))
- else
- chunk:add_block(glm.vec3(x, grass_layer, z), grass)
- end
- local dirt_start, dirt_end = grass_layer - 1, math.max(grass_layer - 5, 0)
- local stone_start, stone_end = grass_layer - 6, 0
- if dirt_start >= 0 then
- for y = dirt_start, dirt_end, -1 do
- chunk:add_block(glm.vec3(x, y, z), dirt)
- end
- end
- if stone_start >= 0 then
- for y = stone_start, stone_end, -1 do
- chunk:add_block(glm.vec3(x, y, z), stone)
+function MapGen.generate(minp, maxp)
+ local data = {}
+ local minx, miny, minz, maxx, maxy, maxz = minp.x, minp.y, minp.z, maxp.x - 1, maxp.y - 1, maxp.z - 1
+ for x = minx, maxx do
+ for z = minz, maxz do
+ local grass_layer = math.floor(grass_layer_start + grass_layer_height * perlin:noise(x / grass_layer_height, z / grass_layer_height))
+ for y = miny, maxy do
+ local pos = glm.vec3(x - minx, y - miny, z - minz)
+ local block
+ if y <= grass_layer - 5 then
+ block = stone
+ elseif y <= grass_layer - 1 then
+ block = dirt
+ elseif y <= grass_layer then
+ block = grass
+ end
+ if block then
+ data[WorldSystem.Chunk.get_pos_hash(pos)] = WorldSystem.Block(pos, block)
end
end
end
end
+ return data
end
local tree_blocks = {
function LocalPlayer:set_position_callback(event)
RenderEngine.camera.pos = self.pos
+ local pos = WorldSystem.Map.get_chunk_pos(self.pos)
+ for x = pos.x - 1, pos.x + 1 do
+ for y = pos.y - 1, pos.y + 1 do
+ for z = pos.z - 1, pos.z + 1 do
+ Client.map:create_chunk_if_not_exists(glm.vec3(x, y, z))
+ end
+ end
+ end
end
function LocalPlayer:move(vec)
local ChunkMesh = Dragonblocks.create_class()
table.assign(ChunkMesh, RenderEngine.Mesh)
-function ChunkMesh:create_vertices(chunk)
+function ChunkMesh:create_faces(blocks)
self.vertices = {}
self.textures = {}
self.vertex_blob_size = 6
glm.vec3( 0, -1, 0),
glm.vec3( 0, 1, 0),
}
- for _, block in pairs(chunk.blocks) do
+ local bc = 0
+ for _, block in pairs(blocks) do
for i, dir in ipairs(face_orientations) do
local pos = block.pos
- if not chunk:get_block(pos + dir) then
+ local dir_pos_hash = WorldSystem.Chunk.get_pos_hash(pos + dir)
+ if not dir_pos_hash or not blocks[dir_pos_hash] then
table.insert(self.textures, block.def.texture)
- self:add_face(block.pos, i)
+ self:add_face(block.pos - glm.vec3(7.5, 7.5, 7.5), i)
end
end
+ bc = bc + 1
+ if bc == 64 then
+ bc = 0
+ coroutine.yield()
+ end
end
self:apply_vertices(self.vertices)
end
end
function RenderEngine:update_projection_matrix()
- gl.uniform_matrix4f(gl.get_uniform_location(self.shaders, "projection"), true, glm.perspective(math.rad(self.fov), self.window_width / self.window_height, 0.0001, 100))
+ gl.uniform_matrix4f(gl.get_uniform_location(self.shaders, "projection"), true, glm.perspective(math.rad(self.fov), self.window_width / self.window_height, 0.01, 100))
end
function RenderEngine:update_view_matrix()
function RenderEngine:set_wireframe(v)
gl.polygon_mode("front and back", (v and "line" or "fill"))
end
+
+function RenderEngine:toggle_fullscreen()
+ self.fullscreen = not self.fullscreen
+ local monitor = glfw.get_primary_monitor()
+ local mode = glfw.get_video_mode(monitor)
+ glfw.set_window_monitor(self.window, self.fullscreen and monitor, 0, 0, mode.width, mode.height, 0)
+end
local Block = Dragonblocks.create_class()
-function Block:constructor(def, pos)
- self.def, self.pos = def, pos
+function Block:constructor(pos, def)
+ self.pos, self.def = pos, def
end
return Block
local size = 16
local size_squared = math.pow(size, 2)
-function Chunk:constructor()
- self.blocks = {}
- MapGen:generate(self)
- if Client then
- Client.graphics:create_chunk_meshes(self)
- end
-end
-
-function Chunk:get_pos_hash(pos)
+function Chunk.get_pos_hash(pos)
local x, y, z = pos.x, pos.y, pos.z
if x > 15 or y > 15 or z > 15 or x < 0 or y < 0 or z < 0 then return end
return x + size * y + size_squared * z
end
+function Chunk:constructor(pos, blocks)
+ self.pos, self.blocks = pos, blocks
+end
+
function Chunk:add_block(pos, def)
- local block = WorldSystem.Block(def, pos)
- self.blocks[self:get_pos_hash(pos)] = block
+ local pos_hash = Chunk.get_pos_hash(pos)
+ if pos_hash then
+ self.blocks[pos_hash] = WorldSystem.Block(pos, def)
+ self:update_mesh()
+ end
end
function Chunk:remove_block(pos)
- self.blocks[self:get_pos_hash(pos)] = nil
+ local pos_hash = Chunk.get_pos_hash(pos)
+ if pos_hash then
+ self.blocks[pos_hash] = nil
+ self:update_mesh()
+ end
end
function Chunk:get_block(pos)
- return self.blocks[self:get_pos_hash(pos)]
+ local pos_hash = Chunk.get_pos_hash(pos)
+ if pos_hash then return self.blocks[pos_hash] end
+end
+
+function Chunk:update_mesh()
+ if #self.blocks == 0 then return end
+ local mesh = RenderEngine.ChunkMesh()
+ mesh:set_pos(self.pos * 16 + glm.vec3(8, 8, 8))
+ mesh:set_size(glm.vec3(1, 1, 1))
+ mesh:create_faces(self.blocks)
+ if not self.mesh then
+ mesh:set_effect(RenderEngine.Mesh.EFFECT_FLYIN)
+ end
+ if self.mesh then
+ self.mesh:remove_from_scene()
+ end
+ self.mesh = mesh
+ mesh:add_to_scene()
end
return Chunk
local Map = Dragonblocks.create_class()
+local size = 1000
+local size_squared = math.pow(size, 2)
+
+
+function Map.get_pos_hash(pos)
+ local x, y, z = pos.x, pos.y, pos.z
+ if x > 999 or y > 999 or z > 999 or x < -999 or y < -999 or z < -999 then return end
+ return x + size * y + size_squared * z
+end
+
+function Map.get_chunk_pos(pos)
+ return glm.vec3(math.floor(pos.x / 16), math.floor(pos.y / 16), math.floor(pos.z / 16))
+end
+
+function Map.get_block_pos(pos)
+ return pos * 16
+end
+
+function Map.get_chunk_pos_and_block_pos(pos)
+ local chunk_pos = Map.get_chunk_pos(pos)
+ local block_pos = pos - Map.get_block_pos(chunk_pos)
+ return chunk_pos, block_pos
+end
+
function Map:constructor()
- self.chunk = WorldSystem.Chunk()
+ self.chunks = {}
+end
+
+function Map:get_block(pos)
+ local chunk, block_pos = self:get_chunk_and_block_pos(pos)
+ if chunk then return chunk:get_block(block_pos) end
+end
+
+function Map:add_block(pos, block)
+ local chunk, block_pos = self:get_chunk_and_block_pos(pos)
+ if chunk then return chunk:add_block(block_pos, block) end
+end
+
+function Map:remove_block(pos)
+ local chunk, block_pos = self:get_chunk_and_block_pos(pos)
+ if chunk then return chunk:remove_block(block_pos) end
+end
+
+function Map:create_chunk(pos, data)
+ local pos_hash = Map.get_pos_hash(pos)
+ if not pos_hash then return end
+ local minp = Map.get_block_pos(pos)
+ local maxp = minp + glm.vec3(16, 16, 16)
+ local data = data or MapGen.generate(minp, maxp)
+ local chunk = WorldSystem.Chunk(pos, data)
+ self.chunks[pos_hash] = chunk
+ Dragonblocks:add_task(function()
+ chunk:update_mesh()
+ end)
+end
+
+function Map:create_chunk_if_not_exists(pos, data)
+ if not self:get_chunk(pos) then self:create_chunk(pos, data) end
+end
+
+function Map:get_chunk(pos)
+ local pos_hash = Map.get_pos_hash(pos)
+ if pos_hash then return self.chunks[pos_hash] end
+end
+
+function Map:get_chunk_and_block_pos(pos)
+ local chunk_pos, block_pos = Map.get_chunk_pos_and_block_pos(pos)
+ local chunk = self:get_chunk(chunk_pos)
+ return chunk, block_pos
end
return Map
-function Dragonblocks.create_class()
+function Dragonblocks:create_class()
local class = self or {}
setmetatable(class, {
__call = function(_, ...)
require("src/taskmgr")
require("src/modulemgr")
require("src/serialisation")
+require("src/timeout")
print("Started Dragonblocks core")
function Dragonblocks:read_modules()
if not lfs.attributes("data", "mode") then
- lfs.mkdir(self.data_path)
+ lfs.mkdir("data")
end
self.modules = {}
for modulename in lfs.dir("modules") do
local tasks = self.tasks
self.tasks = {}
for _, t in ipairs(tasks) do
- local continue, status = coroutine.resume(t)
- if status then
- print(status)
- end
- if continue then
+ if coroutine.status(t) ~= "dead" and coroutine.resume(t) then
table.insert(self.tasks, t)
end
end
--- /dev/null
+local timeout = Dragonblocks.create_class()
+
+timeout.list = {}
+
+function timeout:constructor(sec, func, ...)
+ self.exp, self.func, self.args = socket.gettime() + sec, func, table.pack(...)
+ table.insert(timeout.list, self)
+end
+
+function timeout:clear()
+ self.cleared = true
+end
+
+function Dragonblocks.set_timeout(sec, func, ...)
+ return timeout(sec, func, ...)
+end
+
+function Dragonblocks:clear_timeout()
+ self:clear()
+end
+
+Dragonblocks:add_task(function()
+ while true do
+ local tolist = timeout.list
+ local tm = socket.gettime()
+ timeout.list = {}
+ for _, to in pairs(tolist) do
+ if not to.cleared then
+ if to.exp <= tm then
+ to.func(table.unpack(to.args))
+ else
+ table.insert(timeout.list, to)
+ end
+ end
+ end
+ coroutine.yield()
+ end
+end)
--- /dev/null
+--[[
+ Implemented as described here:
+ http://flafla2.github.io/2014/08/09/perlinnoise.html
+]]--
+
+local perlin = {}
+perlin.p = {}
+
+-- Hash lookup table as defined by Ken Perlin
+-- This is a randomly arranged array of all numbers from 0-255 inclusive
+local permutation = {151,160,137,91,90,15,
+ 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
+ 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
+ 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
+ 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
+ 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
+ 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
+ 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
+ 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
+ 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
+ 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
+ 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
+ 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
+}
+
+-- p is used to hash unit cube coordinates to [0, 255]
+for i=0,255 do
+ -- Convert to 0 based index table
+ perlin.p[i] = permutation[i+1]
+ -- Repeat the array to avoid buffer overflow in hash function
+ perlin.p[i+256] = permutation[i+1]
+end
+
+-- Return range: [-1, 1]
+function perlin:noise(x, y, z)
+ y = y or 0
+ z = z or 0
+
+ -- Calculate the "unit cube" that the point asked will be located in
+ local xi = bit32.band(math.floor(x),255)
+ local yi = bit32.band(math.floor(y),255)
+ local zi = bit32.band(math.floor(z),255)
+
+ -- Next we calculate the location (from 0 to 1) in that cube
+ x = x - math.floor(x)
+ y = y - math.floor(y)
+ z = z - math.floor(z)
+
+ -- We also fade the location to smooth the result
+ local u = self.fade(x)
+ local v = self.fade(y)
+ local w = self.fade(z)
+
+ -- Hash all 8 unit cube coordinates surrounding input coordinate
+ local p = self.p
+ local A, AA, AB, AAA, ABA, AAB, ABB, B, BA, BB, BAA, BBA, BAB, BBB
+ A = p[xi ] + yi
+ AA = p[A ] + zi
+ AB = p[A+1 ] + zi
+ AAA = p[ AA ]
+ ABA = p[ AB ]
+ AAB = p[ AA+1 ]
+ ABB = p[ AB+1 ]
+
+ B = p[xi+1] + yi
+ BA = p[B ] + zi
+ BB = p[B+1 ] + zi
+ BAA = p[ BA ]
+ BBA = p[ BB ]
+ BAB = p[ BA+1 ]
+ BBB = p[ BB+1 ]
+
+ -- Take the weighted average between all 8 unit cube coordinates
+ return self.lerp(w,
+ self.lerp(v,
+ self.lerp(u,
+ self:grad(AAA,x,y,z),
+ self:grad(BAA,x-1,y,z)
+ ),
+ self.lerp(u,
+ self:grad(ABA,x,y-1,z),
+ self:grad(BBA,x-1,y-1,z)
+ )
+ ),
+ self.lerp(v,
+ self.lerp(u,
+ self:grad(AAB,x,y,z-1), self:grad(BAB,x-1,y,z-1)
+ ),
+ self.lerp(u,
+ self:grad(ABB,x,y-1,z-1), self:grad(BBB,x-1,y-1,z-1)
+ )
+ )
+ )
+end
+
+-- Gradient function finds dot product between pseudorandom gradient vector
+-- and the vector from input coordinate to a unit cube vertex
+perlin.dot_product = {
+ [0x0]=function(x,y,z) return x + y end,
+ [0x1]=function(x,y,z) return -x + y end,
+ [0x2]=function(x,y,z) return x - y end,
+ [0x3]=function(x,y,z) return -x - y end,
+ [0x4]=function(x,y,z) return x + z end,
+ [0x5]=function(x,y,z) return -x + z end,
+ [0x6]=function(x,y,z) return x - z end,
+ [0x7]=function(x,y,z) return -x - z end,
+ [0x8]=function(x,y,z) return y + z end,
+ [0x9]=function(x,y,z) return -y + z end,
+ [0xA]=function(x,y,z) return y - z end,
+ [0xB]=function(x,y,z) return -y - z end,
+ [0xC]=function(x,y,z) return y + x end,
+ [0xD]=function(x,y,z) return -y + z end,
+ [0xE]=function(x,y,z) return y - x end,
+ [0xF]=function(x,y,z) return -y - z end
+}
+function perlin:grad(hash, x, y, z)
+ return self.dot_product[bit32.band(hash,0xF)](x,y,z)
+end
+
+-- Fade function is used to smooth final output
+function perlin.fade(t)
+ return t * t * t * (t * (t * 6 - 15) + 10)
+end
+
+function perlin.lerp(t, a, b)
+ return a + t * (b - a)
+end
+
+return perlin