mirror of
https://github.com/GTNewHorizons/Chisel.git
synced 2026-11-12 00:07:37 +00:00
new approach: make each ctm variation sprite a real resource
This commit is contained in:
1 parent
68d65d23ed
commit
36f517cb46
4 files changed
+259
-212
No files matched your search
+3
-3
@@ -107,7 +107,7 @@ apiPackage = api
|
||||
accessTransformersFile = chisel_compile_at.cfg
|
||||
|
||||
# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled!
|
||||
usesMixins = false
|
||||
usesMixins = true
|
||||
|
||||
# Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main.
|
||||
# This can speed up compile times thanks to not running the mixin annotation processor on all input sources.
|
||||
@@ -115,7 +115,7 @@ usesMixins = false
|
||||
separateMixinSourceSet =
|
||||
|
||||
# Adds some debug arguments like verbose output and class export.
|
||||
usesMixinDebug = false
|
||||
usesMixinDebug = true
|
||||
|
||||
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
|
||||
mixinPlugin =
|
||||
@@ -123,7 +123,7 @@ mixinPlugin =
|
||||
# Specify the package that contains all of your Mixins. The package must exist or
|
||||
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
|
||||
# it must match with this or the build will fail.
|
||||
mixinsPackage =
|
||||
mixinsPackage = mixins
|
||||
|
||||
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
|
||||
# This parameter is for legacy compatibility only
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.cricketcraft.chisel.mixins;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.resources.FallbackResourceManager;
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import team.chisel.ctmlib.TextureSubmapResource;
|
||||
|
||||
/**
|
||||
* Provides generated resources for independently stitched CTM submap sprites.
|
||||
* <p>
|
||||
* For example, a request for chisel:textures/blocks/__chisel_submap/4x4/2_1/foo.png is resolved from
|
||||
* chisel:textures/blocks/foo.png and cropped to the requested cell. This lets Minecraft and other mods treat each
|
||||
* submap sprite like an ordinary texture resource.
|
||||
*/
|
||||
@Mixin(FallbackResourceManager.class)
|
||||
public abstract class MixinFallbackResourceManager {
|
||||
|
||||
@Shadow
|
||||
public abstract IResource getResource(ResourceLocation location) throws IOException;
|
||||
|
||||
@Shadow
|
||||
public abstract List<IResource> getAllResources(ResourceLocation location) throws IOException;
|
||||
|
||||
@Inject(method = "getResource", at = @At("HEAD"), cancellable = true)
|
||||
private void chisel$getSubmapResource(ResourceLocation location, CallbackInfoReturnable<IResource> cir)
|
||||
throws IOException {
|
||||
|
||||
TextureSubmapResource.SubmapLocation submap = TextureSubmapResource.parse(location);
|
||||
if (submap == null) return;
|
||||
|
||||
IResource source = getResource(submap.source());
|
||||
cir.setReturnValue(TextureSubmapResource.create(location, source, submap));
|
||||
}
|
||||
|
||||
@Inject(method = "getAllResources", at = @At("HEAD"), cancellable = true)
|
||||
private void chisel$getAllSubmapResources(ResourceLocation location, CallbackInfoReturnable<List<IResource>> cir)
|
||||
throws IOException {
|
||||
|
||||
TextureSubmapResource.SubmapLocation submap = TextureSubmapResource.parse(location);
|
||||
if (submap == null) return;
|
||||
|
||||
List<IResource> sources = getAllResources(submap.source());
|
||||
List<IResource> resources = new ArrayList<>(sources.size());
|
||||
|
||||
for (IResource source : sources) {
|
||||
resources.add(TextureSubmapResource.create(location, source, submap));
|
||||
}
|
||||
|
||||
cir.setReturnValue(resources);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,18 @@
|
||||
package team.chisel.ctmlib;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.data.AnimationMetadataSection;
|
||||
import net.minecraft.client.resources.data.TextureMetadataSection;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import lombok.experimental.Delegate;
|
||||
import team.chisel.Chisel;
|
||||
|
||||
/**
|
||||
* Splits a texture sheet into independently stitched sub-icons used for CTM and other texture manipulation.
|
||||
@@ -113,22 +94,16 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
MinecraftForge.EVENT_BUS.register(new StitchHandler());
|
||||
}
|
||||
|
||||
private static final class StitchHandler {
|
||||
public static final class StitchHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTextureStitchPre(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() != 0) return;
|
||||
TextureSubmapSprite.clearSourceCache();
|
||||
for (TextureSubmap submap : submaps) {
|
||||
submap.registerSubIcons(event.map);
|
||||
}
|
||||
submaps.clear();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTextureStitchPost(TextureStitchEvent.Post event) {
|
||||
TextureSubmapSprite.clearSourceCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,192 +114,12 @@ public class TextureSubmap implements IIcon, ISubmap {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceLocation sourceIcon = new ResourceLocation(baseIcon.getIconName());
|
||||
ResourceLocation sourceLocation = new ResourceLocation(baseIcon.getIconName());
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
String name = getSubIconName(sourceIcon, width, height, x, y);
|
||||
TextureAtlasSprite registered = textureMap.getTextureExtry(name);
|
||||
if (registered == null) {
|
||||
registered = new TextureSubmapSprite(name, sourceIcon, width, height, x, y);
|
||||
textureMap.setTextureEntry(name, registered);
|
||||
}
|
||||
icons[x][y] = registered;
|
||||
String name = TextureSubmapResource.getSubIconName(sourceLocation, width, height, x, y);
|
||||
icons[x][y] = textureMap.registerIcon(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSubIconName(ResourceLocation source, int width, int height, int x, int y) {
|
||||
return source.getResourceDomain() + ":__chisel_submap/"
|
||||
+ width
|
||||
+ "x"
|
||||
+ height
|
||||
+ "/"
|
||||
+ x
|
||||
+ "_"
|
||||
+ y
|
||||
+ "/"
|
||||
+ source.getResourcePath();
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
private static final class TextureSubmapSprite extends TextureAtlasSprite {
|
||||
|
||||
@Desugar
|
||||
private record SourceTexture(BufferedImage[] images, AnimationMetadataSection animation) {}
|
||||
|
||||
private static final Map<ResourceLocation, SourceTexture> sourceCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final ResourceLocation sourceLocation;
|
||||
private final int columns;
|
||||
private final int rows;
|
||||
private final int cellX;
|
||||
private final int cellY;
|
||||
|
||||
private TextureSubmapSprite(String name, ResourceLocation sourceLocation, int columns, int rows, int cellX,
|
||||
int cellY) {
|
||||
|
||||
super(name);
|
||||
this.sourceLocation = sourceLocation;
|
||||
this.columns = columns;
|
||||
this.rows = rows;
|
||||
this.cellX = cellX;
|
||||
this.cellY = cellY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean load(IResourceManager manager, ResourceLocation location) {
|
||||
try {
|
||||
int mipmapLevels = Minecraft.getMinecraft().gameSettings.mipmapLevels;
|
||||
boolean useAnisotropicFiltering = Minecraft.getMinecraft().gameSettings.anisotropicFiltering > 1;
|
||||
|
||||
SourceTexture sourceTexture = getSourceTexture(manager, sourceLocation, mipmapLevels);
|
||||
BufferedImage[] images = new BufferedImage[sourceTexture.images().length];
|
||||
boolean animated = sourceTexture.animation() != null;
|
||||
|
||||
for (int level = 0; level < images.length; level++) {
|
||||
BufferedImage sourceImage = sourceTexture.images()[level];
|
||||
if (sourceImage == null) continue;
|
||||
// A mip level can be too small to split into the submap grid
|
||||
if (sourceImage.getWidth() < columns) continue;
|
||||
images[level] = extractSubImage(sourceImage, animated);
|
||||
}
|
||||
|
||||
loadSprite(images, sourceTexture.animation(), useAnisotropicFiltering);
|
||||
return false;
|
||||
} catch (IOException | RuntimeException e) {
|
||||
Chisel.logger.warn("Unable to create submap sprite {} from {}", getIconName(), sourceLocation, e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static SourceTexture getSourceTexture(IResourceManager manager, ResourceLocation sourceLocation,
|
||||
int mipmapLevels) throws IOException {
|
||||
|
||||
SourceTexture cached = sourceCache.get(sourceLocation);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
ResourceLocation location = getSourceResource(sourceLocation, 0);
|
||||
IResource resource = manager.getResource(location);
|
||||
|
||||
BufferedImage[] images = new BufferedImage[mipmapLevels + 1];
|
||||
images[0] = readImage(resource, location);
|
||||
|
||||
TextureMetadataSection textureMetadata = (TextureMetadataSection) resource.getMetadata("texture");
|
||||
if (textureMetadata != null && !textureMetadata.getListMipmaps()
|
||||
.isEmpty()) {
|
||||
|
||||
int width = images[0].getWidth();
|
||||
int height = images[0].getHeight();
|
||||
|
||||
if (MathHelper.roundUpToPowerOfTwo(width) != width
|
||||
|| MathHelper.roundUpToPowerOfTwo(height) != height) {
|
||||
throw new RuntimeException("Unable to load extra miplevels, source texture is not power of two");
|
||||
}
|
||||
|
||||
for (int mipLevel : textureMetadata.getListMipmaps()) {
|
||||
if (mipLevel <= 0 || mipLevel >= images.length || images[mipLevel] != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceLocation mipLocation = getSourceResource(sourceLocation, mipLevel);
|
||||
try {
|
||||
images[mipLevel] = readImage(manager.getResource(mipLocation), mipLocation);
|
||||
} catch (IOException e) {
|
||||
Chisel.logger.warn("Unable to load mip level {} from {}", mipLevel, mipLocation, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimationMetadataSection animation = (AnimationMetadataSection) resource.getMetadata("animation");
|
||||
SourceTexture sourceTexture = new SourceTexture(images, animation);
|
||||
|
||||
SourceTexture existing = sourceCache.putIfAbsent(sourceLocation, sourceTexture);
|
||||
return existing != null ? existing : sourceTexture;
|
||||
}
|
||||
|
||||
private static BufferedImage readImage(IResource resource, ResourceLocation location) throws IOException {
|
||||
BufferedImage image;
|
||||
try (InputStream stream = resource.getInputStream()) {
|
||||
image = ImageIO.read(stream);
|
||||
}
|
||||
if (image == null) {
|
||||
throw new IOException("ImageIO could not decode " + location);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private static void clearSourceCache() {
|
||||
sourceCache.clear();
|
||||
}
|
||||
|
||||
private static ResourceLocation getSourceResource(ResourceLocation sourceLocation, int mipLevel) {
|
||||
String domain = sourceLocation.getResourceDomain();
|
||||
String path = sourceLocation.getResourcePath();
|
||||
if (mipLevel == 0) return new ResourceLocation(domain, "textures/blocks/" + path + ".png");
|
||||
return new ResourceLocation(domain, "textures/blocks/mipmaps/" + path + "." + mipLevel + ".png");
|
||||
}
|
||||
|
||||
private BufferedImage extractSubImage(BufferedImage source, boolean animated) {
|
||||
int frameSize = source.getWidth();
|
||||
int sourceHeight = source.getHeight();
|
||||
|
||||
if (frameSize % columns != 0 || frameSize % rows != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Texture size " + frameSize + " is not divisible by submap " + columns + "x" + rows);
|
||||
}
|
||||
|
||||
if (animated) {
|
||||
if (sourceHeight % frameSize != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Animated texture height " + sourceHeight + " is not divisible by frame size " + frameSize);
|
||||
}
|
||||
} else if (sourceHeight != frameSize) {
|
||||
throw new IllegalArgumentException(
|
||||
"Non-animated texture is not square: " + frameSize + "x" + sourceHeight);
|
||||
}
|
||||
|
||||
int cellWidth = frameSize / columns;
|
||||
int cellHeight = frameSize / rows;
|
||||
int frameCount = animated ? sourceHeight / frameSize : 1;
|
||||
|
||||
BufferedImage result = new BufferedImage(cellWidth, cellHeight * frameCount, BufferedImage.TYPE_INT_ARGB);
|
||||
int[] pixels = new int[cellWidth * cellHeight];
|
||||
|
||||
for (int frame = 0; frame < frameCount; frame++) {
|
||||
int sourceX = cellX * cellWidth;
|
||||
int sourceY = frame * frameSize + cellY * cellHeight;
|
||||
source.getRGB(sourceX, sourceY, cellWidth, cellHeight, pixels, 0, cellWidth);
|
||||
result.setRGB(0, frame * cellHeight, cellWidth, cellHeight, pixels, 0, cellWidth);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package team.chisel.ctmlib;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.client.resources.data.AnimationFrame;
|
||||
import net.minecraft.client.resources.data.AnimationMetadataSection;
|
||||
import net.minecraft.client.resources.data.IMetadataSection;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
|
||||
public final class TextureSubmapResource implements IResource {
|
||||
|
||||
@Desugar
|
||||
public record SubmapLocation(ResourceLocation source, int columns, int rows, int cellX, int cellY) {}
|
||||
|
||||
private static final String PREFIX = "__chisel_submap/";
|
||||
|
||||
private final IResource source;
|
||||
private final byte[] imageData;
|
||||
private final int columns;
|
||||
private final int rows;
|
||||
|
||||
private TextureSubmapResource(IResource source, byte[] imageData, int columns, int rows) {
|
||||
this.source = source;
|
||||
this.imageData = imageData;
|
||||
this.columns = columns;
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public static String getSubIconName(ResourceLocation source, int columns, int rows, int cellX, int cellY) {
|
||||
return source.getResourceDomain() + ":"
|
||||
+ PREFIX
|
||||
+ columns
|
||||
+ "x"
|
||||
+ rows
|
||||
+ "/"
|
||||
+ cellX
|
||||
+ "_"
|
||||
+ cellY
|
||||
+ "/"
|
||||
+ source.getResourcePath();
|
||||
}
|
||||
|
||||
public static IResource create(ResourceLocation location, IResource source, SubmapLocation submap)
|
||||
throws IOException {
|
||||
BufferedImage sourceImage;
|
||||
try (InputStream stream = source.getInputStream()) {
|
||||
sourceImage = ImageIO.read(stream);
|
||||
}
|
||||
if (sourceImage == null) {
|
||||
throw new IOException("Unable to decode " + submap.source);
|
||||
}
|
||||
|
||||
BufferedImage cropped = crop(sourceImage, submap);
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
if (!ImageIO.write(cropped, "png", output)) {
|
||||
throw new IOException("Unable to encode submap resource " + location);
|
||||
}
|
||||
|
||||
return new TextureSubmapResource(source, output.toByteArray(), submap.columns, submap.rows);
|
||||
}
|
||||
|
||||
private static BufferedImage crop(BufferedImage source, SubmapLocation submap) throws IOException {
|
||||
int frameSize = source.getWidth();
|
||||
int sourceHeight = source.getHeight();
|
||||
|
||||
if (frameSize < submap.columns || frameSize < submap.rows
|
||||
|| frameSize % submap.columns != 0
|
||||
|| frameSize % submap.rows != 0
|
||||
|| sourceHeight % frameSize != 0) {
|
||||
|
||||
throw new IOException(
|
||||
"Texture size " + frameSize
|
||||
+ "x"
|
||||
+ sourceHeight
|
||||
+ " cannot be split into submap "
|
||||
+ submap.columns
|
||||
+ "x"
|
||||
+ submap.rows);
|
||||
}
|
||||
|
||||
int cellWidth = frameSize / submap.columns;
|
||||
int cellHeight = frameSize / submap.rows;
|
||||
int frameCount = sourceHeight / frameSize;
|
||||
|
||||
BufferedImage result = new BufferedImage(cellWidth, cellHeight * frameCount, BufferedImage.TYPE_INT_ARGB);
|
||||
int[] pixels = new int[cellWidth * cellHeight];
|
||||
|
||||
for (int frame = 0; frame < frameCount; frame++) {
|
||||
int sourceX = submap.cellX * cellWidth;
|
||||
int sourceY = frame * frameSize + submap.cellY * cellHeight;
|
||||
|
||||
source.getRGB(sourceX, sourceY, cellWidth, cellHeight, pixels, 0, cellWidth);
|
||||
result.setRGB(0, frame * cellHeight, cellWidth, cellHeight, pixels, 0, cellWidth);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static SubmapLocation parse(ResourceLocation location) {
|
||||
String path = location.getResourcePath();
|
||||
|
||||
int prefix = path.indexOf(PREFIX);
|
||||
if (prefix < 0 || prefix > 0 && path.charAt(prefix - 1) != '/') return null;
|
||||
|
||||
int gridStart = prefix + PREFIX.length();
|
||||
int gridEnd = path.indexOf('/', gridStart);
|
||||
if (gridEnd < 0) return null;
|
||||
|
||||
int cellEnd = path.indexOf('/', gridEnd + 1);
|
||||
if (cellEnd < 0) return null;
|
||||
|
||||
String grid = path.substring(gridStart, gridEnd);
|
||||
String cell = path.substring(gridEnd + 1, cellEnd);
|
||||
|
||||
int gridSeparator = grid.indexOf('x');
|
||||
int cellSeparator = cell.indexOf('_');
|
||||
if (gridSeparator <= 0 || cellSeparator <= 0) return null;
|
||||
|
||||
try {
|
||||
int columns = Integer.parseInt(grid.substring(0, gridSeparator));
|
||||
int rows = Integer.parseInt(grid.substring(gridSeparator + 1));
|
||||
int cellX = Integer.parseInt(cell.substring(0, cellSeparator));
|
||||
int cellY = Integer.parseInt(cell.substring(cellSeparator + 1));
|
||||
|
||||
if (cellX < 0 || cellY < 0 || cellX >= columns || cellY >= rows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String sourcePath = path.substring(0, prefix) + path.substring(cellEnd + 1);
|
||||
ResourceLocation sourceLocation = new ResourceLocation(location.getResourceDomain(), sourcePath);
|
||||
return new SubmapLocation(sourceLocation, columns, rows, cellX, cellY);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(imageData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMetadata() {
|
||||
return source.hasMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMetadataSection getMetadata(String sectionName) {
|
||||
IMetadataSection metadata = source.getMetadata(sectionName);
|
||||
if (!(metadata instanceof AnimationMetadataSection animation)) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
List<AnimationFrame> frames = new ArrayList<>(animation.getFrameCount());
|
||||
for (int i = 0; i < animation.getFrameCount(); i++) {
|
||||
if (animation.frameHasTime(i)) {
|
||||
frames.add(new AnimationFrame(animation.getFrameIndex(i), animation.getFrameTimeSingle(i)));
|
||||
} else {
|
||||
frames.add(new AnimationFrame(animation.getFrameIndex(i)));
|
||||
}
|
||||
}
|
||||
|
||||
int frameWidth = scaleMetadataDimension(animation.getFrameWidth(), columns);
|
||||
int frameHeight = scaleMetadataDimension(animation.getFrameHeight(), rows);
|
||||
return new AnimationMetadataSection(frames, frameWidth, frameHeight, animation.getFrameTime());
|
||||
}
|
||||
|
||||
private static int scaleMetadataDimension(int dimension, int divisions) {
|
||||
if (dimension < 0) {
|
||||
return dimension;
|
||||
}
|
||||
if (dimension % divisions != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Animation frame size " + dimension + " is not divisible by submap size " + divisions);
|
||||
}
|
||||
return dimension / divisions;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user