mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 17:55:08 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
267378a381 | ||
|
|
c22935660a | ||
|
|
79c488102b | ||
|
|
cc1353b73a |
No files matched your search
@@ -0,0 +1,82 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class HugeFarPlaneFrustumTest {
|
||||
|
||||
private static final float SECTION_HALF = 8.0f + 1.0f + 0.125f;
|
||||
private static final float DEGENERATE_FAR = 2.147483648e9f;
|
||||
|
||||
private static Matrix4f combined(float zFar) {
|
||||
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(70.0), 854.0f / 480.0f, 0.05f, zFar);
|
||||
final Matrix4f mv = new Matrix4f()
|
||||
.rotate((float) Math.toRadians(25.0), 1, 0, 0)
|
||||
.rotate((float) Math.toRadians(123.0), 0, 1, 0)
|
||||
.translate(0.0f, -1.62f, 0.0f);
|
||||
return new Matrix4f().set(proj).mul(mv);
|
||||
}
|
||||
|
||||
private static boolean sectionVisible(FrustumIntersection f, int sx, int sy, int sz) {
|
||||
final float cx = sx * 16.0f + 8.0f;
|
||||
final float cy = sy * 16.0f + 8.0f;
|
||||
final float cz = sz * 16.0f + 8.0f;
|
||||
return f.testAab(cx - SECTION_HALF, cy - SECTION_HALF, cz - SECTION_HALF, cx + SECTION_HALF, cy + SECTION_HALF, cz + SECTION_HALF);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unnormalizedFrustumMatchesNormalFarPlane() {
|
||||
final FrustumIntersection normal = new FrustumIntersection(combined(512.0f), false);
|
||||
final FrustumIntersection huge = new FrustumIntersection(combined(DEGENERATE_FAR), false);
|
||||
|
||||
int visible = 0;
|
||||
int mismatches = 0;
|
||||
for (int x = -12; x <= 12; x++) {
|
||||
for (int y = -4; y <= 4; y++) {
|
||||
for (int z = -12; z <= 12; z++) {
|
||||
final boolean vNormal = sectionVisible(normal, x, y, z);
|
||||
if (vNormal) visible++;
|
||||
if (vNormal != sectionVisible(huge, x, y, z)) mismatches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(visible > 100, "sanity check broken: normal frustum accepted only " + visible + " sections");
|
||||
assertEquals(0, mismatches, "sections culled differently under the degenerate far plane");
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalizedSetCullsEverythingOnDegenerateFarPlane() {
|
||||
final FrustumIntersection normalized = new FrustumIntersection();
|
||||
normalized.set(combined(DEGENERATE_FAR));
|
||||
|
||||
int visible = 0;
|
||||
for (int x = -12; x <= 12; x++) {
|
||||
for (int y = -4; y <= 4; y++) {
|
||||
for (int z = -12; z <= 12; z++) {
|
||||
if (sectionVisible(normalized, x, y, z)) visible++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(0, visible, "set(m) with a degenerate far plane no longer culls everything");
|
||||
}
|
||||
|
||||
@Test
|
||||
void identityFrustumOnlyPassesCameraSection() {
|
||||
final FrustumIntersection identity = new FrustumIntersection(new Matrix4f());
|
||||
assertTrue(sectionVisible(identity, 0, 0, 0), "identity frustum rejected the camera's own section");
|
||||
int visible = 0;
|
||||
for (int x = -12; x <= 12; x++) {
|
||||
for (int y = -4; y <= 4; y++) {
|
||||
for (int z = -12; z <= 12; z++) {
|
||||
if (Math.abs(x) <= 1 && Math.abs(y) <= 1 && Math.abs(z) <= 1) continue;
|
||||
if (sectionVisible(identity, x, y, z)) visible++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(0, visible, "non-adjacent sections accepted by the identity frustum");
|
||||
}
|
||||
}
|
||||
@@ -463,7 +463,9 @@ public enum Mixins implements IMixins {
|
||||
|
||||
OPTIMIZE_TEXTURE_LOADING(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.addClientMixins("angelica.textures.MixinTextureUtil_OptimizeMipmap")
|
||||
.addClientMixins(
|
||||
"angelica.textures.MixinTextureUtil_OptimizeMipmap",
|
||||
"angelica.textures.MixinStitcher")
|
||||
.setApplyIf(() -> AngelicaConfig.optimizeTextureLoading)),
|
||||
|
||||
REPLACE_FFP(new MixinBuilder()
|
||||
|
||||
@@ -82,6 +82,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
private static final float SCROLL_SPEED = 1.0f / 256.0f;
|
||||
private static final float EDGE_OVERLAP = 0.0001f;
|
||||
private static final float INTERIOR_INSET = EDGE_OVERLAP * 8.0f;
|
||||
private static final float MAX_FAR_PLANE_DISTANCE = 65536.0f;
|
||||
|
||||
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
|
||||
@@ -169,10 +170,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff) Settings.MODE_CLOUDS.option.getStore();
|
||||
final int cloudQualitySetting = cloudGraphicsQuality == GraphicsQualityOff.FANCY
|
||||
|| cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics ? 2 : 1;
|
||||
final float dimCloudHeight = mc.theWorld == null ? 128.0f : mc.theWorld.provider.getCloudHeight();
|
||||
final boolean finiteCloudHeight = Float.isFinite(dimCloudHeight);
|
||||
final boolean newEnabled = cloudGraphicsQuality != GraphicsQualityOff.OFF
|
||||
&& mc.gameSettings.shouldRenderClouds()
|
||||
&& mc.theWorld != null
|
||||
&& mc.theWorld.provider.isSurfaceWorld();
|
||||
&& mc.theWorld.provider.isSurfaceWorld()
|
||||
&& finiteCloudHeight;
|
||||
final int targetDistance = Math.max(mc.gameSettings.renderDistanceChunks, (int) Settings.RENDER_DISTANCE_CLOUDS.option.getStore());
|
||||
final int cloudScaleMult = (int) Settings.CLOUD_SCALE.option.getStore();
|
||||
|
||||
@@ -188,7 +192,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
renderDistance = targetDistance;
|
||||
scaleMult = cloudScaleMult;
|
||||
|
||||
cloudElevation = mc.theWorld == null ? 128 : (int) mc.theWorld.provider.getCloudHeight();
|
||||
cloudElevation = finiteCloudHeight ? (int) dimCloudHeight : 128;
|
||||
// Allows the setting to work with RFG and similar without hardcoding.
|
||||
// The minimum height check is so stuff like Aether cloud height doesn't get messed up.
|
||||
if (cloudElevation >= 96) {
|
||||
@@ -213,7 +217,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
if (view != null) {
|
||||
verticalSlack = Math.abs(cloudElevation + 0.33f - (float) view.posY) + 4.0f * scaleMult;
|
||||
}
|
||||
return radiusBlocks + verticalSlack;
|
||||
return Math.min(radiusBlocks + verticalSlack, MAX_FAR_PLANE_DISTANCE);
|
||||
}
|
||||
|
||||
private void invalidateGeometry() {
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
package com.gtnewhorizons.angelica.textures;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.renderer.StitcherException;
|
||||
import net.minecraft.client.renderer.texture.Stitcher;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.util.MathHelper;
|
||||
|
||||
public final class FastTextureStitcher {
|
||||
|
||||
private FastTextureStitcher() {}
|
||||
|
||||
public static Result stitch(Stitcher.Holder[] holders, int maxWidth, int maxHeight, boolean forcePowerOf2) {
|
||||
if (holders.length == 0) {
|
||||
return new Result(0, 0, Collections.<TextureAtlasSprite>emptyList());
|
||||
}
|
||||
|
||||
long area = 0L;
|
||||
int minWidth = 1;
|
||||
int minHeight = 1;
|
||||
boolean[] initialRotations = new boolean[holders.length];
|
||||
|
||||
for (int i = 0; i < holders.length; i++) {
|
||||
Stitcher.Holder holder = holders[i];
|
||||
initialRotations[i] = holder.isRotated();
|
||||
int width = holder.getWidth();
|
||||
int height = holder.getHeight();
|
||||
|
||||
if (width > maxWidth || height > maxHeight) {
|
||||
throw unableToFit(holder, maxWidth, maxHeight);
|
||||
}
|
||||
|
||||
area += (long) width * height;
|
||||
minWidth = Math.max(minWidth, width);
|
||||
minHeight = Math.max(minHeight, height);
|
||||
}
|
||||
|
||||
int width = roundDimension(Math.max(minWidth, (int) Math.ceil(Math.sqrt(area))), forcePowerOf2);
|
||||
width = Math.min(width, maxWidth);
|
||||
int height = roundDimension(Math.max(minHeight, (int) ((area + width - 1L) / width)), forcePowerOf2);
|
||||
height = Math.min(height, maxHeight);
|
||||
|
||||
while (width <= maxWidth && height <= maxHeight) {
|
||||
restoreRotations(holders, initialRotations);
|
||||
Result result = tryStitch(holders, width, height, forcePowerOf2);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if ((width <= height && width < maxWidth) || height >= maxHeight) {
|
||||
width = grow(width, maxWidth, forcePowerOf2);
|
||||
} else {
|
||||
height = grow(height, maxHeight, forcePowerOf2);
|
||||
}
|
||||
}
|
||||
|
||||
throw unableToFit(holders[0], maxWidth, maxHeight);
|
||||
}
|
||||
|
||||
private static Result tryStitch(Stitcher.Holder[] holders, int atlasWidth, int atlasHeight, boolean forcePowerOf2) {
|
||||
List<Node> skyline = new ArrayList<>();
|
||||
List<TextureAtlasSprite> sprites = new ArrayList<>(holders.length);
|
||||
int[] xs = new int[holders.length];
|
||||
int[] ys = new int[holders.length];
|
||||
skyline.add(new Node(0, 0, atlasWidth));
|
||||
|
||||
int usedWidth = 0;
|
||||
int usedHeight = 0;
|
||||
|
||||
for (int i = 0; i < holders.length; i++) {
|
||||
Stitcher.Holder holder = holders[i];
|
||||
Placement placement = findPlacement(skyline, atlasWidth, atlasHeight, holder);
|
||||
if (placement == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (holder.isRotated() != placement.rotated) {
|
||||
holder.rotate();
|
||||
}
|
||||
|
||||
place(skyline, placement);
|
||||
int width = holder.getWidth();
|
||||
int height = holder.getHeight();
|
||||
usedWidth = Math.max(usedWidth, placement.x + width);
|
||||
usedHeight = Math.max(usedHeight, placement.y + height);
|
||||
sprites.add(holder.getAtlasSprite());
|
||||
xs[i] = placement.x;
|
||||
ys[i] = placement.y;
|
||||
}
|
||||
|
||||
int resultWidth = forcePowerOf2 ? atlasWidth : usedWidth;
|
||||
int resultHeight = forcePowerOf2 ? atlasHeight : usedHeight;
|
||||
|
||||
for (int i = 0; i < holders.length; i++) {
|
||||
Stitcher.Holder holder = holders[i];
|
||||
TextureAtlasSprite sprite = holder.getAtlasSprite();
|
||||
sprite.initSprite(resultWidth, resultHeight, xs[i], ys[i], holder.isRotated());
|
||||
}
|
||||
|
||||
return new Result(resultWidth, resultHeight, sprites);
|
||||
}
|
||||
|
||||
private static Placement findPlacement(List<Node> skyline, int atlasWidth, int atlasHeight, Stitcher.Holder holder) {
|
||||
Placement best = findPlacement(skyline, atlasWidth, atlasHeight, holder.getWidth(), holder.getHeight(), holder.isRotated());
|
||||
|
||||
holder.rotate();
|
||||
Placement rotated = findPlacement(skyline, atlasWidth, atlasHeight, holder.getWidth(), holder.getHeight(), holder.isRotated());
|
||||
holder.rotate();
|
||||
|
||||
if (best == null) {
|
||||
return rotated;
|
||||
}
|
||||
|
||||
if (rotated == null) {
|
||||
return best;
|
||||
}
|
||||
|
||||
return rotated.bottom < best.bottom || rotated.bottom == best.bottom && rotated.x < best.x ? rotated : best;
|
||||
}
|
||||
|
||||
private static Placement findPlacement(List<Node> skyline, int atlasWidth, int atlasHeight, int width, int height, boolean rotated) {
|
||||
Placement best = null;
|
||||
|
||||
for (int i = 0; i < skyline.size(); i++) {
|
||||
Node node = skyline.get(i);
|
||||
int y = fit(skyline, i, width, height, atlasWidth, atlasHeight);
|
||||
|
||||
if (y >= 0) {
|
||||
int bottom = y + height;
|
||||
if (best == null || bottom < best.bottom || bottom == best.bottom && node.x < best.x) {
|
||||
best = new Placement(i, node.x, y, width, bottom, rotated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
private static int fit(List<Node> skyline, int index, int width, int height, int atlasWidth, int atlasHeight) {
|
||||
Node node = skyline.get(index);
|
||||
if (node.x + width > atlasWidth) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int y = node.y;
|
||||
int widthLeft = width;
|
||||
|
||||
for (int i = index; widthLeft > 0; i++) {
|
||||
if (i >= skyline.size()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Node current = skyline.get(i);
|
||||
y = Math.max(y, current.y);
|
||||
if (y + height > atlasHeight) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
widthLeft -= current.width;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
private static void place(List<Node> skyline, Placement placement) {
|
||||
skyline.add(placement.index, new Node(placement.x, placement.bottom, placement.width));
|
||||
|
||||
for (int i = placement.index + 1; i < skyline.size(); i++) {
|
||||
Node previous = skyline.get(i - 1);
|
||||
Node current = skyline.get(i);
|
||||
int shrink = previous.x + previous.width - current.x;
|
||||
|
||||
if (shrink <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
current.x += shrink;
|
||||
current.width -= shrink;
|
||||
|
||||
if (current.width <= 0) {
|
||||
skyline.remove(i--);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < skyline.size() - 1; i++) {
|
||||
Node current = skyline.get(i);
|
||||
Node next = skyline.get(i + 1);
|
||||
|
||||
if (current.y == next.y) {
|
||||
current.width += next.width;
|
||||
skyline.remove(i-- + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void restoreRotations(Stitcher.Holder[] holders, boolean[] rotations) {
|
||||
for (int i = 0; i < holders.length; i++) {
|
||||
if (holders[i].isRotated() != rotations[i]) {
|
||||
holders[i].rotate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int roundDimension(int value, boolean forcePowerOf2) {
|
||||
return forcePowerOf2 ? MathHelper.roundUpToPowerOfTwo(value) : value;
|
||||
}
|
||||
|
||||
private static int grow(int value, int max, boolean forcePowerOf2) {
|
||||
if (value >= max) {
|
||||
return max + 1;
|
||||
}
|
||||
|
||||
int next = forcePowerOf2 ? MathHelper.roundUpToPowerOfTwo(value + 1) : value * 2;
|
||||
return Math.min(Math.max(next, value + 1), max);
|
||||
}
|
||||
|
||||
private static StitcherException unableToFit(Stitcher.Holder holder, int maxWidth, int maxHeight) {
|
||||
return new StitcherException(holder, "Unable to fit: " + holder.getAtlasSprite().getIconName()
|
||||
+ " - size: " + holder.getAtlasSprite().getIconWidth() + "x" + holder.getAtlasSprite().getIconHeight()
|
||||
+ " - max atlas: " + maxWidth + "x" + maxHeight);
|
||||
}
|
||||
|
||||
public static final class Result {
|
||||
public final int width;
|
||||
public final int height;
|
||||
public final List<TextureAtlasSprite> sprites;
|
||||
|
||||
private Result(int width, int height, List<TextureAtlasSprite> sprites) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.sprites = sprites;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Node {
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
|
||||
private Node(int x, int y, int width) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Placement {
|
||||
final int index;
|
||||
final int x;
|
||||
final int y;
|
||||
final int width;
|
||||
final int bottom;
|
||||
final boolean rotated;
|
||||
|
||||
private Placement(int index, int x, int y, int width, int bottom, boolean rotated) {
|
||||
this.index = index;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.bottom = bottom;
|
||||
this.rotated = rotated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ final class BlockOrientation extends RenderBlockState {
|
||||
rotateUV = 0;
|
||||
textureFace = blockFaceToTextureFace(blockFace);
|
||||
metadataBits = (1 << metadata) | (1 << altMetadata);
|
||||
if(!fixedBottomFaceUV && blockFace == ForgeDirection.DOWN.ordinal()) flipped = true;
|
||||
flipped = !fixedBottomFaceUV && blockFace == ForgeDirection.DOWN.ordinal();
|
||||
}
|
||||
|
||||
void setBlockMetadata(Block block, int metadata, int face) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.function.BooleanSupplier;
|
||||
public enum FeatureFlags {
|
||||
SEPARATE_HARDWARE_SAMPLERS(() -> true, () -> true),
|
||||
PER_BUFFER_BLENDING(() -> true, RenderSystem::supportsBufferBlending),
|
||||
COMPUTE_SHADERS(() -> false, RenderSystem::supportsCompute),
|
||||
COMPUTE_SHADERS(() -> true, RenderSystem::supportsCompute),
|
||||
ENTITY_TRANSLUCENT(() -> true, () -> true),
|
||||
BLOCK_EMISSION_ATTRIBUTE(() -> true, () -> true),
|
||||
CUSTOM_IMAGES(() -> true, RenderSystem::supportsImageLoadStore),
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.textures;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.gtnewhorizons.angelica.textures.FastTextureStitcher;
|
||||
import net.minecraft.client.renderer.texture.Stitcher;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(Stitcher.class)
|
||||
public abstract class MixinStitcher {
|
||||
|
||||
@Shadow @Final private Set setStitchHolders;
|
||||
@Shadow @Final private int maxWidth;
|
||||
@Shadow @Final private int maxHeight;
|
||||
@Shadow @Final private boolean forcePowerOf2;
|
||||
@Shadow private int currentWidth;
|
||||
@Shadow private int currentHeight;
|
||||
|
||||
@Unique private List<TextureAtlasSprite> angelica$stitchedSprites = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* @author Angelica
|
||||
* @reason Use a skyline packer instead of vanilla recursive slots.
|
||||
*/
|
||||
@Overwrite
|
||||
public void doStitch() {
|
||||
Stitcher.Holder[] holders = (Stitcher.Holder[]) this.setStitchHolders.toArray(new Stitcher.Holder[this.setStitchHolders.size()]);
|
||||
Arrays.sort(holders);
|
||||
|
||||
FastTextureStitcher.Result result = FastTextureStitcher.stitch(holders, this.maxWidth, this.maxHeight, this.forcePowerOf2);
|
||||
this.currentWidth = result.width;
|
||||
this.currentHeight = result.height;
|
||||
this.angelica$stitchedSprites = result.sprites;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Angelica
|
||||
* @reason FastTextureStitcher initializes sprites directly.
|
||||
*/
|
||||
@Overwrite
|
||||
public List getStichSlots() {
|
||||
return new ArrayList(this.angelica$stitchedSprites);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ public class MixinClippingHelper implements ClippingHelperExt {
|
||||
this.celeritas$projMatrix.set(this.projectionMatrix);
|
||||
this.celeritas$mvMatrix.set(this.modelviewMatrix);
|
||||
this.celeritas$combined.set(this.celeritas$projMatrix).mul(this.celeritas$mvMatrix);
|
||||
this.celeritas$frustumIntersection.set(this.celeritas$combined);
|
||||
this.celeritas$frustumIntersection.set(this.celeritas$combined, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ public abstract class MixinFrustrum implements ViewportProvider {
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void celeritas$snapshotFrustum(CallbackInfo ci) {
|
||||
this.celeritas$snapshot.set(((ClippingHelperExt) this.clippingHelper).celeritas$getCombinedMatrix());
|
||||
this.celeritas$snapshot.set(((ClippingHelperExt) this.clippingHelper).celeritas$getCombinedMatrix(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.textures;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.renderer.texture.Stitcher;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FastTextureStitcherTest {
|
||||
|
||||
@Test
|
||||
void packsSpritesWithoutOverlap() {
|
||||
Stitcher.Holder[] holders = {
|
||||
holder("a", 32, 16),
|
||||
holder("b", 16, 32),
|
||||
holder("c", 16, 16),
|
||||
holder("d", 8, 40),
|
||||
holder("e", 24, 8)
|
||||
};
|
||||
Arrays.sort(holders);
|
||||
|
||||
FastTextureStitcher.Result result = FastTextureStitcher.stitch(holders, 128, 128, true);
|
||||
|
||||
assertTrue(result.width <= 128);
|
||||
assertTrue(result.height <= 128);
|
||||
assertEquals(holders.length, result.sprites.size());
|
||||
|
||||
List<Rectangle> rectangles = new ArrayList<>();
|
||||
for (Stitcher.Holder holder : holders) {
|
||||
TextureAtlasSprite sprite = holder.getAtlasSprite();
|
||||
Rectangle rectangle = new Rectangle(sprite.getOriginX(), sprite.getOriginY(), holder.getWidth(), holder.getHeight());
|
||||
assertTrue(rectangle.x >= 0);
|
||||
assertTrue(rectangle.y >= 0);
|
||||
assertTrue(rectangle.x + rectangle.width <= result.width);
|
||||
assertTrue(rectangle.y + rectangle.height <= result.height);
|
||||
|
||||
for (Rectangle other : rectangles) {
|
||||
assertFalse(rectangle.intersects(other), rectangle + " overlaps " + other);
|
||||
}
|
||||
|
||||
rectangles.add(rectangle);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stitcher.Holder holder(String name, int width, int height) {
|
||||
TextureAtlasSprite sprite = new TextureAtlasSprite(name);
|
||||
sprite.setIconWidth(width);
|
||||
sprite.setIconHeight(height);
|
||||
return new Stitcher.Holder(sprite, 0);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user