SimpleJavaEngine/src/main/java/speiger/src/coreengine/rendering/texturesOld/atlas/AtlasStitcher.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();
}
}