diff --git a/.gitignore b/.gitignore index 2da9d42dbf..dc8ca90ac5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,10 @@ eclipse run .DS_Store +# Idk what prompted gradle to create this folder +Fabric/Fabric + + # MacOS moment .DS_Store diff --git a/Common/build.gradle b/Common/build.gradle index d5e6128c84..947b7269a9 100644 --- a/Common/build.gradle +++ b/Common/build.gradle @@ -28,6 +28,8 @@ repositories { url = "https://modmaven.dev" } + maven { url "https://maven.shedaniel.me/" } + } dependencies { @@ -37,6 +39,8 @@ dependencies { compileOnly "at.petra-k.paucal:paucal-common-$minecraftVersion:$paucalVersion" compileOnly "vazkii.patchouli:Patchouli-xplat:$minecraftVersion-$patchouliVersion-SNAPSHOT" + compileOnly "com.samsthenerd.inline:inline-forge:$minecraftVersion-$inlineVersion" + compileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" testCompileOnly "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java index fe2ee51f45..2a8023cf01 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/EntityIota.java @@ -2,13 +2,18 @@ import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import com.samsthenerd.inline.api.InlineAPI; +import com.samsthenerd.inline.api.data.EntityInlineData; +import com.samsthenerd.inline.api.data.PlayerHeadData; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,13 +43,29 @@ public boolean isTruthy() { Tag serialize() { var out = new CompoundTag(); out.putUUID("uuid", this.getEntity().getUUID()); - out.putString("name", Component.Serializer.toJson(this.getEntity().getName())); + out.putString("name", Component.Serializer.toJson(getEntityNameWithInline(true))); return out; } @Override public Component display() { - return this.getEntity().getName().copy().withStyle(ChatFormatting.AQUA); + return getEntityNameWithInline(false).copy().withStyle(ChatFormatting.AQUA); + } + + private Component getEntityNameWithInline(boolean fearSerializer){ + MutableComponent baseName = this.getEntity().getName().copy(); + Component inlineEnt = null; + if(this.getEntity() instanceof Player player){ + inlineEnt = new PlayerHeadData(player.getGameProfile()).asText(!fearSerializer); + inlineEnt = inlineEnt.plainCopy().withStyle(InlineAPI.INSTANCE.withSizeModifier(inlineEnt.getStyle(), 1.5)); + } else{ + if(fearSerializer){ // we don't want to have to serialize an entity just to display it + inlineEnt = EntityInlineData.fromType(this.getEntity().getType()).asText(!fearSerializer); + } else { + inlineEnt = EntityInlineData.fromEntity(this.getEntity()).asText(!fearSerializer); + } + } + return baseName.append(Component.literal(": ")).append(inlineEnt); } public static IotaType TYPE = new IotaType<>() { @@ -73,6 +94,7 @@ public Component display(Tag tag) { return Component.translatable("hexcasting.spelldata.entity.whoknows"); } var nameJson = ctag.getString("name"); +// return Component.literal(nameJson); return Component.Serializer.fromJsonLenient(nameJson).withStyle(ChatFormatting.AQUA); } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java index 255d54662f..251c4e161d 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/ListIota.java @@ -107,7 +107,10 @@ public Component display(Tag tag) { out.append(IotaType.getDisplay(csub)); - if (i < list.size() - 1) { + // only add a comma between 2 non-patterns (commas don't look good with Inline patterns) + // TODO: maybe add a config? maybe add a method on IotaType to allow it to opt out of commas + if (i < list.size() - 1 && (IotaType.getTypeFromTag(csub) != PatternIota.TYPE + || IotaType.getTypeFromTag(HexUtils.downcast(list.get(i+1), CompoundTag.TYPE)) != PatternIota.TYPE)) { out.append(", "); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java index 6b52868ad6..b86e8541ce 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/iota/PatternIota.java @@ -14,17 +14,18 @@ import at.petrak.hexcasting.api.casting.mishaps.MishapEvalTooMuch; import at.petrak.hexcasting.api.casting.mishaps.MishapInvalidPattern; import at.petrak.hexcasting.api.casting.mishaps.MishapUnenlightened; -import at.petrak.hexcasting.api.mod.HexConfig; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.api.utils.HexUtils; import at.petrak.hexcasting.common.casting.PatternRegistryManifest; import at.petrak.hexcasting.common.lib.hex.HexEvalSounds; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; +import at.petrak.hexcasting.interop.inline.InlinePatternData; import at.petrak.hexcasting.xplat.IXplatAbstractions; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.NotNull; @@ -163,6 +164,12 @@ public static PatternIota deserialize(Tag tag) throws IllegalArgumentException { } public static Component display(HexPattern pat) { + Component text = (new InlinePatternData(pat)).asText(true); + return text.copy().withStyle(text.getStyle().applyTo(Style.EMPTY.withColor(ChatFormatting.WHITE))); + } + + // keep around just in case it's needed. + public static Component displayNonInline(HexPattern pat){ var bob = new StringBuilder(); bob.append(pat.getStartDir()); @@ -172,7 +179,7 @@ public static Component display(HexPattern pat) { bob.append(sig); } return Component.translatable("hexcasting.tooltip.pattern_iota", - Component.literal(bob.toString()).withStyle(ChatFormatting.WHITE)) - .withStyle(ChatFormatting.GOLD); + Component.literal(bob.toString()).withStyle(ChatFormatting.WHITE)) + .withStyle(ChatFormatting.GOLD); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt index 514cb0e467..03f4ca5a56 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexDir.kt @@ -1,6 +1,7 @@ package at.petrak.hexcasting.api.casting.math import at.petrak.hexcasting.api.utils.getSafe +import com.mojang.serialization.Codec enum class HexDir { NORTH_EAST, EAST, SOUTH_EAST, SOUTH_WEST, WEST, NORTH_WEST; @@ -26,6 +27,11 @@ enum class HexDir { } companion object { + val CODEC: Codec = Codec.STRING.xmap( + HexDir::fromString, + HexDir::name + ) + @JvmStatic fun fromString(key: String): HexDir { return values().getSafe(key, WEST) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt index 6b6c587ea0..001cfdeab4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/math/HexPattern.kt @@ -4,6 +4,8 @@ import at.petrak.hexcasting.api.utils.NBTBuilder import at.petrak.hexcasting.api.utils.coordToPx import at.petrak.hexcasting.api.utils.findCenter import at.petrak.hexcasting.api.utils.getSafe +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.Tag import net.minecraft.world.phys.Vec2 @@ -127,6 +129,13 @@ data class HexPattern(val startDir: HexDir, val angles: MutableList = const val TAG_START_DIR = "start_dir" const val TAG_ANGLES = "angles" + @JvmField + val CODEC: Codec = RecordCodecBuilder.create({instance -> instance.group( + Codec.STRING.fieldOf(TAG_START_DIR).forGetter(HexPattern::anglesSignature), + HexDir.CODEC.fieldOf(TAG_ANGLES).forGetter(HexPattern::startDir) + ).apply(instance, HexPattern::fromAngles) + }) + @JvmStatic fun isPattern(tag: CompoundTag): Boolean { return tag.contains(TAG_START_DIR, Tag.TAG_ANY_NUMERIC.toInt()) 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 351acbef7a..d17e617cd9 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,7 +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.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.entities.EntityWallScroll; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; @@ -15,13 +14,9 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.Vec2; import org.joml.Matrix3f; import org.joml.Matrix4f; -import java.util.List; - import static at.petrak.hexcasting.api.HexAPI.modLoc; public class WallScrollRenderer extends EntityRenderer { @@ -31,7 +26,6 @@ public class WallScrollRenderer extends EntityRenderer { private static final ResourceLocation ANCIENT_BG_LARGE = modLoc("textures/entity/scroll_ancient_large.png"); private static final ResourceLocation ANCIENT_BG_MEDIUM = modLoc("textures/entity/scroll_ancient_medium.png"); private static final ResourceLocation ANCIENT_BG_SMOL = modLoc("textures/block/ancient_scroll_paper.png"); - private static final ResourceLocation WHITE = modLoc("textures/entity/white.png"); public WallScrollRenderer(EntityRendererProvider.Context p_174008_) { super(p_174008_); @@ -64,7 +58,9 @@ public void render(EntityWallScroll wallScroll, float yaw, float partialTicks, P var mat = last.pose(); var norm = last.normal(); - var verts = bufSource.getBuffer(RenderType.entityCutout(this.getTextureLocation(wallScroll))); + RenderType layer = RenderType.entityCutout(this.getTextureLocation(wallScroll)); + + var verts = bufSource.getBuffer(layer); // Remember: CCW // Front face vertex(mat, norm, light, verts, 0, 0, dz, 0, 0, 0, 0, -1); @@ -99,42 +95,8 @@ public void render(EntityWallScroll wallScroll, float yaw, float partialTicks, P ps.popPose(); - if (PatternTextureManager.useTextures && wallScroll.points != null) - PatternTextureManager.renderPatternForScroll(wallScroll.points.pointsKey, ps, bufSource, light, wallScroll.points.zappyPoints, wallScroll.blockSize, wallScroll.getShowsStrokeOrder()); - } - - //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)); - ps.translate(0, 0, 1.1f / 16f); - // make smaller scrolls not be charlie kirk-sized - // i swear, learning about these functions with asymptotes where slope != 0 is the most useful thing - // I've ever learned in a math class - float unCharlieKirk = Mth.sqrt(wallScroll.blockSize * wallScroll.blockSize + 60); - float scale = 1f / 300f * unCharlieKirk; - ps.scale(scale, scale, 0.01f); - - var last = ps.last(); - var mat = last.pose(); - var norm = last.normal(); - var outer = 0xff_d2c8c8; - var inner = 0xc8_322b33; - var verts = bufSource.getBuffer(RenderType.entityCutout(WHITE)); - theCoolerDrawLineSeq(mat, norm, light, verts, points, wallScroll.blockSize * 5f / 3f, outer); - ps.translate(0, 0, 0.01); - theCoolerDrawLineSeq(mat, norm, light, verts, points, wallScroll.blockSize * 2f / 3f, inner); - - if (wallScroll.getShowsStrokeOrder()) { - ps.translate(0, 0, 0.01); - var spotFrac = 0.8f * wallScroll.blockSize; - theCoolerDrawSpot(mat, norm, light, verts, points.get(0), 2f / 3f * spotFrac, - 0xff_5b7bd7); - } - - ps.popPose(); + if(wallScroll.pattern != null) + WorldlyPatternRenderHelpers.renderPatternForScroll(wallScroll.pattern, wallScroll, ps, bufSource, light, wallScroll.blockSize, wallScroll.getShowsStrokeOrder()); } ps.popPose(); @@ -163,146 +125,12 @@ public ResourceLocation getTextureLocation(EntityWallScroll wallScroll) { } 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) { + 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(); - } - - private static void vertexCol(Matrix4f mat, Matrix3f normal, int light, VertexConsumer verts, int col, Vec2 pos) { - verts.vertex(mat, -pos.x, pos.y, 0) - .color(col) - .uv(0, 0).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(light) - .normal(normal, 0, 0, 1) - .endVertex(); - } - - private static void theCoolerDrawLineSeq(Matrix4f mat, Matrix3f normalMat, int light, VertexConsumer verts, - List points, float width, int color - ) { - if (points.size() <= 1) { - return; - } - - // TODO: abstract some of this out with RenderLib to stop WET code - var joinAngles = new float[points.size()]; - var joinOffsets = new float[points.size()]; - for (int i = 2; i < points.size(); i++) { - var p0 = points.get(i - 2); - var p1 = points.get(i - 1); - var p2 = points.get(i); - var prev = p1.add(p0.negated()); - var next = p2.add(p1.negated()); - var angle = (float) Mth.atan2( - prev.x * next.y - prev.y * next.x, - prev.x * next.x + prev.y * next.y); - joinAngles[i - 1] = angle; - var clamp = Math.min(prev.length(), next.length()) / (width * 0.5f); - joinOffsets[i - 1] = Mth.clamp(Mth.sin(angle) / (1 + Mth.cos(angle)), -clamp, clamp); - } - - for (var i = 0; i < points.size() - 1; i++) { - var p1 = points.get(i); - var p2 = points.get(i + 1); - - var tangent = p2.add(p1.negated()).normalized().scale(width * 0.5f); - var normal = new Vec2(-tangent.y, tangent.x); - - var jlow = joinOffsets[i]; - var jhigh = joinOffsets[i + 1]; - - var p1Down = p1.add(tangent.scale(Math.max(0f, jlow))).add(normal); - var p1Up = p1.add(tangent.scale(Math.max(0f, -jlow))).add(normal.negated()); - var p2Down = p2.add(tangent.scale(Math.max(0f, jhigh)).negated()).add(normal); - var p2Up = p2.add(tangent.scale(Math.max(0f, -jhigh)).negated()).add(normal.negated()); - - // Draw the chamfer hexagon as two trapezoids - // the points are in different orders to keep clockwise - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p2); - vertexCol(mat, normalMat, light, verts, color, p2Up); - vertexCol(mat, normalMat, light, verts, color, p1Up); - - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p1Down); - vertexCol(mat, normalMat, light, verts, color, p2Down); - vertexCol(mat, normalMat, light, verts, color, p2); - - if (i > 0) { - var sangle = joinAngles[i]; - var angle = Math.abs(sangle); - var rnormal = normal.negated(); - var joinSteps = Mth.ceil(angle * 180 / (RenderLib.CAP_THETA * Mth.PI)); - if (joinSteps < 1) continue; - - if (sangle < 0) { - var prevVert = new Vec2(p1.x - rnormal.x, p1.y - rnormal.y); - for (var j = 1; j <= joinSteps; j++) { - var fan = RenderLib.rotate(rnormal, -sangle * ((float) j / joinSteps)); - var fanShift = new Vec2(p1.x - fan.x, p1.y - fan.y); - - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, fanShift); - vertexCol(mat, normalMat, light, verts, color, prevVert); - prevVert = fanShift; - } - } else { - var startFan = RenderLib.rotate(normal, -sangle); - var prevVert = new Vec2(p1.x - startFan.x, p1.y - startFan.y); - for (var j = joinSteps - 1; j >= 0; j--) { - var fan = RenderLib.rotate(normal, -sangle * ((float) j / joinSteps)); - var fanShift = new Vec2(p1.x - fan.x, p1.y - fan.y); - - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, p1); - vertexCol(mat, normalMat, light, verts, color, fanShift); - vertexCol(mat, normalMat, light, verts, color, prevVert); - prevVert = fanShift; - } - } - } - } - - for (var pair : new Vec2[][]{ - {points.get(0), points.get(1)}, - {points.get(points.size() - 1), points.get(points.size() - 2)} - }) { - var point = pair[0]; - var prev = pair[1]; - - var tangent = point.add(prev.negated()).normalized().scale(0.5f * width); - var normal = new Vec2(-tangent.y, tangent.x); - var joinSteps = Mth.ceil(180f / RenderLib.CAP_THETA); - for (int j = joinSteps; j > 0; j--) { - var fan0 = RenderLib.rotate(normal, -Mth.PI * ((float) j / joinSteps)); - var fan1 = RenderLib.rotate(normal, -Mth.PI * ((float) (j - 1) / joinSteps)); - - vertexCol(mat, normalMat, light, verts, color, point); - vertexCol(mat, normalMat, light, verts, color, point); - vertexCol(mat, normalMat, light, verts, color, point.add(fan1)); - vertexCol(mat, normalMat, light, verts, color, point.add(fan0)); - } - } - } - - private static void theCoolerDrawSpot(Matrix4f mat, Matrix3f normal, int light, VertexConsumer verts, - Vec2 point, float radius, int color) { - var fracOfCircle = 6; - for (int i = 0; i < fracOfCircle; i++) { - // We do need rects, irritatingly - // so we do fake triangles - vertexCol(mat, normal, light, verts, color, point); - vertexCol(mat, normal, light, verts, color, point); - for (int j = 0; j <= 1; j++) { - var theta = (i - j) / (float) fracOfCircle * Mth.TWO_PI; - var rx = Mth.cos(theta) * radius + point.x; - var ry = Mth.sin(theta) * radius + point.y; - vertexCol(mat, normal, light, verts, color, new Vec2(rx, ry)); - } - } + .color(0xffffffff) + .uv(u, v).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(light) + .normal(normal, nx, ny, nz) + .endVertex(); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java b/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java index 42d768ddda..c6f7da11d8 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/gui/PatternTooltipComponent.java @@ -1,24 +1,18 @@ package at.petrak.hexcasting.client.gui; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.client.render.RenderLib; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.misc.PatternTooltip; -import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.client.renderer.entity.ItemRenderer; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.tooltip.TooltipComponent; -import net.minecraft.world.phys.Vec2; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.stream.Collectors; - import static at.petrak.hexcasting.api.HexAPI.modLoc; // https://github.com/VazkiiMods/Botania/blob/95bd2d3fbc857b7c102687554e1d1b112f8af436/Xplat/src/main/java/vazkii/botania/client/gui/ManaBarTooltipComponent.java @@ -36,23 +30,11 @@ public class PatternTooltipComponent implements ClientTooltipComponent { private static final int TEXTURE_SIZE = 48; private final HexPattern pattern; - private final List zappyPoints; - private final List pathfinderDots; - private final float scale; private final ResourceLocation background; public PatternTooltipComponent(PatternTooltip tt) { this.pattern = tt.pattern(); this.background = tt.background(); - - var pair = RenderLib.getCenteredPattern(pattern, RENDER_SIZE, RENDER_SIZE, 16f); - this.scale = pair.getFirst(); - var dots = pair.getSecond(); - this.zappyPoints = RenderLib.makeZappy( - dots, RenderLib.findDupIndices(pattern.positions()), - 10, 0.8f, 0f, 0f, RenderLib.DEFAULT_READABILITY_OFFSET, RenderLib.DEFAULT_LAST_SEGMENT_LEN_PROP, - 0.0); - this.pathfinderDots = dots.stream().distinct().collect(Collectors.toList()); } @Nullable @@ -65,9 +47,6 @@ public static ClientTooltipComponent tryConvert(TooltipComponent cmp) { @Override public void renderImage(Font font, int mouseX, int mouseY, GuiGraphics graphics) { - var width = this.getWidth(font); - var height = this.getHeight(); - var ps = graphics.pose(); // far as i can tell "mouseX" and "mouseY" are actually the positions of the corner of the tooltip @@ -77,28 +56,13 @@ public void renderImage(Font font, int mouseX, int mouseY, GuiGraphics graphics) renderBG(graphics, this.background); // renderText happens *before* renderImage for some asinine reason -// RenderSystem.disableBlend(); ps.translate(0, 0, 100); + ps.scale(RENDER_SIZE, RENDER_SIZE, 1); - RenderSystem.setShader(GameRenderer::getPositionColorShader); - RenderSystem.disableCull(); - RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, - GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - ps.translate(width / 2f, height / 2f, 1); - - var mat = ps.last().pose(); - var outer = 0xff_d2c8c8; - var innerLight = 0xc8_aba2a2; - var innerDark = 0xc8_322b33; - RenderLib.drawLineSeq(mat, this.zappyPoints, 6f, 0, - outer, outer); - RenderLib.drawLineSeq(mat, this.zappyPoints, 6f * 0.4f, 0, - innerDark, innerLight); - RenderLib.drawSpot(mat, this.zappyPoints.get(0), 2.5f, 1f, 0.1f, 0.15f, 0.6f); - - for (var dot : this.pathfinderDots) { - RenderLib.drawSpot(mat, dot, 1.5f, 0.82f, 0.8f, 0.8f, 0.5f); - } + PatternRenderer.renderPattern(pattern, ps, WorldlyPatternRenderHelpers.READABLE_SCROLL_SETTINGS, + (PatternRenderer.shouldDoStrokeGradient() ? PatternColors.DEFAULT_GRADIENT_COLOR : PatternColors.DEFAULT_PATTERN_COLOR) + .withDots(true, true), + 0, 512); ps.popPose(); } diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java new file mode 100644 index 0000000000..1b82992398 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternLike.java @@ -0,0 +1,70 @@ +package at.petrak.hexcasting.client.render; + +import at.petrak.hexcasting.api.casting.math.HexPattern; +import net.minecraft.world.phys.Vec2; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A simple wrapper around the parts of HexPattern that are actually used for rendering. + * + * This lets the pattern renderer work on arbitrary lists of vecs - this is never used in base hex but is included + * to future-proof and for if addons or something wants to use it. + */ +public interface HexPatternLike { + List getNonZappyPoints(); + + String getName(); + + Set getDups(); + + static HexPatternLike of(HexPattern pat){ + return new HexPatternLikeBecauseItsActuallyAHexPattern(pat); + } + + static HexPatternLike of(List lines, String name){ + return new PureLines(lines, name); + } + + record HexPatternLikeBecauseItsActuallyAHexPattern(HexPattern pat) implements HexPatternLike{ + public List getNonZappyPoints(){ + return pat.toLines(1, Vec2.ZERO); + } + + public String getName(){ + return pat.getStartDir() + "-" + pat.anglesSignature(); + } + + public Set getDups(){ + return RenderLib.findDupIndices(pat.positions()); + } + } + + record PureLines(List lines, String name) implements HexPatternLike{ + + public List getNonZappyPoints(){ + return lines; + } + + public String getName(){ + return name; + } + + public Set getDups(){ + return RenderLib.findDupIndices( + lines().stream().map(p -> + // I hate mojang + new Vec2(p.x, p.y){ + @Override public boolean equals(Object other){ + if(other instanceof Vec2 otherVec) return p.equals(otherVec); + return false; + } + + @Override public int hashCode(){ return Objects.hash(p.x, p.y); } + }).toList() + ); + } + } +} 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 index b033e8de36..1a55d06b5e 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/HexPatternPoints.java @@ -1,15 +1,134 @@ package at.petrak.hexcasting.client.render; +import com.google.common.collect.ImmutableList; import net.minecraft.world.phys.Vec2; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +/** + * static points making up a hex pattern to be rendered. It's used primarily for positioning, so we keep a + * number of extra values here to avoid recomputing them. + */ public class HexPatternPoints { - public List zappyPoints = null; - public String pointsKey = null; //TODO: if a string key isnt performant enough override hashcode for points + public final ImmutableList zappyPoints; + public final ImmutableList zappyPointsScaled; - public HexPatternPoints(List zappyPoints) { - this.zappyPoints = zappyPoints; - pointsKey = PatternTextureManager.getPointsKey(zappyPoints); + public final ImmutableList dotsScaled; + + public final double rangeX; + public final double rangeY; + public final double finalScale; + + public final double fullWidth; + public final double fullHeight; + + private double minX = Double.MAX_VALUE; + private double minY = Double.MAX_VALUE; + + private final double offsetX; + private final double offsetY; + + private static final ConcurrentMap CACHED_STATIC_POINTS = new ConcurrentHashMap<>(); + + private HexPatternPoints(HexPatternLike patternlike, PatternSettings patSets, double seed) { + + List dots = patternlike.getNonZappyPoints(); + + // always do space calculations with the static version of the pattern + // so that it doesn't jump around resizing itself. + List zappyPoints = RenderLib.makeZappy(dots, patternlike.getDups(), + patSets.getHops(), patSets.getVariance(), 0f, patSets.getFlowIrregular(), + patSets.getReadabilityOffset(), patSets.getLastSegmentProp(), seed); + + + this.zappyPoints = ImmutableList.copyOf(zappyPoints); + double maxY = Double.MIN_VALUE; + double maxX = Double.MIN_VALUE; + for (Vec2 point : zappyPoints) { + minX = Math.min(minX, point.x); + maxX = Math.max(maxX, point.x); + minY = Math.min(minY, point.y); + maxY = Math.max(maxY, point.y); + } + rangeX = maxX - minX; + rangeY = maxY - minY; + + // scales the patterns so that each point is patSets.baseScale units apart + double baseScale = patSets.getBaseScale() / 1.5; + + // size of the pattern in pose space with no other adjustments + double baseWidth = rangeX * baseScale; + double baseHeight = rangeY * baseScale; + + // make sure that the scale fits within our min sizes + double scale = Math.max(1.0, Math.max( + (patSets.getMinWidth() - patSets.getStrokeWidthGuess()) / baseWidth, + (patSets.getMinHeight() - patSets.getStrokeWidthGuess()) / baseHeight) + ); + + boolean vertFit = patSets.getVertAlignment().fit; + boolean horFit = patSets.getHorAlignment().fit; + + // scale down if needed to fit in vertical space + if(vertFit){ + scale = Math.min(scale, (patSets.getTargetHeight() - 2 * patSets.getVertPadding() - patSets.getStrokeWidthGuess())/(baseHeight)); + } + + // scale down if needed to fit in horizontal space + if(horFit){ + scale = Math.min(scale, (patSets.getTargetWidth() - 2 * patSets.getHorPadding() - patSets.getStrokeWidthGuess())/(baseWidth)); + } + + finalScale = baseScale * scale; + double finalStroke = patSets.getStrokeWidth(finalScale); + + double inherentWidth = (baseWidth * scale) + 2 * patSets.getHorPadding() + finalStroke; + double inherentHeight = (baseHeight * scale) + 2 * patSets.getVertPadding() + finalStroke; + + // this is the amount of actual wiggle room we have for configurable position-ing. + double widthDiff = Math.max(patSets.getTargetWidth() - inherentWidth, 0); + double heightDiff = Math.max(patSets.getTargetHeight() - inherentHeight, 0); + + this.fullWidth = inherentWidth + widthDiff; + this.fullHeight = inherentHeight + heightDiff; + + // center in inherent space and put extra space according to alignment stuff + offsetX = ((inherentWidth - baseWidth * scale) / 2) + (widthDiff * patSets.getHorAlignment().amtInFront / 2); + offsetY = ((inherentHeight - baseHeight * scale) / 2) + (heightDiff * patSets.getVertAlignment().amtInFront / 2); + + this.zappyPointsScaled = ImmutableList.copyOf(scaleVecs(zappyPoints)); + this.dotsScaled = ImmutableList.copyOf(scaleVecs(dots)); + } + + public Vec2 scaleVec(Vec2 point){ + return new Vec2( + (float) (((point.x - this.minX) * this.finalScale) + this.offsetX), + (float) (((point.y - this.minY) * this.finalScale) + this.offsetY) + ); + } + + public List scaleVecs(List points){ + List scaledPoints = new ArrayList<>(); + for (Vec2 point : points) { + scaledPoints.add(scaleVec(point)); + } + return scaledPoints; + } + + + /** + * Gets the static points for the given pattern, settings, and seed. This is cached. + * + * This is used in rendering static patterns and positioning non-static patterns. + * + */ + public static HexPatternPoints getStaticPoints(HexPatternLike patternlike, PatternSettings patSets, double seed){ + + String cacheKey = patSets.getCacheKey(patternlike, seed); + + return CACHED_STATIC_POINTS.computeIfAbsent(cacheKey, (key) -> new HexPatternPoints(patternlike, patSets, seed) ); } } \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PATTERN_RENDER_LORE.md b/Common/src/main/java/at/petrak/hexcasting/client/render/PATTERN_RENDER_LORE.md new file mode 100644 index 0000000000..fdeb3eb6e1 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PATTERN_RENDER_LORE.md @@ -0,0 +1,52 @@ +# Pattern Rendering Lore + +This is an overview of the new pattern rendering systems introduced alongside the Inline pattern rendering + +## Brief History / Motivations + +In v0.10.3 (and probably before) the pattern rendering was well known for causing lag if many patterns were rendered at once. +The pattern code was also duplicated *a lot*, pretty much anywhere that did pattern rendering needed to do it slightly different and so the rendering/positioning code got copy-pasted all around, frequently with a lot of magic numbers. + +During 1.20 development, we [added texture based rendering](https://github.com/FallingColors/HexMod/pull/555), and switched most static rendering over to it. There was still a fair bit of duplicate code though, especially with pattern positioning. + +Now with the new system, all of the rendering is contained to a few classes and outside users (such as slates for example) can specify how they want patterns to be rendered using PatternSettings and PatternColors. + +## System Walkthrough (External) + +### PatternRenderer + +This is the main entrypoint for pattern rendering. It has 3 main methods, all called `renderPattern`. One is the driver method and the others are convenience wrappers. + +Generally the idea here is that you shouldn't need to worry about whether the pattern will be rendered as a texture or dynamically, the `PatternRenderer` will make that decision, prefering the texture renderer when it can. The dynamic renderer will be used if the pattern is moving (speed != 0), if the pattern has a gradient stroke, or if the texture isn't ready yet. + +### PatternSettings + +This is where the vast majority of the rendering configuration happens. Arguably it is overkill. + +It's a class with many getters constructed from 3 records: `PositionSettings`, `StrokeSettings`, and `ZappySettings`. The getters can be overridden when/if needed, the records are more for user convenience. See javadocs for details on what can be configured here. + +Pattern textures are also generated based on settings, so it's **VERY ENCOURAGED** to re-use pattern settings when you can. + +### PatternColors + +This is just a simple record holding colors for different parts of pattern drawing. It has probably too many helpers. The main thing to note here is that you can set the alpha to 0 to skip rendering a section (such as dots or innerStroke). Transparent colors for strokes are **discouraged** due to the dynamic renderer having a sort of internal overlapping that is only noticeable with transparent strokes. + +### WorldlyPatternRenderHelpers + +This is where all the worldly base-hex renders ended up. Good to look at for examples of using the renderer and some pattern settings that could be re-used. + +## System Walkthrough (Internal) + +### HexPatternPoints + +This is where the positioning actually happens. It generates dots and zappy points based on the pattern and PatternSettings passed in. This object is then cached to prevent needing to calculate it all each frame. Note that this includes scaling and all that, the returned zappy points are in pose units. + +### VCDrawHelper (& RenderLib changes) + +We do a silly with this one lol. This allows us to separate the lower level vertex handling from the higher level 'drawing'. + +Previously `RenderLib.drawLineSeq(..)` drew straight to the tesselator with the `POSITION_COLOR` shader/format. Now it just passes color and position data to the `VCDrawHelper` that we give it, allowing us to create and push a vertex however we want to wherever we want. This lets us draw to other vertex consumers and use other shaders/formats, like the `EntityTranslucentCull` that we use for worldly rendering with light and normals. + +To maintain API stability we have the previous `RenderLib.drawLineSeq(..)` method signature just call the new version using the `Basic` draw helper. + +Conveniently we can also use this for drawing our textures! \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java new file mode 100644 index 0000000000..fb64669834 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternColors.java @@ -0,0 +1,68 @@ +package at.petrak.hexcasting.client.render; + +/** + * An immutable wrapper for pattern colors. + *

