]> git.lizzy.rs Git - dragonblocks3d-lua.git/commitdiff
Dynamic World System
authorElias Fleckenstein <eliasfleckenstein@web.de>
Tue, 18 Aug 2020 15:52:36 +0000 (17:52 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Tue, 18 Aug 2020 15:52:36 +0000 (17:52 +0200)
18 files changed:
README
init.lua
luac.out [new file with mode: 0755]
modules/Client/src/graphics.lua
modules/Client/src/init.lua
modules/MapGen/src/init.lua
modules/PlayerSystem/src/localplayer.lua
modules/RenderEngine/src/chunk_mesh.lua
modules/RenderEngine/src/init.lua
modules/WorldSystem/src/block.lua
modules/WorldSystem/src/chunk.lua
modules/WorldSystem/src/map.lua
src/class.lua
src/init.lua
src/modulemgr.lua
src/taskmgr.lua
src/timeout.lua [new file with mode: 0644]
util/perlin.lua [new file with mode: 0644]

diff --git a/README b/README
index 1115293d197354125b5dca8d6689d1d661aa76cc..8701704b937bae95723e6310933070af920f8bca 100644 (file)
--- a/README
+++ b/README
@@ -1,7 +1,11 @@
 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
index da40c8a8253d70b1c737763c8679ffcc85bc8573..ddb2d1551cf4eff1619e9c5262cf4b9a21c3d557 100755 (executable)
--- a/init.lua
+++ b/init.lua
@@ -4,8 +4,9 @@ socket = require("socket")
 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")
diff --git a/luac.out b/luac.out
new file mode 100755 (executable)
index 0000000..2600662
Binary files /dev/null and b/luac.out differ
index a56074b39be0d6ab1ed152f0de3addc28bb833ce..2910355b25982f96df5dce5e1d8510da5ec1b026 100644 (file)
@@ -4,12 +4,12 @@ function graphics:init()
        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)
@@ -20,17 +20,9 @@ function graphics:init()
        
        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
index c368a2db581ac7bf3e34549ca29a31fe9851ae3e..1096f9f2fb5808696009e2e0dbe37db8f2cd7a35 100644 (file)
@@ -6,13 +6,14 @@ PlayerSystem:init("client")
 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)
index 5d2c4cb135a2d4e78aebee65322ba6f0a61c172c..21a0a3f55a7eee252b4154ec880a8e6edc572736 100644 (file)
@@ -4,42 +4,32 @@ local grass = BlockSystem:get_def("game:grass")
 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 = {
index 753520fbc03113523003962936c0ec35c9ba11b0..c5f4c2808b0dd624fe20d7c6f1bae9ff1b06acc3 100644 (file)
@@ -52,6 +52,14 @@ end
 
 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)
index 4a1369d839b35eea364c483730d6a99ef3764ada..29a092e59d12b3ee91e3da8d77d6c252d9d67cd2 100644 (file)
@@ -1,7 +1,7 @@
 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
@@ -13,14 +13,21 @@ function ChunkMesh:create_vertices(chunk)
                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
index 4b060abc38219d2be7b46a3b41bfe78a9a356a66..ca7a9fa4e9a96365e858f4d7a4c9062747775ce4 100644 (file)
@@ -39,7 +39,7 @@ function RenderEngine:render_loop(is_only_task)
 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()
@@ -76,3 +76,10 @@ end
 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
index 2c67a27b1c3749b9fb3d4ac88ec9a88dabc893e6..b963c940ebd8ac5a43c001074ee42f53dd55515c 100644 (file)
@@ -1,7 +1,7 @@
 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
index f49390ea862f3bab60b3f0b320a417724a27c31d..96eeef49ce6aa0b91cd4794faa7e0ce653e03fc4 100644 (file)
@@ -3,31 +3,51 @@ local Chunk = Dragonblocks.create_class()
 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
index 44d7490c1aafac2d70105b847906ded95fcf6c88..b253f273624c2b69745e251bb595f525f376530b 100644 (file)
@@ -1,7 +1,74 @@
 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
index 5b2c20fb7bf7be7859d9d685eabea9b4e0efa506..470e5ccbe1afd0f54cd1ec41f3dab7d234f19c88 100644 (file)
@@ -1,4 +1,4 @@
-function Dragonblocks.create_class()
+function Dragonblocks:create_class()
        local class = self or {}
        setmetatable(class, {
                __call = function(_, ...)
index b94e1516a47e064cccfddd9680ad8f93a5e6cc75..60c6bd5986f02f462903295fb9805a213c12a6ac 100644 (file)
@@ -5,6 +5,7 @@ require("src/events")
 require("src/taskmgr")
 require("src/modulemgr")
 require("src/serialisation")
+require("src/timeout")
 
 print("Started Dragonblocks core")
 
index 7aaa1cf012a7d24ffb4130afb288ef5273b4e1f3..b102f314c6e4fc72e37b4ad07464e76e2d1bad8e 100644 (file)
@@ -37,7 +37,7 @@ end
 
 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
index 4c4693e6d3ba50920c4379c4224c4dad80622c80..91e5aef256b32984a10f6af151945935c039090d 100644 (file)
@@ -11,11 +11,7 @@ function Dragonblocks:step()
        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
diff --git a/src/timeout.lua b/src/timeout.lua
new file mode 100644 (file)
index 0000000..527b1d9
--- /dev/null
@@ -0,0 +1,38 @@
+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)
diff --git a/util/perlin.lua b/util/perlin.lua
new file mode 100644 (file)
index 0000000..e9f84eb
--- /dev/null
@@ -0,0 +1,129 @@
+--[[
+    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