├── .gitignore ├── .npmignore ├── LICENSE ├── package.json ├── README.md ├── test └── test.js └── greedy.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* 16 | test/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Mikola Lysenko 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 14 | all 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 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greedy-mesher", 3 | "version": "1.0.3", 4 | "description": "Greedy mesh compiler", 5 | "main": "greedy.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "iota-array": "^1.0.0", 11 | "typedarray-pool": "^1.1.0", 12 | "uniq": "^1.0.1" 13 | }, 14 | "devDependencies": { 15 | "ndarray": "~1.0.5", 16 | "ndarray-pack": "~1.0.1", 17 | "tape": "~1.0.4", 18 | "compare": "~0.0.1", 19 | "aabb-2d": "~0.0.0", 20 | "aabb-3d": "~0.0.0", 21 | "ndarray-fill": "~0.1.0", 22 | "zeros": "~0.0.0" 23 | }, 24 | "scripts": { 25 | "test": "tap test/*.js" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/mikolalysenko/greedy-mesher.git" 30 | }, 31 | "keywords": [ 32 | "greedy", 33 | "mesh", 34 | "compiler", 35 | "voxel", 36 | "geometry", 37 | "ndarray" 38 | ], 39 | "author": "Mikola Lysenko", 40 | "license": "MIT", 41 | "readmeFilename": "README.md", 42 | "gitHead": "154f0e316690fccce1148d4622c9274bbeec4401", 43 | "bugs": { 44 | "url": "https://github.com/mikolalysenko/greedy-mesher/issues" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | greedy-mesher 2 | ============= 3 | A flexible system for generating [greedy meshes](http://0fps.wordpress.com/2012/07/07/meshing-minecraft-part-2/) of [ndarrays](https://github.com/mikolalysenko/ndarray). 4 | 5 | ## Example 6 | 7 | ```javascript 8 | var compileMesher = require("greedy-mesher") 9 | 10 | var mesher = compileMesher({ 11 | extraArgs: 1, 12 | order: [1, 0], 13 | append: function(lo_x, lo_y, hi_x, hi_y, val, result) { 14 | result.push([[lo_x, lo_y], [hi_x, hi_y]]) 15 | } 16 | }) 17 | 18 | var test_array = require("ndarray-pack")( 19 | [[0, 2, 0, 0], 20 | [0, 1, 1, 0], 21 | [0, 1, 1, 0], 22 | [0, 0, 0, 0]]) 23 | 24 | var result = [] 25 | mesher(test_array, result) 26 | console.log(result); 27 | // outputs: [ [ [ 1, 0 ], [ 2, 1 ] ], [ [ 1, 1 ], [ 3, 3 ] ] ] 28 | ``` 29 | 30 | ## Install 31 | 32 | npm install greedy-mesher 33 | 34 | ### `require("greedy-mesher")(options)` 35 | This routine generates a greedy mesher for a given order and list of closures 36 | 37 | * `order` An array representing the order in which to mesh the input. 38 | * `extraArgs` The number of optional arguments to pass to each closure. 39 | * `skip(a,...)` A closure which tests if the given voxel should be skipped. (Default: skips 0) 40 | * `merge(a,b,...)` A closure which tests if voxels a and b can be merged in the mesh. (Default: checks if voxels are equal) 41 | * `append(lo0, lo1, ..., lon, hi0, hi1, ..., hin, val, ...)` 42 | * `useGetter` if set, use `.get()` to access underlying data store 43 | * `debug` If set, print out the generated source code to the console 44 | 45 | **Returns** A compiled mesh generator. Call this method using: 46 | 47 | ```javascript 48 | var my_mesh = mesher(array, option1, option2, ...) 49 | ``` 50 | 51 | ## Credits 52 | (c) 2013 Mikola Lysenko. MIT License 53 | 54 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var compileMesher = require("../greedy.js") 2 | var ndarray = require("ndarray") 3 | var compare = require("compare") 4 | var pack = require("ndarray-pack") 5 | var fill = require("ndarray-fill") 6 | var aabb2d = require("aabb-2d") 7 | var aabb3d = require("aabb-3d") 8 | var test = require("tape") 9 | var zeros = require("zeros") 10 | 11 | var mesher_c = compileMesher({ 12 | order: [0, 1], 13 | extraArgs: 1, 14 | skip: function() { 15 | return false 16 | }, 17 | append: function(lo_x, lo_y, hi_x, hi_y, val, result) { 18 | result.push([[lo_x, lo_y], [hi_x, hi_y], val]) 19 | } 20 | }) 21 | 22 | function mesher(array) { 23 | var result = [] 24 | mesher_c(array, result) 25 | return result 26 | } 27 | 28 | test("greedy-mesher", function(t) { 29 | 30 | var test_array = require("ndarray-pack")( 31 | [[0, 0, 0, 0], 32 | [0, 1, 1, 0], 33 | [0, 1, 1, 0], 34 | [0, 0, 0, 0]]) 35 | 36 | var mesh = mesher(test_array) 37 | 38 | t.same(mesh, [ 39 | [[0,0], [4,1], 0], 40 | [[0,1], [1,4], 0], 41 | [[1,1], [3,3], 1], 42 | [[3,1], [4,4], 0], 43 | [[1,3], [3,4], 0] 44 | ]) 45 | 46 | t.end() 47 | }) 48 | 49 | test("skip-voxels", function(t) { 50 | 51 | var mesher_c = compileMesher({ 52 | order: [0, 1], 53 | extraArgs: 1, 54 | append: function(lo_x, lo_y, hi_x, hi_y, val, result) { 55 | result.push([[lo_x, lo_y], [hi_x, hi_y], val]) 56 | } 57 | }) 58 | 59 | function mesher(array) { 60 | var result = [] 61 | mesher_c(array, result) 62 | return result 63 | } 64 | 65 | var test_array = require("ndarray-pack")( 66 | [[0, 0, 0, 0], 67 | [0, 1, 1, 0], 68 | [0, 1, 1, 0], 69 | [0, 0, 0, 0]]) 70 | 71 | var mesh = mesher(test_array) 72 | 73 | t.same(mesh, [ 74 | [[1,1], [3,3], 1] 75 | ]) 76 | 77 | t.end() 78 | }) 79 | 80 | function circle(x, y) { 81 | x -= 19 82 | y -= 21 83 | return x*x + y*y < 20*20 ? 1 : 0 84 | } 85 | 86 | function rect(x, y) { 87 | return x > 21 && x < 40 && y > 22 && y < 40 88 | } 89 | 90 | function noise(x, y) { 91 | return Math.random() > 0.5 ? 1 : 0 92 | } 93 | 94 | ;[circle, rect, noise].map(function(filler) { 95 | var x = zeros([40, 40]) 96 | return [fill(x, filler), filler] 97 | }).forEach(function(layout) { 98 | var grid = layout[0] 99 | var generator = layout[1] 100 | var name = generator.name 101 | 102 | test(name + ': no overlapping quads', function(t) { 103 | var mesh = mesher(grid).map(function(quad) { 104 | // Adjacent AABBs will collide - to only 105 | // catch overlapping quads, we shrink them 106 | // by 0.1 units. 107 | quad[1][0] -= 0.1 108 | quad[1][1] -= 0.1 109 | return aabb2d(quad[0], [ 110 | quad[1][0] - quad[0][0], 111 | quad[1][1] - quad[0][1] 112 | ]) 113 | }) 114 | 115 | var safe = compare(mesh, function(a, b) { 116 | return !a.intersects(b) 117 | }) 118 | 119 | t.ok(safe) 120 | t.end() 121 | }) 122 | 123 | test(name + ': covers the expected points with correct values', function(t) { 124 | var mesh = mesher(grid) 125 | var w = grid.shape[0] 126 | var h = grid.shape[1] 127 | var safe = true 128 | 129 | for (var x = 0; x < w; x += 1) 130 | for (var y = 0; y < h; y += 1) { 131 | safe = safe && mesh.some(function(quad) { 132 | return quad[0][0] <= x && 133 | quad[0][1] <= y && 134 | quad[1][0] >= x && 135 | quad[1][1] >= y && 136 | quad[2] === grid.get(x, y) 137 | }) 138 | } 139 | 140 | t.ok(safe) 141 | t.end() 142 | }) 143 | }) 144 | 145 | function sphere(x, y, z) { 146 | x -= 19 147 | y -= 20 148 | z -= 21 149 | return x*x + y*y + z*z < 20*20 ? 1 : 0 150 | } 151 | 152 | function cuboid(x, y, z) { 153 | return x > 21 && x < 31 && y > 19 && y < 29 && z > 22 && z < 30 ? 1 : 0 154 | } 155 | 156 | function noise3d(x, y) { 157 | return Math.random() > 0.1 ? 1 : 0 158 | } 159 | 160 | ;[sphere, cuboid, noise3d].map(function(filler) { 161 | var x = zeros([32, 32, 32]) 162 | return [fill(x, filler), filler] 163 | }).forEach(function(layout) { 164 | var grid = layout[0] 165 | var generator = layout[1] 166 | var name = generator.name 167 | 168 | var mesher_c = compileMesher({ 169 | order: [0, 1, 2], 170 | extraArgs: 1, 171 | skip: function() { 172 | return false 173 | }, 174 | append: function(lo_x, lo_y, lo_z, hi_x, hi_y, hi_z, val, result) { 175 | result.push([[lo_x, lo_y, lo_z], [hi_x, hi_y, hi_z], val]) 176 | } 177 | }) 178 | function mesher(array) { 179 | var result = [] 180 | mesher_c(array, result) 181 | return result 182 | } 183 | 184 | test(name + ': no overlapping voxels', function(t) { 185 | var mesh = mesher(grid).map(function(voxel) { 186 | voxel[1][0] -= 0.1 187 | voxel[1][1] -= 0.1 188 | voxel[1][2] -= 0.1 189 | return aabb3d(voxel[0], [ 190 | voxel[1][0] - voxel[0][0], 191 | voxel[1][1] - voxel[0][1], 192 | voxel[1][2] - voxel[0][2] 193 | ]) 194 | }) 195 | 196 | var safe = compare(mesh, function(a, b) { 197 | return !a.intersects(b) 198 | }) 199 | 200 | t.ok(safe) 201 | t.end() 202 | }) 203 | 204 | test(name + ': covers the expected points with the correct values', function(t) { 205 | var mesh = mesher(grid) 206 | var w = grid.shape[0] 207 | var h = grid.shape[1] 208 | var l = grid.shape[2] 209 | var safe = true 210 | 211 | for (var x = 0; x < w; x += 1) 212 | for (var y = 0; y < h; y += 1) 213 | for (var z = 0; z < l; z += 1) { 214 | safe = safe && mesh.some(function(voxel) { 215 | return voxel[0][0] <= x && 216 | voxel[0][1] <= y && 217 | voxel[0][2] <= z && 218 | voxel[1][0] >= x && 219 | voxel[1][1] >= y && 220 | voxel[1][2] >= z && 221 | voxel[2] === grid.get(x, y, z) 222 | }) 223 | } 224 | 225 | t.ok(safe) 226 | t.end() 227 | }) 228 | }) -------------------------------------------------------------------------------- /greedy.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var pool = require("typedarray-pool") 4 | var uniq = require("uniq") 5 | var iota = require("iota-array") 6 | 7 | function generateMesher(order, skip, merge, append, num_options, options, useGetter) { 8 | var code = [] 9 | var d = order.length 10 | var i, j, k 11 | 12 | //Build arguments for append macro 13 | var append_args = new Array(2*d+1+num_options) 14 | for(i=0; i 0) { 33 | code.push(["var astep",i,"=(stride",i,"-stride",i-1,"*shape",i-1,")|0"].join("")) 34 | } else { 35 | code.push(["var astep",i,"=stride",i,"|0"].join("")) 36 | } 37 | if(i > 0) { 38 | code.push(["var vstep",i,"=(vstep",i-1,"*shape",i-1,")|0"].join("")) 39 | } else { 40 | code.push(["var vstep",i,"=1"].join("")) 41 | } 42 | code.push(["var i",i,"=0,j",i,"=0,k",i,"=0,ustep",i,"=vstep",i,"|0,bstep",i,"=astep",i,"|0"].join("")) 43 | } 44 | 45 | //Initialize pointers 46 | code.push("var a_ptr=offset>>>0,b_ptr=0,u_ptr=0,v_ptr=0,i=0,d=0,val=0,oval=0") 47 | 48 | //Initialize count 49 | code.push("var count=" + iota(d).map(function(i) { return "shape"+i}).join("*")) 50 | code.push("var visited=mallocUint8(count)") 51 | 52 | //Zero out visited map 53 | code.push("for(;i=0; --i) { 57 | code.push(["for(i",i,"=0;i",i,"=0; --j) { 82 | code.push(["for(k",j,"=i",j,";k",j,"=0; --i) { 124 | code.push(["for(k",i,"=i",i,";k",i,"