+ * This is separate from PatternRenderSettings because it does not affect the shape of the pattern, so we can re-use + * those parts for different colors. + */ +public record PatternColors(int innerStartColor, int innerEndColor, int outerStartColor, int outerEndColor, + int startingDotColor, int gridDotsColor){ + + // keep some handy frequently used colors here. + public static final PatternColors DEFAULT_PATTERN_COLOR = new PatternColors(0xff_554d54, 0xff_d2c8c8); + public static final PatternColors DIMMED_COLOR = new PatternColors(0xFF_B4AAAA, 0xff_d2c8c8); + public static final PatternColors DEFAULT_GRADIENT_COLOR = DEFAULT_PATTERN_COLOR.withGradientEnds(DIMMED_COLOR); + + public static final int STARTING_DOT = 0xff_5b7bd7; + public static final int GRID_DOTS = 0x80_d2c8c8; + + public static final PatternColors READABLE_SCROLL_COLORS = DEFAULT_PATTERN_COLOR.withDots(true, false); + public static final PatternColors READABLE_GRID_SCROLL_COLORS = DEFAULT_PATTERN_COLOR.withDots(true, true); + + public static final PatternColors SLATE_WOBBLY_COLOR = glowyStroke( 0xff_64c8ff); // old blue color + public static final PatternColors SLATE_WOBBLY_PURPLE_COLOR = glowyStroke(0xff_cfa0f3); // shiny new purple one :) + + // no gradient + public PatternColors(int innerColor, int outerColor){ + this(innerColor, innerColor, outerColor, outerColor, 0, 0); + } + + // single color -- no inner layer + public static PatternColors singleStroke(int color){ + return new PatternColors(0, color); + } + + // makes a stroke color similar to the glowy effect that slates have. + public static PatternColors glowyStroke(int color){ + return new PatternColors(RenderLib.screenCol(color), color); + } + + public static PatternColors gradientStrokes(int innerStartColor, int innerEndColor, int outerStartColor, int outerEndColor){ + return new PatternColors(innerStartColor, innerEndColor, outerStartColor, outerEndColor, 0, 0); + } + + // a single stroke with a gradient -- no inner layer. + public static PatternColors gradientStroke(int startColor, int endColor){ + return PatternColors.gradientStrokes(0, 0, startColor, endColor); + } + + public PatternColors withGradientEnds(int endColorInner, int endColorOuter){ + return new PatternColors(this.innerStartColor, endColorInner, this.outerStartColor, endColorOuter, this.startingDotColor, this.gridDotsColor); + } + + public PatternColors withGradientEnds(PatternColors end){ + return withGradientEnds(end.innerEndColor, end.outerEndColor); + } + + // add dots -- note, this is how you tell the renderer to make dots + public PatternColors withDotColors(int startingDotColor, int gridDotsColor){ + return new PatternColors(this.innerStartColor, this.innerEndColor, this.outerStartColor, this.outerEndColor, + startingDotColor, gridDotsColor); + } + + // adds dots with the default colors. + public PatternColors withDots(boolean startingDot, boolean gridDots){ + return withDotColors(startingDot ? STARTING_DOT : 0, gridDots ? GRID_DOTS : 0); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java new file mode 100644 index 0000000000..8fd307a9a4 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternRenderer.java @@ -0,0 +1,151 @@ +package at.petrak.hexcasting.client.render; + + +import at.petrak.hexcasting.api.casting.math.HexPattern; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class PatternRenderer { + + public static void renderPattern(HexPattern pattern, PoseStack ps, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit) { + renderPattern(pattern, ps, null, patSets, patColors, seed, resPerUnit); + } + + public static void renderPattern(HexPattern pattern, PoseStack ps, @Nullable WorldlyBits worldlyBits, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit) { + renderPattern(HexPatternLike.of(pattern), ps, worldlyBits, patSets, patColors, seed, resPerUnit); + } + + /** + * Renders a pattern (or rather a pattern-like) according to the given settings. + * @param patternlike the pattern (or more generally the lines) to render. + * @param ps pose/matrix stack to render based on. (0,0) is treated as the top left corner. The size of the render is determined by patSets. + * @param worldlyBits used for rendering with light/normals/render-layers if possible. This is optional and probably shouldn't be used for UI rendering. + * @param patSets settings that control how the pattern is drawn. + * @param patColors colors to use for drawing the pattern and dots. + * @param seed seed to use for zappy wobbles. + * @param resPerUnit the texture resolution per pose unit space to be used *if* the texture renderer is used. + */ + public static void renderPattern(HexPatternLike patternlike, PoseStack ps, @Nullable WorldlyBits worldlyBits, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit){ + var oldShader = RenderSystem.getShader(); + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); + + boolean shouldRenderDynamic = true; + + // only do texture rendering if it's static and has solid colors + if(patSets.getSpeed() == 0 && PatternTextureManager.useTextures && patColors.innerStartColor() == patColors.innerEndColor() + && patColors.outerStartColor() == patColors.outerEndColor()){ + boolean didRender = renderPatternTexture(patternlike, ps, worldlyBits, patSets, patColors, seed, resPerUnit); + if(didRender) shouldRenderDynamic = false; + } + if(shouldRenderDynamic){ + List zappyPattern; + + if(patSets.getSpeed() == 0) { + // re-use our static points if we're rendering a static pattern anyway + zappyPattern = staticPoints.zappyPoints; + } else { + List nonzappyLines = patternlike.getNonZappyPoints(); + Set dupIndices = RenderLib.findDupIndices(nonzappyLines); + zappyPattern = RenderLib.makeZappy(nonzappyLines, dupIndices, + patSets.getHops(), patSets.getVariance(), patSets.getSpeed(), patSets.getFlowIrregular(), + patSets.getReadabilityOffset(), patSets.getLastSegmentProp(), seed); + } + + List zappyRenderSpace = staticPoints.scaleVecs(zappyPattern); + + if(FastColor.ARGB32.alpha(patColors.outerEndColor()) != 0 && FastColor.ARGB32.alpha(patColors.outerStartColor()) != 0){ + RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, (float)patSets.getOuterWidth(staticPoints.finalScale), + patColors.outerStartColor(), patColors.outerEndColor(), VCDrawHelper.getHelper(worldlyBits, ps,outerZ)); + } + if(FastColor.ARGB32.alpha(patColors.innerEndColor()) != 0 && FastColor.ARGB32.alpha(patColors.innerStartColor()) != 0) { + RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, (float)patSets.getInnerWidth(staticPoints.finalScale), + patColors.innerStartColor(), patColors.innerEndColor(), VCDrawHelper.getHelper(worldlyBits, ps,innerZ)); + } + } + + // render dots and grid dynamically + + float dotZ = 0.0011f; + + if(FastColor.ARGB32.alpha(patColors.startingDotColor()) != 0) { + RenderLib.drawSpot(ps.last().pose(), staticPoints.dotsScaled.get(0), (float)patSets.getStartDotRadius(staticPoints.finalScale), + patColors.startingDotColor(), VCDrawHelper.getHelper(worldlyBits, ps, dotZ)); + } + + if(FastColor.ARGB32.alpha(patColors.gridDotsColor()) != 0) { + for(int i = 1; i < staticPoints.dotsScaled.size(); i++){ + Vec2 gridDot = staticPoints.dotsScaled.get(i); + RenderLib.drawSpot(ps.last().pose(), gridDot, (float)patSets.getGridDotsRadius(staticPoints.finalScale), + patColors.gridDotsColor(), VCDrawHelper.getHelper(worldlyBits, ps, dotZ)); + } + } + + RenderSystem.setShader(() -> oldShader); + } + + private static final float outerZ = 0.0005f; + private static final float innerZ = 0.001f; + + private static boolean renderPatternTexture(HexPatternLike patternlike, PoseStack ps, @Nullable WorldlyBits worldlyBits, PatternSettings patSets, PatternColors patColors, double seed, int resPerUnit){ + Optional> maybeTextures = PatternTextureManager.getTextures(patternlike, patSets, seed, resPerUnit); + if(maybeTextures.isEmpty()){ + return false; + } + + Map textures = maybeTextures.get(); + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); + + VertexConsumer vc; + + if(FastColor.ARGB32.alpha(patColors.outerStartColor()) != 0) { + VCDrawHelper vcHelper = VCDrawHelper.getHelper(worldlyBits, ps, outerZ, textures.get("outer")); + vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.QUADS); + + int cl = patColors.outerStartColor(); + + vcHelper.vertex(vc, cl, new Vec2(0, 0), new Vec2(0, 0), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2(0, (float) staticPoints.fullHeight), new Vec2(0, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, (float) staticPoints.fullHeight), new Vec2(1, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, 0), new Vec2(1, 0), ps.last().pose()); + + vcHelper.vcEndDrawer(vc); + } + + if(FastColor.ARGB32.alpha(patColors.innerStartColor()) != 0) { + VCDrawHelper vcHelper = VCDrawHelper.getHelper(worldlyBits, ps, innerZ, textures.get("inner")); + vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.QUADS); + + int cl = patColors.innerStartColor(); + + vcHelper.vertex(vc, cl, new Vec2(0, 0), new Vec2(0, 0), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2(0, (float) staticPoints.fullHeight), new Vec2(0, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, (float) staticPoints.fullHeight), new Vec2(1, 1), ps.last().pose()); + vcHelper.vertex(vc, cl, new Vec2((float) staticPoints.fullWidth, 0), new Vec2(1, 0), ps.last().pose()); + + vcHelper.vcEndDrawer(vc); + } + + return true; + } + + // TODO did we want to un-hardcode this for accessibility reasons ? + public static boolean shouldDoStrokeGradient(){ + return Screen.hasControlDown(); + } + + public record WorldlyBits(@Nullable MultiBufferSource provider, Integer light, Vec3 normal){} +} diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java new file mode 100644 index 0000000000..4cb223f6f6 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternSettings.java @@ -0,0 +1,142 @@ +package at.petrak.hexcasting.client.render; + +/** + * A class holding settings for shaping and positioning patterns. + * + * By default, it's a simple wrapper of 3 records, however some use cases may require extending and overriding getters. + * This is done to keep the complexity of the records a bit lower. + */ +public class PatternSettings { + + public PatternSettings(String name, PositionSettings posSets, StrokeSettings strokeSets, ZappySettings zapSets){ + this.name = name; + this.posSets = posSets; + this.strokeSets = strokeSets; + this.zapSets = zapSets; + } + + /** + * Settings for positioning the pattern and defining its general size/render area. All values are in 'pose units', + * meaning we use them directly with the pose/matrix stack given to the renderer. + * + *

+ * We do a first pass at the pattern scale using baseScale. We then make sure it's larger than minWidth and + * minHeight. Then on each axis, if that axis is has a FIT alignment then we may scale down the pattern to make sure it + * fits. Note that the padding is not scaled and is always respected. + *

+ */ + public record PositionSettings(double spaceWidth, double spaceHeight, double hPadding, double vPadding, + AxisAlignment hAxis, AxisAlignment vAxis, double baseScale, double minWidth, double minHeight){ + /** + * Makes settings ideal for rendering in a square. This helper exists because this is the most common positioning + * pattern. + * @param padding a value 0-0.5 for how much padding should go on each side. + * @return a PositionSettings object in a 1x1 space with the given padding value such that the pattern is centered + */ + public static PositionSettings paddedSquare(double padding){ + return paddedSquare(padding, 0.25, 0); + } + + public static PositionSettings paddedSquare(double padding, double baseScale, double minSize){ + return new PositionSettings(1.0, 1.0, padding, padding, AxisAlignment.CENTER_FIT, AxisAlignment.CENTER_FIT, baseScale, minSize, minSize); + } + } + + /** + * Settings for stroke and dot sizings. If you want to *not* render dots or inner/outer you should prefer setting + * alpha to 0 in PatternColors. + */ + public record StrokeSettings(double innerWidth, double outerWidth, + double startDotRadius, double gridDotsRadius){ + public static StrokeSettings fromStroke(double stroke){ + return new StrokeSettings(stroke * 2.0/5.0, stroke, 0.8 * stroke * 2.0 / 5.0, 0.4 * stroke * 2.0 / 5.0); + } + } + + /** + * Controls how the pattern is zappified. + * + * @param hops number of little pulses + * @param variance how jumpy/distorting the pulses are + * @param speed how fast the pulses go + * @param flowIrregular randomness of pulse travel + * @param readabilityOffset how curved inward the corners are + * @param lastSegmentLenProportion length of the last segment relative to the others. used for increased readability. + */ + public record ZappySettings(int hops, float variance, float speed, float flowIrregular, float readabilityOffset, float lastSegmentLenProportion){ + public static float READABLE_OFFSET = 0.2f; + public static float READABLE_SEGMENT = 0.8f; + public static ZappySettings STATIC = new ZappySettings(10, 0.5f, 0f, 0.2f, 0, 1f); + public static ZappySettings READABLE = new ZappySettings(10, 0.5f, 0f, 0.2f, READABLE_OFFSET, READABLE_SEGMENT); + public static ZappySettings WOBBLY = new ZappySettings(10, 2.5f, 0.1f, 0.2f, 0, 1f); + } + + public String getCacheKey(HexPatternLike patternlike, double seed){ + return (patternlike.getName() + "-" + getName() + "-" + seed).toLowerCase(); + } + + // determines how the pattern is fit and aligned on a given axis + public enum AxisAlignment{ + // These 3 scale the pattern down to fit if needed. + BEGIN_FIT(true, 0), + CENTER_FIT(true, 1), + END_FIT(true, 2), + // these 3 do *not* scale the pattern down, it will overflow if needed. + BEGIN(false, 0), + CENTER(false, 1), + END(false, 2); + + public final boolean fit; + public final int amtInFront; // how many halves go in front. yes it's a weird way to do it. + + AxisAlignment(boolean fit, int amtInFront){ + this.fit = fit; + this.amtInFront = amtInFront; + } + } + + private final String name; + // leaving these public for more convenient chaining. Should prefer using the getters for overrideability. + public final PositionSettings posSets; + public final StrokeSettings strokeSets; + public final ZappySettings zapSets; + + public String getName(){ return name; } + + public double getTargetWidth(){ return posSets.spaceWidth; } + public double getTargetHeight(){ return posSets.spaceHeight; } + + public double getHorPadding(){ return posSets.hPadding; } + public double getVertPadding(){ return posSets.vPadding; } + + public AxisAlignment getHorAlignment(){ return posSets.hAxis; } + public AxisAlignment getVertAlignment(){ return posSets.vAxis; } + + public double getBaseScale(){ return posSets.baseScale; } + public double getMinWidth(){ return posSets.minWidth; } + public double getMinHeight(){ return posSets.minHeight; } + + /* these sizing getters take in the final pattern scale so that patterns can vary their stroke width when squished. + * the records keep a static value since that's fine for *most* use cases, override these methods if you need to use them. + * note that these widths are still in pose space units. + */ + + public double getInnerWidth(double scale){ return strokeSets.innerWidth; } + public double getOuterWidth(double scale){ return strokeSets.outerWidth; } + + public double getStartDotRadius(double scale){ return strokeSets.startDotRadius; } + public double getGridDotsRadius(double scale){ return strokeSets.gridDotsRadius; } + + public double getStrokeWidth(double scale){ return Math.max(getOuterWidth(scale), getInnerWidth(scale)); } + + // we have a stroke guess getter so that we can *try* to account for the stroke size when fitting the pattern. + public double getStrokeWidthGuess(){ return Math.max(strokeSets.outerWidth, strokeSets.innerWidth); } + + public int getHops(){ return zapSets.hops; } + public float getVariance(){ return zapSets.variance; } + public float getFlowIrregular(){ return zapSets.flowIrregular; } + public float getReadabilityOffset(){ return zapSets.readabilityOffset; } + public float getLastSegmentProp(){ return zapSets.lastSegmentLenProportion; } + + public float getSpeed(){ return zapSets.speed; } +} 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 index c28116900f..8486124920 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/PatternTextureManager.java @@ -1,340 +1,117 @@ 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.*; 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<>(); + private static final ConcurrentMap> patternTexturesToAdd = new ConcurrentHashMap<>(); + private static final Set inProgressPatterns = new HashSet<>(); // 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<>(); - 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(); + public static Optional> getTextures(HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { + String patCacheKey = patSets.getCacheKey(patternlike, seed) + "_" + resPerUnit; - 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); + // move textures from concurrent map to normal hashmap as needed + if (patternTexturesToAdd.containsKey(patCacheKey)) { + var patternTexture = patternTexturesToAdd.remove(patCacheKey); + var oldPatternTexture = patternTextures.put(patCacheKey, patternTexture); + inProgressPatterns.remove(patCacheKey); + if (oldPatternTexture != null) // TODO: is this needed? when does this ever happen? + for(ResourceLocation oldPatternTextureSingle : oldPatternTexture.values()) + Minecraft.getInstance().getTextureManager().getTexture(oldPatternTextureSingle).close(); - 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); - } + return Optional.empty(); // try not giving it immediately to avoid flickering? } - 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 (patternTextures.containsKey(patCacheKey)) + return Optional.of(patternTextures.get(patCacheKey)); - if(isOnCeiling) - { - ps.mulPose(Axis.XP.rotationDegrees(-90)); - ps.translate(0,-1,1); - } - else - ps.mulPose(Axis.XP.rotationDegrees(90)); - nz = -1; + // render a higher-resolution texture in a background thread so it eventually becomes all nice nice and pretty + if(!inProgressPatterns.contains(patCacheKey)){ + inProgressPatterns.add(patCacheKey); + executor.submit(() -> { + var slowTextures = createTextures(patternlike, patSets, seed, resPerUnit); + + // TextureManager#register doesn't look very thread-safe, so move back to the main thread after the slow part is done + Minecraft.getInstance().execute(() -> { + registerTextures(patCacheKey, slowTextures); + }); + }); } - - 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(); + return Optional.empty(); } - 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(); - } + private static Map createTextures(HexPatternLike patternlike, PatternSettings patSets, double seed, int resPerUnit) { + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(patternlike, patSets, seed); - 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(); + List zappyRenderSpace = staticPoints.scaleVecs(staticPoints.zappyPoints); - return patternTexture; - } - if (patternTextures.containsKey(pointsKey)) - return patternTextures.get(pointsKey); + Map patTexts = new HashMap<>(); - // 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); + NativeImage innerLines = drawLines(zappyRenderSpace, staticPoints, (float)patSets.getInnerWidth((staticPoints.finalScale)), resPerUnit); + patTexts.put("inner", new DynamicTexture(innerLines)); - // 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)); - }); + NativeImage outerLines = drawLines(zappyRenderSpace, staticPoints, (float)patSets.getOuterWidth((staticPoints.finalScale)), resPerUnit); + patTexts.put("outer", new DynamicTexture(outerLines)); - // 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); + return patTexts; } - 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; + private static Map registerTextures(String patTextureKeyBase, Map dynamicTextures) { + Map resLocs = new HashMap<>(); + for(Map.Entry textureEntry : dynamicTextures.entrySet()){ + String name = "hex_pattern_texture_" + patTextureKeyBase + "_" + textureEntry.getKey() + "_" + repaintIndex + ".png"; + ResourceLocation resourceLocation = Minecraft.getInstance().getTextureManager().register(name, textureEntry.getValue()); + resLocs.put(textureEntry.getKey(), resourceLocation); } + patternTexturesToAdd.put(patTextureKeyBase, resLocs); + return resLocs; + } - 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); + private static NativeImage drawLines(List points, HexPatternPoints staticPoints, float unscaledLineWidth, int resPerUnit) { + BufferedImage img = new BufferedImage((int)(staticPoints.fullWidth*resPerUnit), (int)(staticPoints.fullHeight*resPerUnit), 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.setColor(new Color(0xFF_FFFFFF)); // set it to white so we can reuse the texture with different colors + g2d.setStroke(new BasicStroke(unscaledLineWidth * resPerUnit, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + for (int i = 0; i < points.size() - 1; i++) { + Tuple pointFrom = getTextureCoordinates(points.get(i), staticPoints, resPerUnit); + Tuple pointTo = getTextureCoordinates(points.get(i+1), staticPoints, resPerUnit); + g2d.drawLine(pointFrom.getA(), pointFrom.getB(), pointTo.getA(), pointTo.getB()); } - 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()); - } + return nativeImage; } - 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; + private static Tuple getTextureCoordinates(Vec2 point, HexPatternPoints staticPoints, int resPerUnit) { + int x = (int) ( point.x * resPerUnit); + int y = (int) ( point.y * resPerUnit); return new Tuple<>(x, y); } + // keeping this around just in case we ever decide to put the dots in the textures instead of dynamic private static void drawHexagon(Graphics2D g2d, int x, int y, int radius) { int fracOfCircle = 6; Polygon hexagon = new Polygon(); diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt b/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt index 38d70fdfa8..823068439f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/RenderLib.kt @@ -4,7 +4,7 @@ package at.petrak.hexcasting.client.render import at.petrak.hexcasting.api.casting.math.HexPattern import at.petrak.hexcasting.api.mod.HexConfig -import at.petrak.hexcasting.api.utils.* +import at.petrak.hexcasting.api.utils.TAU import at.petrak.hexcasting.client.ClientTickCounter import at.petrak.hexcasting.client.gui.GuiSpellcasting import com.mojang.blaze3d.vertex.DefaultVertexFormat @@ -16,8 +16,8 @@ import net.minecraft.client.Minecraft import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.screens.Screen import net.minecraft.client.renderer.MultiBufferSource -import net.minecraft.core.BlockPos import net.minecraft.util.FastColor +import net.minecraft.util.FastColor.ARGB32 import net.minecraft.util.Mth import net.minecraft.world.entity.Entity import net.minecraft.world.level.Level @@ -42,6 +42,18 @@ const val CAP_THETA = 180f / 10f const val DEFAULT_READABILITY_OFFSET = 0.2f const val DEFAULT_LAST_SEGMENT_LEN_PROP = 0.8f + +fun drawLineSeq( + mat: Matrix4f, + points: List, + width: Float, + z: Float, + tail: Int, + head: Int +) { + return drawLineSeq(mat, points, width, tail, head, VCDrawHelper.Basic(z)) +} + /** * Draw a sequence of linePoints spanning the given points. * @@ -51,9 +63,9 @@ fun drawLineSeq( mat: Matrix4f, points: List, width: Float, - z: Float, tail: Int, head: Int, + vcHelper: VCDrawHelper ) { if (points.size <= 1) return @@ -61,6 +73,7 @@ fun drawLineSeq( val g1 = FastColor.ARGB32.green(tail).toFloat() val b1 = FastColor.ARGB32.blue(tail).toFloat() val a = FastColor.ARGB32.alpha(tail) + val a1 = a.toFloat() val headSource = if (Screen.hasControlDown() != HexConfig.client().ctrlTogglesOffStrokeOrder()) head else @@ -68,10 +81,9 @@ fun drawLineSeq( val r2 = FastColor.ARGB32.red(headSource).toFloat() val g2 = FastColor.ARGB32.green(headSource).toFloat() val b2 = FastColor.ARGB32.blue(headSource).toFloat() + val a2 = FastColor.ARGB32.alpha(headSource).toFloat() - // they spell it wrong at mojang lmao - val tess = Tesselator.getInstance() - val buf = tess.builder + var vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLES) val n = points.size val joinAngles = FloatArray(n) @@ -90,10 +102,6 @@ fun drawLineSeq( joinOffsets[i - 1] = Mth.clamp(Mth.sin(angle) / (1 + Mth.cos(angle)), -clamp, clamp) } - fun vertex(color: BlockPos, pos: Vec2) = - buf.vertex(mat, pos.x, pos.y, z).color(color.x, color.y, color.z, a).endVertex() - - buf.begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION_COLOR) for (i in 0 until points.size - 1) { val p1 = points[i] val p2 = points[i + 1] @@ -103,8 +111,9 @@ fun drawLineSeq( val tangent = p2.add(p1.negated()).normalized().scale(width * 0.5f) val normal = Vec2(-tangent.y, tangent.x) - fun color(time: Float): BlockPos = - BlockPos(Mth.lerp(time, r1, r2).toInt(), Mth.lerp(time, g1, g2).toInt(), Mth.lerp(time, b1, b2).toInt()) + fun color(time: Float): Int = + FastColor.ARGB32.color(Mth.lerp(time, a1, a2).toInt(), Mth.lerp(time, r1, r2).toInt(), + Mth.lerp(time, g1, g2).toInt(), Mth.lerp(time, b1, b2).toInt()) val color1 = color(i.toFloat() / n) val color2 = color((i + 1f) / n) @@ -118,21 +127,21 @@ fun drawLineSeq( val p2Down = p2.add(tangent.scale(Math.max(0f, jhigh)).negated()).add(normal) val p2Up = p2.add(tangent.scale(Math.max(0f, -jhigh)).negated()).add(normal.negated()) - vertex(color1, p1Down) - vertex(color1, p1) - vertex(color1, p1Up) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, p1Up, mat) - vertex(color1, p1Down) - vertex(color1, p1Up) - vertex(color2, p2Up) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color1, p1Up, mat) + vcHelper.vertex(vc, color2, p2Up, mat) - vertex(color1, p1Down) - vertex(color2, p2Up) - vertex(color2, p2) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color2, p2Up, mat) + vcHelper.vertex(vc, color2, p2, mat) - vertex(color1, p1Down) - vertex(color2, p2) - vertex(color2, p2Down) + vcHelper.vertex(vc, color1, p1Down, mat) + vcHelper.vertex(vc, color2, p2, mat) + vcHelper.vertex(vc, color2, p2Down, mat) if (i > 0) { // Draw the connector to the next line segment @@ -150,9 +159,9 @@ fun drawLineSeq( val fan = rotate(rnormal, -sangle * (j.toFloat() / joinSteps)) val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) - vertex(color1, p1) - vertex(color1, prevVert) - vertex(color1, fanShift) + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, prevVert, mat) + vcHelper.vertex(vc, color1, fanShift, mat) prevVert = fanShift } } else { @@ -162,32 +171,33 @@ fun drawLineSeq( val fan = rotate(normal, -sangle * (j.toFloat() / joinSteps)) val fanShift = Vec2(p1.x - fan.x, p1.y - fan.y) - vertex(color1, p1) - vertex(color1, prevVert) - vertex(color1, fanShift) + vcHelper.vertex(vc, color1, p1, mat) + vcHelper.vertex(vc, color1, prevVert, mat) + vcHelper.vertex(vc, color1, fanShift, mat) prevVert = fanShift } } } } - tess.end() + vcHelper.vcEndDrawer(vc) - fun drawCaps(color: BlockPos, point: Vec2, prev: Vec2) { + fun drawCaps(color: Int, point: Vec2, prev: Vec2) { val tangent = point.add(prev.negated()).normalized().scale(0.5f * width) val normal = Vec2(-tangent.y, tangent.x) val joinSteps = Mth.ceil(180f / CAP_THETA) - buf.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR) - vertex(color, point) + vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLE_FAN) + vcHelper.vertex(vc, color, point, mat) for (j in joinSteps downTo 0) { val fan = rotate(normal, -Mth.PI * (j.toFloat() / joinSteps)) - buf.vertex(mat, point.x + fan.x, point.y + fan.y, z).color(color.x, color.y, color.z, a).endVertex() + vcHelper.vertex(vc, color, Vec2(point.x + fan.x, point.y + fan.y), mat) } - tess.end() + vcHelper.vcEndDrawer(vc) } - drawCaps(BlockPos(r1.toInt(), g1.toInt(), b1.toInt()), points[0], points[1]) - drawCaps(BlockPos(r2.toInt(), g2.toInt(), b2.toInt()), points[n - 1], points[n - 2]) + drawCaps(ARGB32.color(a1.toInt(), r1.toInt(), g1.toInt(), b1.toInt()), points[0], points[1]) + drawCaps(ARGB32.color(a2.toInt(), r2.toInt(), g2.toInt(), b2.toInt()), points[n - 1], points[n - 2]) } + fun rotate(vec: Vec2, theta: Float): Vec2 { val cos = Mth.cos(theta) val sin = Mth.sin(theta) @@ -343,12 +353,12 @@ fun findDupIndices(pts: Iterable): Set { * include primitive drawing code... */ fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, r: Float, g: Float, b: Float, a: Float) { - val tess = Tesselator.getInstance() - val buf = tess.builder - // https://stackoverflow.com/questions/20394727/gl-triangle-strip-vs-gl-triangle-fan - // Starting point is the center - buf.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR) - buf.vertex(mat, point.x, point.y, 1f).color(r, g, b, a).endVertex() + drawSpot(mat, point, radius, ARGB32.color((a*255).toInt(), (r*255).toInt(), (g*255).toInt(), (b*255).toInt()), VCDrawHelper.Basic(1f)) +} + +fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, color: Int, vcHelper: VCDrawHelper) { + var vc = vcHelper.vcSetupAndSupply(VertexFormat.Mode.TRIANGLE_FAN); + vcHelper.vertex(vc, color, point, mat) // https://github.com/not-fl3/macroquad/blob/master/src/shapes.rs#L98 // yes they are gonna be little hexagons fite me @@ -358,10 +368,10 @@ fun drawSpot(mat: Matrix4f, point: Vec2, radius: Float, r: Float, g: Float, b: F val theta = i.toFloat() / fracOfCircle * TAU.toFloat() val rx = Mth.cos(theta) * radius + point.x val ry = Mth.sin(theta) * radius + point.y - buf.vertex(mat, rx, ry, 1f).color(r, g, b, a).endVertex() + vcHelper.vertex(vc, color, Vec2(rx, ry), mat) } - tess.end() + vcHelper.vcEndDrawer(vc) } fun screenCol(n: Int): Int { diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/VCDrawHelper.kt b/Common/src/main/java/at/petrak/hexcasting/client/render/VCDrawHelper.kt new file mode 100644 index 0000000000..fb6c734e5f --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/VCDrawHelper.kt @@ -0,0 +1,144 @@ +package at.petrak.hexcasting.client.render + +import at.petrak.hexcasting.api.HexAPI +import at.petrak.hexcasting.client.render.PatternRenderer.WorldlyBits +import com.ibm.icu.impl.CurrencyData.provider +import com.mojang.blaze3d.platform.GlStateManager +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.* +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.client.renderer.LightTexture +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.RenderType +import net.minecraft.client.renderer.texture.OverlayTexture +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.phys.Vec2 +import net.minecraft.world.phys.Vec3 +import org.joml.Matrix4f + + +interface VCDrawHelper { + fun vcSetupAndSupply(vertMode: VertexFormat.Mode): VertexConsumer + fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, matrix: Matrix4f){ + vertex(vc, color, pos, Vec2(0f,0f), matrix) + } + fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, uv: Vec2, matrix: Matrix4f) + + fun vcEndDrawer(vc: VertexConsumer) + + companion object { + + @JvmStatic + val WHITE: ResourceLocation = HexAPI.modLoc("textures/entity/white.png") + + @JvmStatic + fun getHelper(worldlyBits: WorldlyBits?, ps: PoseStack, z: Float, texture: ResourceLocation) : VCDrawHelper{ + if(worldlyBits != null){ + return Worldly(worldlyBits, ps, z, texture) + } + return Basic(z, texture) + } + + @JvmStatic + fun getHelper(worldlyBits: WorldlyBits?, ps: PoseStack, z: Float) : VCDrawHelper{ + return getHelper(worldlyBits, ps, z, WHITE) + } + } + + class Basic(val z: Float, val texture: ResourceLocation = WHITE) : VCDrawHelper { + + override fun vcSetupAndSupply(vertMode: VertexFormat.Mode): VertexConsumer{ + val tess = Tesselator.getInstance() + val buf = tess.builder + buf.begin(vertMode, DefaultVertexFormat.POSITION_COLOR_TEX) + RenderSystem.setShader(GameRenderer::getPositionColorTexShader); + RenderSystem.disableCull() + RenderSystem.enableDepthTest() + RenderSystem.enableBlend() + RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, + GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA) + RenderSystem.setShaderTexture(0, texture) + return buf + } + override fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, uv: Vec2, matrix: Matrix4f){ + vc.vertex(matrix, pos.x, pos.y, z).color(color).uv(uv.x, uv.y).endVertex() + } + override fun vcEndDrawer(vc: VertexConsumer){ + Tesselator.getInstance().end() + } + } + + class Worldly(val worldlyBits: WorldlyBits, val ps: PoseStack, val z: Float, val texture: ResourceLocation) : VCDrawHelper { + + var lastVertMode: VertexFormat.Mode ?= null // i guess this assumes that the vcHelper is only used once at a time? maybe reconsider that + + override fun vcSetupAndSupply(vertMode: VertexFormat.Mode): VertexConsumer{ + val provider = worldlyBits.provider + if (provider is MultiBufferSource.BufferSource) { + // tells it to draw whatever was here before so that we don't get depth buffer weirdness + provider.endBatch() + } + lastVertMode = vertMode + val buf = Tesselator.getInstance().builder + if(vertMode == VertexFormat.Mode.QUADS){ + val layer = RenderType.entityTranslucentCull(texture) + layer.setupRenderState() + if (provider == null) { + buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.NEW_ENTITY) + RenderSystem.setShader { GameRenderer.getRendertypeEntityTranslucentCullShader() } + return buf + } else { + return provider.getBuffer(layer) + } + } + buf.begin( vertMode, DefaultVertexFormat.NEW_ENTITY ) + // Generally this would be handled by a RenderLayer, but that doesn't seem to actually work here,, + RenderSystem.setShaderTexture(0, texture) + RenderSystem.enableDepthTest() + RenderSystem.disableCull() + Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer() + RenderSystem.enableBlend() + RenderSystem.blendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, + GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA ) + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + if (Minecraft.useShaderTransparency()) { + Minecraft.getInstance().levelRenderer.translucentTarget!!.bindWrite(false) + } + RenderSystem.setShader( GameRenderer::getRendertypeEntityTranslucentCullShader ) + return buf + } + + override fun vertex(vc: VertexConsumer, color: Int, pos: Vec2, uv: Vec2, matrix: Matrix4f){ + val nv = worldlyBits.normal?: Vec3(1.0, 1.0, 1.0) + vc.vertex(matrix, pos.x, pos.y, z) + .color(color) + .uv(uv.x, uv.y) + .overlayCoords(OverlayTexture.NO_OVERLAY) + .uv2(worldlyBits.light?: LightTexture.FULL_BRIGHT ) + .normal(ps.last().normal(), nv.x.toFloat(), nv.y.toFloat(), nv.z.toFloat()) + + vc.endVertex() + } + override fun vcEndDrawer(vc: VertexConsumer){ + if(lastVertMode == VertexFormat.Mode.QUADS){ + if (provider == null) { + val layer = RenderType.entityTranslucentCull(texture) + layer.end(Tesselator.getInstance().builder, VertexSorting.ORTHOGRAPHIC_Z) + } + } else { + Tesselator.getInstance().end() + Minecraft.getInstance().gameRenderer.lightTexture().turnOffLightLayer() + RenderSystem.disableBlend() + RenderSystem.defaultBlendFunc() + if (Minecraft.useShaderTransparency()) { + Minecraft.getInstance().mainRenderTarget.bindWrite(false) + } + RenderSystem.enableCull() + } + lastVertMode = null + } + } + + +} \ No newline at end of file diff --git a/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java b/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java new file mode 100644 index 0000000000..68c00d5d92 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/client/render/WorldlyPatternRenderHelpers.java @@ -0,0 +1,150 @@ +package at.petrak.hexcasting.client.render; + +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 at.petrak.hexcasting.common.entities.EntityWallScroll; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.core.Vec3i; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.AttachFace; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix3f; + +import javax.annotation.Nullable; + +/** + * Helper methods for rendering patterns in the world. + */ +public class WorldlyPatternRenderHelpers { + + public static final PatternSettings SCROLL_SETTINGS = new PatternSettings("scroll", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.STATIC + ); + + public static final PatternSettings READABLE_SCROLL_SETTINGS = new PatternSettings("scroll_readable", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.READABLE + ); + + public static final PatternSettings WORLDLY_SETTINGS = new PatternSettings("worldly", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.STATIC + ); + + public static final PatternSettings WORLDLY_SETTINGS_WOBBLY = new PatternSettings("wobbly_world", + PatternSettings.PositionSettings.paddedSquare(2.0/16), + PatternSettings.StrokeSettings.fromStroke(0.8/16), + PatternSettings.ZappySettings.WOBBLY + ); + + public static void renderPatternForScroll(HexPattern pattern, EntityWallScroll scroll, PoseStack ps, MultiBufferSource bufSource, int light, int blockSize, boolean showStrokeOrder) + { + ps.pushPose(); + ps.translate(-blockSize / 2f, -blockSize / 2f, 1f / 32f); + // there's almost certainly a better way to do this, but we're just flipping the y and z axes to fix normals + ps.last().normal().mul(new Matrix3f(1, 0, 0, 0, 0, 1, 0, 1, 0)); + renderPattern(pattern, showStrokeOrder ? READABLE_SCROLL_SETTINGS : SCROLL_SETTINGS, + showStrokeOrder ? PatternColors.READABLE_SCROLL_COLORS : PatternColors.DEFAULT_PATTERN_COLOR, + scroll.getPos().hashCode(), ps, bufSource, null, null, light, blockSize); + ps.popPose(); + } + + private static final int[] WALL_ROTATIONS = {180, 270, 0, 90}; + private static final Vec3i[] SLATE_FACINGS = {new Vec3i(0, -1, 0), new Vec3i(-1, -1, 0), new Vec3i(-1, -1, 1), new Vec3i(0, -1 , 1)}; + private static final Vec3[] WALL_NORMALS = {new Vec3(0, 0, -1), new Vec3(-1, 0, 0), new Vec3(0, 0, -1), new Vec3(-1, 0, 0)}; + private static final Vec3i[] SLATE_FLOORCEIL_FACINGS = {new Vec3i(0,0,0), new Vec3i(1,0,0), new Vec3i(1,0,1), new Vec3i(0,0,1)}; + + public static void renderPatternForSlate(BlockEntitySlate tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) + { + + 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(); + + boolean wombly = bs.getValue(BlockSlate.ENERGIZED); + + ps.pushPose(); + + Vec3 normal = null; + if(isOnWall){ + ps.mulPose(Axis.ZP.rotationDegrees(180)); + Vec3i tV = SLATE_FACINGS[facing % 4]; + ps.translate(tV.getX(), tV.getY(), tV.getZ()); + ps.mulPose(Axis.YP.rotationDegrees(WALL_ROTATIONS[facing % 4])); + normal = WALL_NORMALS[facing % 4]; + } else { + Vec3i tV = SLATE_FLOORCEIL_FACINGS[facing % 4]; + ps.translate(tV.getX(), tV.getY(), tV.getZ()); + + ps.mulPose(Axis.YP.rotationDegrees(facing*-90)); + ps.mulPose(Axis.XP.rotationDegrees(90 * (isOnCeiling ? -1 : 1))); + if(isOnCeiling) ps.translate(0,-1,1); + } + + renderPattern(pattern, + wombly ? WORLDLY_SETTINGS_WOBBLY : WORLDLY_SETTINGS, + wombly ? PatternColors.SLATE_WOBBLY_PURPLE_COLOR : PatternColors.DEFAULT_PATTERN_COLOR, + tile.getBlockPos().hashCode(), ps, buffer, normal, null, light, 1); + ps.popPose(); + } + + private static final Vec3i[] BLOCK_FACINGS = {new Vec3i(0, -1, 1), new Vec3i(0, -1, 0), new Vec3i(-1, -1, 0), new Vec3i(-1, -1, 1)}; + + public static void renderPatternForAkashicBookshelf(BlockEntityAkashicBookshelf tile, HexPattern pattern, PoseStack ps, MultiBufferSource buffer, int light, BlockState bs) + { + + int facing = bs.getValue(BlockAkashicBookshelf.FACING).get2DDataValue(); + + ps.pushPose(); + ps.mulPose(Axis.ZP.rotationDegrees(180)); + + Vec3i tV = BLOCK_FACINGS[facing % 4]; + ps.translate(tV.getX(), tV.getY(), tV.getZ()); + ps.mulPose(Axis.YP.rotationDegrees(WALL_ROTATIONS[facing % 4])); + + int actualLight = LevelRenderer.getLightColor(tile.getLevel(), tile.getBlockPos().relative(bs.getValue(BlockAkashicBookshelf.FACING))); + + renderPattern(pattern, WORLDLY_SETTINGS , PatternColors.DEFAULT_PATTERN_COLOR, + tile.getBlockPos().hashCode(), ps, buffer, WALL_NORMALS[facing % 4].multiply(-1, -1, -1), -0.02f, actualLight, 1); + ps.popPose(); + } + + /** + * Renders a pattern in world space based on the given transform requirements + */ + public static void renderPattern(HexPattern pattern, PatternSettings patSets, PatternColors patColors, + double seed, PoseStack ps, MultiBufferSource bufSource, Vec3 normal, @Nullable Float zOffset, + int light, int blockSize) + { + + ps.pushPose(); + + + float z = zOffset != null ? zOffset : ((-1f / 16f) - 0.01f); + + normal = normal != null ? normal : new Vec3(0, 0, -1); + + ps.translate(0,0, z); + ps.scale(blockSize, blockSize, 1); + + + PoseStack noNormalInv = new PoseStack(); + noNormalInv.scale(1, 1, -1); + ps.mulPoseMatrix(noNormalInv.last().pose()); + + PatternRenderer.renderPattern(pattern, ps, new PatternRenderer.WorldlyBits(bufSource, light, normal), + patSets, patColors, seed, blockSize * 512); + + ps.popPose(); + } +} 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 e9064fb54c..5bc52dc211 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,21 +1,12 @@ 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.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.blocks.akashic.BlockEntityAkashicBookshelf; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.Vec2; -import org.joml.AxisAngle4f; -import org.joml.Quaternionf; public class BlockEntityAkashicBookshelfRenderer implements BlockEntityRenderer { public BlockEntityAkashicBookshelfRenderer(BlockEntityRendererProvider.Context ctx) { @@ -32,66 +23,6 @@ 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(); - - ps.pushPose(); - - ps.translate(0.5, 0.5, 0.5); - var quarters = (-bs.getValue(BlockAkashicBookshelf.FACING).get2DDataValue()) % 4; - ps.mulPose(Axis.YP.rotation(Mth.HALF_PI * quarters)); - ps.mulPose(Axis.ZP.rotation(Mth.PI)); - - // and now Z is out? - ps.translate(0, 0, 0.5); - ps.scale(1 / 16f, 1 / 16f, 1 / 16f); - ps.translate(0, 0, 0.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 = pattern.getCenter(1); - var lines1 = pattern.toLines(1, Vec2.ZERO); - - 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; - } - } - var scale = Math.min(3.8f, Math.min(16 / 2.5f / maxDx, 16 / 2.5f / maxDy)); - - var com2 = pattern.getCenter(scale); - var lines2 = 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 j = 0; j < lines2.size(); j++) { - var v = lines2.get(j); - lines2.set(j, new Vec2(-v.x, v.y)); - } - - var stupidHash = tile.getBlockPos().hashCode(); - var zappy = RenderLib.makeZappy(lines2, RenderLib.findDupIndices(pattern.positions()), 10, 0.5f, 0f, 0f, 0f, - 1f, stupidHash); - - int outer = 0xff_d2c8c8; - int inner = 0xc8_322b33; - RenderLib.drawLineSeq(ps.last().pose(), zappy, 1f, 0f, outer, outer); - RenderLib.drawLineSeq(ps.last().pose(), zappy, 0.4f, 0.01f, inner, inner); - - ps.popPose(); - RenderSystem.setShader(() -> oldShader); + WorldlyPatternRenderHelpers.renderPatternForAkashicBookshelf(tile, pattern, ps, buffer, light, bs); } } 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 37a40babcd..7fbf43ffc6 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,21 +1,11 @@ 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.client.render.WorldlyPatternRenderHelpers; import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; -import at.petrak.hexcasting.common.blocks.circles.BlockSlate; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -import net.minecraft.util.Mth; -import net.minecraft.world.level.block.state.properties.AttachFace; -import net.minecraft.world.phys.Vec2; - -import java.util.ArrayList; public class BlockEntitySlateRenderer implements BlockEntityRenderer { public BlockEntitySlateRenderer(BlockEntityRendererProvider.Context ctx) { @@ -30,94 +20,6 @@ public void render(BlockEntitySlate tile, float pPartialTick, PoseStack ps, 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(); - - ps.pushPose(); - - ps.translate(0.5, 0.5, 0.5); - var attchFace = bs.getValue(BlockSlate.ATTACH_FACE); - if (attchFace == AttachFace.WALL) { - var quarters = (-bs.getValue(BlockSlate.FACING).get2DDataValue()) % 4; - ps.mulPose(Axis.YP.rotation(Mth.HALF_PI * quarters)); - ps.mulPose(Axis.ZP.rotation(Mth.PI)); - } else { - var neg = attchFace == AttachFace.FLOOR ? -1 : 1; - ps.mulPose(Axis.XP.rotation(neg * Mth.HALF_PI)); - var quarters = (bs.getValue(BlockSlate.FACING).get2DDataValue() + 2) % 4; - 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(1f / resolution, 1f / resolution, 1f / resolution); - ps.translate(0, 0, 1.01); - - 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); - - // 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); - } - - 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) - )); - } - - // 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(), zappyRenderSpace, 1f, 0f, outer, outer); - RenderLib.drawLineSeq(ps.last().pose(), zappyRenderSpace, 0.4f, 0.01f, inner, inner); - - ps.popPose(); - RenderSystem.setShader(() -> oldShader); + WorldlyPatternRenderHelpers.renderPatternForSlate(tile, tile.pattern, ps, buffer, light, bs); } } \ No newline at end of file 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 63489e8c0e..9a0ec965bc 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,7 +2,6 @@ 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; @@ -15,7 +14,6 @@ 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 index e653759aef..868f02e2de 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/command/PatternTexturesCommand.java @@ -1,32 +1,29 @@ 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; +import net.minecraft.network.chat.Component; public class PatternTexturesCommand { public static void add(LiteralArgumentBuilder cmd) { + // TODO: do we want these in release ?? cmd.then(Commands.literal("textureToggle") .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) .executes(ctx -> { PatternTextureManager.useTextures = !PatternTextureManager.useTextures; + String log = (PatternTextureManager.useTextures ? "Enabled" : "Disabled") + " pattern texture rendering. This is meant for debugging."; + ctx.getSource().sendSuccess(() -> Component.literal(log), true); return 1; })); cmd.then(Commands.literal("textureRepaint") .requires(dp -> dp.hasPermission(Commands.LEVEL_ADMINS)) .executes(ctx -> { PatternTextureManager.repaint(); + ctx.getSource().sendSuccess(() -> Component.literal("Repainting pattern textures. This is meant for debugging."), true); 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 cd9ab651cb..03d916c8f7 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,8 +3,6 @@ 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; import at.petrak.hexcasting.common.lib.HexSounds; @@ -32,12 +30,9 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; -import java.util.List; - public class EntityWallScroll extends HangingEntity { private static final EntityDataAccessor SHOWS_STROKE_ORDER = SynchedEntityData.defineId( EntityWallScroll.class, @@ -48,9 +43,6 @@ public class EntityWallScroll extends HangingEntity { public HexPattern pattern; public boolean isAncient; public int blockSize; - // Client-side only! - @Nullable - public HexPatternPoints points; public EntityWallScroll(EntityType type, Level world) { super(type, world); @@ -72,21 +64,9 @@ public void recalculateDisplay() { CompoundTag patternTag = NBTHelper.getCompound(scroll, ItemScroll.TAG_PATTERN); if (patternTag != null) { this.pattern = HexPattern.fromNBT(patternTag); - if (this.level().isClientSide) { - var pair = RenderLib.getCenteredPattern(pattern, 128f / 3 * blockSize, 128f / 3 * blockSize, - 16f / 3 * blockSize); - var dots = pair.getSecond(); - var readOffset = this.getShowsStrokeOrder() ? RenderLib.DEFAULT_READABILITY_OFFSET : 0f; - var lastProp = this.getShowsStrokeOrder() ? RenderLib.DEFAULT_LAST_SEGMENT_LEN_PROP : 1f; - 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.points = null; this.isAncient = false; } } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java index 96241ea367..036e5129b4 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemScroll.java @@ -9,6 +9,7 @@ import at.petrak.hexcasting.common.entities.EntityWallScroll; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.common.misc.PatternTooltip; +import at.petrak.hexcasting.interop.inline.InlinePatternData; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -132,7 +133,13 @@ public Component getName(ItemStack pStack) { return Component.translatable(descID + ".of", Component.translatable("hexcasting.action." + ResourceLocation.tryParse(ancientId))); } else if (NBTHelper.hasCompound(pStack, TAG_PATTERN)) { - return Component.translatable(descID); + var compound = NBTHelper.getCompound(pStack, ItemScroll.TAG_PATTERN); + var patternLabel = Component.literal(""); + if (compound != null) { + var pattern = HexPattern.fromNBT(compound); + patternLabel = Component.literal(": ").append(new InlinePatternData(pattern).asText(false)); + } + return Component.translatable(descID).append(patternLabel); } else { return Component.translatable(descID + ".empty"); } diff --git a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java index 6ad6f5b51b..98227d1eab 100644 --- a/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java +++ b/Common/src/main/java/at/petrak/hexcasting/common/items/storage/ItemSlate.java @@ -11,6 +11,7 @@ import at.petrak.hexcasting.common.blocks.circles.BlockEntitySlate; import at.petrak.hexcasting.common.lib.hex.HexIotaTypes; import at.petrak.hexcasting.common.misc.PatternTooltip; +import at.petrak.hexcasting.interop.inline.InlinePatternData; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; @@ -38,16 +39,27 @@ public ItemSlate(Block pBlock, Properties pProperties) { @Override public Component getName(ItemStack pStack) { var key = "block." + HexAPI.MOD_ID + ".slate." + (hasPattern(pStack) ? "written" : "blank"); - return Component.translatable(key); + Component patternText = getPattern(pStack) + .map(pat -> Component.literal(": ").append(new InlinePatternData(pat).asText(false))) + .orElse(Component.literal("")); + return Component.translatable(key).append(patternText); } - public static boolean hasPattern(ItemStack stack) { + public static Optional getPattern(ItemStack stack){ var bet = NBTHelper.getCompound(stack, "BlockEntityTag"); - if (bet != null) { - return bet.contains(BlockEntitySlate.TAG_PATTERN, Tag.TAG_COMPOUND) && - !bet.getCompound(BlockEntitySlate.TAG_PATTERN).isEmpty(); + + if (bet != null && bet.contains(BlockEntitySlate.TAG_PATTERN, Tag.TAG_COMPOUND)) { + var patTag = bet.getCompound(BlockEntitySlate.TAG_PATTERN); + if (!patTag.isEmpty()) { + var pattern = HexPattern.fromNBT(patTag); + return Optional.of(pattern); + } } - return false; + return Optional.empty(); + } + + public static boolean hasPattern(ItemStack stack) { + return getPattern(stack).isPresent(); } @SoftImplement("IForgeItem") @@ -112,15 +124,6 @@ public void writeDatum(ItemStack stack, Iota datum) { @Override public Optional getTooltipImage(ItemStack stack) { - var bet = NBTHelper.getCompound(stack, "BlockEntityTag"); - - if (bet != null && bet.contains(BlockEntitySlate.TAG_PATTERN, Tag.TAG_COMPOUND)) { - var patTag = bet.getCompound(BlockEntitySlate.TAG_PATTERN); - if (!patTag.isEmpty()) { - var pattern = HexPattern.fromNBT(patTag); - return Optional.of(new PatternTooltip(pattern, PatternTooltipComponent.SLATE_BG)); - } - } - return Optional.empty(); + return getPattern(stack).map(pat -> new PatternTooltip(pat, PatternTooltipComponent.SLATE_BG)); } } diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java b/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java index 79220f4ab9..6ecbc2d40f 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/HexInterop.java @@ -1,5 +1,7 @@ package at.petrak.hexcasting.interop; +import at.petrak.hexcasting.interop.inline.InlineHex; +import at.petrak.hexcasting.interop.inline.InlineHexClient; import at.petrak.hexcasting.interop.pehkui.PehkuiInterop; import at.petrak.hexcasting.xplat.IClientXplatAbstractions; import at.petrak.hexcasting.xplat.IXplatAbstractions; @@ -30,10 +32,13 @@ public static void init() { } xplat.initPlatformSpecific(); + + InlineHex.init(); } public static void clientInit() { IClientXplatAbstractions.INSTANCE.initPlatformSpecific(); + InlineHexClient.init(); } private static void initPatchouli() { diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/HexPatternMatcher.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/HexPatternMatcher.java new file mode 100644 index 0000000000..55af9756e8 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/HexPatternMatcher.java @@ -0,0 +1,105 @@ +package at.petrak.hexcasting.interop.inline; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.casting.math.HexDir; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import com.samsthenerd.inline.api.InlineAPI; +import com.samsthenerd.inline.api.matching.InlineMatch; +import com.samsthenerd.inline.api.matching.MatchContext; +import com.samsthenerd.inline.api.matching.MatcherInfo; +import com.samsthenerd.inline.api.matching.RegexMatcher; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Tuple; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; + +public class HexPatternMatcher implements RegexMatcher { + + private static final ResourceLocation patternMatcherID = HexAPI.modLoc("pattern"); + private static final MatcherInfo patternMatcherInfo = MatcherInfo.fromId(patternMatcherID); + + // thx kyra <3 + private static final Pattern PATTERN_PATTERN_REGEX = Pattern.compile("(?\\\\?)(?:HexPattern)?[<(\\[{]\\s*(?[a-zA-Z_-]+)(?:\\s*(?[,!+:; ])\\s*(?[aqwedsAQWEDS]+)?)\\s*[>)\\]}]", Pattern.CASE_INSENSITIVE); + + public static HexPatternMatcher INSTANCE = new HexPatternMatcher(); + + public Pattern getRegex(){ + return PATTERN_PATTERN_REGEX; + } + + @Override + @NotNull + public Tuple getMatchAndGroup(MatchResult regexMatch, MatchContext ctx) { + String escaped = regexMatch.group(1); + String dirString = regexMatch.group(2).toLowerCase().strip().replace("_", ""); + String sizeModString = regexMatch.group(3); + String angleSigs = regexMatch.group(4); + if(escaped == null){ + return new Tuple<>(new InlineMatch.TextMatch(Component.literal("")), 1); + } + // need to convert dirString to a HexDir + HexDir dir = dirMap.get(dirString); + if(dir == null) + return new Tuple<>(null, 0); + HexPattern pat; + if(angleSigs == null){ + angleSigs = ""; + } + try{ + pat = HexPattern.fromAngles(angleSigs.toLowerCase(), dir); + InlinePatternData patData = new InlinePatternData(pat); + Style patDataStyle = patData.getExtraStyle(); + if(sizeModString != null && sizeModString.equals("+")) + patDataStyle = InlineAPI.INSTANCE.withSizeModifier(patDataStyle, 2); + if(sizeModString != null && sizeModString.equals("!")) + patDataStyle = InlineAPI.INSTANCE.withSizeModifier(patDataStyle, 1.5); + return new Tuple<>(new InlineMatch.DataMatch(patData,patDataStyle ), 0); + } catch (Exception e){ + return new Tuple<>(null, 0); + } + } + + // not really used since we're doing escaping + @Override + @Nullable + public InlineMatch getMatch(MatchResult mr, MatchContext ctx){ + return null; // nop + } + + public MatcherInfo getInfo(){ + return patternMatcherInfo; + } + + /** + * Get the ID for this matcher + * @return matcher's ID + */ + public ResourceLocation getId(){ + return patternMatcherID; + } + + private static final Map dirMap = new HashMap<>(); + + static { + dirMap.put("northwest", HexDir.NORTH_WEST); + dirMap.put("west", HexDir.WEST); + dirMap.put("southwest", HexDir.SOUTH_WEST); + dirMap.put("southeast", HexDir.SOUTH_EAST); + dirMap.put("east", HexDir.EAST); + dirMap.put("northeast", HexDir.NORTH_EAST); + dirMap.put("nw", HexDir.NORTH_WEST); + + dirMap.put("w", HexDir.WEST); + dirMap.put("sw", HexDir.SOUTH_WEST); + dirMap.put("se", HexDir.SOUTH_EAST); + dirMap.put("e", HexDir.EAST); + dirMap.put("ne", HexDir.NORTH_EAST); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHex.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHex.java new file mode 100644 index 0000000000..fa9ce27a93 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHex.java @@ -0,0 +1,9 @@ +package at.petrak.hexcasting.interop.inline; + +import com.samsthenerd.inline.api.InlineAPI; + +public class InlineHex { + public static void init(){ + InlineAPI.INSTANCE.addDataType(InlinePatternData.InlinePatternDataType.INSTANCE); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHexClient.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHexClient.java new file mode 100644 index 0000000000..aa61b7e193 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlineHexClient.java @@ -0,0 +1,11 @@ +package at.petrak.hexcasting.interop.inline; + +import com.samsthenerd.inline.api.client.InlineClientAPI; + +public class InlineHexClient { + + public static void init(){ + InlineClientAPI.INSTANCE.addMatcher(HexPatternMatcher.INSTANCE); + InlineClientAPI.INSTANCE.addRenderer(InlinePatternRenderer.INSTANCE); + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java new file mode 100644 index 0000000000..5dc2dba2ff --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternData.java @@ -0,0 +1,89 @@ +package at.petrak.hexcasting.interop.inline; + +import at.petrak.hexcasting.api.HexAPI; +import at.petrak.hexcasting.api.casting.PatternShapeMatch; +import at.petrak.hexcasting.api.casting.iota.PatternIota; +import at.petrak.hexcasting.api.casting.math.HexPattern; +import at.petrak.hexcasting.common.casting.PatternRegistryManifest; +import at.petrak.hexcasting.common.lib.HexItems; +import com.mojang.serialization.Codec; +import com.samsthenerd.inline.api.InlineData; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class InlinePatternData implements InlineData{ + + public static final ResourceLocation rendererId = HexAPI.modLoc("pattern"); + + @NotNull + public final HexPattern pattern; + + public InlinePatternData(@NotNull HexPattern pattern){ + this.pattern = pattern; + } + + @Override + public InlinePatternDataType getType(){ + return InlinePatternDataType.INSTANCE; + } + + @Override + public ResourceLocation getRendererId(){ + return rendererId; + } + + @Override + public Style getExtraStyle() { + ItemStack scrollStack = new ItemStack(HexItems.SCROLL_MEDIUM); + HexItems.SCROLL_MEDIUM.writeDatum(scrollStack, new PatternIota(pattern)); + scrollStack.setHoverName(getPatternName(pattern).copy().withStyle(ChatFormatting.WHITE)); + HoverEvent he = new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(scrollStack)); + ClickEvent ce = new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, pattern.toString()); + return Style.EMPTY.withHoverEvent(he).withClickEvent(ce); + } + + public static Component getPatternName(HexPattern pattern){ + try { + PatternShapeMatch shapeMatch = PatternRegistryManifest.matchPattern(pattern, null, false); + if(shapeMatch instanceof PatternShapeMatch.Normal normMatch){ + return HexAPI.instance().getActionI18n(normMatch.key, false); + } + // TODO: this doesn't actually ever hit because it errors out with server castinv env stuff first :( + if(shapeMatch instanceof PatternShapeMatch.Special specialMatch){ + return HexAPI.instance().getSpecialHandlerI18n(specialMatch.key); + } + } catch (Exception e){ + // nop + } + return PatternIota.displayNonInline(pattern); + } + + @Override + public Component asText(boolean withExtra) { + return Component.literal(pattern.toString()).withStyle(asStyle(withExtra)); + } + + public static class InlinePatternDataType implements InlineDataType { + private static final ResourceLocation ID = new ResourceLocation(HexAPI.MOD_ID, "pattern"); + public static final InlinePatternDataType INSTANCE = new InlinePatternDataType(); + + @Override + public ResourceLocation getId(){ + return ID; + } + + @Override + public Codec getCodec(){ + return HexPattern.CODEC.xmap( + InlinePatternData::new, + data -> data.pattern + ); + } + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternRenderer.java b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternRenderer.java new file mode 100644 index 0000000000..eb7b798487 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/interop/inline/InlinePatternRenderer.java @@ -0,0 +1,82 @@ +package at.petrak.hexcasting.interop.inline; + +import at.petrak.hexcasting.client.render.*; +import com.samsthenerd.inline.api.client.GlowHandling; +import com.samsthenerd.inline.api.client.InlineRenderer; +import com.samsthenerd.inline.impl.InlineStyle; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; + +public class InlinePatternRenderer implements InlineRenderer { + + public static final InlinePatternRenderer INSTANCE = new InlinePatternRenderer(); + + public ResourceLocation getId(){ + return InlinePatternData.rendererId; + } + + public static final PatternSettings INLINE_SETTINGS = new PatternSettings("inline", + new PatternSettings.PositionSettings(1.0, 9.0, 0, 0.5, + PatternSettings.AxisAlignment.CENTER, PatternSettings.AxisAlignment.CENTER_FIT, 4.0, 0, 0), + PatternSettings.StrokeSettings.fromStroke(1.0), + new PatternSettings.ZappySettings(10, 0, 0, 0, + PatternSettings.ZappySettings.READABLE_OFFSET, 0.7f) + ){ + @Override + public double getOuterWidth(double scale){ + if(scale >= 1) return 1; + if(scale >= 0.75) return 0.75; + if(scale >= 0.5) return 0.5; + return 0.25; + } + }; + + public static final PatternSettings INLINE_SETTINGS_GLOWY = new PatternSettings("inlineglowy", + new PatternSettings.PositionSettings(1.0, 11.0, 0, 0.5, + PatternSettings.AxisAlignment.CENTER, PatternSettings.AxisAlignment.CENTER_FIT, 4.0, 0, 0), + new PatternSettings.StrokeSettings(1, 3, 0.8 * 1 * 2.0 / 5.0, 0.4 * 1 * 2.0 / 5.0), + INLINE_SETTINGS.zapSets + ){ + @Override + public double getInnerWidth(double scale){ + if(scale >= 1) return 1; + if(scale >= 0.75) return 0.75; + if(scale >= 0.5) return 0.5; + return 0.25; + } + }; + + @Override + public GlowHandling getGlowPreference(InlinePatternData forData) { + return new GlowHandling.None(); + } + + public static final int INLINE_TEXTURE_RES = 16; // 128px so it looks good and pretty on up close signs and whatnot + + public int render(InlinePatternData data, GuiGraphics drawContext, int index, Style style, int codepoint, TextRenderingContext trContext){ + if(trContext.isGlowy()) return charWidth(data, style, codepoint); + int glowyParentColor = ((InlineStyle) style).getComponent(InlineStyle.GLOWY_PARENT_COMP); + boolean isGlowy = glowyParentColor != -1; + drawContext.pose().pushPose(); + drawContext.pose().translate(isGlowy ? -1f : 0, isGlowy ? -1.5f : -0.5f, 0f); + int color = trContext.usableColor(); + PatternRenderer.renderPattern(data.pattern, drawContext.pose(), new PatternRenderer.WorldlyBits(drawContext.bufferSource(), trContext.light(), null), + isGlowy ? INLINE_SETTINGS_GLOWY : INLINE_SETTINGS, + isGlowy ? new PatternColors(color, 0xFF_000000 | glowyParentColor) : PatternColors.singleStroke(color), + 0, INLINE_TEXTURE_RES); + + drawContext.pose().popPose(); + return charWidth(data, style, codepoint); + } + + public int charWidth(InlinePatternData data, Style style, int codepoint){ + + HexPatternPoints staticPoints = HexPatternPoints.getStaticPoints(HexPatternLike.of(data.pattern), INLINE_SETTINGS, 0); + + double baseScale = 4.0 / 1.5; + double baseHeight = staticPoints.rangeY * baseScale; + + return (int)Math.ceil(Math.min(baseHeight, 8.0) * staticPoints.rangeX / staticPoints.rangeY) + 1; // (+2 for padding) + } +} diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java index 4165756417..997bb09411 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/AbstractPatternComponent.java @@ -1,14 +1,11 @@ package at.petrak.hexcasting.interop.patchouli; -import at.petrak.hexcasting.api.casting.math.HexCoord; import at.petrak.hexcasting.api.casting.math.HexPattern; -import at.petrak.hexcasting.client.render.RenderLib; -import at.petrak.hexcasting.interop.utils.PatternDrawingUtil; -import at.petrak.hexcasting.interop.utils.PatternEntry; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.PatternSettings; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.datafixers.util.Pair; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.world.phys.Vec2; import vazkii.patchouli.api.IComponentRenderContext; import vazkii.patchouli.api.ICustomComponent; import vazkii.patchouli.api.IVariable; @@ -23,8 +20,7 @@ abstract public class AbstractPatternComponent implements ICustomComponent { protected transient int x, y; protected transient float hexSize; - private transient List patterns; - private transient List zappyPoints; + private transient List patterns; /** * Pass -1, -1 to center it. @@ -35,29 +31,57 @@ public void build(int x, int y, int pagenum) { this.y = y == -1 ? 70 : y; } - public abstract List> getPatterns(UnaryOperator lookup); + public abstract List getPatterns(UnaryOperator lookup); public abstract boolean showStrokeOrder(); @Override public void render(GuiGraphics graphics, IComponentRenderContext context, float pticks, int mouseX, int mouseY) { - PatternDrawingUtil.drawPattern(graphics, this.x, this.y, this.patterns, this.zappyPoints, - this.showStrokeOrder(), - 0xff_d2c8c8, 0xc8_aba2a2, 0xc8_322b33, 0x80_d1cccc); + PoseStack ps = graphics.pose(); + // want to position x: [0, 116], y: [16, 80] + ps.pushPose(); + + int cols = (int)Math.ceil(Math.sqrt(patterns.size())); + int rows = (int)Math.ceil(patterns.size()/(double)cols); + + double cellW = 116 / (double)cols; + double cellH = 64 / (double)rows; + + PatternSettings patSets = new PatternSettings("book" + patterns.size() + (showStrokeOrder() ? "" : "r"), + new PatternSettings.PositionSettings(cellW, cellH, 2, 2, + PatternSettings.AxisAlignment.CENTER_FIT, PatternSettings.AxisAlignment.CENTER_FIT, 16, 0, 0), + PatternSettings.StrokeSettings.fromStroke(4), + showStrokeOrder() ? PatternSettings.ZappySettings.READABLE : PatternSettings.ZappySettings.STATIC + ); + + PatternColors patCols = PatternColors.DIMMED_COLOR.withDots(false, true); + + if(showStrokeOrder()){ + patCols = PatternRenderer.shouldDoStrokeGradient() ? PatternColors.DEFAULT_GRADIENT_COLOR.withDots(true, true) + : PatternColors.READABLE_GRID_SCROLL_COLORS; + } + + for(int p = 0; p < patterns.size(); p++){ + + int r = p / cols; + int c = p % cols; + HexPattern pattern = patterns.get(p); + + ps.pushPose(); + ps.translate(cellW * c, cellH * r + 16, 100); + + PatternRenderer.renderPattern(pattern, graphics.pose(), patSets, patCols, 0, 4); + ps.popPose(); + } + ps.popPose(); } @Override public void onVariablesAvailable(UnaryOperator lookup) { - var patterns = this.getPatterns(lookup); - var data = PatternDrawingUtil.loadPatterns( - patterns, - this.showStrokeOrder() ? RenderLib.DEFAULT_READABILITY_OFFSET : 0f, - this.showStrokeOrder() ? RenderLib.DEFAULT_LAST_SEGMENT_LEN_PROP : 1f); - this.hexSize = data.hexSize(); - this.patterns = data.patterns(); - this.zappyPoints = data.pathfinderDots(); + this.patterns = this.getPatterns(lookup); } + // used for deserialization from patchi protected static class RawPattern { protected String startdir; protected String signature; diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java index 7f5517b361..632c7abf79 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/LookupPatternComponent.java @@ -1,11 +1,9 @@ package at.petrak.hexcasting.interop.patchouli; -import at.petrak.hexcasting.api.casting.math.HexCoord; import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.xplat.IXplatAbstractions; import com.google.gson.annotations.SerializedName; -import com.mojang.datafixers.util.Pair; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import vazkii.patchouli.api.IVariable; @@ -24,13 +22,13 @@ public class LookupPatternComponent extends AbstractPatternComponent { protected boolean strokeOrder; @Override - public List> getPatterns(UnaryOperator lookup) { + public List getPatterns(UnaryOperator lookup) { var key = ResourceKey.create(IXplatAbstractions.INSTANCE.getActionRegistry().key(), this.opName); var entry = IXplatAbstractions.INSTANCE.getActionRegistry().get(key); this.strokeOrder = !IXplatAbstractions.INSTANCE.getActionRegistry().getHolderOrThrow(key).is(HexTags.Actions.PER_WORLD_PATTERN); - return List.of(new Pair<>(entry.prototype(), HexCoord.getOrigin())); + return List.of(entry.prototype()); } @Override diff --git a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java index f03946384c..06f77903ba 100644 --- a/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java +++ b/Common/src/main/java/at/petrak/hexcasting/interop/patchouli/ManualPatternComponent.java @@ -1,12 +1,10 @@ package at.petrak.hexcasting.interop.patchouli; -import at.petrak.hexcasting.api.casting.math.HexCoord; import at.petrak.hexcasting.api.casting.math.HexDir; import at.petrak.hexcasting.api.casting.math.HexPattern; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; -import com.mojang.datafixers.util.Pair; import vazkii.patchouli.api.IVariable; import java.util.ArrayList; @@ -25,19 +23,18 @@ public class ManualPatternComponent extends AbstractPatternComponent { protected transient boolean strokeOrder; @Override - public List> getPatterns(UnaryOperator lookup) { + public List getPatterns(UnaryOperator lookup) { this.strokeOrder = lookup.apply(IVariable.wrap(this.strokeOrderRaw)).asBoolean(true); var patsRaw = lookup.apply(IVariable.wrap(patternsRaw)).asListOrSingleton(); - var out = new ArrayList>(); + var out = new ArrayList(); for (var ivar : patsRaw) { JsonElement json = ivar.unwrap(); RawPattern raw = new Gson().fromJson(json, RawPattern.class); var dir = HexDir.fromString(raw.startdir); var pat = HexPattern.fromAngles(raw.signature, dir); - var origin = new HexCoord(raw.q, raw.r); - out.add(new Pair<>(pat, origin)); + out.add(pat); } return out; diff --git a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 index dfdc98cdd0..019b49ad41 100644 --- a/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 +++ b/Common/src/main/resources/assets/hexcasting/lang/en_us.flatten.json5 @@ -433,7 +433,15 @@ "fail.already": "%s is already empty", }, }, - + + // === Inline ! === + "matcher.hexcasting.pattern": { + "title": "Hex Pattern", + "title.styled": "Hex Pattern ", + "description": "Visual Hex Casting Patterns", + "example": "" + }, + hexcasting: { "pattern.unknown": "Unknown pattern resource location %s", diff --git a/Fabric/build.gradle b/Fabric/build.gradle index aafe9a125d..9e60ab70d3 100644 --- a/Fabric/build.gradle +++ b/Fabric/build.gradle @@ -84,6 +84,8 @@ dependencies { modImplementation "at.petra-k.paucal:paucal-fabric-$minecraftVersion:$paucalVersion" modImplementation "vazkii.patchouli:Patchouli:$minecraftVersion-$patchouliVersion-FABRIC-SNAPSHOT" + modImplementation "com.samsthenerd.inline:inline-fabric:$minecraftVersion-$inlineVersion" + modImplementation "dev.onyxstudios.cardinal-components-api:cardinal-components-base:$cardinalComponentsVersion" // modImplementation "dev.onyxstudios.cardinal-components-api:cardinal-components-util:$cardinalComponentsVersion" modImplementation "dev.onyxstudios.cardinal-components-api:cardinal-components-entity:$cardinalComponentsVersion" diff --git a/Fabric/gradle.properties b/Fabric/gradle.properties index 403ee97acd..912673b4a7 100644 --- a/Fabric/gradle.properties +++ b/Fabric/gradle.properties @@ -11,7 +11,6 @@ emiVersion=1.0.4+1.20.1 #gravityApiVersion=1.0.6 trinketsVersion=3.7.0 -clothConfigVersion=11.1.106 modmenuVersion=7.0.1 # Optimizations diff --git a/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java b/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java index 3f27fff2ea..da699de7a9 100644 --- a/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java +++ b/Fabric/src/main/java/at/petrak/hexcasting/fabric/interop/emi/PatternRendererEMI.java @@ -1,19 +1,18 @@ package at.petrak.hexcasting.fabric.interop.emi; -import at.petrak.hexcasting.api.casting.math.HexCoord; +import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.api.utils.HexUtils; -import at.petrak.hexcasting.interop.utils.PatternDrawingUtil; -import at.petrak.hexcasting.interop.utils.PatternEntry; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.PatternSettings; +import at.petrak.hexcasting.client.render.PatternSettings.PositionSettings; +import at.petrak.hexcasting.client.render.PatternSettings.StrokeSettings; +import at.petrak.hexcasting.client.render.PatternSettings.ZappySettings; import at.petrak.hexcasting.xplat.IXplatAbstractions; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.datafixers.util.Pair; import dev.emi.emi.api.render.EmiRenderable; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.phys.Vec2; - -import java.util.List; public class PatternRendererEMI implements EmiRenderable { @@ -25,19 +24,21 @@ public class PatternRendererEMI implements EmiRenderable { private boolean strokeOrder; - private final List patterns; - private final List pathfinderDots; + private final HexPattern pat; + private PatternSettings patSets; public PatternRendererEMI(ResourceLocation pattern, int w, int h) { var regi = IXplatAbstractions.INSTANCE.getActionRegistry(); var entry = regi.get(pattern); this.strokeOrder = HexUtils.isOfTag(regi, pattern, HexTags.Actions.PER_WORLD_PATTERN); - var data = PatternDrawingUtil.loadPatterns(List.of(new Pair<>(entry.prototype(), HexCoord.getOrigin())), 0f, - 1f); - this.patterns = data.patterns(); - this.pathfinderDots = data.pathfinderDots(); + this.pat = entry.prototype(); this.width = w; this.height = h; + this.patSets = new PatternSettings("pattern_drawable_" + w + "_" + h, + new PositionSettings(width, height, 0, 0, + PatternSettings.AxisAlignment.CENTER_FIT, PatternSettings.AxisAlignment.CENTER_FIT, Math.max(width, height), 0, 0), + StrokeSettings.fromStroke(0.075 * Math.min(width, height)), + ZappySettings.READABLE); } public PatternRendererEMI shift(int x, int y) { @@ -47,6 +48,13 @@ public PatternRendererEMI shift(int x, int y) { } public PatternRendererEMI strokeOrder(boolean order) { + if(order != strokeOrder){ + patSets = new PatternSettings("pattern_drawable_" + width + "_" + height + (order ? "" : "nostroke"), + patSets.posSets, + patSets.strokeSets, + order ? ZappySettings.READABLE : ZappySettings.STATIC + ); + } strokeOrder = order; return this; } @@ -55,10 +63,11 @@ public PatternRendererEMI strokeOrder(boolean order) { public void render(GuiGraphics graphics, int x, int y, float delta) { var ps = graphics.pose(); ps.pushPose(); - ps.translate(xOffset + x - 0.5f + width / 2f, yOffset + y + 1 + height / 2f, 0); - ps.scale(width / 64f, height / 64f, 1f); - PatternDrawingUtil.drawPattern(graphics, 0, 0, this.patterns, this.pathfinderDots, this.strokeOrder, - 0xff_333030, 0xff_191818, 0xc8_0c0a0c, 0x80_666363); + ps.translate(xOffset + x, yOffset + y + 1, 0); + PatternRenderer.renderPattern(pat, graphics.pose(), patSets, + new PatternColors(0xc8_0c0a0c, 0xff_333030).withDotColors(0x80_666363, 0), + 0, 10 + ); ps.popPose(); } } diff --git a/Fabric/src/main/resources/fabric.mod.json b/Fabric/src/main/resources/fabric.mod.json index 4468af2380..85a8965ace 100644 --- a/Fabric/src/main/resources/fabric.mod.json +++ b/Fabric/src/main/resources/fabric.mod.json @@ -57,7 +57,8 @@ "cardinal-components-block": "~5.2.1", "paucal": ">=0.6.0-pre <0.7.0", "cloth-config": "11.1.*", - "patchouli": ">=1.20.1-80" + "patchouli": ">=1.20.1-80", + "inline": ">=1.20.1-1.0.1" }, "suggests": { "pehkui": ">=3.7.6", diff --git a/Forge/build.gradle b/Forge/build.gradle index c1942d6013..78be55d9aa 100644 --- a/Forge/build.gradle +++ b/Forge/build.gradle @@ -59,6 +59,8 @@ repositories { url = 'https://thedarkcolour.github.io/KotlinForForge/' content { includeGroup "thedarkcolour" } } + + maven { url "https://maven.shedaniel.me/" } } dependencies { @@ -80,6 +82,11 @@ dependencies { implementation fg.deobf("top.theillusivec4.caelus:caelus-forge:$caelusVersion") + implementation fg.deobf("com.samsthenerd.inline:inline-forge:$minecraftVersion-$inlineVersion") + + // needed for inline to run + runtimeOnly fg.deobf("me.shedaniel.cloth:cloth-config-forge:$clothConfigVersion") + // Optional interop compileOnly fg.deobf("mezz.jei:jei-$minecraftVersion-common-api:$jeiVersion") diff --git a/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java b/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java index 62417e5e28..bf96781a00 100644 --- a/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java +++ b/Forge/src/main/java/at/petrak/hexcasting/forge/interop/jei/PatternDrawable.java @@ -1,40 +1,40 @@ package at.petrak.hexcasting.forge.interop.jei; -import at.petrak.hexcasting.api.casting.math.HexCoord; +import at.petrak.hexcasting.api.casting.math.HexPattern; import at.petrak.hexcasting.api.mod.HexTags; import at.petrak.hexcasting.api.utils.HexUtils; -import at.petrak.hexcasting.interop.utils.PatternDrawingUtil; -import at.petrak.hexcasting.interop.utils.PatternEntry; +import at.petrak.hexcasting.client.render.PatternColors; +import at.petrak.hexcasting.client.render.PatternRenderer; +import at.petrak.hexcasting.client.render.PatternSettings; +import at.petrak.hexcasting.client.render.PatternSettings.PositionSettings; +import at.petrak.hexcasting.client.render.PatternSettings.StrokeSettings; +import at.petrak.hexcasting.client.render.PatternSettings.ZappySettings; import at.petrak.hexcasting.xplat.IXplatAbstractions; -import com.mojang.datafixers.util.Pair; import mezz.jei.api.gui.drawable.IDrawable; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.phys.Vec2; - -import java.util.List; public class PatternDrawable implements IDrawable { private final int width; private final int height; private boolean strokeOrder; + private final HexPattern pat; - private final List patterns; - private final List pathfinderDots; + private PatternSettings patSets; public PatternDrawable(ResourceLocation pattern, int w, int h) { var regi = IXplatAbstractions.INSTANCE.getActionRegistry(); var entry = regi.get(pattern); this.strokeOrder = !HexUtils.isOfTag(regi, pattern, HexTags.Actions.PER_WORLD_PATTERN); - var data = PatternDrawingUtil.loadPatterns( - List.of(new Pair<>(entry.prototype(), HexCoord.getOrigin())), - 0f, - 1f); - this.patterns = data.patterns(); - this.pathfinderDots = data.pathfinderDots(); + this.pat = entry.prototype(); this.width = w; this.height = h; + this.patSets = new PatternSettings("pattern_drawable_" + w + "_" + h, + new PositionSettings(width, height, 0, 0, + PatternSettings.AxisAlignment.CENTER_FIT, PatternSettings.AxisAlignment.CENTER_FIT, Math.max(width, height), 0, 0), + StrokeSettings.fromStroke(0.075 * Math.min(width, height)), + ZappySettings.READABLE); } @Override @@ -48,18 +48,26 @@ public int getHeight() { } public PatternDrawable strokeOrder(boolean order) { + if(order != strokeOrder){ + patSets = new PatternSettings("pattern_drawable_" + width + "_" + height + (order ? "" : "nostroke"), + patSets.posSets, + patSets.strokeSets, + order ? ZappySettings.READABLE : ZappySettings.STATIC + ); + } strokeOrder = order; return this; } @Override - public void draw(GuiGraphics guiGraphics, int xOffset, int yOffset) { - var ps = guiGraphics.pose(); + public void draw(GuiGraphics graphics, int x, int y) { + var ps = graphics.pose(); ps.pushPose(); - ps.translate(xOffset - 0.5f + width / 2f, yOffset + height / 2f, 0); - ps.scale(width / 64f, height / 64f, 1f); - PatternDrawingUtil.drawPattern(guiGraphics, 0, 0, this.patterns, this.pathfinderDots, this.strokeOrder, - 0xff_333030, 0xff_191818, 0xc8_0c0a0c, 0x80_666363); + ps.translate(x, y + 1, 0); + PatternRenderer.renderPattern(pat, graphics.pose(), patSets, + new PatternColors(0xc8_0c0a0c, 0xff_333030).withDotColors(0x80_666363, 0), + 0, 10 + ); ps.popPose(); } } diff --git a/Forge/src/main/resources/META-INF/mods.toml b/Forge/src/main/resources/META-INF/mods.toml index 252e3f9273..d3d478bb21 100644 --- a/Forge/src/main/resources/META-INF/mods.toml +++ b/Forge/src/main/resources/META-INF/mods.toml @@ -48,3 +48,10 @@ mandatory = true versionRange = "[3.1.0+1.20,)" ordering = "NONE" side = "BOTH" + +[[dependencies.hexcasting]] +modId = "inline" +mandatory = true +versionRange = "[1.20.1-1.0.1,)" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 88d13e374a..015fb53ab5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,3 +17,6 @@ patchouliVersion=83 jeiVersion=15.0.0.12 pehkuiVersion=3.7.7 + +inlineVersion=1.0.1 +clothConfigVersion=11.1.106 \ No newline at end of file