@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
182 lines (148 loc) • 5.03 kB
JavaScript
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);
}
}
}