diff --git a/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java index 269366d5d2..351acbef7a 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/entity/WallScrollRenderer.java @@ -1,5 +1,6 @@ package at.petrak.hexcasting.client.entity; +import at.petrak.hexcasting.client.render.PatternTextureManager; import at.petrak.hexcasting.client.render.RenderLib; import at.petrak.hexcasting.common.entities.EntityWallScroll; import com.mojang.blaze3d.systems.RenderSystem; @@ -97,10 +98,14 @@ public void render(EntityWallScroll wallScroll, float yaw, float partialTicks, P vertex(mat, norm, light, verts, dx, dy, dz, 1, 1 - margin, 0, 1, 0); ps.popPose(); + + if (PatternTextureManager.useTextures && wallScroll.points != null) + PatternTextureManager.renderPatternForScroll(wallScroll.points.pointsKey, ps, bufSource, light, wallScroll.points.zappyPoints, wallScroll.blockSize, wallScroll.getShowsStrokeOrder()); } - if (wallScroll.zappyPoints != null) { - var points = wallScroll.zappyPoints; + //TODO: remove old rendering if not needed anymore for comparison + if (!PatternTextureManager.useTextures && wallScroll.points != null) { + var points = wallScroll.points.zappyPoints; ps.pushPose(); ps.mulPose(Axis.YP.rotationDegrees(180f)); diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java new file mode 100644 index 0000000000..b033e8de36 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java @@ -0,0 +1,15 @@ +package at.petrak.hexcasting.client.render; + +import net.minecraft.world.phys.Vec2; + +import java.util.List; + +public class HexPatternPoints { + public List zappyPoints = null; + public String pointsKey = null; //TODO: if a string key isnt performant enough override hashcode for points + + public HexPatternPoints(List zappyPoints) { + this.zappyPoints = zappyPoints; + pointsKey = PatternTextureManager.getPointsKey(zappyPoints); + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java new file mode 100644 index 0000000000..c28116900f --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java @@ -0,0 +1,357 @@ +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.Axis; +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 org.joml.Matrix3f; +import org.joml.Matrix4f; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.*; +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 resolutionScaler = 4; + public static int fastRenderScaleFactor = 8; // e.g. this is 8, resolution is 1024, so render at 1024/8 = 128 + + public static int resolutionByBlockSize = 128 * resolutionScaler; + public static int paddingByBlockSize = 16 * resolutionScaler; + public static int circleRadiusByBlockSize = 2 * resolutionScaler; + public static int scaleLimit = 4 * resolutionScaler; + public static int scrollLineWidth = 3 * resolutionScaler; + public static int otherLineWidth = 4 * resolutionScaler; + + public static void setResolutionScaler(int resolutionScaler) { + PatternTextureManager.resolutionScaler = resolutionScaler; + resolutionByBlockSize = 128 * resolutionScaler; + paddingByBlockSize = 16 * resolutionScaler; + circleRadiusByBlockSize = 2 * resolutionScaler; + scaleLimit = 4 * resolutionScaler; + scrollLineWidth = 3 * resolutionScaler; + otherLineWidth = 4 * resolutionScaler; + } + + private static final ConcurrentMap patternTexturesToAdd = new ConcurrentHashMap<>(); + // basically newCachedThreadPool, but with a max pool size + private static final ExecutorService executor = new ThreadPoolExecutor(0, 16, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>()); + + + private static final 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(Axis.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(Axis.YP.rotationDegrees(180)); + if(facing == 1) + ps.mulPose(Axis.YP.rotationDegrees(270)); + if(facing == 3) + ps.mulPose(Axis.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(Axis.YP.rotationDegrees(facing*-90)); + + if(isOnCeiling) + { + ps.mulPose(Axis.XP.rotationDegrees(-90)); + ps.translate(0,-1,1); + } + else + ps.mulPose(Axis.XP.rotationDegrees(90)); + nz = -1; + } + + int lineWidth = otherLineWidth; + int outerColor = 0xff_d2c8c8; + int innerColor = 0xc8_322b33; + if(isScroll) + lineWidth = scrollLineWidth; + + 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, float lineWidth, boolean useFullSize, Color innerColor, Color outerColor) { + if (patternTexturesToAdd.containsKey(pointsKey)) { + var patternTexture = patternTexturesToAdd.remove(pointsKey); + var oldPatternTexture = patternTextures.put(pointsKey, patternTexture); + if (oldPatternTexture != null) + Minecraft.getInstance().getTextureManager().getTexture(oldPatternTexture).close(); + + return patternTexture; + } + if (patternTextures.containsKey(pointsKey)) + return patternTextures.get(pointsKey); + + // render a higher-resolution texture in a background thread so it eventually becomes all nice nice and pretty + executor.submit(() -> { + var slowTexture = createTexture(points, blockSize, showsStrokeOrder, lineWidth, useFullSize, innerColor, outerColor, false); + + // TextureManager#register doesn't look very thread-safe, so move back to the main thread after the slow part is done + Minecraft.getInstance().execute(() -> registerTexture(points, pointsKey, slowTexture, true)); + }); + + // quickly create and cache a low-resolution texture so the client has something to look at + var fastTexture = createTexture(points, blockSize, showsStrokeOrder, lineWidth, useFullSize, innerColor, outerColor, true); + return registerTexture(points, pointsKey, fastTexture, false); + } + + private static DynamicTexture createTexture(List points, int blockSize, boolean showsStrokeOrder, float lineWidth, boolean useFullSize, Color innerColor, Color outerColor, boolean fastRender) + { + int resolution = resolutionByBlockSize * blockSize; + int padding = paddingByBlockSize * blockSize; + + if (fastRender) { + resolution /= fastRenderScaleFactor; + padding /= fastRenderScaleFactor; + lineWidth /= (float)fastRenderScaleFactor; + } + + 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.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + 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)); + + return new DynamicTexture(nativeImage); + } + + private static ResourceLocation registerTexture(List points, String pointsKey, DynamicTexture dynamicTexture, boolean isSlow) { + // isSlow used to register different textures for the low-resolution, fastly rendered version of each texture + // and the high-resolution, slowly rendered version (this means the slow doesn't replace the fast in the texture manager, + // which causes occasional visual stuttering for a frame). + String name = "hex_pattern_texture_" + points.hashCode() + "_" + repaintIndex + "_" + (isSlow ? "slow" : "fast") + ".png"; + ResourceLocation resourceLocation = Minecraft.getInstance().getTextureManager().register(name, dynamicTexture); + patternTexturesToAdd.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++; + patternTexturesToAdd.clear(); + patternTextures.clear(); + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java index 9efdad59c3..e9064fb54c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntityAkashicBookshelfRenderer.java @@ -1,6 +1,7 @@ package at.petrak.hexcasting.client.render.be; import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.client.render.PatternTextureManager; import at.petrak.hexcasting.client.render.RenderLib; import at.petrak.hexcasting.common.blocks.akashic.BlockAkashicBookshelf; import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; @@ -31,6 +32,13 @@ public void render(BlockEntityAkashicBookshelf tile, float pPartialTick, PoseSta var bs = tile.getBlockState(); + if(PatternTextureManager.useTextures) { + PatternTextureManager.renderPatternForAkashicBookshelf(tile, pattern, ps, buffer, light, bs); + return; + } + + //TODO: remove old rendering if not needed anymore for comparison + var oldShader = RenderSystem.getShader(); RenderSystem.setShader(GameRenderer::getPositionColorShader); RenderSystem.enableDepthTest(); diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java index d355f3af55..37a40babcd 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/be/BlockEntitySlateRenderer.java @@ -1,5 +1,6 @@ package at.petrak.hexcasting.client.render.be; +import at.petrak.hexcasting.client.render.PatternTextureManager; import at.petrak.hexcasting.client.render.RenderLib; import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; import at.petrak.hexcasting.common.blocks.circles.BlockSlate; @@ -13,8 +14,8 @@ import net.minecraft.util.Mth; import net.minecraft.world.level.block.state.properties.AttachFace; import net.minecraft.world.phys.Vec2; -import org.joml.AxisAngle4f; -import org.joml.Quaternionf; + +import java.util.ArrayList; public class BlockEntitySlateRenderer implements BlockEntityRenderer { public BlockEntitySlateRenderer(BlockEntityRendererProvider.Context ctx) { @@ -24,12 +25,18 @@ public BlockEntitySlateRenderer(BlockEntityRendererProvider.Context ctx) { @Override public void render(BlockEntitySlate tile, float pPartialTick, PoseStack ps, MultiBufferSource buffer, int light, int overlay) { - if (tile.pattern == null) { + if (tile.pattern == null) return; - } var bs = tile.getBlockState(); + if(PatternTextureManager.useTextures && !bs.getValue(BlockSlate.ENERGIZED)) { + PatternTextureManager.renderPatternForSlate(tile, tile.pattern, ps, buffer, light, bs); + return; + } + + //TODO: remove old rendering if not needed anymore for comparison + var oldShader = RenderSystem.getShader(); RenderSystem.setShader(GameRenderer::getPositionColorShader); RenderSystem.enableDepthTest(); @@ -49,51 +56,68 @@ public void render(BlockEntitySlate tile, float pPartialTick, PoseStack ps, ps.mulPose(Axis.ZP.rotation(neg * Mth.HALF_PI * quarters)); } + // Resolution is the number of sub-voxels in the block for rendering purposes, 16 is the default + // padding is the space to leave on the edges free of pattern + var resolution = 16; + var padding = resolution * PatternTextureManager.paddingByBlockSize / PatternTextureManager.resolutionByBlockSize; + // and now Z is out? ps.translate(0, 0, -0.5); - ps.scale(1 / 16f, 1 / 16f, 1 / 16f); + ps.scale(1f / resolution, 1f / resolution, 1f / resolution); ps.translate(0, 0, 1.01); - // yoink code from the pattern greeble - // Do two passes: one with a random size to find a good COM and one with the real calculation - var com1 = tile.pattern.getCenter(1); + var isLit = bs.getValue(BlockSlate.ENERGIZED); + var variance = isLit ? 2.5f : 0.5f; + var speed = isLit ? 0.1f : 0f; + var lines1 = tile.pattern.toLines(1, Vec2.ZERO); + var stupidHash = tile.getBlockPos().hashCode(); + var zappyPattern = RenderLib.makeZappy(lines1, RenderLib.findDupIndices(tile.pattern.positions()), + 10, variance, speed, 0.2f, 0f, 1f, stupidHash); - var maxDx = -1f; - var maxDy = -1f; - for (var dot : lines1) { - var dx = Mth.abs(dot.x - com1.x); - if (dx > maxDx) { - maxDx = dx; - } - var dy = Mth.abs(dot.y - com1.y); - if (dy > maxDy) { - maxDy = dy; - } + // always do space calculations with the static version of the pattern + // so that it doesn't jump around resizing itself. + var zappyPatternSpace = RenderLib.makeZappy(lines1, RenderLib.findDupIndices(tile.pattern.positions()), + 10, 0.5f, 0f, 0.2f, 0f, 1f, stupidHash); + + double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE, minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE; + for (Vec2 point : zappyPatternSpace) + { + minX = Math.min(minX, point.x); + maxX = Math.max(maxX, point.x); + minY = Math.min(minY, point.y); + maxY = Math.max(maxY, point.y); } - var scale = Math.min(3.8f, Math.min(16 / 2.5f / maxDx, 16 / 2.5f / maxDy)); - var com2 = tile.pattern.getCenter(scale); - var lines2 = tile.pattern.toLines(scale, com2.negated()); - // For some reason it is mirrored left to right and i can't seem to posestack-fu it into shape - for (int i = 0; i < lines2.size(); i++) { - var v = lines2.get(i); - lines2.set(i, new Vec2(-v.x, v.y)); + double rangeX = maxX - minX; + double rangeY = maxY - minY; + + double scale = Math.min((resolution - 2 * padding) / rangeX, (resolution - 2 * padding) / rangeY); + + double offsetX = ((- 2 * padding) - rangeX * scale) / 2; + double offsetY = ((- 2 * padding) - rangeY * scale) / 2; + + var zappyRenderSpace = new ArrayList(); + + for (Vec2 point : zappyPattern) { + zappyRenderSpace.add(new Vec2( + (float) (((point.x - minX) * scale + offsetX) + padding), + (float) (((point.y - minY) * scale + offsetY) + padding) + )); } - var isLit = bs.getValue(BlockSlate.ENERGIZED); - var variance = isLit ? 2.5f : 0.5f; - var speed = isLit ? 0.1f : 0f; - var stupidHash = tile.getBlockPos().hashCode(); - var zappy = RenderLib.makeZappy(lines2, RenderLib.findDupIndices(tile.pattern.positions()), - 10, variance, speed, 0.2f, 0f, 1f, stupidHash); + // For some reason it is mirrored left to right and i can't seem to posestack-fu it into shape + for (int i = 0; i < zappyRenderSpace.size(); i++) { + var v = zappyRenderSpace.get(i); + zappyRenderSpace.set(i, new Vec2(-v.x, v.y)); + } int outer = isLit ? 0xff_64c8ff : 0xff_d2c8c8; int inner = isLit ? RenderLib.screenCol(outer) : 0xc8_322b33; - RenderLib.drawLineSeq(ps.last().pose(), zappy, 1f, 0f, outer, outer); - RenderLib.drawLineSeq(ps.last().pose(), zappy, 0.4f, 0.01f, inner, inner); + RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, 1f, 0f, outer, outer); + RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, 0.4f, 0.01f, inner, inner); ps.popPose(); RenderSystem.setShader(() -> oldShader); } -} +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java index fe10b75918..d6e4cd8041 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/akashic/BlockEntityAkashicBookshelf.java @@ -4,6 +4,7 @@ import at.petrak.hexcasting.api.casting.iota.Iota; import at.petrak.hexcasting.api.casting.iota.IotaType; import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.client.render.HexPatternPoints; import at.petrak.hexcasting.common.lib.HexBlockEntities; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; @@ -22,6 +23,8 @@ public class BlockEntityAkashicBookshelf extends HexBlockEntity { // For both these cases we save just the tag of the iota. private CompoundTag iotaTag = null; + public HexPatternPoints points; + public BlockEntityAkashicBookshelf(BlockPos pWorldPosition, BlockState pBlockState) { super(HexBlockEntities.AKASHIC_BOOKSHELF_TILE, pWorldPosition, pBlockState); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java index 9a0ec965bc..63489e8c0e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/blocks/circles/BlockEntitySlate.java @@ -2,6 +2,7 @@ import at.petrak.hexcasting.api.block.HexBlockEntity; import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.client.render.HexPatternPoints; import at.petrak.hexcasting.common.lib.HexBlockEntities; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; @@ -14,6 +15,7 @@ public class BlockEntitySlate extends HexBlockEntity { @Nullable public HexPattern pattern; + public HexPatternPoints points; public BlockEntitySlate(BlockPos pos, BlockState state) { super(HexBlockEntities.SLATE_TILE, pos, state); diff --git a/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java b/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java new file mode 100644 index 0000000000..e653759aef --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java @@ -0,0 +1,32 @@ +package at.petrak.hexcasting.common.command; + +import at.petrak.hexcasting.client.render.PatternTextureManager; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; + +public class PatternTexturesCommand +{ + public static void add(LiteralArgumentBuilder cmd) { + cmd.then(Commands.literal("textureToggle") + .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) + .executes(ctx -> { + PatternTextureManager.useTextures = !PatternTextureManager.useTextures; + return 1; + })); + cmd.then(Commands.literal("textureRepaint") + .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) + .executes(ctx -> { + PatternTextureManager.repaint(); + return 1; + })); + cmd.then(Commands.literal("textureSetResolutionScaler") + .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) + .then(Commands.argument("integer", IntegerArgumentType.integer()).executes(ctx -> { + PatternTextureManager.setResolutionScaler(IntegerArgumentType.getInteger(ctx, "integer")); + PatternTextureManager.repaint(); + return 1; + }))); + } +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java b/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java index 16a8b31c86..cd9ab651cb 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/entities/EntityWallScroll.java @@ -3,6 +3,7 @@ import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.api.utils.NBTHelper; +import at.petrak.hexcasting.client.render.HexPatternPoints; import at.petrak.hexcasting.client.render.RenderLib; import at.petrak.hexcasting.common.items.storage.ItemScroll; import at.petrak.hexcasting.common.lib.HexItems; @@ -43,11 +44,13 @@ public class EntityWallScroll extends HangingEntity { EntityDataSerializers.BOOLEAN); public ItemStack scroll; + @Nullable public HexPattern pattern; public boolean isAncient; public int blockSize; // Client-side only! - public List zappyPoints; + @Nullable + public HexPatternPoints points; public EntityWallScroll(EntityType type, Level world) { super(type, world); @@ -75,14 +78,15 @@ public void recalculateDisplay() { var dots = pair.getSecond(); var readOffset = this.getShowsStrokeOrder() ? RenderLib.DEFAULT_READABILITY_OFFSET : 0f; var lastProp = this.getShowsStrokeOrder() ? RenderLib.DEFAULT_LAST_SEGMENT_LEN_PROP : 1f; - this.zappyPoints = RenderLib.makeZappy(dots, RenderLib.findDupIndices(pattern.positions()), 10, 0.4f, + var zappyPoints = RenderLib.makeZappy(dots, RenderLib.findDupIndices(pattern.positions()), 10, 0.4f, 0f, 0f, readOffset, lastProp, this.getId()); + this.points = new HexPatternPoints(zappyPoints); } this.isAncient = NBTHelper.hasString(scroll, ItemScroll.TAG_OP_ID); } else { this.pattern = null; - this.zappyPoints = null; + this.points = null; this.isAncient = false; } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexCommands.java b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexCommands.java index 4e4d44181a..0a4aca8773 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/lib/HexCommands.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/lib/HexCommands.java @@ -2,6 +2,7 @@ import at.petrak.hexcasting.common.command.BrainsweepCommand; import at.petrak.hexcasting.common.command.ListPerWorldPatternsCommand; +import at.petrak.hexcasting.common.command.PatternTexturesCommand; import at.petrak.hexcasting.common.command.RecalcPatternsCommand; import com.mojang.brigadier.CommandDispatcher; import net.minecraft.commands.CommandSourceStack; @@ -14,6 +15,7 @@ public static void register(CommandDispatcher dispatcher) { BrainsweepCommand.add(mainCmd); ListPerWorldPatternsCommand.add(mainCmd); RecalcPatternsCommand.add(mainCmd); + PatternTexturesCommand.add(mainCmd); dispatcher.register(mainCmd); }