diff --git a/src/main/java/org/teacon/slides/GifDecoder.java b/src/main/java/org/teacon/slides/GifDecoder.java deleted file mode 100644 index d3206a4..0000000 --- a/src/main/java/org/teacon/slides/GifDecoder.java +++ /dev/null @@ -1,721 +0,0 @@ -package org.teacon.slides; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; - -/** Class GifDecoder - Decodes a GIF file into one or more frames. - * - * Example: - * - *
- * {
- *     @code
- *     GifDecoder d = new GifDecoder();
- *     d.read("sample.gif");
- *     int n = d.getFrameCount();
- *     for (int i = 0; i < n; i++) {
- *         BufferedImage frame = d.getFrame(i); // frame i
- *         int t = d.getDelay(i); // display duration of frame in milliseconds
- *         // do something with frame
- *     }
- * }
- * 
- * - * No copyright asserted on the source code of this class. May be used for - * any purpose, however, refer to the Unisys LZW patent for any additional - * restrictions. Please forward any corrections to questions at fmsware.com. - * - * @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's ImageMagick. - * @version 1.03 November 2003 */ - -public class GifDecoder { - - /** File read status: No errors. */ - public static final int STATUS_OK = 0; - - /** File read status: Error decoding file (may be partially decoded) */ - public static final int STATUS_FORMAT_ERROR = 1; - - /** File read status: Unable to open source. */ - public static final int STATUS_OPEN_ERROR = 2; - - protected BufferedInputStream in; - protected int status; - - protected int width; // full image width - protected int height; // full image height - protected boolean gctFlag; // global color table used - protected int gctSize; // size of global color table - protected int loopCount = 1; // iterations; 0 = repeat forever - - protected int[] gct; // global color table - protected int[] lct; // local color table - protected int[] act; // active color table - - protected int bgIndex; // background color index - protected int bgColor; // background color - protected int lastBgColor; // previous bg color - protected int pixelAspect; // pixel aspect ratio - - protected boolean lctFlag; // local color table flag - protected boolean interlace; // interlace flag - protected int lctSize; // local color table size - - protected int ix, iy, iw, ih; // current image rectangle - protected Rectangle lastRect; // last image rect - protected BufferedImage image; // current frame - protected BufferedImage lastImage; // previous frame - - protected byte[] block = new byte[256]; // current data block - protected int blockSize = 0; // block size - - // last graphic control extension info - protected int dispose = 0; - // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev - protected int lastDispose = 0; - protected boolean transparency = false; // use transparent color - protected int delay = 0; // delay in milliseconds - protected int transIndex; // transparent color index - - protected static final int MaxStackSize = 4096; - // max decoder pixel stack size - - // LZW decoder working arrays - protected short[] prefix; - protected byte[] suffix; - protected byte[] pixelStack; - protected byte[] pixels; - - protected ArrayList frames; // frames read from current file - protected int frameCount; - - static class GifFrame { - public GifFrame(BufferedImage im, int del) { - image = im; - delay = del; - } - - public BufferedImage image; - public int delay; - } - - /** Gets display duration for specified frame. - * - * @param n - * int index of frame - * @return delay in milliseconds */ - public int getDelay(int n) { - // - delay = -1; - if ((n >= 0) && (n < frameCount)) { - delay = ((GifFrame) frames.get(n)).delay; - } - return delay; - } - - /** Gets the number of frames read from file. - * - * @return frame count */ - public int getFrameCount() { - return frameCount; - } - - /** Gets the first (or only) image read. - * - * @return BufferedImage containing first frame, or null if none. */ - public BufferedImage getImage() { - return getFrame(0); - } - - /** Gets the "Netscape" iteration count, if any. - * A count of 0 means repeat indefinitiely. - * - * @return iteration count if one was specified, else 1. */ - public int getLoopCount() { - return loopCount; - } - - /** Creates new frame image from current data (and previous - * frames as specified by their disposition codes). */ - protected void setPixels() { - // expose destination image's pixels as int array - int[] dest = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - - // fill in starting image contents based on last image's dispose code - if (lastDispose > 0) { - if (lastDispose == 3) { - // use image before last - int n = frameCount - 2; - if (n > 0) { - lastImage = getFrame(n - 1); - } else { - lastImage = null; - } - } - - if (lastImage != null) { - int[] prev = ((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData(); - System.arraycopy(prev, 0, dest, 0, width * height); - // copy pixels - - if (lastDispose == 2) { - // fill last image rect area with background color - Graphics2D g = image.createGraphics(); - Color c = null; - if (transparency) { - c = new Color(0, 0, 0, 0); // assume background is transparent - } else { - c = new Color(lastBgColor); // use given background color - } - g.setColor(c); - g.setComposite(AlphaComposite.Src); // replace area - g.fill(lastRect); - g.dispose(); - } - } - } - - // copy each source line to the appropriate place in the destination - int pass = 1; - int inc = 8; - int iline = 0; - for (int i = 0; i < ih; i++) { - int line = i; - if (interlace) { - if (iline >= ih) { - pass++; - switch (pass) { - case 2: - iline = 4; - break; - case 3: - iline = 2; - inc = 4; - break; - case 4: - iline = 1; - inc = 2; - } - } - line = iline; - iline += inc; - } - line += iy; - if (line < height) { - int k = line * width; - int dx = k + ix; // start of line in dest - int dlim = dx + iw; // end of dest line - if ((k + width) < dlim) { - dlim = k + width; // past dest edge - } - int sx = i * iw; // start of line in source - while (dx < dlim) { - // map color and insert in destination - int index = (pixels[sx++]) & 0xff; - int c = act[index]; - if (c != 0) { - dest[dx] = c; - } - dx++; - } - } - } - } - - /** Gets the image contents of frame n. - * - * @return BufferedImage representation of frame, or null if n is invalid. */ - public BufferedImage getFrame(int n) { - BufferedImage im = null; - if ((n >= 0) && (n < frameCount)) { - im = ((GifFrame) frames.get(n)).image; - } - return im; - } - - /** Gets image size. - * - * @return GIF image dimensions */ - public Dimension getFrameSize() { - return new Dimension(width, height); - } - - /** Reads GIF image from stream - * - * @param is - * BufferedInputStream containing GIF file. - * @return read status code (0 = no errors) */ - public int read(BufferedInputStream is) { - init(); - if (is != null) { - in = is; - readHeader(); - if (!err()) { - readContents(); - if (frameCount < 0) { - status = STATUS_FORMAT_ERROR; - } - } - } else { - status = STATUS_OPEN_ERROR; - } - try { - is.close(); - } catch (IOException e) { - } - return status; - } - - /** Reads GIF image from stream - * - * @param is - * InputStream containing GIF file. - * @return read status code (0 = no errors) */ - public int read(InputStream is) { - init(); - if (is != null) { - if (!(is instanceof BufferedInputStream)) - is = new BufferedInputStream(is); - in = (BufferedInputStream) is; - readHeader(); - if (!err()) { - readContents(); - if (frameCount < 0) { - status = STATUS_FORMAT_ERROR; - } - } - } else { - status = STATUS_OPEN_ERROR; - } - try { - is.close(); - } catch (IOException e) { - } - return status; - } - - /** Reads GIF file from specified file/URL source - * (URL assumed if name contains ":/" or "file:") - * - * @param name - * String containing source - * @return read status code (0 = no errors) */ - public int read(String name) { - status = STATUS_OK; - try { - name = name.trim().toLowerCase(); - if ((name.indexOf("file:") >= 0) || (name.indexOf(":/") > 0)) { - URL url = new URL(name); - in = new BufferedInputStream(url.openStream()); - } else { - in = new BufferedInputStream(new FileInputStream(name)); - } - status = read(in); - } catch (IOException e) { - status = STATUS_OPEN_ERROR; - } - - return status; - } - - /** Decodes LZW image data into pixel array. - * Adapted from John Cristy's ImageMagick. */ - protected void decodeImageData() { - int NullCode = -1; - int npix = iw * ih; - int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, - data_size, first, top, bi, pi; - - if ((pixels == null) || (pixels.length < npix)) { - pixels = new byte[npix]; // allocate new pixel array - } - if (prefix == null) - prefix = new short[MaxStackSize]; - if (suffix == null) - suffix = new byte[MaxStackSize]; - if (pixelStack == null) - pixelStack = new byte[MaxStackSize + 1]; - - // Initialize GIF data stream decoder. - - data_size = read(); - clear = 1 << data_size; - end_of_information = clear + 1; - available = clear + 2; - old_code = NullCode; - code_size = data_size + 1; - code_mask = (1 << code_size) - 1; - for (code = 0; code < clear; code++) { - prefix[code] = 0; - suffix[code] = (byte) code; - } - - // Decode GIF pixel stream. - - datum = bits = count = first = top = pi = bi = 0; - - for (i = 0; i < npix;) { - if (top == 0) { - if (bits < code_size) { - // Load bytes until there are enough bits for a code. - if (count == 0) { - // Read a new data block. - count = readBlock(); - if (count <= 0) - break; - bi = 0; - } - datum += ((block[bi]) & 0xff) << bits; - bits += 8; - bi++; - count--; - continue; - } - - // Get the next code. - - code = datum & code_mask; - datum >>= code_size; - bits -= code_size; - - // Interpret the code - - if ((code > available) || (code == end_of_information)) - break; - if (code == clear) { - // Reset decoder. - code_size = data_size + 1; - code_mask = (1 << code_size) - 1; - available = clear + 2; - old_code = NullCode; - continue; - } - if (old_code == NullCode) { - pixelStack[top++] = suffix[code]; - old_code = code; - first = code; - continue; - } - in_code = code; - if (code == available) { - pixelStack[top++] = (byte) first; - code = old_code; - } - while (code > clear) { - pixelStack[top++] = suffix[code]; - code = prefix[code]; - } - first = (suffix[code]) & 0xff; - - // Add a new string to the string table, - - if (available >= MaxStackSize) { - pixelStack[top++] = (byte) first; - continue; - } - pixelStack[top++] = (byte) first; - prefix[available] = (short) old_code; - suffix[available] = (byte) first; - available++; - if (((available & code_mask) == 0) && (available < MaxStackSize)) { - code_size++; - code_mask += available; - } - old_code = in_code; - } - - // Pop a pixel off the pixel stack. - - top--; - pixels[pi++] = pixelStack[top]; - i++; - } - - for (i = pi; i < npix; i++) { - pixels[i] = 0; // clear missing pixels - } - - } - - /** Returns true if an error was encountered during reading/decoding */ - protected boolean err() { - return status != STATUS_OK; - } - - /** Initializes or re-initializes reader */ - protected void init() { - status = STATUS_OK; - frameCount = 0; - frames = new ArrayList(); - gct = null; - lct = null; - } - - /** Reads a single byte from the input stream. */ - protected int read() { - int curByte = 0; - try { - curByte = in.read(); - } catch (IOException e) { - status = STATUS_FORMAT_ERROR; - } - return curByte; - } - - /** Reads next variable length block from input. - * - * @return number of bytes stored in "buffer" */ - protected int readBlock() { - blockSize = read(); - int n = 0; - if (blockSize > 0) { - try { - int count = 0; - while (n < blockSize) { - count = in.read(block, n, blockSize - n); - if (count == -1) - break; - n += count; - } - } catch (IOException e) { - } - - if (n < blockSize) { - status = STATUS_FORMAT_ERROR; - } - } - return n; - } - - /** Reads color table as 256 RGB integer values - * - * @param ncolors - * int number of colors to read - * @return int array containing 256 colors (packed ARGB with full alpha) */ - protected int[] readColorTable(int ncolors) { - int nbytes = 3 * ncolors; - int[] tab = null; - byte[] c = new byte[nbytes]; - int n = 0; - try { - n = in.read(c); - } catch (IOException e) { - } - if (n < nbytes) { - status = STATUS_FORMAT_ERROR; - } else { - tab = new int[256]; // max size to avoid bounds checks - int i = 0; - int j = 0; - while (i < ncolors) { - int r = (c[j++]) & 0xff; - int g = (c[j++]) & 0xff; - int b = (c[j++]) & 0xff; - tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; - } - } - return tab; - } - - /** Main file parser. Reads GIF content blocks. */ - protected void readContents() { - // read GIF file content blocks - boolean done = false; - while (!(done || err())) { - int code = read(); - switch (code) { - - case 0x2C: // image separator - readImage(); - break; - - case 0x21: // extension - code = read(); - switch (code) { - case 0xf9: // graphics control extension - readGraphicControlExt(); - break; - - case 0xff: // application extension - readBlock(); - String app = ""; - for (int i = 0; i < 11; i++) { - app += (char) block[i]; - } - if (app.equals("NETSCAPE2.0")) { - readNetscapeExt(); - } else - skip(); // don't care - break; - - default: // uninteresting extension - skip(); - } - break; - - case 0x3b: // terminator - done = true; - break; - - case 0x00: // bad byte, but keep going and see what happens - break; - - default: - status = STATUS_FORMAT_ERROR; - } - } - } - - /** Reads Graphics Control Extension values */ - protected void readGraphicControlExt() { - read(); // block size - int packed = read(); // packed fields - dispose = (packed & 0x1c) >> 2; // disposal method - if (dispose == 0) { - dispose = 1; // elect to keep old image if discretionary - } - transparency = (packed & 1) != 0; - delay = readShort() * 10; // delay in milliseconds - transIndex = read(); // transparent color index - read(); // block terminator - } - - /** Reads GIF file header information. */ - protected void readHeader() { - String id = ""; - for (int i = 0; i < 6; i++) { - id += (char) read(); - } - if (!id.startsWith("GIF")) { - status = STATUS_FORMAT_ERROR; - return; - } - - readLSD(); - if (gctFlag && !err()) { - gct = readColorTable(gctSize); - bgColor = gct[bgIndex]; - } - } - - /** Reads next frame image */ - protected void readImage() { - ix = readShort(); // (sub)image position & size - iy = readShort(); - iw = readShort(); - ih = readShort(); - - int packed = read(); - lctFlag = (packed & 0x80) != 0; // 1 - local color table flag - interlace = (packed & 0x40) != 0; // 2 - interlace flag - // 3 - sort flag - // 4-5 - reserved - lctSize = 2 << (packed & 7); // 6-8 - local color table size - - if (lctFlag) { - lct = readColorTable(lctSize); // read table - act = lct; // make local table active - } else { - act = gct; // make global table active - if (bgIndex == transIndex) - bgColor = 0; - } - int save = 0; - if (transparency) { - save = act[transIndex]; - act[transIndex] = 0; // set transparent color if specified - } - - if (act == null) { - status = STATUS_FORMAT_ERROR; // no color table defined - } - - if (err()) - return; - - decodeImageData(); // decode pixel data - skip(); - - if (err()) - return; - - frameCount++; - - // create new image to receive frame data - image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); - - setPixels(); // transfer pixel data to image - - frames.add(new GifFrame(image, delay)); // add image to frame list - - if (transparency) { - act[transIndex] = save; - } - resetFrame(); - - } - - /** Reads Logical Screen Descriptor */ - protected void readLSD() { - - // logical screen size - width = readShort(); - height = readShort(); - - // packed fields - int packed = read(); - gctFlag = (packed & 0x80) != 0; // 1 : global color table flag - // 2-4 : color resolution - // 5 : gct sort flag - gctSize = 2 << (packed & 7); // 6-8 : gct size - - bgIndex = read(); // background color index - pixelAspect = read(); // pixel aspect ratio - } - - /** Reads Netscape extenstion to obtain iteration count */ - protected void readNetscapeExt() { - do { - readBlock(); - if (block[0] == 1) { - // loop count sub-block - int b1 = (block[1]) & 0xff; - int b2 = (block[2]) & 0xff; - loopCount = (b2 << 8) | b1; - } - } while ((blockSize > 0) && !err()); - } - - /** Reads next 16-bit value, LSB first */ - protected int readShort() { - // read 16-bit value, LSB first - return read() | (read() << 8); - } - - /** Resets frame state for reading next image. */ - protected void resetFrame() { - lastDispose = dispose; - lastRect = new Rectangle(ix, iy, iw, ih); - lastImage = image; - lastBgColor = bgColor; - int dispose = 0; - boolean transparency = false; - int delay = 0; - lct = null; - } - - /** Skips variable length blocks up to and including - * next zero length block. */ - protected void skip() { - do { - readBlock(); - } while ((blockSize > 0) && !err()); - } -} diff --git a/src/main/java/org/teacon/slides/projector/ProjectorBlock.java b/src/main/java/org/teacon/slides/projector/ProjectorBlock.java index 6089ba9..3d8a604 100644 --- a/src/main/java/org/teacon/slides/projector/ProjectorBlock.java +++ b/src/main/java/org/teacon/slides/projector/ProjectorBlock.java @@ -1,7 +1,8 @@ package org.teacon.slides.projector; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.*; +import com.mojang.math.Matrix3f; +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector4f; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.StringRepresentable; @@ -23,11 +24,8 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.util.Arrays; import java.util.Locale; @@ -205,11 +203,12 @@ public void transform(Vector4f vector) { vector.transform(mMatrix); } - @OnlyIn(Dist.CLIENT) - public void transform(PoseStack pStack) { - PoseStack.Pose last = pStack.last(); - last.pose().multiply(mMatrix); - last.normal().mul(mNormal); + public void transform(Matrix4f poseMatrix) { + poseMatrix.multiply(mMatrix); + } + + public void transform(Matrix3f normalMatrix) { + normalMatrix.mul(mNormal); } @Nonnull diff --git a/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java b/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java index b7e82f1..99827c5 100644 --- a/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java +++ b/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java @@ -1,6 +1,11 @@ package org.teacon.slides.projector; +import com.mojang.math.Matrix3f; +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector3f; +import com.mojang.math.Vector4f; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; @@ -13,6 +18,8 @@ import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.phys.AABB; import net.minecraftforge.network.NetworkHooks; import org.teacon.slides.Registries; import org.teacon.slides.renderer.ProjectorWorldRender; @@ -140,4 +147,45 @@ public CompoundTag getUpdateTag() { public void handleUpdateTag(CompoundTag tag) { load(tag); } + + @Override + public AABB getRenderBoundingBox() { + Matrix3f normal = Matrix3f.createScaleMatrix(1, 1, 1); // identity + Matrix4f pose = Matrix4f.createScaleMatrix(1, 1, 1); // identity + this.transformToSlideSpace(pose, normal); + + Vector3f nHalf = new Vector3f(0, 0.5f, 0); + Vector4f v00 = new Vector4f(0, 0, 0, 1); + Vector4f v01 = new Vector4f(1, 0, 1, 1); + nHalf.transform(normal); + v00.transform(pose); + v01.transform(pose); + + AABB base = new AABB(v00.x(), v00.y(), v00.z(), v01.x(), v01.y(), v01.z()); + return base.move(this.getBlockPos()).inflate(nHalf.x(), nHalf.y(), nHalf.z()); + } + + public void transformToSlideSpace(Matrix4f pose, Matrix3f normal) { + BlockState state = getBlockState(); + // get direction + Direction direction = state.getValue(BlockStateProperties.FACING); + // get internal rotation + ProjectorBlock.InternalRotation rotation = state.getValue(ProjectorBlock.ROTATION); + // matrix 1: translation to block center + pose.multiplyWithTranslation(0.5f, 0.5f, 0.5f); + // matrix 2: rotation + pose.multiply(direction.getRotation()); + normal.mul(direction.getRotation()); + // matrix 3: translation to block surface + pose.multiplyWithTranslation(0.0f, 0.5f, 0.0f); + // matrix 4: internal rotation + rotation.transform(pose); + rotation.transform(normal); + // matrix 5: translation for slide + pose.multiplyWithTranslation(-0.5F, 0.0F, 0.5F - mHeight); + // matrix 6: offset for slide + pose.multiplyWithTranslation(mOffsetX, -mOffsetZ, mOffsetY); + // matrix 7: scaling + pose.multiply(Matrix4f.createScaleMatrix(mWidth, 1.0F, mHeight)); + } } diff --git a/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java b/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java index 2247af4..2f5aa25 100644 --- a/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java +++ b/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java @@ -1,13 +1,17 @@ package org.teacon.slides.renderer; -import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import com.mojang.math.Matrix4f; +import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.core.Direction; +import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.phys.AABB; import org.teacon.slides.projector.ProjectorBlock; import org.teacon.slides.projector.ProjectorBlockEntity; @@ -30,6 +34,10 @@ public ProjectorRenderer onCreate(@SuppressWarnings("unused") @Nonnull BlockEnti @Override public void render(ProjectorBlockEntity tile, float partialTick, PoseStack pStack, MultiBufferSource source, int packedLight, int packedOverlay) { + + // render bounding box for DEBUG +// renderBoundingBox(pStack, tile); + // always update slide state final Slide slide = SlideState.getSlide(tile.mLocation); if (slide == null) { @@ -40,32 +48,15 @@ public void render(ProjectorBlockEntity tile, float partialTick, PoseStack pStac if ((color & 0xFF000000) == 0) { return; } - ProjectorBlock.InternalRotation rotation = tile.getBlockState().getValue(ProjectorBlock.ROTATION); - final boolean flipped = rotation.isFlipped(); pStack.pushPose(); - final float width = tile.mWidth, height = tile.mHeight; - - Direction facing = tile.getBlockState().getValue(BlockStateProperties.FACING); - // matrix 1: translation to block center - pStack.translate(0.5, 0.5, 0.5); - // matrix 2: rotation - pStack.mulPose(facing.getRotation()); - // matrix 3: translation to block surface - pStack.translate(0.0, 0.5, 0.0); - // matrix 4: internal rotation - rotation.transform(pStack); - // matrix 5: translation for slide - pStack.translate(-0.5F, 0.0F, 0.5F - height); - // matrix 6: offset for slide - pStack.translate(tile.mOffsetX, -tile.mOffsetZ, tile.mOffsetY); - // matrix 7: scaling - pStack.scale(width, 1.0F, height); - PoseStack.Pose last = pStack.last(); + tile.transformToSlideSpace(last.pose(), last.normal()); + + final boolean flipped = tile.getBlockState().getValue(ProjectorBlock.ROTATION).isFlipped(); - slide.render(source, last.pose(), last.normal(), width, height, color, LightTexture.FULL_BRIGHT, + slide.render(source, last.pose(), last.normal(), tile.mWidth, tile.mHeight, color, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, flipped || tile.mDoubleSided, !flipped || tile.mDoubleSided, SlideState.getAnimationTick(), partialTick); @@ -83,4 +74,65 @@ public boolean shouldRenderOffScreen(ProjectorBlockEntity tile) { public int getViewDistance() { return 256; } + + public static void renderBoundingBox(PoseStack matrixStack, ProjectorBlockEntity tile) { + AABB box = tile.getRenderBoundingBox(); + BlockPos pos = tile.getBlockPos(); + Tesselator tessellator = Tesselator.getInstance(); + BufferBuilder buffer = tessellator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + RenderSystem.depthMask(false); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + matrixStack.pushPose(); + + matrixStack.translate(-pos.getX(), -pos.getY(), -pos.getZ()); + + float minX = (float) box.minX; + float minY = (float) box.minY; + float minZ = (float) box.minZ; + float maxX = (float) box.maxX; + float maxY = (float) box.maxY; + float maxZ = (float) box.maxZ; + + Matrix4f mat = matrixStack.last().pose(); + float b = 1; + buffer.vertex(mat, minX, minY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, minX, minY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, minX, maxY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, minX, maxY, minZ).color(0, 0, b, 0.5f).endVertex(); + + buffer.vertex(mat, maxX, minY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, maxY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, maxY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, minY, maxZ).color(0, 0, b, 0.5f).endVertex(); + + b = 0.5f; + buffer.vertex(mat, minX, minY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, minY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, minY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, minX, minY, maxZ).color(0, 0, b, 0.5f).endVertex(); + + b = 1; + buffer.vertex(mat, minX, maxY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, minX, maxY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, maxY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, maxY, minZ).color(0, 0, b, 0.5f).endVertex(); + + b = 0.8f; + buffer.vertex(mat, minX, minY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, minX, maxY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, maxY, minZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, minY, minZ).color(0, 0, b, 0.5f).endVertex(); + + buffer.vertex(mat, minX, minY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, minY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, maxX, maxY, maxZ).color(0, 0, b, 0.5f).endVertex(); + buffer.vertex(mat, minX, maxY, maxZ).color(0, 0, b, 0.5f).endVertex(); + + tessellator.end(); + RenderSystem.depthMask(true); + matrixStack.popPose(); + } } diff --git a/src/main/java/org/teacon/slides/renderer/SlideState.java b/src/main/java/org/teacon/slides/renderer/SlideState.java index 2f277d0..a17fc17 100644 --- a/src/main/java/org/teacon/slides/renderer/SlideState.java +++ b/src/main/java/org/teacon/slides/renderer/SlideState.java @@ -1,6 +1,5 @@ package org.teacon.slides.renderer; -import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; import net.minecraftforge.api.distmarker.Dist; @@ -13,8 +12,6 @@ import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL46C; import org.lwjgl.opengl.GLCapabilities; -import org.lwjgl.system.MemoryUtil; -import org.teacon.slides.GifDecoder; import org.teacon.slides.SlideShow; import org.teacon.slides.cache.ImageCache; import org.teacon.slides.texture.FrameTexture; @@ -28,10 +25,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; -import java.nio.ByteBuffer; import java.util.Iterator; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; @@ -206,29 +201,9 @@ public String toString() { private static CompletableFuture createTexture(byte[] data) { return CompletableFuture.supplyAsync(() -> { if (isGif(data)) { - try (ByteArrayInputStream stream = new ByteArrayInputStream(data)) { - GifDecoder gif = new GifDecoder(); - int status = gif.read(stream); - if (status == GifDecoder.STATUS_OK) { - return new GifTexture(gif, sMaxAnisotropic); - } else { - SlideShow.LOGGER.error("Failed to decode gif: {}", status); - } - } catch (IOException exception) { - SlideShow.LOGGER.error("Failed to read gif", exception); - } - } - // copy to native memory - ByteBuffer buffer = MemoryUtil.memAlloc(data.length) - .put(data) - .rewind(); - // specify null to use image intrinsic format - try (NativeImage image = NativeImage.read(null, buffer)) { - return new NativeImageTexture(image, sMaxAnisotropic); - } catch (Throwable t) { - throw new CompletionException(t); - } finally { - MemoryUtil.memFree(buffer); + return new GifTexture(data, sMaxAnisotropic); + } else { + return new NativeImageTexture(data, sMaxAnisotropic); } }, RENDER_EXECUTOR); } diff --git a/src/main/java/org/teacon/slides/texture/FrameTexture.java b/src/main/java/org/teacon/slides/texture/FrameTexture.java index e7392d4..8198e98 100644 --- a/src/main/java/org/teacon/slides/texture/FrameTexture.java +++ b/src/main/java/org/teacon/slides/texture/FrameTexture.java @@ -1,6 +1,6 @@ package org.teacon.slides.texture; -public sealed interface FrameTexture permits NativeImageTexture, GifTexture { +public sealed interface FrameTexture permits GifTexture, NativeImageTexture { int currentTextureID(long tick, float partialTick); diff --git a/src/main/java/org/teacon/slides/texture/GifTexture.java b/src/main/java/org/teacon/slides/texture/GifTexture.java index ed6197d..6d0b1c8 100644 --- a/src/main/java/org/teacon/slides/texture/GifTexture.java +++ b/src/main/java/org/teacon/slides/texture/GifTexture.java @@ -2,15 +2,16 @@ import com.mojang.blaze3d.platform.GlStateManager; import net.minecraft.util.Mth; -import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.GL11; +import org.lwjgl.PointerBuffer; import org.lwjgl.opengl.GL46C; -import org.teacon.slides.GifDecoder; +import org.lwjgl.stb.STBImage; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; -import java.awt.image.BufferedImage; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.Arrays; +import java.util.concurrent.CompletionException; import static org.lwjgl.opengl.GL11C.*; import static org.lwjgl.opengl.GL12C.*; @@ -19,55 +20,93 @@ public final class GifTexture implements FrameTexture { private static final int TICK_AS_MILLIS = 1000 / 20; - - private final int[] textures; + private static final int MAX_WIDTH = 4096; + private static final int MAX_HEIGHT = 2160; + private static final int MAX_Frame = 1024; + private final float sMaxAnisotropic; + private final int[] pixels; + public final int width, height, channelCount, frameCount; private final long[] delay; private final long duration; - private final float sMaxAnisotropic; - private final GifDecoder gif; - - public GifTexture(GifDecoder gif, float sMaxAnisotropic) { - this.sMaxAnisotropic = sMaxAnisotropic; - delay = new long[gif.getFrameCount()]; - long time = 0; - for (int i = 0; i < gif.getFrameCount(); i++) { - delay[i] = time; - time += gif.getDelay(i); + private final int[] textures; + + public GifTexture(byte[] data, float sMaxAnisotropic) { + // copy to native memory + final ByteBuffer buffer = MemoryUtil.memAlloc(data.length).put(data).rewind(); + + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer delaysBuffer = stack.mallocPointer(1); + IntBuffer x = stack.mallocInt(1); + IntBuffer y = stack.mallocInt(1); + IntBuffer z = stack.mallocInt(1); + IntBuffer channels = stack.mallocInt(1); + + ByteBuffer image = STBImage.stbi_load_gif_from_memory(buffer, delaysBuffer, x, y, z, channels, 0); + try { + if (image == null) { + throw new RuntimeException(STBImage.stbi_failure_reason()); // assumes program termination: if not, cleanup of resources is required + } + + channelCount = channels.get(); + + width = x.get(); + height = y.get(); + frameCount = z.get(); + + IntBuffer delaysIntBuffer = delaysBuffer.getIntBuffer(frameCount); + int[] delays = new int[frameCount]; + delaysIntBuffer.get(delays); + + delay = new long[this.frameCount]; + long time = 0; + for (int i = 0; i < this.frameCount; i++) { + delay[i] = time; + time += delays[i]; + } + duration = time; + + IntBuffer pixelData = image.asIntBuffer(); + pixels = new int[width * height * frameCount]; + pixelData.get(pixels); + + this.sMaxAnisotropic = sMaxAnisotropic; + textures = new int[this.frameCount]; + Arrays.fill(textures, -1); + } + finally { + if (image != null) { + STBImage.stbi_image_free(image); + } + } + } catch (Throwable t) { + throw new CompletionException(t); + } + finally { + MemoryUtil.memFree(buffer); } - this.gif = gif; - duration = time; - textures = new int[gif.getFrameCount()]; - Arrays.fill(textures, -1); } - private int uploadFrame(BufferedImage image) { + private int uploadFrame(int index) { + int texture = -1; + ByteBuffer buffer = null; try { - int width = image.getWidth(); - int height = image.getHeight(); - int[] pixels = new int[width * height]; - image.getRGB(0, 0, width, height, pixels, 0, width); - boolean hasAlpha = false; - if (image.getColorModel().hasAlpha()) { - for (int pixel : pixels) { - if ((pixel >> 24 & 0xFF) < 0xFF) { - hasAlpha = true; - break; - } - } - } + boolean hasAlpha = channelCount > 3; int bytesPerPixel = hasAlpha ? 4 : 3; - ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bytesPerPixel); - for (int pixel : pixels) { - buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component - buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component + + int size = width * height; + buffer = MemoryUtil.memAlloc(size * bytesPerPixel); + for (int i = index * size; i < (index + 1) * size; i++) { + int pixel = pixels[i]; buffer.put((byte) (pixel & 0xFF)); // Blue component + buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component + buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component if (hasAlpha) { buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component. Only for RGBA } } buffer.flip(); - final int texture = glGenTextures(); + texture = glGenTextures(); final int maxLevel = 31 - Integer.numberOfLeadingZeros(Math.max(width, height)); GlStateManager._bindTexture(texture); @@ -80,9 +119,9 @@ private int uploadFrame(BufferedImage image) { glTexParameterf(GL_TEXTURE_2D, GL46C.GL_TEXTURE_MAX_ANISOTROPY, sMaxAnisotropic); } - int internalFormat = hasAlpha ? GL_RGBA8 : GL_RGB8; for (int level = 0; level <= maxLevel; ++level) { - glTexImage2D(GL_TEXTURE_2D, level, internalFormat, width >> level, height >> level, 0, GL_RED, GL_UNSIGNED_BYTE, (IntBuffer) null); + glTexImage2D(GL_TEXTURE_2D, level, hasAlpha ? GL_RGBA8 : GL_RGB8, width >> level, height >> level, + 0, GL_RED, GL_UNSIGNED_BYTE, (IntBuffer) null); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -100,18 +139,28 @@ private int uploadFrame(BufferedImage image) { // specify pixel row alignment to 1 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, hasAlpha ? GL11.GL_RGBA8 : GL11.GL_RGB8, width, height, 0, hasAlpha ? GL11.GL_RGBA : GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, hasAlpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, buffer); // auto generate mipmap glGenerateMipmap(GL_TEXTURE_2D); return texture; } catch (Throwable e) { + if (texture != -1 ) { + if (texture > 0) { + GlStateManager._deleteTexture(texture); + } + } return -2; + } finally { + if (buffer != null) { + MemoryUtil.memFree(buffer); + } } } @Override public int currentTextureID(long tick, float partialTick) { + if (this.frameCount > MAX_Frame || width > MAX_WIDTH || height > MAX_HEIGHT) return -1; long time = duration > 0 ? (tick * TICK_AS_MILLIS + Mth.floor(partialTick * TICK_AS_MILLIS)) % duration : 0; int index = 0; for (int i = 0; i < delay.length; i++) { @@ -121,7 +170,7 @@ public int currentTextureID(long tick, float partialTick) { } } if (textures[index] == -1) { - textures[index] = uploadFrame(gif.getFrame(index)); + textures[index] = uploadFrame(index); } if (textures[index] == -2) { return -1; diff --git a/src/main/java/org/teacon/slides/texture/NativeImageTexture.java b/src/main/java/org/teacon/slides/texture/NativeImageTexture.java index 6d7f727..6d4efc1 100644 --- a/src/main/java/org/teacon/slides/texture/NativeImageTexture.java +++ b/src/main/java/org/teacon/slides/texture/NativeImageTexture.java @@ -4,9 +4,12 @@ import com.mojang.blaze3d.platform.NativeImage; import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import org.lwjgl.opengl.GL46C; +import org.lwjgl.system.MemoryUtil; import java.lang.reflect.Field; +import java.nio.ByteBuffer; import java.nio.IntBuffer; +import java.util.concurrent.CompletionException; import static org.lwjgl.opengl.GL11C.*; import static org.lwjgl.opengl.GL12C.*; @@ -21,53 +24,63 @@ public final class NativeImageTexture implements FrameTexture { IMAGE_PIXELS = ObfuscationReflectionHelper.findField(NativeImage.class, "f_84964_"); // pixels } - public NativeImageTexture(NativeImage image, float sMaxAnisotropic) { - texture = glGenTextures(); - final int width = image.getWidth(); - final int height = image.getHeight(); - final int maxLevel = 31 - Integer.numberOfLeadingZeros(Math.max(width, height)); - - GlStateManager._bindTexture(texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, maxLevel); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, maxLevel); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, 0.0F); - if (sMaxAnisotropic > 0) { - glTexParameterf(GL_TEXTURE_2D, GL46C.GL_TEXTURE_MAX_ANISOTROPY, sMaxAnisotropic); - } - - int internalFormat = image.format() == NativeImage.Format.RGB ? GL_RGB8 : GL_RGBA8; - for (int level = 0; level <= maxLevel; ++level) { - glTexImage2D(GL_TEXTURE_2D, level, internalFormat, width >> level, height >> level, - 0, GL_RED, GL_UNSIGNED_BYTE, (IntBuffer) null); - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - // specify 0 to use width * bbp - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); - - // specify pixel row alignment to 1 - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - try (image) { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, image.format().glFormat(), GL_UNSIGNED_BYTE, IMAGE_PIXELS.getLong(image)); + public NativeImageTexture(byte[] data, float sMaxAnisotropic) { + // copy to native memory + final ByteBuffer buffer = MemoryUtil.memAlloc(data.length).put(data).rewind(); + + // specify null to use image intrinsic format + try (NativeImage image = NativeImage.read(null, buffer)) { + texture = glGenTextures(); + final int width = image.getWidth(); + final int height = image.getHeight(); + final int maxLevel = 31 - Integer.numberOfLeadingZeros(Math.max(width, height)); + + GlStateManager._bindTexture(texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, maxLevel); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, maxLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, 0.0F); + if (sMaxAnisotropic > 0) { + glTexParameterf(GL_TEXTURE_2D, GL46C.GL_TEXTURE_MAX_ANISOTROPY, sMaxAnisotropic); + } + + int internalFormat = image.format() == NativeImage.Format.RGB ? GL_RGB8 : GL_RGBA8; + for (int level = 0; level <= maxLevel; ++level) { + glTexImage2D(GL_TEXTURE_2D, level, internalFormat, width >> level, height >> level, + 0, GL_RED, GL_UNSIGNED_BYTE, (IntBuffer) null); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // specify 0 to use width * bbp + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + + // specify pixel row alignment to 1 + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + try (image) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, image.format().glFormat(), GL_UNSIGNED_BYTE, IMAGE_PIXELS.getLong(image)); + } catch (Throwable t) { + GlStateManager._deleteTexture(texture); + texture = -1; + throw new AssertionError("Failed to get image pointer", t); + } + + // auto generate mipmap + glGenerateMipmap(GL_TEXTURE_2D); } catch (Throwable t) { - GlStateManager._deleteTexture(texture); - texture = -1; - throw new AssertionError("Failed to get image pointer", t); + throw new CompletionException(t); + } finally { + MemoryUtil.memFree(buffer); } - - // auto generate mipmap - glGenerateMipmap(GL_TEXTURE_2D); } @Override