├── LICENSE ├── README.md └── greedy_meshing.ts /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alec Thilenius 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Greedy Meshing Algorithm for Voxel Volumes 2 | 3 | This repo exists purely as reference for anyone else trying to implement greedy 4 | meshing. It is a cleanup of [this Javascript 5 | implementation](https://github.com/mikolalysenko/mikolalysenko.github.com/blob/gh-pages/MinecraftMeshes/js/greedy.js) 6 | which is from [this very math-heavy 0FPS 7 | article](https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/) also by 8 | [Mikola Lysenko](https://github.com/mikolalysenko). 9 | 10 | Unfortunately, I'm a bit slow, and found the source code unreadable. So I 11 | rewrote it in something that looks a little less like C from 1980 😉 12 | 13 | `⚠️ Important Note ⚠️ 14 | This is totally untested (I've never run this code) it was just a reference to 15 | then rewrite in Rust. If you use it please let me know if you found any issues, 16 | or open a PR.` 17 | -------------------------------------------------------------------------------- /greedy_meshing.ts: -------------------------------------------------------------------------------- 1 | // A simple 2D mask (2D matrix of booleans) stored as a row-major flat array. 2 | class Mask2D { 3 | private data: boolean[]; 4 | 5 | public constructor(private width: number, height: number) { 6 | this.data = new Array(width * height); 7 | } 8 | 9 | public get(x: number, y: number): boolean { 10 | return this.data[y * this.width + x]; 11 | } 12 | 13 | public set(x: number, y: number, v: boolean) { 14 | this.data[y * this.width + x] = v; 15 | } 16 | 17 | public clear() { 18 | for (let i = 0; i < this.data.length; i++) { 19 | this.data[i] = false; 20 | } 21 | } 22 | } 23 | 24 | export function greedyMesh( 25 | getVoxelBoundsChecked: (x: number, y: number, z: number) => boolean, 26 | dims: [number, number, number] 27 | ) { 28 | var quads = []; 29 | 30 | // Sweep over 3-axes. `norm` is a number from [0, 3) which is used as an array 31 | // selection into a [x, y, z] vector3. Aka `norm` sweeps across the three 32 | // euclidean axises. 33 | for (let norm = 0; norm < 3; norm++) { 34 | // The tangent and bitangent axises. If you think of `norm` as the 'forward' 35 | // vector then `tan` is the 'right' vector, and `biTan` is the 'down' 36 | // vector. Again, each of these are just selectors into a [x, y, z] Vector3. 37 | const tan = (norm + 1) % 3; 38 | const biTan = (norm + 2) % 3; 39 | 40 | // The normal vector3, as a number triple (ideally you would replace this 41 | // with a THREE.Vector3 or something). 42 | const normalVector: [number, number, number] = [0, 0, 0]; 43 | normalVector[norm] = 1; 44 | 45 | // Explained below. 46 | const mask = new Mask2D(dims[tan], dims[biTan]); 47 | 48 | // Move through the volume in 2D 'slices' perpendicular to the 49 | // `normal_vector`. 50 | for (let slice = 0; slice < dims[norm]; slice++) { 51 | // A 'voxel cursor' used to sample the 'slice' in the correct euclidean 52 | // plane. 53 | const cursor: [number, number, number] = [0, 0, 0]; 54 | cursor[norm] = slice; 55 | 56 | // Compute the 2D mask of which voxels need to be tessellated. 57 | for (cursor[biTan] = 0; cursor[biTan] < dims[biTan]; ++cursor[biTan]) { 58 | for (cursor[tan] = 0; cursor[tan] < dims[tan]; ++cursor[tan]) { 59 | // The mask is set to true anywhere a voxel in the current 'slice` 60 | // differs from a voxel in the previous 'slice' in the 61 | // `-normalVector` direction. Aka anywhere a solid voxel face touches 62 | // a non-solid voxel. Note that this will cause sampling of 63 | // non-existent negative voxel coordinates, which 64 | // `getVoxelBoundsChecked` needs to handle by returning false. 65 | const voxelInSlice = getVoxelBoundsChecked(...cursor); 66 | const voxelInPreviousSlice = getVoxelBoundsChecked( 67 | cursor[0] - normalVector[0], 68 | cursor[1] - normalVector[1], 69 | cursor[2] - normalVector[2] 70 | ); 71 | mask.set( 72 | cursor[tan], 73 | cursor[biTan], 74 | voxelInSlice !== voxelInPreviousSlice 75 | ); 76 | } 77 | } 78 | 79 | // Generate mesh for mask using lexicographic ordering 80 | for (let y = 0; y < dims[biTan]; y++) { 81 | for (let x = 0; x < dims[tan]; ) { 82 | // If the mask isn't set, then just increment and continue (nothing to 83 | // tessellate). 84 | if (!mask.get(x, y)) { 85 | x++; 86 | continue; 87 | } 88 | 89 | // Compute the max-width of the combined quad going left-to-right. 90 | let width = 1; 91 | while (mask.get(x + width, y) && x + width < dims[tan]) { 92 | width++; 93 | } 94 | 95 | // Compute max-height (extend the row `width` downward as much as 96 | // possible). 97 | let height = 1; 98 | outer: for (; y + height < dims[biTan]; height++) { 99 | for (let k = x; k < width; k++) { 100 | if (!mask.get(k, y + height)) { 101 | break outer; 102 | } 103 | } 104 | } 105 | 106 | // The base of the quad to add 107 | const b = [0, 0, 0]; 108 | b[norm] = slice; 109 | b[tan] = x; 110 | b[biTan] = y; 111 | 112 | // The 'width' of the quad. 113 | const du = [0, 0, 0]; 114 | du[tan] = width; 115 | 116 | // The 'height' of the quad. 117 | const dv = [0, 0, 0]; 118 | dv[biTan] = height; 119 | 120 | quads.push([ 121 | [b[0], b[1], b[2]], 122 | [b[0] + du[0], b[1] + du[1], b[2] + du[2]], 123 | [b[0] + du[0] + dv[0], b[1] + du[1] + dv[1], b[2] + du[2] + dv[2]], 124 | [b[0] + dv[0], b[1] + dv[1], b[2] + dv[2]], 125 | ]); 126 | 127 | // Clear the mask and increment x by the width of this quad. 128 | mask.clear(); 129 | x += width; 130 | } 131 | } 132 | } 133 | } 134 | return quads; 135 | } 136 | --------------------------------------------------------------------------------