From 93d6975cd34ee9ca10165530b1de5283f68221f1 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Sat, 16 Mar 2024 22:43:38 +0800 Subject: [PATCH] Add New Feature --- core/src/cn/harryh/arkpets/ArkChar.java | 173 ++++++------ core/src/cn/harryh/arkpets/ArkPets.java | 12 +- core/src/cn/harryh/arkpets/Const.java | 2 +- .../cn/harryh/arkpets/utils/CroppingCtrl.java | 211 --------------- .../utils/DynamicOrthographicCamara.java | 255 ++++++++++++++++++ 5 files changed, 343 insertions(+), 310 deletions(-) delete mode 100644 core/src/cn/harryh/arkpets/utils/CroppingCtrl.java create mode 100644 core/src/cn/harryh/arkpets/utils/DynamicOrthographicCamara.java diff --git a/core/src/cn/harryh/arkpets/ArkChar.java b/core/src/cn/harryh/arkpets/ArkChar.java index 11ab9e8c..88bbdcfd 100644 --- a/core/src/cn/harryh/arkpets/ArkChar.java +++ b/core/src/cn/harryh/arkpets/ArkChar.java @@ -4,45 +4,48 @@ package cn.harryh.arkpets; import cn.harryh.arkpets.animations.AnimClip; +import cn.harryh.arkpets.animations.AnimClip.AnimStage; import cn.harryh.arkpets.animations.AnimClipGroup; import cn.harryh.arkpets.animations.AnimData; import cn.harryh.arkpets.assets.AssetItem; -import cn.harryh.arkpets.utils.*; -import cn.harryh.arkpets.transitions.*; +import cn.harryh.arkpets.transitions.TernaryFunction; +import cn.harryh.arkpets.transitions.TransitionFloat; +import cn.harryh.arkpets.transitions.TransitionVector3; +import cn.harryh.arkpets.utils.DynamicOrthographicCamara; +import cn.harryh.arkpets.utils.DynamicOrthographicCamara.Insert; +import cn.harryh.arkpets.utils.Logger; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.*; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.glutils.FrameBuffer; -import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.SerializationException; import com.esotericsoftware.spine.*; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; +import java.util.HashMap; + import static cn.harryh.arkpets.Const.*; import static java.io.File.separator; public class ArkChar { - protected final OrthographicCamera camera; - protected final TwoColorPolygonBatch batch; - protected final FrameBuffer fbo; - protected Texture bgTexture; - - protected final AnimComposer composer; + protected final DynamicOrthographicCamara camera; protected final TransitionVector3 position; - protected final TransitionFloat offsetY; - protected final Skeleton skeleton; - protected final SkeletonRenderer renderer; - protected final SkeletonData skeletonData; - protected final AnimationState animationState; + private final TwoColorPolygonBatch batch; + private Texture bgTexture; + private final AnimComposer composer; + private final TransitionFloat offsetY; + + private final Skeleton skeleton; + private final SkeletonRenderer renderer; + private final AnimationState animationState; - public final CroppingCtrl flexibleLayout; - public final AnimClipGroup animList; + protected final AnimClipGroup animList; + protected final HashMap stageInsertMap; /** Initializes an ArkPets character. * @param assetLocation The path string to the model's directory. @@ -51,9 +54,10 @@ public class ArkChar { */ public ArkChar(String assetLocation, AssetItem.AssetAccessor assetAccessor, float scale) { // 1.Graphics setup - camera = new OrthographicCamera(); + camera = new DynamicOrthographicCamara(canvasMaxSize, canvasMaxSize, Math.round(canvasReserveLength * scale)); + camera.setMaxInsert(0); + camera.setMinInsert(canvasReserveLength - canvasMaxSize); batch = new TwoColorPolygonBatch(); - fbo = new FrameBuffer(Format.RGBA8888, canvasMaxSize, canvasMaxSize, false); renderer = new SkeletonRenderer(); /* Pre-multiplied alpha shouldn't be applied to models released in Arknights 2.1.41 or later, otherwise you may get a corrupted rendering result. */ @@ -61,8 +65,8 @@ public ArkChar(String assetLocation, AssetItem.AssetAccessor assetAccessor, floa // 2.Geometry setup position = new TransitionVector3(TernaryFunction.EASE_OUT_CUBIC, easingDuration); offsetY = new TransitionFloat(TernaryFunction.EASE_OUT_CUBIC, easingDuration); - flexibleLayout = new CroppingCtrl(new Vector2(canvasMaxSize, canvasMaxSize), 0); // 3.Skeleton setup + SkeletonData skeletonData; try { String path2atlas = assetLocation + separator + assetAccessor.getFirstFileOf(".atlas"); String path2skel = assetLocation + separator + assetAccessor.getFirstFileOf(".skel"); @@ -98,24 +102,35 @@ protected void onApply(AnimData playing) { } }; // 6.Canvas setup - for (int i = 0; i < animList.size(); i++) - adjustCanvas(Math.round(canvasReserveLength * scale), animList.get(i), i == 0); - Logger.info("Character", "Canvas size " + flexibleLayout.getWidth() + " * " + flexibleLayout.getHeight()); - updateCanvas(); - } - - /** Sets the canvas with transparent background. - */ - public void setCanvas() { - this.setCanvas(new Color(0, 0, 0, 0)); + setCanvas(new Color(0, 0, 0, 0)); + stageInsertMap = new HashMap<>(); + for (AnimStage stage : animList.clusterByStage().keySet()) { + AnimClipGroup stageClips = animList.findAnimations(stage); + double maxHypSize = 0; + for (int i = 0; i < stageClips.size(); i++) { + camera.setInsertMaxed(); + adjustCanvas(stageClips.get(i)); + if (camera.isInsertMaxed()) + continue; + double hypSize = Math.hypot(camera.getWidth(), camera.getHeight()); + if (hypSize > maxHypSize) { + maxHypSize = hypSize; + stageInsertMap.put(stage, camera.getInsert().clone()); + } + } + if (!stageInsertMap.containsKey(stage)) { + stageInsertMap.put(stage, new Insert((canvasReserveLength << 1) - (canvasMaxSize >> 1))); + Logger.warn("Character", stage + " figuring camera size failed"); + } + Logger.info("Character", stage + " using " + camera); + } } /** Sets the canvas with the specified background color. */ public void setCanvas(Color bgColor) { // Set position (centered) - position.reset(canvasMaxSize >> 1, 0, 1); - updateCanvas(); + position.reset(camera.getWidth() >> 1, 0, 1); // Set background texture Pixmap pixmap = new Pixmap(canvasMaxSize, canvasMaxSize, Format.RGBA8888); pixmap.setColor(bgColor); @@ -123,33 +138,6 @@ public void setCanvas(Color bgColor) { bgTexture = new Texture(pixmap); } - /** Updates the canvas and the camera to keep the character in the center. - */ - public void updateCanvas() { - camera.setToOrtho(false, flexibleLayout.getWidth(), flexibleLayout.getHeight()); - camera.translate((canvasMaxSize - flexibleLayout.getWidth()) >> 1, 0); - camera.update(); - batch.getProjectionMatrix().set(camera.combined); - } - - /** Adjusts the canvas to fit the character's size. - */ - public void adjustCanvas(int reserved_length, AnimClip anim_clip, boolean initialize) { - setCanvas(); - composer.reset(); - composer.offer(new AnimData(anim_clip)); - position.addProgress(Float.MAX_VALUE); - animationState.update(animationState.getCurrent(0).getAnimation().getDuration() / 2); // Take the middle frame as sample - Pixmap snapshot = new Pixmap(canvasMaxSize, canvasMaxSize, Format.RGBA8888); - renderToPixmap(snapshot); - flexibleLayout.fitToBestCroppedSize( - snapshot, - 1, reserved_length, - false, true, initialize - ); - snapshot.dispose(); - } - /** Requests to set the current animation of the character. * @param animData The animation data. * @return true if success. @@ -178,26 +166,34 @@ public int getPixel(int x, int y) { return pixel; } - /** Saves the current framebuffer contents as an image file. (Only test-use) - * Note that the image may be flipped along the y-axis. - * @param debug Whether to show debug additions in the pixmap. Note that this will modify the original pixmap. - */ - @Deprecated - protected void saveCurrentTexture(boolean debug) { - Pixmap pixmap = Pixmap.createFromFrameBuffer(0, 0, flexibleLayout.getWidth(), flexibleLayout.getHeight()); - if (debug) { - pixmap.setColor(new Color(1, 0, 0, 0.75f)); - if (flexibleLayout.curInsert.bottom > 0) - pixmap.drawRectangle(0, 0, pixmap.getWidth(), flexibleLayout.curInsert.bottom); - if (flexibleLayout.curInsert.top > 0) - pixmap.drawRectangle(0, pixmap.getHeight() - flexibleLayout.curInsert.top, pixmap.getWidth(), flexibleLayout.curInsert.top); - if (flexibleLayout.curInsert.left > 0) - pixmap.drawRectangle(0, 0, flexibleLayout.curInsert.left, pixmap.getHeight()); - if (flexibleLayout.curInsert.right > 0) - pixmap.drawRectangle(pixmap.getWidth() - flexibleLayout.curInsert.right, 0, flexibleLayout.curInsert.right, pixmap.getHeight()); - } - PixmapIO.writePNG(new FileHandle("temp.png"), pixmap); - pixmap.dispose(); + private void adjustCanvas(AnimClip animClip) { + composer.reset(); + composer.offer(new AnimData(animClip)); + position.reset(camera.getWidth() >> 1, position.end().y, position.end().z); + position.setToEnd(); + animationState.update(animationState.getCurrent(0).getAnimation().getDuration() / 2); // Take the middle frame as sample + + FrameBuffer fbo = new FrameBuffer(Format.RGBA8888, camera.getWidth(), camera.getHeight(), false); + fbo.begin(); + renderToBatch(); + Pixmap snapshot = new Pixmap(camera.getWidth(), camera.getHeight(), Format.RGBA8888); + Gdx.gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1); + Gdx.gl.glReadPixels(0, 0, snapshot.getWidth(), snapshot.getHeight(), + GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, snapshot.getPixels()); + fbo.end(); + + if (camera.isInsertMaxed()) + camera.cropTo(snapshot, false, true); + else + camera.extendTo(snapshot, false, true); + fbo.dispose(); + snapshot.dispose(); + } + + protected void adjustCanvas(AnimStage animStage) { + if (!stageInsertMap.containsKey(animStage)) + throw new IndexOutOfBoundsException("No such key " + animStage); + camera.setInsert(stageInsertMap.get(animStage)); } /** Renders the character to the Gdx 2D Batch. @@ -205,6 +201,7 @@ protected void saveCurrentTexture(boolean debug) { */ protected void renderToBatch() { // Apply Animation + position.reset(camera.getWidth() >> 1, position.end().y, position.end().z); position.addProgress(Gdx.graphics.getDeltaTime()); offsetY.addProgress(Gdx.graphics.getDeltaTime()); skeleton.setPosition(position.now().x, position.now().y + offsetY.now()); @@ -213,27 +210,15 @@ protected void renderToBatch() { animationState.apply(skeleton); animationState.update(Gdx.graphics.getDeltaTime()); // Render the skeleton to the batch - updateCanvas(); ScreenUtils.clear(0, 0, 0, 0, true); + camera.update(); + batch.getProjectionMatrix().set(camera.combined); batch.begin(); - if (bgTexture != null) - batch.draw(bgTexture, 0, 0); + batch.draw(bgTexture, 0, 0); renderer.draw(batch, skeleton); batch.end(); } - /** Renders the character to a given Pixmap. - * Note that the mismatch of the Pixmap's size and the screen's size may cause data loss; - * @param pixmap The reference of the given Pixmap object. - */ - protected void renderToPixmap(Pixmap pixmap) { - fbo.begin(); - renderToBatch(); - Gdx.gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1); - Gdx.gl.glReadPixels(0, 0, pixmap.getWidth(), pixmap.getHeight(), - GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixmap.getPixels()); - fbo.end(); - } public static class AnimComposer { protected final AnimationState state; diff --git a/core/src/cn/harryh/arkpets/ArkPets.java b/core/src/cn/harryh/arkpets/ArkPets.java index 4c0a78b2..1bab7661 100644 --- a/core/src/cn/harryh/arkpets/ArkPets.java +++ b/core/src/cn/harryh/arkpets/ArkPets.java @@ -67,17 +67,17 @@ public void create() { // 2.Character setup Logger.info("App", "Using model asset \"" + config.character_asset + "\""); cha = new ArkChar(config.character_asset, new AssetItem.AssetAccessor(config.character_files), skelBaseScale); - cha.setCanvas(); behavior = new GeneralBehavior(config, cha.animList); + cha.adjustCanvas(behavior.defaultAnim().animClip().stage); cha.setAnimation(behavior.defaultAnim()); - Logger.info("Animation", "Animation stages " + behavior.getStages()); + Logger.info("Animation", "Available animation stages " + behavior.getStages()); // 3.Window params setup windowPosition = new TransitionVector2(TernaryFunction.EASE_OUT_CUBIC, easingDuration); windowAlpha = new TransitionFloat(TernaryFunction.EASE_OUT_CUBIC, easingDuration); windowAlpha.reset(1f); WD_SCALE = config.display_scale; - WD_W = (int)(WD_SCALE * cha.flexibleLayout.getWidth()); - WD_H = (int)(WD_SCALE * cha.flexibleLayout.getHeight()); + WD_W = (int)(WD_SCALE * cha.camera.getWidth()); + WD_H = (int)(WD_SCALE * cha.camera.getHeight()); // 4.Plane setup plane = new Plane(new ArrayList<>()); plane.setGravity(config.physic_gravity_acc); @@ -152,6 +152,10 @@ public boolean canChangeStage() { public void changeStage() { if (canChangeStage()) { behavior.nextStage(); + cha.adjustCanvas(behavior.getCurrentStage()); + WD_W = (int)(WD_SCALE * cha.camera.getWidth()); + WD_H = (int)(WD_SCALE * cha.camera.getHeight()); + plane.setObjSize(WD_W, WD_H); Logger.info("Animation", "Changed to " + behavior.getCurrentStage()); changeAnimation(behavior.defaultAnim()); } diff --git a/core/src/cn/harryh/arkpets/Const.java b/core/src/cn/harryh/arkpets/Const.java index 5c9f7aa3..8132289c 100644 --- a/core/src/cn/harryh/arkpets/Const.java +++ b/core/src/cn/harryh/arkpets/Const.java @@ -37,7 +37,7 @@ public final class Const { public static final int coreWidthDefault = 150; public static final int coreHeightDefault = 150; public static final int canvasReserveLength = 80; - public static final int canvasMaxSize = 720; + public static final int canvasMaxSize = 1080; public static final float skelBaseScale = 0.3f; // Behavior presets diff --git a/core/src/cn/harryh/arkpets/utils/CroppingCtrl.java b/core/src/cn/harryh/arkpets/utils/CroppingCtrl.java deleted file mode 100644 index 13463bf9..00000000 --- a/core/src/cn/harryh/arkpets/utils/CroppingCtrl.java +++ /dev/null @@ -1,211 +0,0 @@ -/** Copyright (c) 2022-2024, Harry Huang - * At GPL-3.0 License - */ -package cn.harryh.arkpets.utils; - -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.math.Vector2; - - -public class CroppingCtrl { - private final Vector2 origin; - private final Insert maxInsert; - public final Insert curInsert; - - /** Initializes a Cropping Controller instance. - * @param originSize The original size of the cropper. - * @param maxInsertLength The max insert length of each side. - */ - public CroppingCtrl(Vector2 originSize, int maxInsertLength) { - origin = originSize; - maxInsert = new Insert( - maxInsertLength, maxInsertLength, - maxInsertLength, maxInsertLength - ); - curInsert = new Insert(); - } - - /** Fits the size to the best cropped size using the given parameters. - * @param pixmap The pixmap got from the current rendered frame of libGDX. - * @param extended The extension length to be added to each overflowed side (px). - * @param reserved The reservation length of the white space (px). - * @param flipX Flip the pixmap along the x-axis. - * @param flipY Flip the pixmap along the y-axis. - * @param initialize Allow to decrease the size. - */ - public void fitToBestCroppedSize(Pixmap pixmap, int extended, int reserved, boolean flipX, boolean flipY, boolean initialize) { - Insert insert = curInsert.clone(); - final int alphaThreshold = 255; - final int edgeWidth = pixmap.getWidth() - 1; - final int edgeHeight = pixmap.getHeight() - 1; - //PixmapIO.writePNG(new FileHandle("temp.png"), pixmap); - - if (flipX) - insert.swapHorizontal(); - if (flipY) - insert.swapVertical(); - - // TOP - for (int y = 0; y <= edgeHeight; y++) - for (int x = 0; x <= edgeWidth; x++) - if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { - if (y == 0) - insert.top += extended; - else - insert.top -= y - reserved; - x = Integer.MAX_VALUE - 1; - y = Integer.MAX_VALUE - 1; - } - // BOTTOM - for (int y = edgeHeight; y >= 0; y--) - for (int x = 0; x <= edgeWidth; x++) - if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { - if (y == edgeHeight) - insert.bottom += extended; - else - insert.bottom -= edgeHeight - y - reserved; - x = Integer.MAX_VALUE - 1; - y = Integer.MIN_VALUE + 1; - } - // LEFT - for (int x = 0; x <= edgeWidth; x++) - for (int y = 0; y <= edgeHeight; y++) - if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { - if (x == 0) - insert.left += extended; - else - insert.left -= x - reserved; - x = Integer.MAX_VALUE - 1; - y = Integer.MAX_VALUE - 1; - } - // RIGHT - for (int x = edgeWidth; x >= 0; x--) - for (int y = 0; y <= edgeHeight; y++) - if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { - if (x == edgeWidth) - insert.right += extended; - else - insert.right -= edgeWidth - x - reserved; - x = Integer.MIN_VALUE + 1; - y = Integer.MAX_VALUE - 1; - } - - if (flipX) - insert.swapHorizontal(); - if (flipY) - insert.swapVertical(); - - insert.limitMax(maxInsert); - if (!initialize) - insert.limitMin(curInsert); - curInsert.moderatelyModify(insert, 0); - } - - /** Gets the total width. - * @return The total width. - */ - public int getWidth() { - return (int) (getLeft() + getRight() + origin.x); - } - - /** Gets the total height. - * @return The total height. - */ - public int getHeight() { - return (int) (getTop() + getBottom() + origin.y); - } - - public int getTop() { - return curInsert.top; - } - - public int getBottom() { - return curInsert.bottom; - } - - public int getLeft() { - return curInsert.left; - } - - public int getRight() { - return curInsert.right; - } - - public static class Insert { - public int top; - public int bottom; - public int left; - public int right; - - public Insert() { - top = 0; - bottom = 0; - left = 0; - right = 0; - } - - public Insert(int top, int bottom, int left, int right) { - this.top = top; - this.bottom = bottom; - this.left = left; - this.right = right; - } - - public void limitMax(Insert maxInsert) { - top = Math.min(top, maxInsert.top); - bottom = Math.min(bottom, maxInsert.bottom); - left = Math.min(left, maxInsert.left); - right = Math.min(right, maxInsert.right); - } - - public void limitMin(Insert minInsert) { - top = Math.max(top, minInsert.top); - bottom = Math.max(bottom, minInsert.bottom); - left = Math.max(left, minInsert.left); - right = Math.max(right, minInsert.right); - } - - public void swapHorizontal() { - left += right; - right = left - right; - left -= right; - } - - public void swapVertical() { - bottom += top; - top = bottom - top; - bottom -= top; - } - - public boolean moderatelyModify(Insert changeTo, int threshold) { - int top_ = Math.abs(changeTo.top - top) > threshold ? changeTo.top : top; - int bottom_ = Math.abs(changeTo.bottom - bottom) > threshold ? changeTo.bottom : bottom; - int left_ = Math.abs(changeTo.left - left) > threshold ? changeTo.left : left; - int right_ = Math.abs(changeTo.right - right) > threshold ? changeTo.right : right; - - if (top_ == top && bottom_ == bottom && left_ == left && right_ == right) { - return false; - } else { - top = top_; - bottom = bottom_; - left = left_; - right = right_; - return true; - } - } - - @Override - public Insert clone() { - try { - return (Insert)super.clone(); - } catch (CloneNotSupportedException e) { - return new Insert(top, bottom, left, right); - } - } - - @Override - public String toString() { - return top + "\t" + bottom + "\t" + left + "\t" + right; - } - } -} diff --git a/core/src/cn/harryh/arkpets/utils/DynamicOrthographicCamara.java b/core/src/cn/harryh/arkpets/utils/DynamicOrthographicCamara.java new file mode 100644 index 00000000..04af6899 --- /dev/null +++ b/core/src/cn/harryh/arkpets/utils/DynamicOrthographicCamara.java @@ -0,0 +1,255 @@ +/** Copyright (c) 2022-2024, Harry Huang + * At GPL-3.0 License + */ +package cn.harryh.arkpets.utils; + +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Pixmap; + +import java.util.Objects; + + +public class DynamicOrthographicCamara extends OrthographicCamera { + protected final int initWidth; + protected final int initHeight; + protected final int paddingLength; + protected final Insert curInsert = new Insert(); + protected final Insert maxInsert = new Insert(); + protected final Insert minInsert = new Insert(); + + protected static final int alphaThreshold = 128; + protected static final int stepLength = 2; + protected static final boolean cameraYDown = false; + + /** Initializes a Dynamic Orthographic Camera instance. + * @param paddingLength The padding length of each side. + */ + public DynamicOrthographicCamara(int initWidth, int initHeight, int paddingLength) { + this.initWidth = initWidth; + this.initHeight = initHeight; + this.paddingLength = paddingLength; + setInsert(new Insert()); + } + + public void setInsert(Insert insert) { + // Update insert + insert.limitMax(maxInsert); + insert.limitMin(minInsert); + curInsert.set(insert); + // Update camera geometry + setToOrtho(cameraYDown, getWidth(), getHeight()); + } + + public void setMaxInsert(int length) { + maxInsert.set(length); + setInsert(curInsert); + } + + public void setMinInsert(int length) { + minInsert.set(length); + setInsert(curInsert); + } + + public void cropTo(Pixmap pixmap, boolean flippedX, boolean flippedY) { + Insert insert = getFittedInsert(pixmap, flippedX, flippedY); + insert.limitMax(curInsert); + setInsert(insert); + } + + public void extendTo(Pixmap pixmap, boolean flippedX, boolean flippedY) { + Insert insert = getFittedInsert(pixmap, flippedX, flippedY); + insert.limitMin(curInsert); + setInsert(insert); + } + + public Insert getFittedInsert(Pixmap pixmap, boolean flippedX, boolean flippedY) { + final Insert insert = curInsert.clone(); + final int edgeWidth = pixmap.getWidth() - 1; + final int edgeHeight = pixmap.getHeight() - 1; + final int extendedX = paddingLength; + final int extendedY = paddingLength; + final int reservedX = paddingLength; + final int reservedY = paddingLength; + //PixmapIO.writePNG(new FileHandle("temp.png"), pixmap); + + if (flippedX) + insert.swapHorizontal(); + if (flippedY) + insert.swapVertical(); + + // TOP + for (int y = 0; y <= edgeHeight; y += stepLength) + for (int x = 0; x <= edgeWidth; x += stepLength) + if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { + if (y == 0) + insert.top += extendedY; + else + insert.top -= y - reservedY; + x = Integer.MAX_VALUE - stepLength; + y = Integer.MAX_VALUE - stepLength; + } + // BOTTOM + for (int y = edgeHeight; y >= 0; y -= stepLength) + for (int x = 0; x <= edgeWidth; x += stepLength) + if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { + if (y == edgeHeight) + insert.bottom += extendedY; + else + insert.bottom -= edgeHeight - y - reservedY; + x = Integer.MAX_VALUE - stepLength; + y = Integer.MIN_VALUE + stepLength; + } + // LEFT + for (int x = 0; x <= edgeWidth; x += stepLength) + for (int y = 0; y <= edgeHeight; y += stepLength) + if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { + if (x == 0) + insert.left += extendedX; + else + insert.left -= x - reservedX; + x = Integer.MAX_VALUE - stepLength; + y = Integer.MAX_VALUE - stepLength; + } + // RIGHT + for (int x = edgeWidth; x >= 0; x -= stepLength) + for (int y = 0; y <= edgeHeight; y += stepLength) + if ((pixmap.getPixel(x, y) & 0x000000FF) >= alphaThreshold) { + if (x == edgeWidth) + insert.right += extendedX; + else + insert.right -= edgeWidth - x - reservedX; + x = Integer.MIN_VALUE + stepLength; + y = Integer.MAX_VALUE - stepLength; + } + + if (flippedX) + insert.swapHorizontal(); + if (flippedY) + insert.swapVertical(); + + return insert; + } + + /** Gets the total width. + * @return The total width. + */ + public int getWidth() { + return curInsert.left + curInsert.right + initWidth; + } + + /** Gets the total height. + * @return The total height. + */ + public int getHeight() { + return curInsert.top + curInsert.bottom + initHeight; + } + + public Insert getInsert() { + return curInsert; + } + + public boolean isInsertMaxed() { + return curInsert.equals(maxInsert); + } + + public void setInsertMaxed() { + curInsert.set(maxInsert); + } + + @Override + public String toString() { + return "DynamicOrthographicCamara " + getWidth() + "*" + getHeight() + " {Insert: " + curInsert + "}"; + } + + + public static class Insert { + public int top; + public int bottom; + public int left; + public int right; + + public Insert(int top, int bottom, int left, int right) { + this.top = top; + this.bottom = bottom; + this.left = left; + this.right = right; + } + + public Insert(int length) { + set(length, length, length, length); + } + + public Insert() { + set(0); + } + + public void limitMax(Insert maxInsert) { + if (maxInsert == null) + return; + top = Math.min(top, maxInsert.top); + bottom = Math.min(bottom, maxInsert.bottom); + left = Math.min(left, maxInsert.left); + right = Math.min(right, maxInsert.right); + } + + public void limitMin(Insert minInsert) { + if (minInsert == null) + return; + top = Math.max(top, minInsert.top); + bottom = Math.max(bottom, minInsert.bottom); + left = Math.max(left, minInsert.left); + right = Math.max(right, minInsert.right); + } + + public void swapHorizontal() { + left += right; + right = left - right; + left -= right; + } + + public void swapVertical() { + bottom += top; + top = bottom - top; + bottom -= top; + } + + public void set(int top, int bottom, int left, int right) { + this.top = top; + this.bottom = bottom; + this.left = left; + this.right = right; + } + + public void set(int length) { + set(length, length, length, length); + } + + public void set(Insert insert) { + set(insert.top, insert.bottom, insert.left, insert.right); + } + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public Insert clone() { + return new Insert(top, bottom, left, right); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Insert insert = (Insert)o; + return top == insert.top && bottom == insert.bottom && left == insert.left && right == insert.right; + } + + @Override + public int hashCode() { + return Objects.hash(top, bottom, left, right); + } + + @Override + public String toString() { + return "^" + top + "\tv" + bottom + "\t<" + left + "\t>" + right; + } + } +}