-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WIP-ish] Implement initial gear rotation generator, refactor unit te…
…sts to be worse - MC-TextureGen now generates textures for every angle of the gear rotation animation! The code is very unreadable right now, and needs to be refactored. - The TextureGenerator class now has some math utility methods to help with the gear rotation generator. These utility methods should produce identical results to Minecraft's, because they're both based on Riven's code. - The unit tests are now worse. I've implemented a messy way of signalling that something went wrong with a TextureGenerator, but this needs to be reworked. I've had this code nearly finished for some weeks now, and I've decided that I might as well commit it as-is. I've been moving into a new house, and haven't had much time to work on this project, so this code is not really up to standard. I'll probably refactor it a bit before a new release.
- Loading branch information
1 parent
7a80037
commit 567425a
Showing
7 changed files
with
234 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,17 @@ | ||
# MC Texture Generator | ||
|
||
A Java program that programatically generates textures generated by certain versions of Minecraft at runtime, and then saves them to individual files. | ||
|
||
Currently, this program generates textures from the following Minecraft versions: | ||
- Minecraft 4k-1 | ||
- Minecraft 4k-2 | ||
Currently, this program generates: | ||
|
||
- All textures generated by Minecraft 4k-1 | ||
- All textures generated by Minecraft 4k-2 | ||
- All frames of the gear rotation animation | ||
|
||
### How to generate the textures. | ||
## How to generate the textures. | ||
|
||
Make sure to have a Java runtime environment of 7 or higher installed on your computer. The easiest way to run this program is just to double click the .jar file, which will generate and save all textures to a folder named "GeneratedTextures". The .jar file can also be run from the command line. Doing so will allow you to see log output when generating the textures, which can be useful if you need to debug anything. When this program is run from the command line, the "GeneratedTextures" folder will be placed at the current directory that your command line is in. | ||
Make sure to have a Java runtime environment of 6 or higher installed on your computer. The easiest way to run this program is just to double click the .jar file, which will generate and save all textures to a folder named "GeneratedTextures". The .jar file can also be run from the command line. Doing so will allow you to see log output when generating the textures, which can be useful if you need to debug anything. When this program is run from the command line, the "GeneratedTextures" folder will be placed at the current directory that your command line is in. | ||
|
||
### Why have a dedicated program for this? | ||
## Why have a dedicated program for this? | ||
|
||
The idea behind this program is to create an improvably accurate source for these textures - if this code contains any mistakes, the mistakes can simply be fixed, and the textures will then be generated accurately. The same improvement in accuracy cannot be achieved for an inaccurate screenshot, or a file accidentally saved in a reduced resolution or color depth. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 138 additions & 0 deletions
138
src/main/java/mcTextureGen/generators/GearRotationFramesGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package mcTextureGen.generators; | ||
|
||
import java.awt.image.BufferedImage; | ||
import java.awt.image.DataBufferByte; | ||
import java.io.IOException; | ||
|
||
import javax.imageio.ImageIO; | ||
|
||
import mcTextureGen.data.TextureGroup; | ||
|
||
public final class GearRotationFramesGenerator extends TextureGenerator { | ||
|
||
/** | ||
* This variable controls how many distinct angles (frames) the gear animation has. | ||
* Minecraft had 64 distinct angles. | ||
* The gear rotation animation would advance by one frame on every tick. | ||
*/ | ||
private static final int gearRotationSteps = 64; | ||
|
||
/** | ||
* This variable can be changed to create higher resolution output rotated textures. | ||
* This is just for convenience. | ||
*/ | ||
private static final int rotatedTextureSizeMultiplier = 1; | ||
|
||
/** | ||
* gearmiddle.png has a resolution of 16 by 16. | ||
*/ | ||
private static final int middleGearTextureSize = 16; | ||
|
||
/** | ||
* gear.png has a resolution of 32 by 32. | ||
*/ | ||
private static final int originalGearTextureSize = 32; | ||
|
||
/** | ||
* This variable controls the resolution of the output rotated gear textures. | ||
*/ | ||
private static final int rotatedTextureSize = middleGearTextureSize * rotatedTextureSizeMultiplier; | ||
|
||
// Integer arrays to store the ARGB values of the original gear images | ||
private static final int[] gearMiddleARGBValues = new int[middleGearTextureSize * middleGearTextureSize]; | ||
private static final int[] gearARGBValues = new int[originalGearTextureSize * originalGearTextureSize]; | ||
|
||
// Only used during testing. | ||
private static boolean generationIssueFlag = false; | ||
|
||
static { | ||
// TODO this is janky | ||
try { | ||
ImageIO.read(ClassLoader.getSystemResource("gear.png")).getRGB(0, 0, originalGearTextureSize, originalGearTextureSize, gearARGBValues, 0, originalGearTextureSize); | ||
ImageIO.read(ClassLoader.getSystemResource("gearmiddle.png")).getRGB(0, 0, middleGearTextureSize, middleGearTextureSize, gearMiddleARGBValues, 0, middleGearTextureSize); | ||
} catch (final IOException e) { | ||
// Should never happen when running, as the tests must pass to build the application, and the tests don't pass if this happens. | ||
generationIssueFlag = true; | ||
} | ||
} | ||
|
||
private TextureGroup gearRotationTextures() { | ||
final BufferedImage[] gearTextures = new BufferedImage[gearRotationSteps]; | ||
|
||
// For each angle of the gear animation, generate a texture | ||
for (int i = 0; i < gearRotationSteps; i++) { | ||
gearTextures[i] = generateGearTextureForRotation(i); | ||
} | ||
|
||
// Only one TextureGroup is returned, as the clockwise and counter-clockwise animations | ||
// are comprised of identical frames, played in the opposite order. | ||
return new TextureGroup("Gear_Rotations", gearTextures); | ||
} | ||
|
||
// TODO better documentation, variable names are way to verbose, check if gear rotation animation was consistent across all versions of Minecraft | ||
private BufferedImage generateGearTextureForRotation(int rotationStep) { | ||
final BufferedImage rotatedImage = new BufferedImage(rotatedTextureSize, rotatedTextureSize, BufferedImage.TYPE_4BYTE_ABGR); | ||
final byte[] imageByteData = ((DataBufferByte) rotatedImage.getRaster().getDataBuffer()).getData(); | ||
// Convert the current rotation step into an angle in radians, | ||
// and use the lookup table to get the current sine and cosine of the angle. | ||
final float sinRotationAngle = lookupSin((rotationStep / (float) gearRotationSteps) * (float) Math.PI * 2.0F); | ||
final float cosRotationAngle = lookupCos((rotationStep / (float) gearRotationSteps) * (float) Math.PI * 2.0F); | ||
|
||
for (int rotatedImageX = 0; rotatedImageX < rotatedTextureSize; ++rotatedImageX) { | ||
for (int rotatedImageY = 0; rotatedImageY < rotatedTextureSize; ++rotatedImageY) { | ||
// TODO I'm not sure why this is done this way | ||
final float gearImageX = ((rotatedImageX / (rotatedTextureSize - 1.0F)) - 0.5F) * (originalGearTextureSize - 1.0F); | ||
final float gearImageY = ((rotatedImageY / (rotatedTextureSize - 1.0F)) - 0.5F) * (originalGearTextureSize - 1.0F); | ||
// Rotate the coordinates TODO document more | ||
final float rotatedOffsetGearImageX = (cosRotationAngle * gearImageX) - (sinRotationAngle * gearImageY); | ||
final float rotatedOffsetGearImageY = (cosRotationAngle * gearImageY) + (sinRotationAngle * gearImageX); | ||
// Fix the offset after rotating | ||
final int rotatedGearImageX = (int) (rotatedOffsetGearImageX + (originalGearTextureSize / 2)); | ||
final int rotatedGearImageY = (int) (rotatedOffsetGearImageY + (originalGearTextureSize / 2)); | ||
final int ARGB; | ||
|
||
if ((rotatedGearImageX >= 0) && (rotatedGearImageY >= 0) && (rotatedGearImageX < originalGearTextureSize) && (rotatedGearImageY < originalGearTextureSize)) { | ||
final int gearDiv = rotatedTextureSize / middleGearTextureSize; | ||
final int gearMiddleARGB = gearMiddleARGBValues[(rotatedImageX / gearDiv) + ((rotatedImageY / gearDiv) * middleGearTextureSize)]; | ||
|
||
// Is the alpha component of the RGBA value for the middle piece of the gear greater than 128? | ||
// (i.e. in the context of the gear images, is there a non-transparent pixel at that position? | ||
// TODO refactor, this is dumb) | ||
if ((gearMiddleARGB >>> 24) > 128) { | ||
// If so, use the RGBA value for the middle of the gear as the RGBA value | ||
ARGB = gearMiddleARGB; | ||
} else { | ||
ARGB = gearARGBValues[rotatedGearImageX + (rotatedGearImageY * originalGearTextureSize)]; | ||
} | ||
} else { | ||
ARGB = 0; | ||
} | ||
|
||
final int imageOffset = (rotatedImageX + (rotatedImageY * rotatedTextureSize)) * 4; | ||
// Set ABGR values | ||
imageByteData[imageOffset + 0] = (byte) ((ARGB >>> 24) > 128 ? 255 : 0); | ||
imageByteData[imageOffset + 1] = (byte) ((ARGB >> 16) & 0xFF); | ||
imageByteData[imageOffset + 2] = (byte) ((ARGB >> 8) & 0xFF); | ||
imageByteData[imageOffset + 3] = (byte) (ARGB & 0xFF); | ||
} | ||
} | ||
|
||
return rotatedImage; | ||
} | ||
|
||
@Override | ||
public String getGeneratorName() { | ||
return "Gear_Rotation"; | ||
} | ||
|
||
@Override | ||
public TextureGroup[] getTextureGroups() { | ||
return new TextureGroup[] { gearRotationTextures() }; | ||
} | ||
|
||
@Override | ||
public boolean hasGenerationIssue() { | ||
return generationIssueFlag; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 51 additions & 29 deletions
80
src/test/java/mcTextureGen/test/MCTextureGeneratorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,68 @@ | ||
package mcTextureGen.test; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
import java.util.stream.Stream; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import mcTextureGen.MCTextureGenerator; | ||
import mcTextureGen.data.TextureGroup; | ||
import mcTextureGen.generators.TextureGenerator; | ||
|
||
// TODO refactor tests to use MethodSource | ||
// TODO refactor | ||
public class MCTextureGeneratorTest { | ||
|
||
@Test | ||
@DisplayName("Ensure all names of TextureGenerators and TextureGroups only contain characters which are safe to be used in file names") | ||
void testSafeCharactersInNames() { | ||
// This regex matches if the whole string only contains alpha-numeric characters and / or underscores. | ||
final Pattern checkUnsafeCharactersRegex = Pattern.compile("^\\w+$"); | ||
// The Matcher is initially given a dummy value, as it is reused each loop. | ||
// If it somehow fails to get reset, this ASCII table flip should cause the test to fail. | ||
// P.S applicants welcome to submit a better ASCII-only table flip (I really tried). | ||
// Using non-ASCII characters causes Eclipse to space lines weirdly. | ||
final Matcher checkUnsafeCharacters = checkUnsafeCharactersRegex.matcher("(/@_@/) `` _|__|_"); | ||
|
||
final String unsafeCharacterStart = "The name of the "; | ||
final String unsafeCharacterQuotesStart = " \""; | ||
final String unsafeCharacterEnd = "\" contained a character which might be potentially unsafe to use in a file name"; | ||
for (final TextureGenerator generator : MCTextureGenerator.getTextureGenerators()) { | ||
// Re-use Matcher instead of creating a new one for each String | ||
checkUnsafeCharacters.reset(generator.getGeneratorName()); | ||
// Lambda used for lazy evaluation of error message | ||
assertTrue(checkUnsafeCharacters.matches(), () -> (unsafeCharacterStart + TextureGenerator.class.getSimpleName() + unsafeCharacterQuotesStart + generator.getGeneratorName() + unsafeCharacterEnd)); | ||
for (final TextureGroup group : generator.getTextureGroups()) { | ||
// Re-use Matcher instead of creating a new one for each String | ||
checkUnsafeCharacters.reset(group.textureGroupName); | ||
// Lambda used for lazy evaluation of error message | ||
assertTrue(checkUnsafeCharacters.matches(), () -> (unsafeCharacterStart + TextureGroup.class.getSimpleName() + unsafeCharacterQuotesStart + group.textureGroupName + unsafeCharacterEnd)); | ||
} | ||
} | ||
} | ||
// TODO this is bad | ||
private static final Stream<TextureGenerator> textureGeneratorProvider() { | ||
return Stream.of(MCTextureGenerator.getTextureGenerators()); | ||
} | ||
|
||
// TODO this is bad | ||
private static final Stream<TextureGroup> textureGroupProvider() { | ||
return Stream.of(MCTextureGenerator.getTextureGenerators()).map(x -> x.getTextureGroups()).flatMap(Stream::of); | ||
} | ||
|
||
// This regex matches if the whole string only contains alpha-numeric characters and / or underscores. | ||
private final static Pattern checkUnsafeCharactersRegex = Pattern.compile("^\\w+$"); | ||
// The Matcher is initially given a dummy value, as it is reused each loop. | ||
// If it somehow fails to get reset, this ASCII table flip should cause the test to fail. | ||
// P.S applicants welcome to submit a better ASCII-only table flip (I really tried). | ||
// Using non-ASCII characters causes Eclipse to space lines weirdly. | ||
private final static Matcher checkUnsafeCharacters = checkUnsafeCharactersRegex.matcher("(/@_@/) `` _|__|_"); | ||
|
||
private final static String unsafeCharacterStart = "The name of the "; | ||
private final static String unsafeCharacterQuotesStart = " \""; | ||
private final static String unsafeCharacterEnd = "\" contained a character which might be potentially unsafe to use in a file name"; | ||
|
||
private static final boolean isSafeName(String toCheck) { | ||
return checkUnsafeCharacters.reset(toCheck).matches(); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("textureGeneratorProvider") | ||
@DisplayName("Test if any TextureGenerator reports generation errors.") | ||
final void testGenerationIssues(TextureGenerator generator) { | ||
assertFalse(generator.hasGenerationIssue(), () -> ("The " + TextureGenerator.class.getSimpleName() + " \"" + generator.getGeneratorName() + "\" has an unspecified texture generation issue.")); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("textureGeneratorProvider") | ||
@DisplayName("Ensure all names of TextureGenerators only contain characters which are safe to be used in file names") | ||
final void testSafeCharactersInTextureGeneratorNames(TextureGenerator generator) { | ||
assertTrue(isSafeName(generator.getGeneratorName()), () -> (unsafeCharacterStart + TextureGenerator.class.getSimpleName() + unsafeCharacterQuotesStart + generator.getGeneratorName() + unsafeCharacterEnd)); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("textureGroupProvider") | ||
@DisplayName("Ensure all names of TextureGenerators only contain characters which are safe to be used in file names") | ||
final void testSafeCharactersInTextureGroupNames(TextureGroup group) { | ||
assertTrue(isSafeName(group.textureGroupName), () -> (unsafeCharacterStart + TextureGroup.class.getSimpleName() + unsafeCharacterQuotesStart + group.textureGroupName + unsafeCharacterEnd)); | ||
} | ||
|
||
} |