├── Assets └── aim.png ├── Bundled ├── canvas │ ├── MyCanvas.files │ └── MyCanvas.json ├── dig.wav ├── drop.wav ├── step.wav └── tileset.png ├── README.md ├── Shaders ├── vox.frag.glsl ├── vox.vert.glsl ├── voxsm.frag.glsl └── voxsm.vert.glsl ├── Sources └── arm │ ├── Perlin.hx │ ├── VoxelNavigation.hx │ └── VoxelWorld.hx └── voxels.blend /Assets/aim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/voxel_world/3522d0df6df500f79ede3e749c13d33e38843f07/Assets/aim.png -------------------------------------------------------------------------------- /Bundled/canvas/MyCanvas.files: -------------------------------------------------------------------------------- 1 | ../../Assets/aim.png 2 | -------------------------------------------------------------------------------- /Bundled/canvas/MyCanvas.json: -------------------------------------------------------------------------------- 1 | {"name":"untitled","x":0,"y":0,"width":960,"height":540,"elements":[{"id":0,"type":1,"name":"Image","event":"","x":448,"y":218,"width":64,"height":64,"text":"Image","asset":"aim.png","color":-1,"anchor":4,"children":[]}],"assets":[{"name":"aim.png","file":"../../Assets/aim.png","id":0}]} -------------------------------------------------------------------------------- /Bundled/dig.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/voxel_world/3522d0df6df500f79ede3e749c13d33e38843f07/Bundled/dig.wav -------------------------------------------------------------------------------- /Bundled/drop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/voxel_world/3522d0df6df500f79ede3e749c13d33e38843f07/Bundled/drop.wav -------------------------------------------------------------------------------- /Bundled/step.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/voxel_world/3522d0df6df500f79ede3e749c13d33e38843f07/Bundled/step.wav -------------------------------------------------------------------------------- /Bundled/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/voxel_world/3522d0df6df500f79ede3e749c13d33e38843f07/Bundled/tileset.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voxel_world 2 | Voxel worlds for Armory 3 | 4 | - Open `voxels.blend` and hit `Armory Player - Play` 5 | - `Bundled/tileset.png` is used for texturing of the voxels 6 | - `Shaders/` folder contains voxel specific shaders 7 | - In `VoxelWorld.hx`, voxel rendering material is created 8 | - In `VoxelWorld.hx`, voxel world is generated using perlin noise 9 | 10 | [![voxel_world](http://img.youtube.com/vi/K47bNVIqv-4/0.jpg)](http://www.youtube.com/watch?v=K47bNVIqv-4) 11 | -------------------------------------------------------------------------------- /Shaders/vox.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "../compiled.inc" 4 | #include "../std/gbuffer.glsl" 5 | 6 | uniform sampler2D tileset; 7 | 8 | in vec2 tc; 9 | in vec3 normal; 10 | #ifdef _Deferred 11 | in float occ; 12 | #endif 13 | 14 | #ifdef _Deferred 15 | out vec4 fragColor[2]; 16 | #else 17 | out vec4 fragColor; 18 | #endif 19 | 20 | void main() { 21 | 22 | vec3 n = normalize(normal); 23 | 24 | n /= (abs(n.x) + abs(n.y) + abs(n.z)); 25 | n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy); 26 | 27 | vec3 col = texture(tileset, tc).rgb; 28 | 29 | #ifdef _Deferred 30 | fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, 0)); 31 | fragColor[1] = vec4(col, packFloat2(occ, 0.0)); 32 | #else 33 | fragColor = vec4(col, 1.0); 34 | #endif 35 | } 36 | -------------------------------------------------------------------------------- /Shaders/vox.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "../compiled.inc" 4 | 5 | in vec4 pos; 6 | in vec2 nor; 7 | in vec2 tex; 8 | in vec3 ipos; 9 | 10 | uniform mat4 WVP; 11 | uniform int s; 12 | uniform int s2; 13 | uniform sampler3D volume; 14 | 15 | out vec2 tc; 16 | out vec3 normal; 17 | #ifdef _Deferred 18 | out float occ; 19 | #endif 20 | 21 | void main() { 22 | 23 | if (ipos.x == 0.0) { 24 | gl_Position.x = -1000; 25 | gl_Position.y = -1000; 26 | return; 27 | } 28 | 29 | tc = tex + ipos.xy; 30 | normal = vec3(nor.xy, pos.w); 31 | 32 | int i = gl_InstanceID % s2; 33 | ivec3 pi; 34 | pi.x = i % s; 35 | pi.y = int(i / s); 36 | pi.z = int(gl_InstanceID / s2); 37 | 38 | #ifdef _Deferred 39 | ivec3 posn = ivec3(pos.xyz * 2.1); 40 | 41 | float a; 42 | float b; 43 | float c = texelFetch(volume, ivec3(pi.x + posn.x, pi.y + posn.y, pi.z + posn.z), 0).r;; 44 | 45 | // Unify this.. 46 | if (abs(pos.w) > 0.1) { // nor.z 47 | a = texelFetch(volume, ivec3(pi.x + posn.x, pi.y, pi.z + posn.z), 0).r; 48 | b = texelFetch(volume, ivec3(pi.x, pi.y + posn.y, pi.z + posn.z), 0).r; 49 | } 50 | else if (abs(nor.x) > 0.1) { 51 | a = texelFetch(volume, ivec3(pi.x + posn.x, pi.y + posn.y, pi.z), 0).r; 52 | b = texelFetch(volume, ivec3(pi.x + posn.x, pi.y, pi.z + posn.z), 0).r; 53 | } 54 | else { 55 | a = texelFetch(volume, ivec3(pi.x + posn.x, pi.y + posn.y, pi.z), 0).r; 56 | b = texelFetch(volume, ivec3(pi.x, pi.y + posn.y, pi.z + posn.z), 0).r; 57 | } 58 | 59 | occ = (3.0 - (a + b + c)) / 3.0; 60 | occ = max(occ, 0.2); 61 | #endif 62 | 63 | gl_Position = WVP * vec4(pos.xyz + pi, 1.0); 64 | } 65 | -------------------------------------------------------------------------------- /Shaders/voxsm.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | void main() { 4 | } 5 | -------------------------------------------------------------------------------- /Shaders/voxsm.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | in vec4 pos; 4 | in vec3 ipos; 5 | 6 | uniform mat4 LWVP; 7 | uniform int s; 8 | uniform int s2; 9 | 10 | void main() { 11 | 12 | if (ipos.x == 0.0) { 13 | gl_Position.x = -1000; 14 | gl_Position.y = -1000; 15 | return; 16 | } 17 | 18 | vec3 p = pos.xyz; 19 | int i = gl_InstanceID % s2; 20 | p.x += i % s; 21 | p.y += int(i / s); 22 | p.z += int(gl_InstanceID / s2); 23 | 24 | gl_Position = LWVP * vec4(p, 1.0); 25 | } 26 | -------------------------------------------------------------------------------- /Sources/arm/Perlin.hx: -------------------------------------------------------------------------------- 1 | // https://github.com/whuop/hxNoise/blob/master/hxnoise/Perlin.hx 2 | // The MIT License (MIT) 3 | 4 | // Copyright (c) 2016 Kristian Brodal 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | package arm; 24 | 25 | class Perlin { 26 | // Hash lookup defined by Ken Perlin 27 | private static var PERMUTATIONS : Array = [ 151,160,137,91,90,15, 28 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 29 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 30 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 31 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 32 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 33 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 34 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 35 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 36 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 37 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 38 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 39 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 40 | ]; 41 | 42 | private static var P : Array; 43 | public var repeat(default, default) : Int; 44 | 45 | public function new(repeat : Int = -1) : Void 46 | { 47 | this.repeat = repeat; 48 | if (P == null) 49 | { 50 | P = [ for (x in 0 ... 512) PERMUTATIONS[x % 256] ]; 51 | } 52 | } 53 | 54 | public function perlin(x : Float, y : Float, z : Float) : Float 55 | { 56 | if (this.repeat > 0) 57 | { 58 | x = x % repeat; 59 | y = y % repeat; 60 | z = z % repeat; 61 | } 62 | 63 | var xi : Int = Math.floor(x) & 255; 64 | var yi : Int = Math.floor(y) & 255; 65 | var zi : Int = Math.floor(z) & 255; 66 | 67 | var xf : Float = x - Math.ffloor(x); 68 | var yf : Float = y - Math.ffloor(y); 69 | var zf : Float = z - Math.ffloor(z); 70 | 71 | var u : Float = fade(xf); 72 | var v : Float = fade(yf); 73 | var w : Float = fade(zf); 74 | 75 | var aaa, aba, aab, abb, baa, bba, bab, bbb : Int; 76 | aaa = P[P[P[xi ]+yi ]+zi ]; 77 | aba = P[P[P[xi ]+inc(yi) ]+zi ]; 78 | aab = P[P[P[xi ]+yi ]+inc(zi) ]; 79 | abb = P[P[P[xi ]+inc(yi) ]+inc(zi) ]; 80 | baa = P[P[P[inc(xi) ]+yi ]+zi ]; 81 | bba = P[P[P[inc(xi) ]+inc(yi) ]+zi ]; 82 | bab = P[P[P[inc(xi) ]+yi ]+inc(zi) ]; 83 | bbb = P[P[P[inc(xi) ]+inc(yi) ]+inc(zi) ]; 84 | 85 | var x1, x2, y1, y2 : Float; 86 | x1 = lerp(grad(aaa, xf, yf, zf), 87 | grad(baa, xf-1, yf, zf), 88 | u); 89 | 90 | x2 = lerp(grad(aba, xf, yf-1, zf), 91 | grad(bba, xf-1, yf-1, zf), 92 | u); 93 | 94 | y1 = lerp(x1, x2, v); 95 | 96 | x1 = lerp(grad(aab, xf, yf, zf-1), 97 | grad(bab, xf-1, yf, zf-1), 98 | u); 99 | 100 | x2 = lerp(grad(abb, xf, yf-1, zf-1), 101 | grad(bbb, xf-1, yf-1, zf-1), 102 | u); 103 | 104 | y2 = lerp(x1, x2, v); 105 | return (lerp(y1,y2,w)+1)/2; 106 | } 107 | 108 | public function OctavePerlin(x : Float, y : Float, z : Float, octaves : Int, persistence : Float, frequency : Float) 109 | { 110 | var total : Float = 0.0; 111 | var maxValue : Float = 0.0; 112 | var amplitude : Float = 1.0; 113 | 114 | for(i in 0...octaves) 115 | { 116 | total += perlin(x * frequency, y * frequency, z * frequency) * amplitude; 117 | maxValue += amplitude; 118 | 119 | amplitude *= persistence; 120 | frequency *= 2.0; 121 | } 122 | 123 | return total / maxValue; 124 | } 125 | 126 | public function fade(t : Float) : Float 127 | { 128 | return t * t * t * (t * (t * 6 - 15) + 10); 129 | } 130 | 131 | public function inc(num :Int) :Int { 132 | num++; 133 | if (repeat > 0) num %= repeat; 134 | 135 | return num; 136 | } 137 | 138 | public static function grad(hash :Int, x :Float, y :Float, z :Float) :Float 139 | { 140 | var h :Int = hash & 15; 141 | var u :Float = h < 8 ? x : y; 142 | 143 | var v :Float; 144 | 145 | if(h < 4) 146 | v = y; 147 | else if(h == 12 || h == 14) 148 | v = x; 149 | else 150 | v = z; 151 | 152 | return ((h&1) == 0 ? u : -u)+((h&2) == 0 ? v : -v); 153 | } 154 | 155 | public function lerp(a : Float, b : Float, x : Float) 156 | { 157 | return a + (x * (b - a)); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Sources/arm/VoxelNavigation.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | import iron.Trait; 4 | import iron.system.Input; 5 | import iron.system.Time; 6 | import iron.object.CameraObject; 7 | import iron.math.Vec4; 8 | 9 | class VoxelNavigation extends Trait { 10 | 11 | public static var enabled = true; 12 | static inline var speed = 5.0; 13 | var dir = new Vec4(); 14 | var xvec = new Vec4(); 15 | var yvec = new Vec4(); 16 | var easing = true; 17 | var ease = 1.0; 18 | 19 | var camera:CameraObject; 20 | 21 | var keyboard:Keyboard; 22 | var gamepad:Gamepad; 23 | var mouse:Mouse; 24 | 25 | var stepTime = 0.0; 26 | var soundStep:kha.Sound = null; 27 | var soundDig:kha.Sound = null; 28 | var soundDrop:kha.Sound = null; 29 | 30 | var cooldown = 0.0; 31 | var jump = 0.0; 32 | 33 | public function new(easing = true) { 34 | super(); 35 | 36 | this.easing = easing; 37 | notifyOnInit(init); 38 | } 39 | 40 | function init() { 41 | iron.data.Data.getSound("step.wav", function(sound:kha.Sound) { 42 | soundStep = sound; 43 | }); 44 | 45 | iron.data.Data.getSound("dig.wav", function(sound:kha.Sound) { 46 | soundDig = sound; 47 | }); 48 | 49 | iron.data.Data.getSound("drop.wav", function(sound:kha.Sound) { 50 | soundDrop = sound; 51 | }); 52 | 53 | keyboard = Input.getKeyboard(); 54 | gamepad = Input.getGamepad(); 55 | mouse = Input.getMouse(); 56 | 57 | camera = cast object; 58 | if (camera != null){ 59 | notifyOnUpdate(update); 60 | } 61 | } 62 | 63 | function update() { 64 | if (!enabled || Input.occupied) return; 65 | 66 | if (mouse.started() && !mouse.locked) mouse.lock(); 67 | else if (keyboard.started("escape") && mouse.locked) mouse.unlock(); 68 | 69 | var moveForward = keyboard.down(keyUp) || keyboard.down("up"); 70 | var moveBackward = keyboard.down(keyDown) || keyboard.down("down"); 71 | var strafeLeft = keyboard.down(keyLeft) || keyboard.down("left"); 72 | var strafeRight = keyboard.down(keyRight) || keyboard.down("right"); 73 | var strafeUp = keyboard.down(keyStrafeUp); 74 | var strafeDown = keyboard.down(keyStrafeDown); 75 | var fast = keyboard.down("shift") ? 2.0 : (keyboard.down("alt") ? 0.5 : 1.0); 76 | 77 | if (gamepad != null) { 78 | var leftStickY = Math.abs(gamepad.leftStick.y) > 0.05; 79 | var leftStickX = Math.abs(gamepad.leftStick.x) > 0.05; 80 | var r1 = gamepad.down("r1") > 0.0; 81 | var l1 = gamepad.down("l1") > 0.0; 82 | var rightStickX = Math.abs(gamepad.rightStick.x) > 0.1; 83 | var rightStickY = Math.abs(gamepad.rightStick.y) > 0.1; 84 | 85 | if (leftStickY || leftStickX || r1 || l1 || rightStickX || rightStickY) { 86 | dir.set(0, 0, 0); 87 | 88 | if (leftStickY) { 89 | yvec.setFrom(camera.look()); 90 | yvec.mult(gamepad.leftStick.y); 91 | dir.add(yvec); 92 | } 93 | if (leftStickX) { 94 | xvec.setFrom(camera.right()); 95 | xvec.mult(gamepad.leftStick.x); 96 | dir.add(xvec); 97 | } 98 | if (r1) dir.addf(0, 0, 1); 99 | if (l1) dir.addf(0, 0, -1); 100 | 101 | var d = Time.delta * speed * fast; 102 | move(dir, d); 103 | 104 | if (rightStickX) { 105 | camera.transform.rotate(Vec4.zAxis(), -gamepad.rightStick.x / 15.0); 106 | } 107 | if (rightStickY) { 108 | camera.transform.rotate(camera.right(), gamepad.rightStick.y / 15.0); 109 | } 110 | } 111 | } 112 | 113 | if (moveForward || moveBackward || strafeRight || strafeLeft || strafeUp || strafeDown) { 114 | if (easing) { 115 | ease += Time.delta * 15; 116 | if (ease > 1.0) ease = 1.0; 117 | } 118 | else ease = 1.0; 119 | dir.set(0, 0, 0); 120 | if (moveForward) dir.addf(camera.look().x, camera.look().y, camera.look().z); 121 | if (moveBackward) dir.addf(-camera.look().x, -camera.look().y, -camera.look().z); 122 | if (strafeRight) dir.addf(camera.right().x, camera.right().y, camera.right().z); 123 | if (strafeLeft) dir.addf(-camera.right().x, -camera.right().y, -camera.right().z); 124 | if (strafeUp) dir.addf(0, 0, 1); 125 | if (strafeDown) dir.addf(0, 0, -1); 126 | 127 | stepTime += Time.delta; 128 | if (stepTime > 0.4 / fast) { 129 | stepTime = 0; 130 | iron.system.Audio.play(soundStep); 131 | } 132 | } 133 | else { 134 | if (easing) { 135 | ease -= Time.delta * 20.0 * ease; 136 | if (ease < 0.0) ease = 0.0; 137 | } 138 | else ease = 0.0; 139 | } 140 | 141 | var d = Time.delta * speed * fast * ease; 142 | if (d > 0.0) move(dir, d); 143 | 144 | if (mouse.locked) { 145 | camera.transform.rotate(Vec4.zAxis(), -mouse.movementX / 200); 146 | camera.transform.rotate(camera.right(), -mouse.movementY / 200); 147 | } 148 | 149 | // Voxels 150 | // Action 151 | cooldown -= Time.delta; 152 | if (mouse.down()) dig(); 153 | else if (mouse.started("right")) drop(); 154 | 155 | // Fall down 156 | var s = VoxelWorld.size; 157 | var v = camera.transform.world.getLoc(); 158 | var x = Std.int(v.x); 159 | var y = Std.int(v.y); 160 | var z = Std.int(v.z); 161 | if (x >= 0 && x < s && 162 | y >= 0 && y < s && 163 | z >= 2 && z < s + 2) { 164 | var i = (x + y * s + (z - 2) * s * s) * 3; 165 | var a = VoxelWorld.instancedData; 166 | if (a[i] == 0) camera.transform.move(new Vec4(0, 0, -1), 0.15); 167 | } 168 | if (x >= 0 && x < s && 169 | y >= 0 && y < s && 170 | z >= s + 2) { 171 | camera.transform.move(new Vec4(0, 0, -1), 0.15); 172 | } 173 | 174 | // Jump 175 | if (jump <= 0 && keyboard.started("space")) { 176 | if (x >= 0 && x < s && 177 | y >= 0 && y < s && 178 | z >= 1 && z < s - 2) { 179 | var i = (x + y * s + (z + 1) * s * s) * 3; 180 | var a = VoxelWorld.instancedData; 181 | var tile1 = a[i]; 182 | i = (x + y * s + (z + 2) * s * s) * 3; 183 | var tile2 = a[i]; 184 | if (tile1 == 0 && tile2 == 0) { 185 | jump = 0.4; 186 | } 187 | } 188 | else jump = 0.5; // Always jump when out of volume 189 | } 190 | if (jump > 0) { 191 | camera.transform.move(new Vec4(0,0,1), jump); 192 | jump -= 0.03; 193 | } 194 | } 195 | 196 | function dig() { 197 | if (cooldown > 0) return; 198 | 199 | var s = VoxelWorld.size; 200 | var dir = camera.look().normalize(); 201 | var v = camera.transform.world.getLoc(); 202 | v.addf(dir.x * 1.5, dir.y * 1.5, dir.z * 1.5); 203 | 204 | var x = Std.int(v.x); 205 | var y = Std.int(v.y); 206 | var z = Std.int(v.z); 207 | 208 | // Dig under player 209 | var cl = camera.transform.world.getLoc(); 210 | var cx = Std.int(cl.x); 211 | var cy = Std.int(cl.y); 212 | var cz = Std.int(cl.z); 213 | if (x == cx && y == cy && z == cz - 1) z -= 1; 214 | 215 | if (x >= 0 && x < s && 216 | y >= 0 && y < s && 217 | z >= 0 && z < s) { 218 | 219 | var a = VoxelWorld.instancedData; 220 | 221 | var i = (x + y * s + z * s * s) * 3; 222 | 223 | if (a[i] != 0) { 224 | a[i] = 0; 225 | 226 | var o = iron.Scene.active.getChild("Cube"); 227 | var instancedVB = cast(o, iron.object.MeshObject).data.geom.instancedVB; 228 | 229 | var texi = (x + y * s + z * s * s); 230 | var vertices = instancedVB.lock(); 231 | vertices.set(i, 0.0); 232 | #if kha_krom // Voxel ao 233 | var b = VoxelWorld.voxelImage.lock(); 234 | b.set(texi, 0); 235 | #end 236 | 237 | instancedVB.unlock(); 238 | #if kha_krom // Voxel ao 239 | VoxelWorld.voxelImage.unlock(); 240 | #end 241 | 242 | iron.system.Audio.play(soundDig); 243 | cooldown = 0.3; 244 | } 245 | } 246 | } 247 | 248 | function drop() { 249 | var s = VoxelWorld.size; 250 | var dir = camera.look().normalize(); 251 | var v = camera.transform.world.getLoc(); 252 | v.addf(dir.x * 1.5, dir.y * 1.5, dir.z * 1.5); 253 | 254 | var x = Std.int(v.x); 255 | var y = Std.int(v.y); 256 | var z = Std.int(v.z); 257 | 258 | if (x >= 0 && x < s && 259 | y >= 0 && y < s && 260 | z >= 0 && z < s) { 261 | 262 | var a = VoxelWorld.instancedData; 263 | 264 | var i = (x + y * s + z * s * s) * 3; 265 | var tile = a[i]; 266 | 267 | if (a[i] == 0) { 268 | 269 | var o = iron.Scene.active.getChild("Cube"); 270 | var instancedVB = cast(o, iron.object.MeshObject).data.geom.instancedVB; 271 | 272 | var vertices = instancedVB.lock(); 273 | 274 | vertices.set(i, 5.0/6.0); 275 | vertices.set(i + 1, 0.0/6.0); 276 | 277 | instancedVB.unlock(); 278 | 279 | iron.system.Audio.play(soundDrop); 280 | 281 | #if kha_krom // Voxel ao 282 | var texi = (x + y * s + z * s * s); 283 | var b = VoxelWorld.voxelImage.lock(); 284 | b.set(texi, 255); 285 | VoxelWorld.voxelImage.unlock(); 286 | #end 287 | } 288 | } 289 | } 290 | 291 | function move(dir:Vec4, d:Float) { 292 | var s = VoxelWorld.size; 293 | dir.normalize(); 294 | var v = camera.transform.world.getLoc(); 295 | v.addf(dir.x, dir.y, dir.z); 296 | 297 | var x = Std.int(v.x); 298 | var y = Std.int(v.y); 299 | var z = Std.int(v.z); 300 | 301 | if (x >= 0 && x < s && 302 | y >= 0 && y < s && 303 | z >= 1 && z < s - 1) { 304 | 305 | var a = VoxelWorld.instancedData; 306 | 307 | var i = (x + y * s + (z - 1) * s * s) * 3; 308 | var tile1 = a[i]; 309 | i = (x + y * s + z * s * s) * 3; 310 | var tile2 = a[i]; 311 | 312 | // Obstacle ahead 313 | if (tile1 != 0 || tile2 != 0) { 314 | return; 315 | } 316 | } 317 | 318 | var vv = new Vec4(); 319 | vv.setFrom(dir); 320 | vv.z = 0; 321 | camera.transform.move(vv, d); 322 | } 323 | 324 | #if arm_azerty 325 | static inline var keyUp = 'z'; 326 | static inline var keyDown = 's'; 327 | static inline var keyLeft = 'q'; 328 | static inline var keyRight = 'd'; 329 | static inline var keyStrafeUp = 'e'; 330 | static inline var keyStrafeDown = 'a'; 331 | #else 332 | static inline var keyUp = 'w'; 333 | static inline var keyDown = 's'; 334 | static inline var keyLeft = 'a'; 335 | static inline var keyRight = 'd'; 336 | static inline var keyStrafeUp = 'e'; 337 | static inline var keyStrafeDown = 'q'; 338 | #end 339 | } 340 | -------------------------------------------------------------------------------- /Sources/arm/VoxelWorld.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | import iron.data.SceneFormat; 4 | import iron.data.MaterialData; 5 | import iron.RenderPath; 6 | 7 | class VoxelWorld extends iron.Trait { 8 | 9 | public static var instancedData:kha.arrays.Float32Array; 10 | public static var voxelImage:kha.Image = null; 11 | public static inline var size = 64; 12 | 13 | static var p = new Perlin(); 14 | static var b:haxe.io.Bytes; 15 | 16 | public function new() { 17 | super(); 18 | notifyOnInit(init); 19 | } 20 | 21 | function init() { 22 | var s = size; 23 | var raw = iron.Scene.active.raw; 24 | 25 | // Voxel rendering shader 26 | var sh:TShaderData = { 27 | name: "MyShader", 28 | contexts: [ 29 | { 30 | name: "mesh", 31 | vertex_shader: "vox.vert", 32 | fragment_shader: "vox.frag", 33 | compare_mode: "less", 34 | cull_mode: "clockwise", 35 | depth_write: true, 36 | constants: [ 37 | { name: "WVP", type: "mat4", link: "_worldViewProjectionMatrix" }, 38 | { name: "s", type: "int" }, 39 | { name: "s2", type: "int" } 40 | ], 41 | texture_units: [ 42 | { name: "tileset" }, 43 | { name: "volume", link: "_volume" } 44 | ], 45 | vertex_elements: [ 46 | { name: "pos", data: 'short4norm' }, 47 | { name: "nor", data: 'short2norm' }, 48 | { name: "tex", data: 'short2norm' }, 49 | { name: "ipos", data: 'float3' } 50 | ] 51 | }, 52 | { 53 | name: "shadowmap", 54 | vertex_shader: "voxsm.vert", 55 | fragment_shader: "voxsm.frag", 56 | compare_mode: "less", 57 | cull_mode: "clockwise", 58 | depth_write: true, 59 | constants: [ 60 | { name: "LWVP", type: "mat4", link: "_lightWorldViewProjectionMatrix" }, 61 | { name: "s", type: "int" }, 62 | { name: "s2", type: "int" } 63 | ], 64 | texture_units: [], 65 | vertex_elements: [ 66 | { name: "pos", data: 'short4norm' }, 67 | { name: "ipos", data: 'float3' } 68 | ] 69 | } 70 | ] 71 | } 72 | 73 | if (raw.shader_datas == null) raw.shader_datas = []; 74 | raw.shader_datas.push(sh); 75 | 76 | var md:TMaterialData = { 77 | name: "MyMaterial", 78 | shader: "MyShader", 79 | contexts: [ 80 | { 81 | name: "mesh", 82 | bind_textures: [ 83 | { name: "tileset", file: "tileset.png", min_filter: "point", mag_filter: "point" } 84 | ], 85 | bind_constants: [ 86 | { name: "s", int: s }, 87 | { name: "s2", int: s * s } 88 | ] 89 | }, 90 | { 91 | name: "shadowmap", 92 | bind_constants: [ 93 | { name: "s", int: s }, 94 | { name: "s2", int: s * s } 95 | ] 96 | } 97 | ] 98 | } 99 | raw.material_datas.push(md); 100 | 101 | MaterialData.parse(raw.name, md.name, function(res:MaterialData) { 102 | cast(object, iron.object.MeshObject).materials[0] = res; 103 | 104 | // Generate voxel world data 105 | var num = s * s * s; 106 | instancedData = new kha.arrays.Float32Array(num * 3); 107 | for (i in 0...s) { 108 | for (j in 0...s) { 109 | for (k in 0...s) { 110 | // Using perlin here for simplicity, a predefined data file could be loaded instead 111 | var f = p.OctavePerlin(i / s, j / s, k / s, 5, 0.9, 0.25); 112 | 113 | var tx = 0.0; 114 | var ty = 0.0; 115 | 116 | if (f > 0.5) { 117 | var a = Std.random(2) + 1; 118 | var b = Std.random(3); 119 | tx = a / 6; 120 | ty = b / 6; 121 | } 122 | 123 | var a = i + j * s + k * s * s; 124 | instancedData[a * 3 + 0] = tx; 125 | instancedData[a * 3 + 1] = ty; 126 | instancedData[a * 3 + 2] = 0; 127 | } 128 | } 129 | } 130 | 131 | // Setup instanced rendering 132 | cast(object, iron.object.MeshObject).data.geom.setupInstanced(instancedData, 1, kha.graphics4.Usage.DynamicUsage); 133 | instancedData = cast(object, iron.object.MeshObject).data.geom.instancedVB.lock(); 134 | 135 | // 3D texture for voxel AO 136 | b = haxe.io.Bytes.alloc(s * s * s); 137 | for (i in 0...b.length) { 138 | b.set(i, instancedData[i * 3] == 0 ? 0 : 255); 139 | } 140 | iron.object.Uniforms.externalTextureLinks.push(textureLink); 141 | voxelImage = kha.Image.fromBytes3D(b, s, s, s, kha.graphics4.TextureFormat.L8, kha.graphics4.Usage.DynamicUsage); 142 | }); 143 | } 144 | 145 | public static function textureLink(object:iron.object.Object, mat:MaterialData, link:String):kha.Image { 146 | if (link == "_volume") return voxelImage; 147 | return null; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /voxels.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/voxel_world/3522d0df6df500f79ede3e749c13d33e38843f07/voxels.blend --------------------------------------------------------------------------------