├── .gitignore ├── .npmignore ├── example ├── textures │ ├── 0.png │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── brick.png │ ├── dirt.png │ ├── grass.png │ ├── shama.png │ ├── terrain.png │ └── grass_dirt.png ├── style.css ├── index.html └── world.js ├── .editorconfig ├── LICENSE-MIT ├── package.json ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /example/textures/ 3 | -------------------------------------------------------------------------------- /example/textures/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/0.png -------------------------------------------------------------------------------- /example/textures/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/1.png -------------------------------------------------------------------------------- /example/textures/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/2.png -------------------------------------------------------------------------------- /example/textures/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/3.png -------------------------------------------------------------------------------- /example/textures/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/4.png -------------------------------------------------------------------------------- /example/textures/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/5.png -------------------------------------------------------------------------------- /example/textures/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/6.png -------------------------------------------------------------------------------- /example/textures/brick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/brick.png -------------------------------------------------------------------------------- /example/textures/dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/dirt.png -------------------------------------------------------------------------------- /example/textures/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/grass.png -------------------------------------------------------------------------------- /example/textures/shama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/shama.png -------------------------------------------------------------------------------- /example/textures/terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/terrain.png -------------------------------------------------------------------------------- /example/textures/grass_dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shama/voxel-texture/HEAD/example/textures/grass_dirt.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px; 3 | font-family: Arial, sans-serif; 4 | font-size: 80%; 5 | } 6 | #container { 7 | -moz-user-select: none; 8 | -webkit-user-select: none; 9 | user-select: none; 10 | } 11 | #crosshair { 12 | position: fixed; 13 | top: 50%; 14 | left: 50%; 15 | margin: -2px 0 0 -2px; 16 | width: 4px; 17 | height: 4px; 18 | background-color: #d00; 19 | opacity: 0.5; 20 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | voxel-texture 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Kyle Robinson Young 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voxel-texture", 3 | "description": "A texture helper for voxeljs", 4 | "version": "0.5.9", 5 | "homepage": "https://github.com/shama/voxel-texture", 6 | "author": { 7 | "name": "Kyle Robinson Young", 8 | "email": "kyle@dontkry.com", 9 | "url": "http://dontkry.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/shama/voxel-texture.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/shama/voxel-texture/issues" 17 | }, 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">= 0.8.0" 21 | }, 22 | "scripts": { 23 | "start": "cd example && ../node_modules/.bin/browservefy world.js 8080 -- -d", 24 | "gh-pages": "cp -R ./example/* ./ && browserify world.js -o world.js" 25 | }, 26 | "dependencies": { 27 | "opaque": "0.0.1", 28 | "tic": "~0.2.1", 29 | "atlaspack": "~0.2.5", 30 | "voxel-fakeao": "~0.1.1" 31 | }, 32 | "devDependencies": { 33 | "voxel-engine": "~0.17.1", 34 | "voxel-debris": "~0.0.4", 35 | "browservefy": "0.0.10", 36 | "voxel-player": "~0.1.0" 37 | }, 38 | "keywords": [ 39 | "voxel", 40 | "texture" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /example/world.js: -------------------------------------------------------------------------------- 1 | var tic = require('tic')(); 2 | var createGame = require('voxel-engine'); 3 | 4 | var game = createGame({ 5 | chunkDistance: 2, 6 | generate: function(x, y, z) { 7 | return (Math.sqrt(x*x + y*y + z*z) > 20 || y*y > 10) ? 0 : (Math.random() * 3) + 1; 8 | }, 9 | materials: ['brick', ['grass', 'dirt', 'grass_dirt'], 'dirt'], 10 | texturePath: 'textures/' 11 | }); 12 | var container = document.getElementById('container'); 13 | game.appendTo(container); 14 | 15 | game.addStats(); 16 | 17 | // Our texture builder 18 | var materialEngine = game.materials; 19 | 20 | // Give console access to game 21 | window.game = game; 22 | 23 | // hold the cubes we create 24 | var cubes = []; 25 | 26 | // create a player 27 | var createPlayer = require('voxel-player')(game); 28 | var shama = createPlayer('textures/shama.png'); 29 | shama.yaw.position.set(0, 10, 0); 30 | shama.possess(); 31 | 32 | // explode voxel on click 33 | var explode = require('voxel-debris')(game, { power : 1.5 }); 34 | game.on('fire', function(pos) { 35 | var pos = game.raycast(game.cameraPosition(), game.cameraVector(), 100).voxel; 36 | if (erase) explode(pos); 37 | else game.createBlock(pos, 1); 38 | }); 39 | 40 | window.addEventListener('keydown', ctrlToggle); 41 | window.addEventListener('keyup', ctrlToggle); 42 | 43 | var erase = true; 44 | function ctrlToggle (ev) { erase = !ev.ctrlKey } 45 | 46 | game.on('tick', function(dt) { 47 | materialEngine.tick(dt); 48 | tic.tick(dt); 49 | cubes.forEach(function(cube, i) { 50 | cube.rotation.y += Math.PI / 180; 51 | }); 52 | }); 53 | 54 | function createCube(i, materials) { 55 | // create a mesh 56 | var obj = new game.THREE.Object3D(); 57 | var mesh = new game.THREE.Mesh( 58 | new game.THREE.CubeGeometry(game.cubeSize, game.cubeSize, game.cubeSize), 59 | game.materials.material 60 | ); 61 | mesh.translateY(game.cubeSize/2); 62 | obj.add(mesh); 63 | obj.position.set(3, 5, i * 2); 64 | 65 | // paint the mesh 66 | materialEngine.paint(mesh, materials); 67 | mesh.geometry.uvsNeedUpdate = true; 68 | 69 | // create a rotating jumping cube 70 | var cube = game.addItem({ 71 | mesh: obj, 72 | size: game.cubeSize, 73 | velocity: {x: 0, y: 0, z: 0} 74 | }); 75 | 76 | tic.interval(function() { 77 | cube.velocity.y += 0.03; 78 | }, (i * 200) + 2000); 79 | 80 | cubes.push(cube); 81 | return cube; 82 | } 83 | 84 | // load materials 85 | var materials = [ 86 | ['0'], 87 | ['0', '1'], 88 | ['0', '1', '2'], 89 | ['0', '1', '2', '3'], 90 | ['0', '1', '2', '3', '4', '5'], 91 | { 92 | top: 'grass', 93 | bottom: 'dirt', 94 | front: 'grass_dirt', 95 | back: 'grass_dirt', 96 | left: 'grass_dirt', 97 | right: 'grass_dirt' 98 | } 99 | ]; 100 | materialEngine.load(materials, function() { 101 | for (var i = 0; i < materials.length; i++) { 102 | createCube(i, materials[i]); 103 | } 104 | 105 | // load a sprite map 106 | materialEngine.sprite('terrain', 32, function(textures) { 107 | // create cubes randomly textured from the sprite map 108 | for (var x = 0; x < 6; x++) { 109 | var cube = createCube(x, textures[Math.floor(Math.random() * textures.length)]); 110 | cube.mesh.translateX(3); 111 | } 112 | 113 | // create animated materials 114 | var all = [].concat.apply([], materialEngine.materials); 115 | var discoCube = createCube(3, 1).mesh; 116 | discoCube.translateX(6); 117 | discoCube.children[0].mat = materialEngine.animate(discoCube.children[0], all, 100); 118 | 119 | var breaking = createCube(4, 1).mesh; 120 | breaking.translateX(6); 121 | breaking.children[0].mat = materialEngine.animate(breaking.children[0], [ 122 | 'terrain_0_480', 'terrain_32_480', 'terrain_64_480', 123 | 'terrain_96_480', 'terrain_128_480', 'terrain_160_480', 124 | 'terrain_192_480', 'terrain_224_480', 'terrain_256_480', 125 | ], 1000); 126 | 127 | var torch = createCube(5, 1).mesh; 128 | torch.translateX(6); 129 | var blank = new game.THREE.MeshLambertMaterial({transparent:true,opacity:0}); 130 | var torchMat = materialEngine.animate(torch.children[0], [ 131 | 'terrain_96_192', 'terrain_96_224' 132 | ], 500); 133 | torch.children[0].mat = new game.THREE.MeshFaceMaterial([ 134 | torchMat, torchMat, blank, blank, torchMat, torchMat 135 | ]); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voxel-texture 2 | 3 | > Add textures to an atlas and set UV mapping on geometries. Used for texturing 4 | > in [voxel.js](http://voxeljs.com). 5 | 6 | View [the demo](https://shama.github.io/voxel-texture). 7 | 8 | **ATTENTION! v0.5.0 has changed dramatically. This library is no longer is 9 | materials API but just loads textures onto an atlas and sets UV mappings.** 10 | 11 | ## example 12 | 13 | ```js 14 | // create a material engine 15 | var textureEngine = require('voxel-texture')({ 16 | // a copy of your voxel.js game 17 | game: game, 18 | 19 | // path to your textures 20 | texturePath: 'textures/' 21 | }); 22 | 23 | // load textures and it returns textures just loaded 24 | textureEngine.load(['grass', 'dirt', 'grass_dirt'], function(textures) { 25 | // create a new mesh 26 | var cube = new game.THREE.Mesh( 27 | new game.THREE.CubeGeometry(game.cubeSize, game.cubeSize, game.cubeSize), 28 | // use the texture engine atlas material 29 | textureEngine.material 30 | ); 31 | // paint the cube with grass on top, dirt on bottom and grass_dirt on sides 32 | textureEngine.paint(cube, ['grass', 'dirt', 'grass_dirt']); 33 | }); 34 | ``` 35 | 36 | ## api 37 | 38 | ### `require('voxel-texture')(options)` 39 | Returns a new texture engine instance. Must pass a copy of your voxel.js 40 | `game`. `options` defaults to: 41 | 42 | ```js 43 | { 44 | texturePath: '/textures/', 45 | materialParams: { ambient: 0xbbbbbb }, 46 | materialType: THREE.MeshLambertMaterial, 47 | applyTextureParams: function(map) { 48 | map.magFilter = this.THREE.NearestFilter; 49 | map.minFilter = this.THREE.LinearMipMapLinearFilter; 50 | } 51 | } 52 | ``` 53 | 54 | ### `textureEngine.load(textures, callback)` 55 | Loads textures onto the atlas by expanding the texture names: 56 | 57 | ```js 58 | textureEngine.load('grass', function(textures) { 59 | // textures = [grass, grass, grass, grass, grass, grass] 60 | }); 61 | ``` 62 | 63 | ```js 64 | textureEngine.load(['grass', 'dirt', 'grass_dirt'], function(textures) { 65 | // textures = [grass_dirt, grass_dirt, grass, dirt, grass_dirt, grass_dirt] 66 | }); 67 | ``` 68 | 69 | ```js 70 | textureEngine.load([ 71 | 'obsidian', 72 | ['back', 'front', 'top', 'bottom', 'left', 'right'], 73 | 'brick' 74 | ], function(textures) { 75 | /* 76 | textures = [ 77 | obsidian, obsidian, obsidian, obsidian, obsidian, obsidian, 78 | back, front, top, bottom, left, right, 79 | brick, brick, brick, brick, brick, brick 80 | ] 81 | */ 82 | }); 83 | ``` 84 | 85 | ### `textureEngine.find(name)` 86 | Finds the type of block by texture name: 87 | 88 | ```js 89 | // Find and change the center block to grass 90 | game.setBlock([0, 0, 0], textureEngine.find('grass')); 91 | ``` 92 | 93 | Although this is built into the voxel engine so you could just do: 94 | 95 | ```js 96 | game.setBlock([0, 0, 0], 'grass'); 97 | ``` 98 | 99 | ### `textureEngine.paint(mesh, textures)` 100 | Modifies the UV mapping of given `mesh` to the `textures` names supplied: 101 | 102 | ```js 103 | // create a custom mesh and load all materials 104 | var mesh = new game.THREE.Mesh( 105 | new game.THREE.Geometry(), 106 | textureEngine.material 107 | ); 108 | 109 | // paint the geometry 110 | textureEngine.paint(mesh, ['grass', 'dirt', 'grass_dirt']); 111 | ``` 112 | 113 | Or if you have the `face.color` set on the faces of your geometry (such as how 114 | voxel-mesh does it) then omit the `textures` argument. It will select the 115 | texture based on color from all the previously loaded textures: 116 | 117 | ```js 118 | textureEngine.paint(voxelMesh); 119 | ``` 120 | 121 | ### `textureEngine.sprite(name, w, h, callback)` 122 | Create textures from a sprite map. If you have a single image with a bunch of 123 | textures do: 124 | 125 | ```js 126 | // load terrain.png, it is 512x512 127 | // each texture is 32x32 128 | textureEngine.sprite('terrain', 32, function(textures) { 129 | // each texture will be named: terrain_x_y 130 | }); 131 | ``` 132 | 133 | The width and height default to `16x16`. 134 | 135 | ### `textureEngine.animate(mesh, textures, delay)` 136 | Create an animated material. A material that after each delay will paint the 137 | mesh by iterating through `textures`. Must run `textureEngine.tick()` to 138 | actually animate. 139 | 140 | ```js 141 | var mesh = new game.THREE.Mesh( 142 | new game.THREE.Geometry(), 143 | new game.THREE.MeshFaceMaterial() 144 | ); 145 | mesh.material = textureEngine.animate(mesh, ['one', 'two', 'three'], 1000); 146 | ``` 147 | 148 | ### `textureEngine.tick(delta)` 149 | Run the animations for any animated materials. 150 | 151 | ```js 152 | game.on('tick', function(dt) { 153 | textureEngine.tick(dt); 154 | }); 155 | ``` 156 | 157 | ## install 158 | With [npm](http://npmjs.org) do: 159 | 160 | ``` 161 | npm install voxel-texture 162 | ``` 163 | 164 | ## release history 165 | * 0.5.9 - Don't call `delete` on an identifier. [#24](https://github.com/shama/voxel-texture/issues/24) 166 | * 0.5.8 - Add missing opaque module dependency. Thanks @deathcap! 167 | * 0.5.7 - Separately load transparent textures. Fill atlas bg in black. Replace AO with voxel-fakeao. Dynamically set amount of sides with texturing, thanks @morganrallen! 168 | * 0.5.6 - Add materialFlatColor option for using simple flat colors instead of textures. 169 | * 0.5.5 - Only call document.createElement if available. 170 | * 0.5.4 - Allow null placeholder materials. 171 | * 0.5.3 - Force texture to dimensions that are power of 2 for mipmaps. 172 | * 0.5.2 - Use atlaspack tilepad to avoid mipmap texture bleed. 173 | * 0.5.1 - Fix CORS support. 174 | * 0.5.0 - No longer a materials API. Loads textures onto an atlas and sets UV mappings. 175 | * 0.4.0 - Add findIndex for finding block type index. 176 | * 0.3.3 - Move three to peerDependencies. thanks @niftylettuce! 177 | * 0.3.2 - Use face.color instead of face.vertexColors[0] 178 | * 0.3.1 - Support for animated materials. 179 | * 0.3.0 - refactored entire module. removed rotate. added load, get, paint, sprite methods. auto detect transparent. 180 | * 0.2.2 - ability to set material type and params. thanks @hughsk! 181 | * 0.2.1 - fix rotation of front and left textures when loading mesh 182 | * 0.2.0 - ability to set multiple textures on voxel meshes 183 | * 0.1.1 - fix texture sharpness 184 | * 0.1.0 - initial release 185 | 186 | ## license 187 | Copyright (c) 2025 Kyle Robinson Young 188 | Licensed under the MIT license. 189 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var tic = require('tic')(); 2 | var createAtlas = require('atlaspack'); 3 | var isTransparent = require('opaque').transparent; 4 | 5 | function Texture(opts) { 6 | if (!(this instanceof Texture)) return new Texture(opts || {}); 7 | var self = this; 8 | this.game = opts.game; delete opts.game; 9 | this.THREE = this.game.THREE; 10 | this.materials = []; 11 | this.transparents = []; 12 | this.texturePath = opts.texturePath || '/textures/'; 13 | this.loading = 0; 14 | this.ao = require('voxel-fakeao')(this.game); 15 | 16 | var useFlatColors = opts.materialFlatColor === true; 17 | delete opts.materialFlatColor; 18 | 19 | this.options = defaults(opts || {}, { 20 | crossOrigin: 'Anonymous', 21 | materialParams: defaults(opts.materialParams || {}, { 22 | ambient: 0xbbbbbb, 23 | transparent: false, 24 | side: this.THREE.DoubleSide, 25 | }), 26 | materialTransparentParams: defaults(opts.materialTransparentParams || {}, { 27 | ambient: 0xbbbbbb, 28 | transparent: true, 29 | side: this.THREE.DoubleSide, 30 | //depthWrite: false, 31 | //depthTest: false 32 | }), 33 | materialType: this.THREE.MeshLambertMaterial, 34 | applyTextureParams: function(map) { 35 | map.magFilter = self.THREE.NearestFilter; 36 | map.minFilter = self.THREE.LinearMipMapLinearFilter; 37 | } 38 | }); 39 | 40 | // create a canvas for the texture atlas 41 | this.canvas = (typeof document !== 'undefined') ? document.createElement('canvas') : {}; 42 | this.canvas.width = opts.atlasWidth || 512; 43 | this.canvas.height = opts.atlasHeight || 512; 44 | var ctx = this.canvas.getContext('2d'); 45 | ctx.fillStyle = 'black'; 46 | ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); 47 | 48 | // create core atlas and texture 49 | this.atlas = createAtlas(this.canvas); 50 | this.atlas.tilepad = true; 51 | this._atlasuv = false; 52 | this._atlaskey = false; 53 | this.texture = new this.THREE.Texture(this.canvas); 54 | this.options.applyTextureParams(this.texture); 55 | 56 | if (useFlatColors) { 57 | // If were using simple colors 58 | this.material = new this.THREE.MeshBasicMaterial({ 59 | vertexColors: this.THREE.VertexColors 60 | }); 61 | } else { 62 | var opaque = new this.options.materialType(this.options.materialParams); 63 | opaque.map = this.texture; 64 | var transparent = new this.options.materialType(this.options.materialTransparentParams); 65 | transparent.map = this.texture; 66 | this.material = new this.THREE.MeshFaceMaterial([ 67 | opaque, 68 | transparent 69 | ]); 70 | } 71 | 72 | // a place for meshes to wait while textures are loading 73 | this._meshQueue = []; 74 | } 75 | module.exports = Texture; 76 | 77 | Texture.prototype.load = function(names, done) { 78 | var self = this; 79 | if (!Array.isArray(names)) names = [names]; 80 | done = done || function() {}; 81 | this.loading++; 82 | 83 | var materialSlice = names.map(self._expandName); 84 | self.materials = self.materials.concat(materialSlice); 85 | 86 | // load onto the texture atlas 87 | var load = Object.create(null); 88 | materialSlice.forEach(function(mats) { 89 | mats.forEach(function(mat) { 90 | if (mat.slice(0, 1) === '#') return; 91 | // todo: check if texture already exists 92 | load[mat] = true; 93 | }); 94 | }); 95 | if (Object.keys(load).length > 0) { 96 | each(Object.keys(load), self.pack.bind(self), function() { 97 | self._afterLoading(); 98 | done(materialSlice); 99 | }); 100 | } else { 101 | self._afterLoading(); 102 | } 103 | }; 104 | 105 | Texture.prototype.pack = function(name, done) { 106 | var self = this; 107 | function pack(img) { 108 | var node = self.atlas.pack(img); 109 | if (node === false) { 110 | self.atlas = self.atlas.expand(img); 111 | self.atlas.tilepad = true; 112 | } 113 | done(); 114 | } 115 | if (typeof name === 'string') { 116 | var img = new Image(); 117 | img.id = name; 118 | img.crossOrigin = self.options.crossOrigin; 119 | img.src = self.texturePath + ext(name); 120 | img.onload = function() { 121 | if (isTransparent(img)) { 122 | self.transparents.push(name); 123 | } 124 | pack(img); 125 | }; 126 | img.onerror = function() { 127 | console.error('Couldn\'t load URL [' + img.src + ']'); 128 | done(); 129 | }; 130 | } else { 131 | pack(name); 132 | } 133 | return self; 134 | }; 135 | 136 | Texture.prototype.find = function(name) { 137 | var self = this; 138 | var type = 0; 139 | self.materials.forEach(function(mats, i) { 140 | mats.forEach(function(mat) { 141 | if (mat === name) { 142 | type = i + 1; 143 | return false; 144 | } 145 | }); 146 | if (type !== 0) return false; 147 | }); 148 | return type; 149 | }; 150 | 151 | Texture.prototype._expandName = function(name) { 152 | if (name === null) return Array(6); 153 | if (name.top) return [name.back, name.front, name.top, name.bottom, name.left, name.right]; 154 | if (!Array.isArray(name)) name = [name]; 155 | // load the 0 texture to all 156 | if (name.length === 1) name = [name[0],name[0],name[0],name[0],name[0],name[0]]; 157 | // 0 is top/bottom, 1 is sides 158 | if (name.length === 2) name = [name[1],name[1],name[0],name[0],name[1],name[1]]; 159 | // 0 is top, 1 is bottom, 2 is sides 160 | if (name.length === 3) name = [name[2],name[2],name[0],name[1],name[2],name[2]]; 161 | // 0 is top, 1 is bottom, 2 is front/back, 3 is left/right 162 | if (name.length === 4) name = [name[2],name[2],name[0],name[1],name[3],name[3]]; 163 | return name; 164 | }; 165 | 166 | Texture.prototype._afterLoading = function() { 167 | var self = this; 168 | function alldone() { 169 | self.loading--; 170 | self._atlasuv = self.atlas.uv(self.canvas.width, self.canvas.height); 171 | self._atlaskey = Object.create(null); 172 | self.atlas.index().forEach(function(key) { 173 | self._atlaskey[key.name] = key; 174 | }); 175 | self.texture.needsUpdate = true; 176 | self.material.needsUpdate = true; 177 | //window.open(self.canvas.toDataURL()); 178 | if (self._meshQueue.length > 0) { 179 | self._meshQueue.forEach(function(queue, i) { 180 | self.paint.apply(queue.self, queue.args); 181 | delete self._meshQueue[i]; 182 | }); 183 | } 184 | } 185 | self._powerof2(function() { 186 | setTimeout(alldone, 100); 187 | }); 188 | }; 189 | 190 | // Ensure the texture stays at a power of 2 for mipmaps 191 | // this is cheating :D 192 | Texture.prototype._powerof2 = function(done) { 193 | var w = this.canvas.width; 194 | var h = this.canvas.height; 195 | function pow2(x) { 196 | x--; 197 | x |= x >> 1; 198 | x |= x >> 2; 199 | x |= x >> 4; 200 | x |= x >> 8; 201 | x |= x >> 16; 202 | x++; 203 | return x; 204 | } 205 | if (h > w) w = h; 206 | var old = this.canvas.getContext('2d').getImageData(0, 0, this.canvas.width, this.canvas.height); 207 | this.canvas.width = this.canvas.height = pow2(w); 208 | this.canvas.getContext('2d').putImageData(old, 0, 0); 209 | done(); 210 | }; 211 | 212 | Texture.prototype.paint = function(mesh, materials) { 213 | var self = this; 214 | 215 | // if were loading put into queue 216 | if (self.loading > 0) { 217 | self._meshQueue.push({self: self, args: arguments}); 218 | return false; 219 | } 220 | 221 | var isVoxelMesh = (materials) ? false : true; 222 | if (!isVoxelMesh) materials = self._expandName(materials); 223 | 224 | mesh.geometry.faces.forEach(function(face, i) { 225 | if (mesh.geometry.faceVertexUvs[0].length < 1) return; 226 | 227 | if (isVoxelMesh) { 228 | var index = Math.floor(face.color.b*255 + face.color.g*255*255 + face.color.r*255*255*255); 229 | materials = self.materials[index - 1]; 230 | if (!materials) materials = self.materials[0]; 231 | } 232 | 233 | // BACK, FRONT, TOP, BOTTOM, LEFT, RIGHT 234 | var name = materials[0] || ''; 235 | if (face.normal.z === 1) name = materials[1] || ''; 236 | else if (face.normal.y === 1) name = materials[2] || ''; 237 | else if (face.normal.y === -1) name = materials[3] || ''; 238 | else if (face.normal.x === -1) name = materials[4] || ''; 239 | else if (face.normal.x === 1) name = materials[5] || ''; 240 | 241 | // if just a simple color 242 | if (name.slice(0, 1) === '#') { 243 | self.ao(face, name); 244 | return; 245 | } 246 | 247 | var atlasuv = self._atlasuv[name]; 248 | if (!atlasuv) return; 249 | 250 | // If a transparent texture use transparent material 251 | face.materialIndex = (self.transparents.indexOf(name) !== -1) ? 1 : 0; 252 | 253 | // 0 -- 1 254 | // | | 255 | // 3 -- 2 256 | // faces on these meshes are flipped vertically, so we map in reverse 257 | // TODO: tops need rotate 258 | if (isVoxelMesh) { 259 | if (face.normal.z === -1 || face.normal.x === 1) { 260 | atlasuv = uvrot(atlasuv, 90); 261 | } 262 | atlasuv = uvinvert(atlasuv); 263 | } else { 264 | atlasuv = uvrot(atlasuv, -90); 265 | } 266 | for (var j = 0; j < mesh.geometry.faceVertexUvs[0][i].length; j++) { 267 | mesh.geometry.faceVertexUvs[0][i][j].set(atlasuv[j][0], 1 - atlasuv[j][1]); 268 | } 269 | }); 270 | 271 | mesh.geometry.uvsNeedUpdate = true; 272 | }; 273 | 274 | Texture.prototype.sprite = function(name, w, h, cb) { 275 | var self = this; 276 | if (typeof w === 'function') { cb = w; w = null; } 277 | if (typeof h === 'function') { cb = h; h = null; } 278 | w = w || 16; h = h || w; 279 | self.loading++; 280 | var img = new Image(); 281 | img.src = self.texturePath + ext(name); 282 | img.onerror = cb; 283 | img.onload = function() { 284 | var canvases = []; 285 | for (var x = 0; x < img.width; x += w) { 286 | for (var y = 0; y < img.height; y += h) { 287 | var canvas = document.createElement('canvas'); 288 | canvas.width = w; canvas.height = h; 289 | canvas.name = name + '_' + x + '_' + y; 290 | canvas.getContext('2d').drawImage(img, x, y, w, h, 0, 0, w, h); 291 | canvases.push(canvas); 292 | } 293 | } 294 | var textures = []; 295 | each(canvases, function(canvas, next) { 296 | var tex = new Image(); 297 | tex.name = canvas.name; 298 | tex.src = canvas.toDataURL(); 299 | tex.onload = function() { 300 | self.pack(tex, next); 301 | }; 302 | tex.onerror = next; 303 | textures.push([ 304 | tex.name, tex.name, tex.name, 305 | tex.name, tex.name, tex.name 306 | ]); 307 | }, function() { 308 | self._afterLoading(); 309 | canvases = null; 310 | self.materials = self.materials.concat(textures); 311 | cb(textures); 312 | }); 313 | }; 314 | return self; 315 | }; 316 | 317 | Texture.prototype.animate = function(mesh, names, delay) { 318 | var self = this; 319 | delay = delay || 1000; 320 | if (!Array.isArray(names) || names.length < 2) return false; 321 | var i = 0; 322 | var mat = new this.options.materialType(this.options.materialParams); 323 | mat.map = this.texture; 324 | mat.transparent = true; 325 | mat.needsUpdate = true; 326 | tic.interval(function() { 327 | self.paint(mesh, names[i % names.length]); 328 | i++; 329 | }, delay); 330 | return mat; 331 | }; 332 | 333 | Texture.prototype.tick = function(dt) { 334 | tic.tick(dt); 335 | }; 336 | 337 | function uvrot(coords, deg) { 338 | if (deg === 0) return coords; 339 | var c = []; 340 | var i = (4 - Math.ceil(deg / 90)) % 4; 341 | for (var j = 0; j < 4; j++) { 342 | c.push(coords[i]); 343 | if (i === 3) i = 0; else i++; 344 | } 345 | return c; 346 | } 347 | 348 | function uvinvert(coords) { 349 | var c = coords.slice(0); 350 | return [c[3], c[2], c[1], c[0]]; 351 | } 352 | 353 | function ext(name) { 354 | return (String(name).indexOf('.') !== -1) ? name : name + '.png'; 355 | } 356 | 357 | function defaults(obj) { 358 | [].slice.call(arguments, 1).forEach(function(from) { 359 | if (from) for (var k in from) if (obj[k] == null) obj[k] = from[k]; 360 | }); 361 | return obj; 362 | } 363 | 364 | function each(arr, it, done) { 365 | var count = 0; 366 | arr.forEach(function(a) { 367 | it(a, function() { 368 | count++; 369 | if (count >= arr.length) done(); 370 | }); 371 | }); 372 | } 373 | --------------------------------------------------------------------------------