UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

182 lines (148 loc) 5.03 kB
import { ClampToEdgeWrapping, DataTexture, LinearEncoding, LinearFilter, LinearMipMapLinearFilter, RGBAFormat, UnsignedByteType, UVMapping } from "three"; import { MaxRectanglesPacker } from "../../../../../core/geom/packing/max-rect/MaxRectanglesPacker.js"; import IdPool from "../../../../../core/IdPool.js"; import { formatToChannelCount } from "../../formatToChannelCount.js"; import { AtlasPatch } from "../AtlasPatch.js"; export class WebGLTextureAtlas { constructor({ format = RGBAFormat, minFilter = LinearMipMapLinearFilter, magFilter = LinearFilter, type = UnsignedByteType, mapping = UVMapping, anisotropy = 4, encoding = LinearEncoding }) { /** * * @type {Texture[]} * @private */ this.__sources = []; /** * * @type {AtlasPatch[]} * @private */ this.__patches = []; /** * * @type {IdPool} * @private */ this.__ids = new IdPool(); /** * * @type {number} * @private */ this.__width = 1; /** * * @type {number} * @private */ this.__height = 1; /** * * @type {number} * @private */ this.__total_area = 0; /** * * @type {number} * @private */ this.__max_size = 16000; /** * * @type {DataTexture} * @private */ this.__texture = new DataTexture(new Uint8Array(0), 0, 0, format, type, mapping, ClampToEdgeWrapping, ClampToEdgeWrapping, magFilter, minFilter, anisotropy, encoding); } /** * * @param {Texture} source * @param {number} source_x * @param {number} source_y * @param {number} source_width * @param {number} source_height * @param {number} target_width * @param {number} target_height */ add(source, source_x, source_y, source_width, source_height, target_width, target_height) { const id = this.__ids.get(); const patch = new AtlasPatch(); patch.id = id; this.__patches.push(patch); this.__sources[id] = source; // compute area of the target patch const area = target_width * target_height; this.__total_area += area; return patch; } layout() { // estimate initial size based on the total area let power = Math.ceil(Math.log2(this.__total_area)); let size = 0; const aabbs = this.__patches.map(p => p.packing.clone()); // attempt packing for (; ;) { size = Math.pow(2, power); if (size > this.__max_size) { throw new Error(`Failed to pack patches, maximum atlas size exceeded`); } const packer = new MaxRectanglesPacker(size, size); const success = packer.addMany(aabbs); if (success) { break; } else { // failed to pack, increase texture size to next power of 2 power++; } } this.__width = size; this.__height = size; // copy packed positions back to patches aabbs.forEach((aabb, i) => { const patch = this.__patches[i]; patch.packing.copy(aabb); }); } /** * Paint patches onto the atlas texture * NOTE: all patches are assumed to be packed properly at this point * @param {WebGLRenderer} renderer */ build(renderer) { // check if texture needs to be rebuilt const destination_texture = this.__texture; if (destination_texture.image.width !== this.__width || destination_texture.image.height !== this.__height) { destination_texture.dispose(); const channel_count = formatToChannelCount(destination_texture.format); destination_texture.image.data = new Uint8Array(this.__height * this.__width * channel_count); destination_texture.image.height = this.__height; destination_texture.image.width = this.__width; } // mark for update destination_texture.needsUpdate = true; // paint individual patches const patch_count = this.__patches.length; for (let i = 0; i < patch_count; i++) { const atlas_patch = this.__patches[i]; const source_texture = this.__sources[i]; renderer.copyTextureToTexture(atlas_patch.position, source_texture, destination_texture); } } }