209 lines
6.4 KiB
Java
209 lines
6.4 KiB
Java
package speiger.src.coreengine.rendering.texturesOld.custom;
|
|
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.function.BiConsumer;
|
|
|
|
import speiger.src.collections.objects.lists.ObjectArrayList;
|
|
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectLinkedOpenHashMap;
|
|
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
|
|
import speiger.src.collections.objects.misc.pairs.ObjectObjectPair;
|
|
import speiger.src.collections.objects.sets.ObjectOpenHashSet;
|
|
import speiger.src.collections.objects.utils.ObjectIterators;
|
|
import speiger.src.coreengine.assets.AssetLocation;
|
|
import speiger.src.coreengine.math.vector.ints.Vec2i;
|
|
|
|
/**
|
|
* Inspired by: <a href=https://github.com/lukaszdk/texture-atlas-generator/blob/master/AtlasGenerator.java>AtlasGenerator</a>
|
|
*/
|
|
public class TextureAtlas {
|
|
int width;
|
|
int height;
|
|
Object2ObjectMap<AssetLocation, AtlasEntry> textures;
|
|
|
|
protected TextureAtlas(Vec2i bounds, Object2ObjectMap<AssetLocation, AtlasEntry> textures) {
|
|
width = bounds.x();
|
|
height = bounds.y();
|
|
this.textures = textures;
|
|
}
|
|
|
|
public int getWidth() { return width; }
|
|
|
|
public int getHeight() { return height; }
|
|
|
|
public AtlasEntry getTexture(AssetLocation texture) {
|
|
return textures.getObject(texture);
|
|
}
|
|
|
|
public Iterator<AtlasEntry> getContents() { return ObjectIterators.unmodifiable(textures.values().iterator()); }
|
|
|
|
public static Builder create() {
|
|
return new Builder();
|
|
}
|
|
|
|
public static class Builder {
|
|
Set<AssetLocation> names = new ObjectOpenHashSet<>();
|
|
List<Record> records = new ObjectArrayList<>();
|
|
int pixelsUsed = 0;
|
|
|
|
public boolean add(AssetLocation location, int width, int height) {
|
|
return add(location, width, height, 0);
|
|
}
|
|
|
|
public boolean add(AssetLocation location, int width, int height, int padding) {
|
|
if(location == null || width <= 0 || height <= 0 || padding < 0 || !names.add(location)) return false;
|
|
records.add(new Record(location, width, height, padding));
|
|
pixelsUsed += (width + padding) * (height + padding);
|
|
return true;
|
|
}
|
|
|
|
private ObjectObjectPair<Vec2i, Object2ObjectMap<AssetLocation, AtlasEntry>> stitch() {
|
|
int textureWidth = 2;
|
|
int textureHeight = 2;
|
|
boolean height = false;
|
|
for(;textureHeight * textureWidth <= pixelsUsed;height = !height) {
|
|
if(height) textureHeight *= 2;
|
|
else textureWidth *= 2;
|
|
}
|
|
records.sort(null);
|
|
int attempts = 0;
|
|
while(attempts < 50) {
|
|
Slot slot = new Slot(0, 0, textureWidth, textureHeight);
|
|
boolean failed = false;
|
|
for(int i = 0,m = records.size();i < m;i++) {
|
|
if(slot.addRecord(records.get(i))) continue;
|
|
failed = true;
|
|
break;
|
|
}
|
|
if(failed) {
|
|
if(height) textureHeight *= 2;
|
|
else textureWidth *= 2;
|
|
attempts++;
|
|
height = !height;
|
|
continue;
|
|
}
|
|
Object2ObjectMap<AssetLocation, AtlasEntry> entries = new Object2ObjectLinkedOpenHashMap<>();
|
|
slot.build(entries::put);
|
|
return ObjectObjectPair.of(Vec2i.of(textureWidth, textureHeight), entries);
|
|
}
|
|
throw new IllegalStateException("Couldn't fit Texture Atlas after growing it 50 Times");
|
|
}
|
|
|
|
public void buildHollow(BiConsumer<Vec2i, Iterable<AtlasEntry>> builder) {
|
|
ObjectObjectPair<Vec2i, Object2ObjectMap<AssetLocation, AtlasEntry>> pairs = stitch();
|
|
builder.accept(pairs.getKey(), pairs.getValue().values());
|
|
}
|
|
|
|
public TextureAtlas build(BiConsumer<Vec2i, Iterable<AtlasEntry>> builder) {
|
|
ObjectObjectPair<Vec2i, Object2ObjectMap<AssetLocation, AtlasEntry>> pairs = stitch();
|
|
builder.accept(pairs.getKey(), pairs.getValue().values());
|
|
return new TextureAtlas(pairs.getKey(), pairs.getValue());
|
|
}
|
|
|
|
public TextureAtlas build() {
|
|
ObjectObjectPair<Vec2i, Object2ObjectMap<AssetLocation, AtlasEntry>> pairs = stitch();
|
|
return new TextureAtlas(pairs.getKey(), pairs.getValue());
|
|
}
|
|
}
|
|
|
|
public static class AtlasEntry {
|
|
AssetLocation location;
|
|
int x;
|
|
int y;
|
|
int width;
|
|
int height;
|
|
|
|
private AtlasEntry(Slot slot) {
|
|
Record record = slot.record;
|
|
location = record == null ? null : record.location();
|
|
x = slot.x;
|
|
y = slot.y;
|
|
width = record == null ? slot.width : record.width;
|
|
height = record == null ? slot.height : record.height;
|
|
}
|
|
|
|
public AssetLocation getLocation() { return location; }
|
|
|
|
public int getX() { return x; }
|
|
|
|
public int getY() { return y; }
|
|
|
|
public int getWidth() { return width; }
|
|
|
|
public int getHeight() { return height; }
|
|
}
|
|
|
|
private static class Slot {
|
|
int x;
|
|
int y;
|
|
int width;
|
|
int height;
|
|
Record record;
|
|
Slot[] children = null;
|
|
|
|
public Slot(int x, int y, int width, int height) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
public boolean isLeaf() { return children == null; }
|
|
|
|
public void build(BiConsumer<AssetLocation, AtlasEntry> builder) {
|
|
if(!isLeaf()) {
|
|
for(int i = 0,m = children.length;i < m;i++) {
|
|
children[i].build(builder);
|
|
}
|
|
return;
|
|
}
|
|
if(record == null) return;
|
|
builder.accept(record.location(), new AtlasEntry(this));
|
|
}
|
|
|
|
public boolean addRecord(Record record) {
|
|
if(isLeaf()) {
|
|
int rw = record.width();
|
|
int rh = record.height();
|
|
if(this.record != null || rw > width || rh > height) return false;
|
|
if(rw == width && rh == height) {
|
|
this.record = record;
|
|
return true;
|
|
}
|
|
int p = record.padding();
|
|
int dw = width - rw;
|
|
int dh = height - rh;
|
|
children = new Slot[dw > 0 && dh > 0 ? 3 : 2];
|
|
children[0] = new Slot(x, y, rw, rh);
|
|
if(dw > 0 && dh > 0) {
|
|
if(dw > dh) {
|
|
children[1] = new Slot(x + rw + p, y, dw - p, rh);
|
|
children[2] = new Slot(x, y + rh + p, width, dh - p);
|
|
}
|
|
else {
|
|
children[1] = new Slot(x, y + rh + p, rw, dh - p);
|
|
children[2] = new Slot(x + rw + p, y, dw - p, height);
|
|
}
|
|
}
|
|
else if(dw == 0) children[1] = new Slot(x, y + rh + p, rw, dh - p);
|
|
else if(dh == 0) children[1] = new Slot(x + rw + p, y, dw - p, rh);
|
|
return children[0].addRecord(record);
|
|
}
|
|
for(int i = 0,m = children.length;i < m;i++) {
|
|
if(children[i].addRecord(record)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static record Record(AssetLocation location, int width, int height, int padding) implements Comparable<Record> {
|
|
private int getPixelCount() { return (width + padding) * (height + padding); }
|
|
|
|
@Override
|
|
public int compareTo(Record o) {
|
|
int result = Integer.compare(o.getPixelCount(), getPixelCount());
|
|
return result == 0 ? location().compareTo(o.location()) : result;
|
|
}
|
|
}
|
|
} |