packingTexture function
Reference:https://observablehq.com/@mourner/simple-rectangle-packing
Implementation
Future<Image?> packingTexture(List<Mesh> meshes) async {
// generate a key for a mesh.
String getMeshKey(Mesh mesh) {
if (mesh.texture != null) return mesh.texturePath ?? '${mesh.textureRect}';
return toColor(mesh.material.diffuse.bgr).toString();
}
// only pack the different textures.
final allMeshes = meshes;
final textures = <String, Mesh>{};
for (Mesh mesh in allMeshes) {
if (mesh.vertices.isEmpty) continue;
final String key = getMeshKey(mesh);
textures.putIfAbsent(key, () => mesh);
}
// if there is only one texture then return the texture directly.
meshes = textures.values.toList();
if (meshes.length == 1) return meshes[0].texture;
if (meshes.isEmpty) return null;
// packing
double area = 0;
double maxWidth = 0;
for (Mesh mesh in meshes) {
area += mesh.textureRect.width * mesh.textureRect.height;
maxWidth = math.max(maxWidth, mesh.textureRect.width);
}
meshes.sort((Mesh a, Mesh b) => b.textureRect.height.compareTo(a.textureRect.height));
final double startWidth = math.max(math.sqrt(area / 0.95), maxWidth);
final List<Rect> spaces = <Rect>[];
spaces.add(Rect.fromLTWH(0, 0, startWidth, double.infinity));
for (Mesh mesh in meshes) {
for (int i = spaces.length - 1; i >= 0; i--) {
final Rect block = mesh.textureRect;
final Rect space = spaces[i];
if (block.width > space.width || block.height > space.height) continue;
mesh.textureRect = Rect.fromLTWH(space.left, space.top, block.width, block.height);
if (block.width == space.width && block.height == space.height) {
final Rect last = spaces.removeLast();
if (i < spaces.length) spaces[i] = last;
} else if (block.height == space.height) {
spaces[i] = Rect.fromLTWH(space.left + block.width, space.top, space.width - block.width, space.height);
} else if (block.width == space.width) {
spaces[i] = Rect.fromLTWH(space.left, space.top + block.height, space.width, space.height - block.height);
} else {
spaces.add(Rect.fromLTWH(space.left + block.width, space.top, space.width - block.width, block.height));
spaces[i] = Rect.fromLTWH(space.left, space.top + block.height, space.width, space.height - block.height);
}
break;
}
}
// get the packed texture size
int textureWidth = 0;
int textureHeight = 0;
for (Mesh mesh in meshes) {
final Rect box = mesh.textureRect;
if (textureWidth < box.left + box.width) textureWidth = (box.left + box.width).ceil();
if (textureHeight < box.top + box.height) textureHeight = (box.top + box.height).ceil();
}
// get the pixels from mesh.texture
final texture = Uint32List(textureWidth * textureHeight);
for (Mesh mesh in meshes) {
final int imageWidth = mesh.textureRect.width.toInt();
final int imageHeight = mesh.textureRect.height.toInt();
Uint32List pixels;
if (mesh.texture != null) {
final Uint32List data = await getImagePixels(mesh.texture!);
pixels = data.buffer.asUint32List();
} else {
final int length = imageWidth * imageHeight;
pixels = Uint32List(length);
// color mode then set texture to transparent.
const int color = 0; //mesh.material == null ? 0 : toColor(mesh.material.kd.bgr, mesh.material.d).value;
for (int i = 0; i < length; i++) {
pixels[i] = color;
}
}
// break if the mesh.texture has changed
if (mesh.textureRect.right > textureWidth || mesh.textureRect.bottom > textureHeight) break;
// copy pixels from mesh.texture to texture
int fromIndex = 0;
int toIndex = mesh.textureRect.top.toInt() * textureWidth + mesh.textureRect.left.toInt();
for (int y = 0; y < imageHeight; y++) {
for (int x = 0; x < imageWidth; x++) {
texture[toIndex + x] = pixels[fromIndex + x];
}
fromIndex += imageWidth;
toIndex += textureWidth;
}
}
// apply the packed textureRect to all meshes.
for (Mesh mesh in allMeshes) {
final String? key = getMeshKey(mesh);
if (key != null) {
final Rect? rect = textures[key]?.textureRect;
if (rect != null) mesh.textureRect = rect;
}
}
final c = Completer<Image>();
decodeImageFromPixels(texture.buffer.asUint8List(), textureWidth, textureHeight, PixelFormat.rgba8888, (image) {
c.complete(image);
});
return c.future;
}