Skip to content

Commit

Permalink
Further optimize rendering methods (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
Litorom authored Apr 3, 2024
2 parents 2b2c0e7 + 67a1769 commit 48a208f
Show file tree
Hide file tree
Showing 11 changed files with 612 additions and 310 deletions.
1 change: 0 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ ij_java_packages_to_use_import_on_demand = unset
ij_java_class_count_to_use_import_on_demand = 99999999
ij_java_names_count_to_use_import_on_demand = 99999999
ij_java_use_single_class_imports = true
ij_java_use_fq_class_names = true
ij_java_insert_inner_class_imports = false
ij_java_layout_static_imports_separately = true
ij_java_space_before_array_initializer_left_brace = true
Expand Down
30 changes: 17 additions & 13 deletions src/client/java/minicraft/core/Renderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import javax.imageio.ImageIO;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferStrategy;
Expand Down Expand Up @@ -69,7 +69,6 @@ private Renderer() {
static Canvas canvas = new Canvas();
private static BufferedImage image; // Creates an image to be displayed on the screen.

private static Screen lightScreen; // Creates a front screen to render the darkness in caves (Fog of war).

public static boolean readyToRenderGameplay = false;
public static boolean showDebugInfo = false;
Expand All @@ -86,12 +85,12 @@ public static MinicraftImage loadDefaultSkinSheet() {
MinicraftImage skinsSheet;
try {
// These set the sprites to be used.
skinsSheet = new MinicraftImage(ImageIO.read(Objects.requireNonNull(Game.class.getResourceAsStream("/resources/textures/skins.png"))));
skinsSheet = MinicraftImage.createDefaultCompatible(ImageIO.read(Objects.requireNonNull(Game.class.getResourceAsStream("/resources/textures/skins.png"))));
} catch (NullPointerException e) {
// If a provided InputStream has no name. (in practice meaning it cannot be found.)
CrashHandler.crashHandle(e, new ErrorInfo("Sprite Sheet Not Found", ErrorInfo.ErrorType.UNEXPECTED, true, "A sprite sheet was not found."));
return null;
} catch (IOException | IllegalArgumentException e) {
} catch (IOException | IllegalArgumentException | MinicraftImage.MinicraftImageDimensionIncompatibleException e) {
// If there is an error reading the file.
CrashHandler.crashHandle(e, new ErrorInfo("Sprite Sheet Could Not be Loaded", ErrorInfo.ErrorType.UNEXPECTED, true, "Could not load a sprite sheet."));
return null;
Expand All @@ -101,11 +100,10 @@ public static MinicraftImage loadDefaultSkinSheet() {
}

public static void initScreen() {
screen = new Screen();
lightScreen = new Screen();

image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
screen.pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
screen = new Screen(image);
//lightScreen = new Screen();

hudSheet = new LinkedSprite(SpriteType.Gui, "hud");

canvas.createBufferStrategy(3);
Expand All @@ -118,6 +116,8 @@ public static void initScreen() {
public static void render() {
if (screen == null) return; // No point in this if there's no gui... :P

screen.clear(0);

if (readyToRenderGameplay) {
renderLevel();
if (player.renderGUI) renderGui();
Expand All @@ -131,8 +131,13 @@ public static void render() {


BufferStrategy bs = canvas.getBufferStrategy(); // Creates a buffer strategy to determine how the graphics should be buffered.
Graphics g = bs.getDrawGraphics(); // Gets the graphics in which java draws the picture
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); // Draws a rect to fill the whole window (to cover last?)
Graphics2D g = (Graphics2D) bs.getDrawGraphics(); // Gets the graphics in which java draws the picture
g.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); // Draws a rect to fill the whole window (to cover last?)



// Flushes the screen to the renderer.
screen.flush();

// Scale the pixels.
int ww = getWindowSize().width;
Expand Down Expand Up @@ -213,10 +218,9 @@ private static void renderLevel() {

// This creates the darkness in the caves
if ((currentLevel != 3 || Updater.tickCount < Updater.dayLength / 4 || Updater.tickCount > Updater.dayLength / 2) && !isMode("minicraft.settings.mode.creative")) {
lightScreen.clear(0); // This doesn't mean that the pixel will be black; it means that the pixel will be DARK, by default; lightScreen is about light vs. dark, not necessarily a color. The light level it has is compared with the minimum light values in dither to decide whether to leave the cell alone, or mark it as "dark", which will do different things depending on the game level and time of day.
int brightnessMultiplier = player.potioneffects.containsKey(PotionType.Light) ? 12 : 8; // Brightens all light sources by a factor of 1.5 when the player has the Light potion effect. (8 above is normal)
level.renderLight(lightScreen, xScroll, yScroll, brightnessMultiplier); // Finds (and renders) all the light from objects (like the player, lanterns, and lava).
screen.overlay(lightScreen, currentLevel, xScroll, yScroll); // Overlays the light screen over the main screen.
level.renderLight(screen, xScroll, yScroll, brightnessMultiplier); // Finds (and renders) all the light from objects (like the player, lanterns, and lava).
screen.overlay(currentLevel, xScroll, yScroll); // Overlays the light screen over the main screen.
}
}

Expand Down
130 changes: 98 additions & 32 deletions src/client/java/minicraft/gfx/MinicraftImage.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package minicraft.gfx;

import minicraft.core.CrashHandler;
import minicraft.gfx.SpriteLinker.LinkedSprite;
import minicraft.util.Logging;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Range;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Objects;

/**
* Although we have SpriteLinker, we still need SpriteSheet for buffering.
Expand All @@ -20,43 +22,46 @@ public class MinicraftImage {
public final int[] pixels; // Integer array of the image's pixels

/**
* Default with maximum size of image.
* @param image The image to be added.
* @throws IOException if I/O exception occurs.
* Initializes a {@code MinicraftImage} instance from the provided size.
* All values are filled with zero after construction.
* @param width the final width of this image
* @param height the final height of this image
* @throws IllegalArgumentException if either {@code width} or {@code height} is zero or negative
*/
public MinicraftImage(BufferedImage image) throws IOException {
public MinicraftImage(int width, int height) {
if (width < 1) throw new IllegalArgumentException("width cannot be zero or negative");
if (height < 1) throw new IllegalArgumentException("height cannot be zero or negative");

this.width = width;
this.height = height;
pixels = new int[width * height];
}

/**
* Constructs a {@code MinicraftImage} with the maximum size of dimension supplied from the source {@code BufferedImage}.
* @param image the {@code BufferedImage} to be constructed from
* @see LinkedSprite
*/
public MinicraftImage(@NotNull BufferedImage image) {
this(image, image.getWidth(), image.getHeight());
}

/**
* Custom size.
* @param image The image to be added.
* @param width The width of the {@link MinicraftImage} to be applied to the {@link LinkedSprite}.
* @param height The height of the {@link MinicraftImage} to be applied to the {@link LinkedSprite}.
* @throws IOException
* Constructs a {@code MinicraftImage} with the given size from the source {@code BufferedImage}.
* If the requested size is out of the source's dimension, the remaining values will be left {@code 0}.
* @param image the {@code BufferedImage} to be constructed from
* @param width the requested width for this image, must be a non-zero natural number
* @param height the requested height for this image, must be a non-zero natural number
* @throws IllegalArgumentException if either {@code width} or {@code height} is zero or negative
* @see LinkedSprite
*/
public MinicraftImage(BufferedImage image, int width, int height) throws IOException {
if (width % 8 != 0)
CrashHandler.errorHandle(new IllegalArgumentException("Invalid width of SpriteSheet."), new CrashHandler.ErrorInfo(
"Invalid SpriteSheet argument.", CrashHandler.ErrorInfo.ErrorType.HANDLED,
String.format("Invalid width: {}, SpriteSheet width should be a multiple of 8.")
));
if (height % 8 != 0)
CrashHandler.errorHandle(new IllegalArgumentException("Invalid height of SpriteSheet."), new CrashHandler.ErrorInfo(
"Invalid SpriteSheet argument.", CrashHandler.ErrorInfo.ErrorType.HANDLED,
String.format("Invalid height: {}, SpriteSheet height should be a multiple of 8.")
));

// Sets width and height to that of the image
this.width = width - width % 8;
this.height = height - height % 8;

// If size is bigger than image source, throw error.
if (this.width > image.getWidth() || this.height > image.getHeight()) {
throw new IOException(new IndexOutOfBoundsException(String.format("Requested size %s*%s out of source size %s*%s",
this.width, this.height, image.getWidth(), image.getHeight())));
}
public MinicraftImage(@NotNull BufferedImage image, int width, int height) {
Objects.requireNonNull(image, "image");
if (width < 1) throw new IllegalArgumentException("width cannot be zero or negative");
if (height < 1) throw new IllegalArgumentException("height cannot be zero or negative");

this.width = width;
this.height = height;
pixels = image.getRGB(0, 0, width, height, null, 0, width); // Gets the color array of the image pixels

// Applying the RGB array into Minicraft rendering system 25 bits RBG array.
Expand Down Expand Up @@ -87,4 +92,65 @@ public MinicraftImage(BufferedImage image, int width, int height) throws IOExcep
pixels[i] = (transparent << 24) + red + green + blue;
}
}

/**
* Creates a {@link MinicraftImage} from the provided image with default dimension validation.
* @param image the {@code BufferedImage} to be constructed from
* @return the constructed {@code Minicraft}
* @throws MinicraftImageDimensionIncompatibleException if the image's dimension is not a multiple of 8
*/
public static MinicraftImage createDefaultCompatible(BufferedImage image) throws MinicraftImageDimensionIncompatibleException {
validateImageDimension(image);
return new MinicraftImage(image);
}

/**
* Validates if the provided image is compatible with the game's general sprite rendering system.
* @param image The image to be validated
* @throws MinicraftImageDimensionIncompatibleException if the image's dimension is not a multiple of 8
*/
public static void validateImageDimension(BufferedImage image) throws MinicraftImageDimensionIncompatibleException {
if (image.getHeight() % 8 != 0 || image.getWidth() % 8 != 0)
throw new MinicraftImageDimensionIncompatibleException(image.getWidth(), image.getHeight());
}

/**
* Validates if the provided image is respective to the required size.
* @param image The image to be validated
* @param width The requested width
* @param height The requested height
* @throws MinicraftImageRequestOutOfBoundsException if the requested size is out of the image's dimension
*/
public static void validateImageDimension(BufferedImage image, int width, int height)
throws MinicraftImageRequestOutOfBoundsException {
if (image.getWidth() < width || image.getHeight() < height)
throw new MinicraftImageRequestOutOfBoundsException(image.getWidth(), image.getHeight(), width, height);
}

public static class MinicraftImageDimensionIncompatibleException extends Exception {
private final int width, height;

public MinicraftImageDimensionIncompatibleException(int width, int height) {
this.width = width;
this.height = height;
}

public final int getWidth() { return width; }
public final int getHeight() { return height; }
}

public static class MinicraftImageRequestOutOfBoundsException extends Exception {
private final int srcW, srcH, rqtW, rqtH;
public MinicraftImageRequestOutOfBoundsException(int srcW, int srcH, int rqtW, int rqtH) {
this.srcW = srcW;
this.srcH = srcH;
this.rqtW = rqtW;
this.rqtH = rqtH;
}

public final int getSourceWidth() { return srcW; }
public final int getSourceHeight() { return srcH; }
public final int getRequestedWidth() { return rqtW; }
public final int getRequestedHeight() { return rqtH; }
}
}
Loading

0 comments on commit 48a208f

Please sign in to comment.