├── .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 |
--------------------------------------------------------------------------------