├── .gitignore ├── lib ├── wireShader.js ├── createMesh.js └── examples.js ├── README.md ├── LICENSE ├── index.html ├── package.json ├── main.css └── main.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/* -------------------------------------------------------------------------------- /lib/wireShader.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var createShader = require("gl-shader") 3 | 4 | module.exports = function(gl) { 5 | return createShader(gl, 6 | "attribute vec3 position;\ 7 | uniform mat4 projection;\ 8 | uniform mat4 view;\ 9 | uniform mat4 model;\ 10 | void main() {\ 11 | gl_Position=projection * view * model * vec4(position, 1.0);\ 12 | }", 13 | "void main() {\ 14 | gl_FragColor = vec4(0, 1, 0, 1);\ 15 | }") 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | voxel-mipmap-demo 2 | ================= 3 | Demonstration of texture mapping with greedy meshing. 4 | 5 | * Left click rotates 6 | * Right click/shift pans 7 | * Middle click/scroll/alt zooms 8 | 9 | [Check it out in your browser](http://mikolalysenko.github.io/voxel-mipmap-demo/) 10 | 11 | For more information see the following blog post: 12 | 13 | * [Texture atlases, wrapping and mip mapping](http://0fps.wordpress.com/2013/07/09/texture-atlases-wrapping-and-mip-mapping/) 14 | 15 | ## How to run locally 16 | First you will need to install npm, which comes with node.js. You can get that by going to here: 17 | 18 | * [https://nodejs.org/](https://nodejs.org/) 19 | 20 | Then, go into the root directory of the project and type: 21 | 22 | npm install 23 | 24 | Once that is done, you can run the project in your browser by typing: 25 | 26 | npm start 27 | 28 | That will start a local server. To view the demo in your browser, connect to localhost on the port that shows up. For example, http://localhost:9966 29 | 30 | ## Credits 31 | (c) 2013 Mikola Lysenko. MIT License 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Voxel Mipmap Demo 6 | 7 | 8 | 9 | Fork me on GitHub 10 |
11 |

12 | 13 | 15 |

16 |

17 | 18 | 19 |

20 |

21 | 22 | 27 |

28 |
29 |
Requires WebGL!
30 | 31 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voxel-mipmap-demo", 3 | "version": "0.0.0", 4 | "description": "Demonstration of mipmapping for voxel terrain", 5 | "main": "main.js", 6 | "dependencies": { 7 | "ao-mesher": "^0.2.9", 8 | "game-shell-orbit-camera": "^0.0.0", 9 | "ao-shader": "^0.3.0", 10 | "gl-buffer": "^2.0.6", 11 | "gl-matrix": "^2.2.1", 12 | "gl-now": "^1.0.1", 13 | "gl-shader": "^3.0.0", 14 | "gl-vao": "^1.1.2", 15 | "gl-tile-map": "^0.3.0", 16 | "isabella-texture-pack": "^0.0.1", 17 | "lazy-property": "^0.0.2", 18 | "ndarray": "^1.0.10", 19 | "ndarray-fill": "^0.1.0", 20 | "ndarray-ops": "^1.2.1", 21 | "bunny": "^1.0.1", 22 | "voxelize": "^0.1.0", 23 | "brfs": "^1.0.0", 24 | "teapot": "^0.0.1" 25 | }, 26 | "devDependencies": { 27 | "beefy": "^1.1.0", 28 | "browserify": "^3.38.0" 29 | }, 30 | "scripts": { 31 | "test": "echo \"Error: no test specified\" && exit 1", 32 | "start": "beefy main.js --open" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/mikolalysenko/voxel-mipmap-demo.git" 37 | }, 38 | "keywords": [ 39 | "mipmap", 40 | "voxel", 41 | "terrain" 42 | ], 43 | "author": "Mikola Lysenko", 44 | "license": "MIT", 45 | "gitHead": "0f57d9dfbe095ce86f182d2e70c2c0a1f7927ca9", 46 | "bugs": { 47 | "url": "https://github.com/mikolalysenko/voxel-mipmap-demo/issues" 48 | }, 49 | "browser": { 50 | "transform": "brfs" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | .controlPanel { 2 | position: absolute; 3 | top: 1%; 4 | left: 1%; 5 | z-index: 10; 6 | background-color: rgba(129, 129, 129, 0.5); 7 | padding-top: 10px; 8 | padding-left: 10px; 9 | padding-right: 10px; 10 | padding-bottom: 10px; 11 | border-radius: 10px; 12 | } 13 | .noWebGL { 14 | background-color: red; 15 | color: white; 16 | transform:rotate(-37deg); 17 | -ms-transform:rotate(-37deg); 18 | -webkit-transform:rotate(-37deg); 19 | position: absolute; 20 | width: 100%; 21 | font-size: 90pt; 22 | top: 50%; 23 | text-align: center; 24 | z-index:30; 25 | display: none; 26 | } 27 | html,body{ 28 | font-family: sans-serif; 29 | } 30 | body{ 31 | background: rgb(240,249,255); /* Old browsers */ 32 | background: -moz-linear-gradient(-45deg, rgba(240,249,255,1) 0%, rgba(203,235,255,1) 53%, rgba(161,219,255,1) 100%); /* FF3.6+ */ 33 | background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,rgba(240,249,255,1)), color-stop(53%,rgba(203,235,255,1)), color-stop(100%,rgba(161,219,255,1))); /* Chrome,Safari4+ */ 34 | background: -webkit-linear-gradient(-45deg, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 53%,rgba(161,219,255,1) 100%); /* Chrome10+,Safari5.1+ */ 35 | background: -o-linear-gradient(-45deg, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 53%,rgba(161,219,255,1) 100%); /* Opera 11.10+ */ 36 | background: -ms-linear-gradient(-45deg, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 53%,rgba(161,219,255,1) 100%); /* IE10+ */ 37 | background: linear-gradient(135deg, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 53%,rgba(161,219,255,1) 100%); /* W3C */ 38 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f0f9ff', endColorstr='#a1dbff',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ 39 | } -------------------------------------------------------------------------------- /lib/createMesh.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var ndarray = require("ndarray") 4 | var createBuffer = require("gl-buffer") 5 | var createVAO = require("gl-vao") 6 | var createAOMesh = require("ao-mesher") 7 | var ops = require("ndarray-ops") 8 | 9 | var cached = {} 10 | 11 | //Creates a mesh from a set of voxels 12 | function createVoxelMesh(gl, name, voxels) { 13 | if(name in cached) { 14 | return cached[name] 15 | } 16 | 17 | //Create mesh 18 | var vert_data = createAOMesh(voxels) 19 | 20 | //Upload triangle mesh to WebGL 21 | var triangleVertexCount = Math.floor(vert_data.length/8) 22 | var vert_buf = createBuffer(gl, vert_data) 23 | var triangleVAO = createVAO(gl, [ 24 | { "buffer": vert_buf, 25 | "type": gl.UNSIGNED_BYTE, 26 | "size": 4, 27 | "offset": 0, 28 | "stride": 8, 29 | "normalized": false 30 | }, 31 | { "buffer": vert_buf, 32 | "type": gl.UNSIGNED_BYTE, 33 | "size": 4, 34 | "offset": 4, 35 | "stride": 8, 36 | "normalized": false 37 | } 38 | ]) 39 | 40 | //Create wire mesh 41 | var wireVertexCount = 2 * triangleVertexCount 42 | var wireVertexArray = ndarray(new Uint8Array(wireVertexCount * 3), [triangleVertexCount, 2, 3]) 43 | var trianglePositions = ndarray(vert_data, [triangleVertexCount, 3], [8, 1], 0) 44 | ops.assign(wireVertexArray.pick(undefined, 0, undefined), trianglePositions) 45 | var wires = wireVertexArray.pick(undefined, 1, undefined) 46 | for(var i=0; i<3; ++i) { 47 | ops.assign(wires.lo(i).step(3), trianglePositions.lo((i+1)%3).step(3)) 48 | } 49 | var wireBuf = createBuffer(gl, wireVertexArray.data) 50 | var wireVAO = createVAO(gl, [ 51 | { "buffer": wireBuf, 52 | "type": gl.UNSIGNED_BYTE, 53 | "size": 3, 54 | "offset": 0, 55 | "stride": 3, 56 | "normalized": false 57 | } 58 | ]) 59 | 60 | //Bundle result and return 61 | var result = { 62 | triangleVertexCount: triangleVertexCount, 63 | triangleVAO: triangleVAO, 64 | wireVertexCount: wireVertexCount, 65 | wireVAO: wireVAO, 66 | center: [voxels.shape[0]>>1, voxels.shape[1]>>1, voxels.shape[2]>>1], 67 | radius: voxels.shape[2] 68 | } 69 | cached[name] = result 70 | return result 71 | } 72 | 73 | module.exports = createVoxelMesh -------------------------------------------------------------------------------- /lib/examples.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var ndarray = require("ndarray") 4 | var fill = require("ndarray-fill") 5 | var ops = require("ndarray-ops") 6 | var voxelize = require("voxelize") 7 | var lazyProperty = require("lazy-property") 8 | 9 | var bunny = require("bunny") 10 | var teapot = require("teapot") 11 | 12 | //Fill ndarray with function 13 | function makeFill(name, size, func) { 14 | lazyProperty(exports, name, function() { 15 | var result = ndarray(new Int32Array(size[0]*size[1]*size[2]), size) 16 | fill(result, func) 17 | return result 18 | }, true) 19 | } 20 | 21 | //Fill ndarray with voxelized mesh 22 | function makeMesh(name, mesh, tolerance, fill) { 23 | lazyProperty(exports, name, function() { 24 | var result = voxelize(mesh.cells, mesh.positions, tolerance) 25 | var voxels = result.voxels 26 | var nshape = result.voxels.shape.slice(0) 27 | for(var i=0; i<3; ++i) { 28 | nshape[i] += 2 29 | } 30 | var padded = ndarray(new Int32Array(nshape[0]*nshape[1]*nshape[2]), nshape) 31 | ops.muls(padded, result.voxels, fill) 32 | return padded 33 | }, true) 34 | } 35 | 36 | //Sphere 37 | makeFill("Sphere", [32,32,32], function(i,j,k) { 38 | var x = i - 16 39 | var y = j - 16 40 | var z = k - 16 41 | return (x*x + y*y + z*z) < 30 ? (1<<15) + 0x18 : 0 42 | }) 43 | 44 | //Cuboid 45 | makeFill("Box", [32,32,32], function(i,j,k) { 46 | var x = Math.abs(i - 16) 47 | var y = Math.abs(j - 16) 48 | var z = Math.abs(k - 16) 49 | return Math.max(x,y,z) < 13 ? (1<<15) + 0x91 : 0 50 | }) 51 | 52 | //Terrain 53 | makeFill("Terrain", [33,33,33], function(i,j,k) { 54 | if(i <=1 || i>=31 || j <= 1 || j >= 31 || k <= 1 || k >= 31) { 55 | return 0 56 | } 57 | var h0 = 3.0 * Math.sin(Math.PI * i / 12.0 - Math.PI * k * 0.1) + 27 58 | if(j > h0+1) { 59 | return 0 60 | } 61 | if(h0 <= j) { 62 | return (1<<15)+0x19 63 | } 64 | var h1 = 2.0 * Math.sin(Math.PI * i * 0.25 - Math.PI * k * 0.3) + 20 65 | if(h1 <= j) { 66 | return (1<<15)+0x20 67 | } 68 | if(4 < j) { 69 | return Math.random() < 0.1 ? (1<<15)+0x23 : (1<<15)+0x10 70 | } 71 | return (1<<15)+0xff 72 | }) 73 | 74 | //Random 75 | makeFill("Random", [16,16,16], function(i,j,k) { 76 | if(i <=1 || i>=14 || j <= 1 || j >= 14 || k <= 1 || k >= 14) { 77 | return 0 78 | } 79 | if(Math.random() < 0.6) { 80 | return 0 81 | } 82 | return ((Math.random()*255)|0) + (1<<15) 83 | }) 84 | 85 | makeFill("Tree", [16,16,16], function(i,j,k) { 86 | if(i === 8 && k === 8 && 4>4,terrain.shape[1]>>4,4], 75 | [terrain.stride[0]*16, terrain.stride[1]*16, terrain.stride[0], terrain.stride[1], terrain.stride[2]], 0) 76 | texture = createTileMap(gl, tiles, 2) 77 | 78 | //Hook event listeners 79 | selectMip.addEventListener("change", mipChange) 80 | mipChange() 81 | 82 | selectModel.addEventListener("change", meshChange) 83 | meshChange() 84 | }) 85 | 86 | shell.on("gl-error", function() { 87 | document.querySelector(".selectModel").style.display = "none" 88 | document.querySelector(".noWebGL").style.display = "none" 89 | }) 90 | 91 | shell.on("gl-render", function(t) { 92 | var gl = shell.gl 93 | 94 | //Calculation projection matrix 95 | var projection = mat4.perspective(new Float32Array(16), Math.PI/4.0, shell.width/shell.height, 1.0, 1000.0) 96 | var model = mat4.identity(new Float32Array(16)) 97 | var view = camera.view() 98 | 99 | gl.enable(gl.CULL_FACE) 100 | gl.enable(gl.DEPTH_TEST) 101 | 102 | //Bind the shader 103 | shader.bind() 104 | shader.attributes.attrib0.location = 0 105 | shader.attributes.attrib1.location = 1 106 | shader.uniforms.projection = projection 107 | shader.uniforms.view = view 108 | shader.uniforms.model = model 109 | shader.uniforms.tileSize = TILE_SIZE 110 | shader.uniforms.tileCount = TILE_COUNT 111 | shader.uniforms.tileMap = texture.bind() 112 | 113 | mesh.triangleVAO.bind() 114 | gl.drawArrays(gl.TRIANGLES, 0, mesh.triangleVertexCount) 115 | mesh.triangleVAO.unbind() 116 | 117 | if(showWire.checked) { 118 | //Bind the wire shader 119 | wireShader.bind() 120 | wireShader.attributes.position.location = 0 121 | wireShader.uniforms.projection = projection 122 | wireShader.uniforms.model = model 123 | wireShader.uniforms.view = view 124 | 125 | mesh.wireVAO.bind() 126 | gl.drawArrays(gl.LINES, 0, mesh.wireVertexCount) 127 | mesh.wireVAO.unbind() 128 | } 129 | }) 130 | --------------------------------------------------------------------------------