207 lines
5.4 KiB
Java
207 lines
5.4 KiB
Java
package speiger.src.coreengine.rendering.texturesOld.atlas;
|
|
|
|
import java.util.List;
|
|
|
|
import speiger.src.collections.objects.lists.ObjectArrayList;
|
|
import speiger.src.collections.objects.utils.ObjectIterables;
|
|
import speiger.src.collections.utils.HashUtil;
|
|
import speiger.src.coreengine.assets.AssetLocation;
|
|
import speiger.src.coreengine.rendering.texturesOld.atlas.AtlasStitcher.Entry;
|
|
|
|
|
|
public class AtlasStitcher<T extends Entry> {
|
|
|
|
final int maxWidth;
|
|
final int maxHeight;
|
|
int width;
|
|
int height;
|
|
int pixelsUsed;
|
|
boolean valid = true;
|
|
List<Record<T>> toStitch = new ObjectArrayList<>();
|
|
Slot<T> slot = null;
|
|
|
|
public AtlasStitcher(int bounds) {
|
|
this(bounds, bounds);
|
|
}
|
|
|
|
public AtlasStitcher(int maxWidth, int maxHeight) {
|
|
this.maxWidth = maxWidth;
|
|
this.maxHeight = maxHeight;
|
|
if(maxWidth < 16 || maxHeight < 16) throw new IndexOutOfBoundsException("Minimum Size has to be 16x16 pixels");
|
|
}
|
|
|
|
public int width() { return width; }
|
|
public int height() { return height; }
|
|
public boolean isValid() { return valid; }
|
|
|
|
public void add(T entry) { add(new Record<T>(entry)); }
|
|
|
|
public void addAll(Iterable<? extends T> iterables) {
|
|
ObjectIterables.map(iterables, Record<T>::new).forEach(this::add);
|
|
}
|
|
|
|
private void add(Record<T> entry) {
|
|
toStitch.add(entry);
|
|
pixelsUsed += entry.pixels();
|
|
}
|
|
|
|
public void stitch() {
|
|
toStitch.sort(null);
|
|
int expected = HashUtil.nextPowerOfTwo((int)Math.sqrt(pixelsUsed)) / 2;
|
|
if(expected > maxWidth && expected > maxHeight) {
|
|
valid = false;
|
|
return;
|
|
}
|
|
for(Record<T> entry : toStitch) {
|
|
if(!addToSlot(entry)) {
|
|
valid = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void process(IAtlasScanner<T> scanner) {
|
|
if(slot == null) return;
|
|
slot.scan(scanner);
|
|
}
|
|
|
|
private boolean addToSlot(Record<T> entry) {
|
|
if(slot != null && slot.insert(entry)) return true;
|
|
if(expand(entry)) {
|
|
width = slot.width;
|
|
height = slot.height;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean expand(Record<T> entry) {
|
|
int width = HashUtil.nextPowerOfTwo(entry.width());
|
|
int height = HashUtil.nextPowerOfTwo(entry.height());
|
|
if(width > maxWidth || height > maxHeight) return false;
|
|
if(slot == null) {
|
|
int min = HashUtil.nextPowerOfTwo((int)Math.sqrt(pixelsUsed)) / 2;
|
|
slot = new Slot<>(0, 0, Math.max(width, min), Math.max(height, min));
|
|
return slot.insert(entry);
|
|
}
|
|
if(width > slot.width) {
|
|
if(slot.height * 2 > maxHeight) return false;
|
|
slot = new Slot<>(slot, width - slot.width, slot.height);
|
|
return slot.insert(entry);
|
|
}
|
|
slot = slot.height >= slot.width ? new Slot<>(slot, slot.width, 0) : new Slot<>(slot, 0, slot.height);
|
|
return slot.insert(entry);
|
|
}
|
|
|
|
private static record Record<T extends Entry>(T entry, int width, int height) implements Comparable<Record<T>> {
|
|
public Record(T entry) {
|
|
this(entry, entry.width(), entry.height());
|
|
}
|
|
|
|
public int pixels() { return width() * height(); }
|
|
|
|
@Override
|
|
public int compareTo(Record<T> o) {
|
|
int comp = Integer.compare(o.height, height);
|
|
if(comp != 0) return comp;
|
|
comp = Integer.compare(o.width, width);
|
|
if(comp != 0) return comp;
|
|
return entry.id().compareTo(o.entry.id());
|
|
}
|
|
}
|
|
|
|
private static class Slot<T extends Entry> {
|
|
int x;
|
|
int y;
|
|
int width;
|
|
int height;
|
|
Record<T> record;
|
|
Slot<T>[] children = null;
|
|
|
|
public Slot(int x, int y, int width, int height) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public Slot(Slot<T> slot, int eWidth, int eHeight) {
|
|
x = 0;
|
|
y = 0;
|
|
width = slot.width + eWidth;
|
|
height = slot.height + eHeight;
|
|
children = new Slot[eWidth > 0 && eHeight > 0 ? 3 : 2];
|
|
children[0] = slot;
|
|
expandSlot(slot.width, slot.height, eWidth, eHeight);
|
|
}
|
|
|
|
public boolean isLeaf() { return children == null; }
|
|
|
|
public void scan(IAtlasScanner<T> scanner) {
|
|
if(record != null) {
|
|
scanner.accept(record.entry(), x, y);
|
|
return;
|
|
}
|
|
if(children != null) {
|
|
for(int i = 0,m = children.length;i < m;i++) {
|
|
children[i].scan(scanner);
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public boolean insert(Record<T> 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 dw = width - rw;
|
|
int dh = height - rh;
|
|
children = new Slot[dw > 0 && dh > 0 ? 3 : 2];
|
|
children[0] = new Slot<>(x, y, rw, rh);
|
|
expandSlot(rw, rh, dw, dh);
|
|
return children[0].insert(record);
|
|
}
|
|
for(int i = 0,m = children.length;i < m;i++) {
|
|
if(children[i].insert(record)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void expandSlot(int rw, int rh, int dw, int dh) {
|
|
if(dw > 0 && dh > 0) {
|
|
if(dw >= dh) {
|
|
children[1] = new Slot<>(x + rw, y, dw, rh);
|
|
children[2] = new Slot<>(x, y + rh, width, dh);
|
|
}
|
|
else {
|
|
children[1] = new Slot<>(x, y + rh, rw, dh);
|
|
children[2] = new Slot<>(x + rw, y, dw, height);
|
|
}
|
|
}
|
|
else if(dw == 0) children[1] = new Slot<>(x, y + rh, rw, dh);
|
|
else if(dh == 0) children[1] = new Slot<>(x + rw, y, dw, rh);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Slot[x="+x+", y="+y+", w="+width+", h="+height+"]";
|
|
}
|
|
}
|
|
|
|
public static interface IAtlasScanner<T> {
|
|
public void accept(T entry, int x, int y);
|
|
}
|
|
|
|
public static interface Entry {
|
|
public AssetLocation id();
|
|
public int width();
|
|
public int height();
|
|
}
|
|
}
|