package at.petrak.hexcasting.client.render; import at.petrak.hexcasting.api.block.HexBlockEntity; import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf; import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; import at.petrak.hexcasting.common.blocks.circles.BlockSlate; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.math.Matrix3f; import com.mojang.math.Matrix4f; import com.mojang.math.Vector3f; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Tuple; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.AttachFace; import net.minecraft.world.phys.Vec2; import java.awt.*; import java.awt.image.BufferedImage; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; public class PatternTextureManager { //TODO: remove if not needed anymore for comparison public static boolean useTextures = true; public static int repaintIndex = 0; public static int resolutionByBlockSize = 512; public static int paddingByBlockSize = 50; public static int circleRadiusByBlockSize = 8; public static int scaleLimit = 16; private static HashMap patternTextures = new HashMap<>(); public static String getPointsKey(List zappyPoints) { return zappyPoints.stream() .map(p -> String.format("(%f,%f)", p.x, p.y)) .collect(Collectors.joining(";")); } public static HexPatternPoints generateHexPatternPoints(HexBlockEntity tile, HexPattern pattern, float flowIrregular) { var stupidHash = tile.getBlockPos().hashCode(); var lines1 = pattern.toLines(1, Vec2.ZERO); var zappyPoints = RenderLib.makeZappy(lines1, RenderLib.findDupIndices(pattern.positions()), 10, 0.5f, 0f, flowIrregular, 0f, 1f, stupidHash); return new HexPatternPoints(zappyPoints); } public static void renderPatternForScroll(String pointsKey, PoseStack ps, MultiBufferSource bufSource, int light, List zappyPoints, int blockSize, boolean showStrokeOrder) { renderPattern(pointsKey, ps, bufSource, light, zappyPoints, blockSize, showStrokeOrder, false, true, false,false, true,-1); } public static void renderPatternForSlate(BlockEntitySlate tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) { if(tile.points == null) tile.points = generateHexPatternPoints(tile, pattern, 0.2f); boolean isOnWall = bs.getValue(BlockSlate.ATTACH_FACE) == AttachFace.WALL; boolean isOnCeiling = bs.getValue(BlockSlate.ATTACH_FACE) == AttachFace.CEILING; int facing = bs.getValue(BlockSlate.FACING).get2DDataValue(); renderPatternForBlockEntity(tile.points, ps, buffer, light, isOnWall, isOnCeiling, true, facing); } public static void renderPatternForAkashicBookshelf(BlockEntityAkashicBookshelf tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) { if(tile.points == null) tile.points = generateHexPatternPoints(tile, pattern, 0f); int facing = bs.getValue(BlockAkashicBookshelf.FACING).get2DDataValue(); renderPatternForBlockEntity(tile.points, ps, buffer, light, true, false, false, facing); } public static void renderPatternForBlockEntity(HexPatternPoints points, PoseStack ps, MultiBufferSource buffer, int light, boolean isOnWall, boolean isOnCeiling, boolean isSlate, int facing) { var oldShader = RenderSystem.getShader(); ps.pushPose(); RenderSystem.setShader(GameRenderer::getPositionTexShader); renderPattern(points.pointsKey, ps, buffer, light, points.zappyPoints, 1, false, true, isOnWall, isOnCeiling, isSlate, false, facing); ps.popPose(); RenderSystem.setShader(() -> oldShader); } public static void renderPattern(String pointsKey, PoseStack ps, MultiBufferSource bufSource, int light, List zappyPoints, int blockSize, boolean showStrokeOrder, boolean useFullSize, boolean isOnWall, boolean isOnCeiling, boolean isSlate, boolean isScroll, int facing) { ps.pushPose(); PoseStack.Pose last = ps.last(); Matrix4f mat = last.pose(); Matrix3f normal = last.normal(); float x = blockSize, y = blockSize, z = (-1f / 16f) - 0.01f; float nx = 0, ny = 0, nz = 0; //TODO: refactor this mess of a method if(isOnWall) { if(isScroll) { ps.translate(-blockSize / 2f, -blockSize / 2f, 1f / 32f); nz = -1; } else { ps.mulPose(Vector3f.ZP.rotationDegrees(180)); if(isSlate) { if(facing == 0) ps.translate(0,-1,0); if(facing == 1) ps.translate(-1,-1,0); if(facing == 2) ps.translate(-1,-1,1); if(facing == 3) ps.translate(0,-1,1); } else { z = -0.01f; if(facing == 0) ps.translate(0,-1,1); if(facing == 1) ps.translate(0,-1,0); if(facing == 2) ps.translate(-1,-1,0); if(facing == 3) ps.translate(-1,-1,1); } if(facing == 0) ps.mulPose(Vector3f.YP.rotationDegrees(180)); if(facing == 1) ps.mulPose(Vector3f.YP.rotationDegrees(270)); if(facing == 3) ps.mulPose(Vector3f.YP.rotationDegrees(90)); if(facing == 0 || facing == 2) nz = -1; if(facing == 1 || facing == 3) nx = -1; ps.translate(0,0,0); } } else //slates on the floor or ceiling { if(facing == 0) ps.translate(0,0,0); if(facing == 1) ps.translate(1,0,0); if(facing == 2) ps.translate(1,0,1); if(facing == 3) ps.translate(0,0,1); ps.mulPose(Vector3f.YP.rotationDegrees(facing*-90)); if(isOnCeiling) { ps.mulPose(Vector3f.XP.rotationDegrees(-90)); ps.translate(0,-1,1); } else ps.mulPose(Vector3f.XP.rotationDegrees(90)); nz = -1; } int lineWidth = 16; int outerColor = 0xB4B4BE;//0xff_c8c8d2; int innerColor = 0x2A2A2A;//0xc8_322b33; if(isScroll) { lineWidth = 20; outerColor = 0xDEDEDE;//0xff_d2c8c8; innerColor = 0x343434;//0xc8_322b33; } ResourceLocation texture = getTexture(zappyPoints, pointsKey, blockSize, showStrokeOrder, lineWidth, useFullSize, new Color(innerColor), new Color(outerColor)); VertexConsumer verts = bufSource.getBuffer(RenderType.entityCutout(texture)); vertex(mat, normal, light, verts, 0, 0, z, 0, 0, nx, ny, nz); vertex(mat, normal, light, verts, 0, y, z, 0, 1, nx, ny, nz); vertex(mat, normal, light, verts, x, y, z, 1, 1, nx, ny, nz); vertex(mat, normal, light, verts, x, 0, z, 1, 0, nx, ny, nz); ps.popPose(); } private static void vertex(Matrix4f mat, Matrix3f normal, int light, VertexConsumer verts, float x, float y, float z, float u, float v, float nx, float ny, float nz) { verts.vertex(mat, x, y, z) .color(0xffffffff) .uv(u, v).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(light) .normal(normal, nx, ny, nz) .endVertex(); } public static ResourceLocation getTexture(List points, String pointsKey, int blockSize, boolean showsStrokeOrder, int lineWidth, boolean useFullSize, Color innerColor, Color outerColor) { if (patternTextures.containsKey(pointsKey)) return patternTextures.get(pointsKey); return createTexture(points, pointsKey, blockSize, showsStrokeOrder, lineWidth, useFullSize, innerColor, outerColor); } public static ResourceLocation createTexture(List points, String pointsKey, int blockSize, boolean showsStrokeOrder, int lineWidth, boolean useFullSize, Color innerColor, Color outerColor) { int resolution = resolutionByBlockSize * blockSize; int padding = paddingByBlockSize * blockSize; double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE, minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE; for (Vec2 point : points) { minX = Math.min(minX, point.x); maxX = Math.max(maxX, point.x); minY = Math.min(minY, point.y); maxY = Math.max(maxY, point.y); } double rangeX = maxX - minX; double rangeY = maxY - minY; double scale = Math.min((resolution - 2 * padding) / rangeX, (resolution - 2 * padding) / rangeY); double limit = blockSize * scaleLimit; if (!useFullSize && scale > limit) scale = limit; double offsetX = ((resolution - 2 * padding) - rangeX * scale) / 2; double offsetY = ((resolution - 2 * padding) - rangeY * scale) / 2; BufferedImage img = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); g2d.setColor(outerColor); g2d.setStroke(new BasicStroke((blockSize * 5f / 3f) * lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); drawLines(g2d, points, minX, minY, scale, offsetX, offsetY, padding); g2d.setColor(innerColor); g2d.setStroke(new BasicStroke((blockSize * 2f / 3f) * lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); drawLines(g2d, points, minX, minY, scale, offsetX, offsetY, padding); if (showsStrokeOrder) { g2d.setColor(new Color(0xff_d77b5b)); Tuple point = getTextureCoordinates(points.get(0), minX, minY, scale, offsetX, offsetY, padding); int spotRadius = circleRadiusByBlockSize * blockSize; drawHexagon(g2d, point.getA(), point.getB(), spotRadius); } g2d.dispose(); NativeImage nativeImage = new NativeImage(img.getWidth(), img.getHeight(), true); for (int y = 0; y < img.getHeight(); y++) for (int x = 0; x < img.getWidth(); x++) nativeImage.setPixelRGBA(x, y, img.getRGB(x, y)); DynamicTexture dynamicTexture = new DynamicTexture(nativeImage); ResourceLocation resourceLocation = Minecraft.getInstance().getTextureManager().register("hex_pattern_texture_" + points.hashCode() + "_" + repaintIndex + ".png", dynamicTexture); patternTextures.put(pointsKey, resourceLocation); return resourceLocation; } private static void drawLines(Graphics2D g2d, List points, double minX, double minY, double scale, double offsetX, double offsetY, int padding) { for (int i = 0; i < points.size() - 1; i++) { Tuple pointFrom = getTextureCoordinates(points.get(i), minX, minY, scale, offsetX, offsetY, padding); Tuple pointTo = getTextureCoordinates(points.get(i+1), minX, minY, scale, offsetX, offsetY, padding); g2d.drawLine(pointFrom.getA(), pointFrom.getB(), pointTo.getA(), pointTo.getB()); } } private static Tuple getTextureCoordinates(Vec2 point, double minX, double minY, double scale, double offsetX, double offsetY, int padding) { int x = (int) ((point.x - minX) * scale + offsetX) + padding; int y = (int) ((point.y - minY) * scale + offsetY) + padding; return new Tuple(x, y); } private static void drawHexagon(Graphics2D g2d, int x, int y, int radius) { int fracOfCircle = 6; Polygon hexagon = new Polygon(); for (int i = 0; i < fracOfCircle; i++) { double theta = (i / (double) fracOfCircle) * Math.PI * 2; int hx = (int) (x + Math.cos(theta) * radius); int hy = (int) (y + Math.sin(theta) * radius); hexagon.addPoint(hx, hy); } g2d.fill(hexagon); } public static void repaint() { repaintIndex++; patternTextures.clear(); } }