From 3dac11899e8c03177fa5315874fad8207143aa7f Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sat, 5 Oct 2024 20:17:13 -0700 Subject: [PATCH] Improve and polish text utilities --- .../lib/instance/ColoredLitInstance.java | 11 +- .../instance/ColoredLitOverlayInstance.java | 18 + .../flywheel/lib/instance/GlyphInstance.java | 97 ++--- .../flywheel/lib/instance/InstanceTypes.java | 4 +- .../lib/instance/OrientedInstance.java | 2 +- .../flywheel/lib/instance/PosedInstance.java | 2 +- .../lib/instance/TransformedInstance.java | 2 +- ...xtension.java => BakedGlyphExtension.java} | 2 +- .../flywheel/lib/internal/FlwLibLink.java | 6 +- .../lib/internal/FontTextureExtension.java | 7 - .../flywheel/lib/material/LightShaders.java | 2 +- .../flywheel/lib/math/DataPacker.java | 28 ++ .../lib/visual/component/FireComponent.java | 2 +- .../lib/visual/text/SimpleTextLayer.java | 55 ++- .../flywheel/lib/visual/text/TextLayer.java | 232 +++++++----- .../flywheel/lib/visual/text/TextLayers.java | 45 +++ .../flywheel/lib/visual/text/TextVisual.java | 342 +++++++++++------- .../flywheel/flywheel/instance/glyph.vert | 2 +- .../flywheel/impl/FlwLibLinkImpl.java | 17 +- .../impl/extension/FontTextureExtension.java | 7 + .../impl/mixin/text/BakedGlyphMixin.java | 4 +- .../impl/mixin/text/FontAccessor.java | 4 + .../impl/mixin/text/FontSetMixin.java | 2 +- .../impl/mixin/text/FontTextureMixin.java | 10 +- .../flywheel/vanilla/SignVisual.java | 184 +++++----- gradle.properties | 2 +- 26 files changed, 626 insertions(+), 463 deletions(-) create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitOverlayInstance.java rename common/src/lib/java/dev/engine_room/flywheel/lib/internal/{GlyphExtension.java => BakedGlyphExtension.java} (90%) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/internal/FontTextureExtension.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayers.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/extension/FontTextureExtension.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitInstance.java index 6d2f0ed66..e8f95baf5 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitInstance.java @@ -2,7 +2,6 @@ package dev.engine_room.flywheel.lib.instance; import dev.engine_room.flywheel.api.instance.InstanceHandle; import dev.engine_room.flywheel.api.instance.InstanceType; -import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.util.FastColor; public abstract class ColoredLitInstance extends AbstractInstance implements FlatLit { @@ -11,7 +10,6 @@ public abstract class ColoredLitInstance extends AbstractInstance implements Fla public byte blue = (byte) 0xFF; public byte alpha = (byte) 0xFF; - public int overlay = OverlayTexture.NO_OVERLAY; public int light = 0; public ColoredLitInstance(InstanceType type, InstanceHandle handle) { @@ -49,9 +47,12 @@ public abstract class ColoredLitInstance extends AbstractInstance implements Fla return this; } - public ColoredLitInstance overlay(int overlay) { - this.overlay = overlay; - return this; + public ColoredLitInstance color(float red, float green, float blue, float alpha) { + return color((byte) (red * 255f), (byte) (green * 255f), (byte) (blue * 255f), (byte) (alpha * 255f)); + } + + public ColoredLitInstance color(float red, float green, float blue) { + return color((byte) (red * 255f), (byte) (green * 255f), (byte) (blue * 255f)); } @Override diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitOverlayInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitOverlayInstance.java new file mode 100644 index 000000000..ab0725d9b --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/ColoredLitOverlayInstance.java @@ -0,0 +1,18 @@ +package dev.engine_room.flywheel.lib.instance; + +import dev.engine_room.flywheel.api.instance.InstanceHandle; +import dev.engine_room.flywheel.api.instance.InstanceType; +import net.minecraft.client.renderer.texture.OverlayTexture; + +public abstract class ColoredLitOverlayInstance extends ColoredLitInstance { + public int overlay = OverlayTexture.NO_OVERLAY; + + public ColoredLitOverlayInstance(InstanceType type, InstanceHandle handle) { + super(type, handle); + } + + public ColoredLitOverlayInstance overlay(int overlay) { + this.overlay = overlay; + return this; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/GlyphInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/GlyphInstance.java index 6d2c925e8..c5e999d9f 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/GlyphInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/GlyphInstance.java @@ -1,107 +1,66 @@ package dev.engine_room.flywheel.lib.instance; import org.joml.Matrix4f; +import org.joml.Matrix4fc; import dev.engine_room.flywheel.api.instance.InstanceHandle; import dev.engine_room.flywheel.api.instance.InstanceType; +import dev.engine_room.flywheel.lib.internal.BakedGlyphExtension; import dev.engine_room.flywheel.lib.internal.FlwLibLink; -import dev.engine_room.flywheel.lib.internal.GlyphExtension; +import dev.engine_room.flywheel.lib.math.DataPacker; import net.minecraft.client.gui.font.glyphs.BakedGlyph; -import net.minecraft.util.FastColor; -public class GlyphInstance extends AbstractInstance { +public class GlyphInstance extends ColoredLitInstance { // Skew x by 1 - 0.25 * y // Note that columns are written as rows. - private static final Matrix4f ITALIC_SKEW = new Matrix4f(1, 0, 0, 0, -0.25f, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + private static final Matrix4fc ITALIC_SKEW = new Matrix4f(1, 0, 0, 0, -0.25f, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); public final Matrix4f pose = new Matrix4f(); - public int us; - public int vs; + public int packedUs; + public int packedVs; - public byte red = (byte) 0xFF; - public byte green = (byte) 0xFF; - public byte blue = (byte) 0xFF; - public byte alpha = (byte) 0xFF; - - public int light = 0; - - public GlyphInstance(InstanceType type, InstanceHandle handle) { + public GlyphInstance(InstanceType type, InstanceHandle handle) { super(type, handle); } - public GlyphInstance setGlyph(BakedGlyph glyph, float x, float y, boolean italic) { - var glyphReader = FlwLibLink.INSTANCE.getGlyphExtension(glyph); + public GlyphInstance setGlyph(BakedGlyph glyph, Matrix4fc initialPose, float x, float y, boolean italic) { + var glyphExtension = FlwLibLink.INSTANCE.getBakedGlyphExtension(glyph); + setUvs(glyphExtension); - setUvs(glyphReader); - float left = glyphReader.flywheel$left(); - float right = glyphReader.flywheel$right(); - float up = glyphReader.flywheel$up(); - float down = glyphReader.flywheel$down(); + float left = glyphExtension.flywheel$left(); + float up = glyphExtension.flywheel$up(); - pose.translate(x + left, y + up - 3.0f, 0.0f); - pose.scale(right - left, down - up, 1.0f); + pose.set(initialPose); + pose.translate(x, y, 0.0f); if (italic) { pose.mul(ITALIC_SKEW); } + pose.translate(left, up - 3.0f, 0.0f); + return this; } - public GlyphInstance setEffect(BakedGlyph glyph, float x0, float y0, float x1, float y1, float depth) { - var glyphReader = FlwLibLink.INSTANCE.getGlyphExtension(glyph); - - setUvs(glyphReader); + public GlyphInstance setEffect(BakedGlyph glyph, Matrix4fc initialPose, float x0, float y0, float x1, float y1, float depth) { + var glyphExtension = FlwLibLink.INSTANCE.getBakedGlyphExtension(glyph); + setUvs(glyphExtension); + pose.set(initialPose); pose.translate(x0, y0, depth); pose.scale(x1 - x0, y1 - y0, 1.0f); return this; } - public GlyphInstance colorArgb(int argb) { - return color(FastColor.ARGB32.red(argb), FastColor.ARGB32.green(argb), FastColor.ARGB32.blue(argb), FastColor.ARGB32.alpha(argb)); - } + private void setUvs(BakedGlyphExtension glyphExtension) { + float u0 = glyphExtension.flywheel$u0(); + float u1 = glyphExtension.flywheel$u1(); + float v0 = glyphExtension.flywheel$v0(); + float v1 = glyphExtension.flywheel$v1(); - public GlyphInstance colorRgb(int rgb) { - return color(FastColor.ARGB32.red(rgb), FastColor.ARGB32.green(rgb), FastColor.ARGB32.blue(rgb)); - } - - public GlyphInstance color(int red, int green, int blue, int alpha) { - return color((byte) red, (byte) green, (byte) blue, (byte) alpha); - } - - public GlyphInstance color(int red, int green, int blue) { - return color((byte) red, (byte) green, (byte) blue); - } - - public GlyphInstance color(float red, float green, float blue, float alpha) { - return color((byte) (red * 255f), (byte) (green * 255f), (byte) (blue * 255f), (byte) (alpha * 255f)); - } - - public GlyphInstance color(byte red, byte green, byte blue, byte alpha) { - this.red = red; - this.green = green; - this.blue = blue; - this.alpha = alpha; - return this; - } - - public GlyphInstance color(byte red, byte green, byte blue) { - this.red = red; - this.green = green; - this.blue = blue; - return this; - } - - private void setUvs(GlyphExtension glyphReader) { - float u0 = glyphReader.flywheel$u0(); - float u1 = glyphReader.flywheel$u1(); - float v0 = glyphReader.flywheel$v0(); - float v1 = glyphReader.flywheel$v1(); - - us = (int) (u0 * 65536) | ((int) (u1 * 65536) << 16); - vs = (int) (v0 * 65536) | ((int) (v1 * 65536) << 16); + packedUs = ((int) DataPacker.packNormU16(u1) << 16) | (int) DataPacker.packNormU16(u0); + packedVs = ((int) DataPacker.packNormU16(v1) << 16) | (int) DataPacker.packNormU16(v0); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java index ec220ce63..c47cfadcb 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java @@ -112,8 +112,8 @@ public final class InstanceTypes { .build()) .writer((ptr, instance) -> { ExtraMemoryOps.putMatrix4f(ptr, instance.pose); - ExtraMemoryOps.put2x16(ptr + 64, instance.us); - ExtraMemoryOps.put2x16(ptr + 68, instance.vs); + ExtraMemoryOps.put2x16(ptr + 64, instance.packedUs); + ExtraMemoryOps.put2x16(ptr + 68, instance.packedVs); MemoryUtil.memPutByte(ptr + 72, instance.red); MemoryUtil.memPutByte(ptr + 73, instance.green); MemoryUtil.memPutByte(ptr + 74, instance.blue); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/OrientedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/OrientedInstance.java index 321caf936..db59429e2 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/OrientedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/OrientedInstance.java @@ -10,7 +10,7 @@ import dev.engine_room.flywheel.lib.transform.Rotate; import net.minecraft.core.Vec3i; import net.minecraft.world.phys.Vec3; -public class OrientedInstance extends ColoredLitInstance implements Rotate { +public class OrientedInstance extends ColoredLitOverlayInstance implements Rotate { public float posX; public float posY; public float posZ; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java index 43e6df47b..f00a6ac5d 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java @@ -13,7 +13,7 @@ import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.lib.transform.Transform; import net.minecraft.util.Mth; -public class PosedInstance extends ColoredLitInstance implements Transform { +public class PosedInstance extends ColoredLitOverlayInstance implements Transform { public final Matrix4f pose = new Matrix4f(); public final Matrix3f normal = new Matrix3f(); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java index 8bab28975..97ad11337 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java @@ -10,7 +10,7 @@ import dev.engine_room.flywheel.api.instance.InstanceHandle; import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.lib.transform.Affine; -public class TransformedInstance extends ColoredLitInstance implements Affine { +public class TransformedInstance extends ColoredLitOverlayInstance implements Affine { public final Matrix4f pose = new Matrix4f(); public TransformedInstance(InstanceType type, InstanceHandle handle) { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/GlyphExtension.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/BakedGlyphExtension.java similarity index 90% rename from common/src/lib/java/dev/engine_room/flywheel/lib/internal/GlyphExtension.java rename to common/src/lib/java/dev/engine_room/flywheel/lib/internal/BakedGlyphExtension.java index 6d62472e1..5cfa0e45e 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/GlyphExtension.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/BakedGlyphExtension.java @@ -2,7 +2,7 @@ package dev.engine_room.flywheel.lib.internal; import net.minecraft.resources.ResourceLocation; -public interface GlyphExtension { +public interface BakedGlyphExtension { float flywheel$u0(); float flywheel$u1(); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java index 4899298c6..0920cf104 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java @@ -29,7 +29,9 @@ public interface FlwLibLink { Deque getPoseStack(PoseStack stack); - GlyphExtension getGlyphExtension(BakedGlyph glyph); - FontSet getFontSet(Font font, ResourceLocation loc); + + boolean getFilterFishyGlyphs(Font font); + + BakedGlyphExtension getBakedGlyphExtension(BakedGlyph glyph); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FontTextureExtension.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FontTextureExtension.java deleted file mode 100644 index 8b7b5cce7..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FontTextureExtension.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.engine_room.flywheel.lib.internal; - -import net.minecraft.resources.ResourceLocation; - -public interface FontTextureExtension { - void flywheel$setName(ResourceLocation value); -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/LightShaders.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/LightShaders.java index 3e15c76ca..ac9e351cc 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/LightShaders.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/LightShaders.java @@ -3,7 +3,7 @@ package dev.engine_room.flywheel.lib.material; import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.api.material.LightShader; -public class LightShaders { +public final class LightShaders { public static final LightShader SMOOTH_WHEN_EMBEDDED = new SimpleLightShader(Flywheel.rl("light/smooth_when_embedded.glsl")); public static final LightShader SMOOTH = new SimpleLightShader(Flywheel.rl("light/smooth.glsl")); public static final LightShader FLAT = new SimpleLightShader(Flywheel.rl("light/flat.glsl")); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/math/DataPacker.java b/common/src/lib/java/dev/engine_room/flywheel/lib/math/DataPacker.java index 6fa5f33fe..363888f1a 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/math/DataPacker.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/math/DataPacker.java @@ -33,4 +33,32 @@ public final class DataPacker { public static float unpackNormI8(byte b) { return (float) b / 127f; } + + /** + * Pack a float as an unsigned, normalized short. + */ + public static short packNormU16(float f) { + return (short) (int) (Mth.clamp(f, 0.0f, 1.0f) * 65535); + } + + /** + * Unpack an unsigned, normalized short to a float. + */ + public static float unpackNormU16(short s) { + return (float) (Short.toUnsignedInt(s)) / 65535f; + } + + /** + * Pack a float as a signed, normalized byte. + */ + public static short packNormI16(float f) { + return (short) (Mth.clamp(f, -1.0f, 1.0f) * 32767); + } + + /** + * Unpack a signed, normalized byte to a float. + */ + public static float unpackNormI16(short s) { + return (float) s / 32767f; + } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java index 0e0cb8301..e9032b931 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java @@ -125,7 +125,7 @@ public final class FireComponent implements EntityComponent { } private record FireMesh(TextureAtlasSprite sprite) implements QuadMesh { - private static final Vector4fc BOUNDING_SPHERE = new Vector4f(0, 0.5f, 0, (float) (Math.sqrt(2) * 0.5)); + private static final Vector4fc BOUNDING_SPHERE = new Vector4f(0, 0.5f, 0, Mth.SQRT_OF_TWO * 0.5f); @Override public int vertexCount() { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/SimpleTextLayer.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/SimpleTextLayer.java index 8b2943929..39bdd1dd5 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/SimpleTextLayer.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/SimpleTextLayer.java @@ -3,25 +3,24 @@ package dev.engine_room.flywheel.lib.visual.text; import java.util.Objects; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2f; +import org.joml.Vector2fc; -public record SimpleTextLayer(GlyphMeshStyle style, GlyphMaterial material, GlyphColor color, int bias, float offsetX, - float offsetY, float effectOffsetX, float effectOffsetY) implements TextLayer { +public record SimpleTextLayer(GlyphPattern pattern, GlyphMaterial material, GlyphColor color, Vector2fc offset, int bias) implements TextLayer { public static class Builder { + private static final Vector2fc NO_OFFSET = new Vector2f(); + @Nullable - private GlyphMeshStyle style; + private GlyphPattern pattern; @Nullable private GlyphMaterial material; @Nullable private GlyphColor color; + private Vector2fc offset = NO_OFFSET; + private int bias = 0; - private int bias; - private float offsetX = 0; - private float offsetY = 0; - private float effectOffsetX = 1; - private float effectOffsetY = 1; - - public Builder style(GlyphMeshStyle style) { - this.style = style; + public Builder pattern(GlyphPattern pattern) { + this.pattern = pattern; return this; } @@ -35,37 +34,27 @@ public record SimpleTextLayer(GlyphMeshStyle style, GlyphMaterial material, Glyp return this; } + public Builder offset(Vector2fc offset) { + this.offset = offset; + return this; + } + + public Builder offset(float offsetX, float offsetY) { + offset = new Vector2f(offsetX, offsetY); + return this; + } + public Builder bias(int bias) { this.bias = bias; return this; } - public Builder offsetX(float offsetX) { - this.offsetX = offsetX; - return this; - } - - public Builder offsetY(float offsetY) { - this.offsetY = offsetY; - return this; - } - - public Builder effectOffsetX(float effectOffsetX) { - this.effectOffsetX = effectOffsetX; - return this; - } - - public Builder effectOffsetY(float effectOffsetY) { - this.effectOffsetY = effectOffsetY; - return this; - } - public SimpleTextLayer build() { - Objects.requireNonNull(style); + Objects.requireNonNull(pattern); Objects.requireNonNull(material); Objects.requireNonNull(color); - return new SimpleTextLayer(style, material, color, bias, offsetX, offsetY, effectOffsetX, effectOffsetY); + return new SimpleTextLayer(pattern, material, color, offset, bias); } } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayer.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayer.java index 7bc1f8bcc..35151c5d2 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayer.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayer.java @@ -2,24 +2,29 @@ package dev.engine_room.flywheel.lib.visual.text; import java.util.function.Consumer; -import org.joml.Vector3f; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector2f; +import org.joml.Vector2fc; +import dev.engine_room.flywheel.api.material.DepthTest; import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.material.Transparency; +import dev.engine_room.flywheel.api.material.WriteMask; import dev.engine_room.flywheel.lib.material.CutoutShaders; +import dev.engine_room.flywheel.lib.material.FogShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import net.minecraft.network.chat.Style; +import net.minecraft.client.gui.Font; import net.minecraft.network.chat.TextColor; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; public interface TextLayer { - float ONE_PIXEL = 0.125f; - /** - * The style of individual glyphs. + * The pattern of individual glyphs. * - * @return A GlyphMeshStyle. + * @return A GlyphPattern. */ - GlyphMeshStyle style(); + GlyphPattern pattern(); /** * A mapping from texture ResourceLocations to Flywheel materials. @@ -35,6 +40,13 @@ public interface TextLayer { */ GlyphColor color(); + /** + * The offset of text in this layer. + * + * @return The offset. + */ + Vector2fc offset(); + /** * The instancer bias for this layer. * @@ -42,33 +54,85 @@ public interface TextLayer { */ int bias(); - /** - * The x offset of text content in this layer. - * - * @return The x offset. - */ - float offsetX(); + // TODO: probably just convert this to Iterable + @FunctionalInterface + interface GlyphPattern { + /** + * The pattern for a single glyph with no offset. + */ + GlyphPattern SINGLE = out -> out.accept(new Vector2f(0, 0)); - /** - * The y offset of text content in this layer. - * - * @return The y offset. - */ - float offsetY(); + /** + * The pattern for an 8x outline as used by glowing text on signs. + */ + GlyphPattern OUTLINE = out -> { + for (int x = -1; x <= 1; ++x) { + for (int y = -1; y <= 1; ++y) { + if (x == 0 && y == 0) { + continue; + } - /** - * The x offset of text effects such as strikethrough or underline in this layer. - * - * @return The x offset. - */ - float effectOffsetX(); + out.accept(new Vector2f(x, y)); + } + } + }; - /** - * The y offset of text effects such as strikethrough or underline in this layer. - * - * @return The y offset. - */ - float effectOffsetY(); + /** + * Add an arbitrary amount of glyphs. Each accepted vector represents + * the offset of a new glyph quad. + * + * @param out The consumer to accept the offset of a new glyph quad + */ + void addGlyphs(Consumer out); + } + + @FunctionalInterface + interface GlyphMaterial { + // FIXME: account for intensity + GlyphMaterial NORMAL = texture -> SimpleMaterial.builder() + .cutout(CutoutShaders.ONE_TENTH) + .texture(texture) + .mipmap(false) + .transparency(Transparency.TRANSLUCENT) + .diffuse(false) + .build(); + + GlyphMaterial SEE_THROUGH = texture -> SimpleMaterial.builder() + .fog(FogShaders.NONE) + .cutout(CutoutShaders.ONE_TENTH) + .texture(texture) + .mipmap(false) + .depthTest(DepthTest.ALWAYS) + .transparency(Transparency.TRANSLUCENT) + .writeMask(WriteMask.COLOR) + .diffuse(false) + .build(); + + GlyphMaterial POLYGON_OFFSET = texture -> SimpleMaterial.builder() + .cutout(CutoutShaders.ONE_TENTH) + .texture(texture) + .mipmap(false) + .polygonOffset(true) + .transparency(Transparency.TRANSLUCENT) + .diffuse(false) + .build(); + + static GlyphMaterial fromDisplayMode(Font.DisplayMode displayMode) { + return switch (displayMode) { + case NORMAL -> NORMAL; + case SEE_THROUGH -> SEE_THROUGH; + case POLYGON_OFFSET -> POLYGON_OFFSET; + }; + } + + /** + * Create a Flywheel material for the given glyph texture. + * + * @param texture The texture to use. + * @return A material. + */ + Material create(ResourceLocation texture); + } @FunctionalInterface interface GlyphColor { @@ -78,16 +142,47 @@ public interface TextLayer { * @param color The ARGB color to default to. * @return A new GlyphColor. */ - static GlyphColor defaultTo(int color) { - return style -> { - TextColor textColor = style.getColor(); + static GlyphColor defaultTo(int color, float dimFactor) { + int finalColor; + if (dimFactor != 1.0f) { + finalColor = FastColor.ARGB32.color( + FastColor.ARGB32.alpha(color), + (int) (FastColor.ARGB32.red(color) * dimFactor), + (int) (FastColor.ARGB32.green(color) * dimFactor), + (int) (FastColor.ARGB32.blue(color) * dimFactor) + ); + } else { + finalColor = color; + } + + return textColor -> { if (textColor != null) { - return adjustColor(textColor.getValue()); + int textColorArgb = textColor.getValue(); + if (dimFactor != 1.0f) { + return FastColor.ARGB32.color( + FastColor.ARGB32.alpha(finalColor), + (int) (FastColor.ARGB32.red(textColorArgb) * dimFactor), + (int) (FastColor.ARGB32.green(textColorArgb) * dimFactor), + (int) (FastColor.ARGB32.blue(textColorArgb) * dimFactor) + ); + } else { + return (finalColor & 0xFF000000) | (textColorArgb & 0xFFFFFF); + } } - return color; + return finalColor; }; } + /** + * Default to the given color if no color is specified in the style. + * + * @param color The ARGB color to default to. + * @return A new GlyphColor. + */ + static GlyphColor defaultTo(int color) { + return defaultTo(color, 1.0f); + } + /** * Always use the given color, regardless of the style. * @@ -95,7 +190,7 @@ public interface TextLayer { * @return A new GlyphColor. */ static GlyphColor always(int color) { - return style -> color; + return textColor -> color; } /** @@ -112,68 +207,11 @@ public interface TextLayer { } /** - * Convert a style to a color. + * Convert a nullable text color to a color. * - * @param style The style of the text to colorize. + * @param textColor The color of the text to colorize. * @return The color to use, in ARGB format. */ - int color(Style style); - } - - @FunctionalInterface - interface GlyphMaterial { - GlyphMaterial SIMPLE = texture -> SimpleMaterial.builder() - .texture(texture) - .cutout(CutoutShaders.ONE_TENTH) - .diffuse(false) - .build(); - - GlyphMaterial POLYGON_OFFSET = texture -> SimpleMaterial.builder() - .texture(texture) - .cutout(CutoutShaders.ONE_TENTH) - .diffuse(false) - .polygonOffset(true) - .build(); - - /** - * Create a Flywheel material for the given glyph texture. - * - * @param texture The texture to use. - * @return A material. - */ - Material create(ResourceLocation texture); - } - - @FunctionalInterface - interface GlyphMeshStyle { - /** - * The standard style for glyphs with no repetition. - */ - GlyphMeshStyle SIMPLE = out -> out.accept(new Vector3f(0, 0, 0)); - - /** - * The style for glyphs with a 8x outline as used by glowing text on signs. - */ - GlyphMeshStyle OUTLINE = out -> { - for (int x = -1; x <= 1; ++x) { - for (int y = -1; y <= 1; ++y) { - if (x == 0 && y == 0) { - continue; - } - - out.accept(new Vector3f(x * ONE_PIXEL, y * ONE_PIXEL, 0)); - } - } - }; - - /** - * Add quads to the mesh. Each vec3 submitted to out will be expanded - * into a unit quad in the XY plane with the lowest corner at the given vec3. - * You can think of each submitted vec3 as a duplication of a glyph. - * - * @param out The consumer to accept the quads - */ - void addQuads(Consumer out); - + int color(@Nullable TextColor textColor); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayers.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayers.java new file mode 100644 index 000000000..57701ce76 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextLayers.java @@ -0,0 +1,45 @@ +package dev.engine_room.flywheel.lib.visual.text; + +import net.minecraft.client.gui.Font; + +public final class TextLayers { + public static TextLayer normal(int color, Font.DisplayMode displayMode, int bias) { + return new SimpleTextLayer.Builder().pattern(TextLayer.GlyphPattern.SINGLE) + .material(TextLayer.GlyphMaterial.fromDisplayMode(displayMode)) + .color(TextLayer.GlyphColor.defaultTo(TextLayer.GlyphColor.adjustColor(color))) + .bias(bias) + .build(); + } + + public static TextLayer normal(int color, Font.DisplayMode displayMode) { + return normal(color, displayMode, 0); + } + + public static TextLayer dropShadow(int color, Font.DisplayMode displayMode, int bias) { + return new SimpleTextLayer.Builder().pattern(TextLayer.GlyphPattern.SINGLE) + .material(TextLayer.GlyphMaterial.fromDisplayMode(displayMode)) + .color(TextLayer.GlyphColor.defaultTo(TextLayer.GlyphColor.adjustColor(color), 0.25f)) + .offset(1, 1) + .bias(bias) + .build(); + } + + public static TextLayer dropShadow(int color, Font.DisplayMode displayMode) { + return dropShadow(color, displayMode, 0); + } + + public static TextLayer outline(int color, int bias) { + return new SimpleTextLayer.Builder().pattern(TextLayer.GlyphPattern.OUTLINE) + .material(TextLayer.GlyphMaterial.NORMAL) + .color(TextLayer.GlyphColor.always(TextLayer.GlyphColor.adjustColor(color))) + .bias(bias) + .build(); + } + + public static TextLayer outline(int color) { + return outline(color, 0); + } + + private TextLayers() { + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextVisual.java index 32c6943bc..677a60ab2 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/text/TextVisual.java @@ -4,15 +4,18 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import org.joml.Matrix4f; -import org.joml.Vector3f; +import org.joml.Vector2f; +import org.joml.Vector2fc; import org.joml.Vector4f; import org.joml.Vector4fc; import com.mojang.blaze3d.font.GlyphInfo; import dev.engine_room.flywheel.api.instance.InstancerProvider; +import dev.engine_room.flywheel.api.model.Mesh; import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.vertex.MutableVertexList; import dev.engine_room.flywheel.lib.instance.GlyphInstance; @@ -27,65 +30,66 @@ import net.minecraft.client.gui.Font; import net.minecraft.client.gui.font.FontSet; import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.gui.font.glyphs.EmptyGlyph; -import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.FormattedCharSink; +import net.minecraft.util.Mth; /** * A visual that renders a single line of text. */ -public class TextVisual { +public final class TextVisual { + private static final Font FONT = Minecraft.getInstance().font; + + private static final ResourceReloadCache GLYPH_MESH_CACHE = new ResourceReloadCache<>(GlyphMeshKey::into); + private static final ResourceReloadCache GLYPH_MODEL_CACHE = new ResourceReloadCache<>(GlyphModelKey::into); + private static final ThreadLocal SINKS = ThreadLocal.withInitial(Sink::new); - private final SmartRecycler recycler; + private final SmartRecycler recycler; + private final List layers = new ArrayList<>(); + private final Matrix4f pose = new Matrix4f(); - private FormattedCharSequence content = FormattedCharSequence.EMPTY; + private FormattedCharSequence text = FormattedCharSequence.EMPTY; private float x; private float y; private int backgroundColor = 0; private int light; - private boolean fullBright; - - private final List layers = new ArrayList<>(); - - private final Matrix4f pose = new Matrix4f(); public TextVisual(InstancerProvider provider) { - recycler = new SmartRecycler<>(key -> provider.instancer(InstanceTypes.GLYPH, GLYPH_CACHE.get(key.modelKey), key.bias) + recycler = new SmartRecycler<>(key -> provider.instancer(InstanceTypes.GLYPH, GLYPH_MODEL_CACHE.get(key.modelKey), key.bias) .createInstance()); } - public TextVisual content(FormattedCharSequence content) { - this.content = content; - return this; - } - - public Matrix4f pose() { - return pose; - } - - public TextVisual clearLayers() { - layers.clear(); - return this; - } - public TextVisual addLayer(TextLayer layer) { layers.add(layer); return this; } + public TextVisual addLayers(Collection layers) { + this.layers.addAll(layers); + return this; + } + public TextVisual layers(Collection layers) { this.layers.clear(); this.layers.addAll(layers); return this; } - public TextVisual pos(float x, float y) { - this.x = x; - this.y = y; + public TextVisual clearLayers() { + layers.clear(); + return this; + } + + public Matrix4f pose() { + return pose; + } + + public TextVisual text(FormattedCharSequence text) { + this.text = text; return this; } @@ -99,6 +103,12 @@ public class TextVisual { return this; } + public TextVisual pos(float x, float y) { + this.x = x; + this.y = y; + return this; + } + public TextVisual backgroundColor(int backgroundColor) { this.backgroundColor = backgroundColor; return this; @@ -109,33 +119,35 @@ public class TextVisual { return this; } - public TextVisual fullBright(boolean fullBright) { - this.fullBright = fullBright; + public TextVisual reset() { + layers.clear(); + pose.identity(); + + text = FormattedCharSequence.EMPTY; + x = 0; + y = 0; + backgroundColor = 0; + light = 0; + return this; } - // TODO: method to just update pose or light without recalculating text + // TODO: track glyph instances and add method to update only UVs of obfuscated glyphs, method to update only + // background color, and method to only update light public void setup() { recycler.resetCount(); var sink = SINKS.get(); - - var light = fullBright ? LightTexture.FULL_BRIGHT : this.light; sink.prepare(recycler, pose, light); int maxX = 0; - // Can we flip the inner and outer loops here? - // Would that even be better? for (TextLayer layer : layers) { - sink.x = x; - sink.y = y; - sink.layer = layer; - content.accept(sink); + sink.prepareForLayer(layer, x, y); + text.accept(sink); maxX = Math.max(maxX, (int) sink.x); } sink.addBackground(backgroundColor, x, maxX); - sink.clear(); recycler.discardExtra(); @@ -145,166 +157,226 @@ public class TextVisual { recycler.delete(); } - private static class Sink implements FormattedCharSink { - private final Font font; + private record GlyphMeshKey(float glyphWidth, float glyphHeight, TextLayer.GlyphPattern pattern, boolean bold, float boldOffset, float shadowOffset) { + public GlyphMesh into() { + List out = new ArrayList<>(); + pattern.addGlyphs(offsetc -> { + Vector2f offset = new Vector2f(offsetc).mul(shadowOffset); + out.add(offset); + + if (bold) { + out.add(new Vector2f(offset.x() + boldOffset, offset.y())); + } + }); + + return new GlyphMesh(glyphWidth, glyphHeight, out.toArray(Vector2fc[]::new)); + } + } + + private record GlyphModelKey(@Nullable GlyphMeshKey meshKey, TextLayer.GlyphMaterial material, ResourceLocation texture) { + public Model into() { + Mesh mesh; + + if (meshKey != null) { + mesh = GLYPH_MESH_CACHE.get(meshKey); + } else { + mesh = GlyphEffectMesh.INSTANCE; + } + + return new SingleMeshModel(mesh, material.create(texture)); + } + } + + private record GlyphInstanceKey(GlyphModelKey modelKey, int bias) { + } + + private static class Sink implements FormattedCharSink { @UnknownNullability - private SmartRecycler recycler; + private SmartRecycler recycler; @UnknownNullability private Matrix4f pose; - @UnknownNullability - private TextLayer layer; - private int light; + @UnknownNullability + private TextLayer layer; private float x; private float y; - private Sink() { - font = Minecraft.getInstance().font; - } - - private void prepare(SmartRecycler recycler, Matrix4f pose, int light) { + public void prepare(SmartRecycler recycler, Matrix4f pose, int light) { this.recycler = recycler; this.pose = pose; this.light = light; } - private void clear() { + public void prepareForLayer(TextLayer layer, float x, float y) { + this.layer = layer; + this.x = x; + this.y = y; + } + + public void clear() { recycler = null; pose = null; layer = null; } @Override - public boolean accept(int i, Style style, int j) { - FontSet fontSet = FlwLibLink.INSTANCE.getFontSet(font, style.getFont()); - GlyphInfo glyphInfo = fontSet.getGlyphInfo(j, false); - BakedGlyph bakedGlyph = style.isObfuscated() && j != 32 ? fontSet.getRandomGlyph(glyphInfo) : fontSet.getGlyph(j); + public boolean accept(int index, Style style, int codePoint) { + FontSet fontSet = FlwLibLink.INSTANCE.getFontSet(FONT, style.getFont()); + GlyphInfo glyphInfo = fontSet.getGlyphInfo(codePoint, FlwLibLink.INSTANCE.getFilterFishyGlyphs(FONT)); + BakedGlyph glyph = style.isObfuscated() && codePoint != ' ' ? fontSet.getRandomGlyph(glyphInfo) : fontSet.getGlyph(codePoint); + boolean bold = style.isBold(); - int color = layer.color() - .color(style); + .color(style.getColor()); + Vector2fc offset = layer.offset(); - if (!(bakedGlyph instanceof EmptyGlyph)) { - var glyphExtension = FlwLibLink.INSTANCE.getGlyphExtension(bakedGlyph); - - GlyphInstance glyph = recycler.get(key(glyphExtension.flywheel$texture(), bold, layer.style())); - - glyph.pose.set(pose); - glyph.setGlyph(bakedGlyph, this.x + layer.offsetX(), this.y + layer.offsetY(), style.isItalic()); - glyph.colorArgb(color); - glyph.light = light; - glyph.setChanged(); + if (!(glyph instanceof EmptyGlyph)) { + GlyphInstance instance = recycler.get(key(glyphInfo, glyph, layer.pattern(), bold)); + float shadowOffset = glyphInfo.getShadowOffset(); + instance.setGlyph(glyph, pose, x + offset.x() * shadowOffset, y + offset.y() * shadowOffset, style.isItalic()); + instance.colorArgb(color); + instance.light(light); + instance.setChanged(); } + float advance = glyphInfo.getAdvance(bold); - float effectX = layer.effectOffsetX(); - float effectY = layer.effectOffsetY(); + // SpecialGlyphs.WHITE, which effects use, has a shadowOffset of 1, so don't modify the offset returned by the layer. + float effectOffsetX = offset.x(); + float effectOffsetY = offset.y(); if (style.isStrikethrough()) { - this.addEffect(this.x + effectX - 1.0f, this.y + effectY + 4.5f, this.x + effectX + advance, this.y + effectY + 4.5f - 1.0f, 0.01f, color); + addEffect(x + effectOffsetX - 1.0f, y + effectOffsetY + 4.5f, x + effectOffsetX + advance, y + effectOffsetY + 4.5f - 1.0f, 0.01f, color); } if (style.isUnderlined()) { - this.addEffect(this.x + effectX - 1.0f, this.y + effectY + 9.0f, this.x + effectX + advance, this.y + effectY + 9.0f - 1.0f, 0.01f, color); + addEffect(x + effectOffsetX - 1.0f, y + effectOffsetY + 9.0f, x + effectOffsetX + advance, y + effectOffsetY + 9.0f - 1.0f, 0.01f, color); } - this.x += advance; + + x += advance; return true; } - private void addEffect(float x0, float y0, float x1, float y1, float depth, int colorArgb) { - BakedGlyph bakedGlyph = FlwLibLink.INSTANCE.getFontSet(font, Style.DEFAULT_FONT) - .whiteGlyph(); - - var glyphExtension = FlwLibLink.INSTANCE.getGlyphExtension(bakedGlyph); - - GlyphInstance glyph = recycler.get(key(glyphExtension.flywheel$texture(), false, TextLayer.GlyphMeshStyle.SIMPLE)); - - glyph.pose.set(pose); - glyph.setEffect(bakedGlyph, x0, y0, x1, y1, depth); - glyph.colorArgb(colorArgb); - glyph.light = light; - glyph.setChanged(); - } - public void addBackground(int backgroundColor, float startX, float endX) { if (backgroundColor != 0) { - this.addEffect(startX - 1.0f, this.y + 9.0f, endX + 1.0f, this.y - 1.0f, 0.01f, backgroundColor); + addEffect(startX - 1.0f, y + 9.0f, endX + 1.0f, y - 1.0f, 0.01f, backgroundColor); } } - private GlyphInstancerKey key(ResourceLocation texture, boolean bold, TextLayer.GlyphMeshStyle style) { - var meshKey = new GlyphMeshKey(style, bold); - var modelKey = new GlyphModelKey(texture, meshKey, layer.material()); - return new GlyphInstancerKey(modelKey, layer.bias()); + private void addEffect(float x0, float y0, float x1, float y1, float depth, int colorArgb) { + BakedGlyph glyph = FlwLibLink.INSTANCE.getFontSet(FONT, Style.DEFAULT_FONT) + .whiteGlyph(); + + GlyphInstance instance = recycler.get(effectKey(glyph)); + instance.setEffect(glyph, pose, x0, y0, x1, y1, depth); + instance.colorArgb(colorArgb); + instance.light(light); + instance.setChanged(); } - } - private record GlyphInstancerKey(GlyphModelKey modelKey, int bias) { - } + private GlyphInstanceKey key(GlyphInfo glyphInfo, BakedGlyph glyph, TextLayer.GlyphPattern pattern, boolean bold) { + var glyphExtension = FlwLibLink.INSTANCE.getBakedGlyphExtension(glyph); + float glyphWidth = glyphExtension.flywheel$right() - glyphExtension.flywheel$left(); + float glyphHeight = glyphExtension.flywheel$down() - glyphExtension.flywheel$up(); - private static final ResourceReloadCache GLYPH_CACHE = new ResourceReloadCache<>(GlyphModelKey::into); - private static final ResourceReloadCache MESH_CACHE = new ResourceReloadCache<>(GlyphMeshKey::into); - - private record GlyphModelKey(ResourceLocation font, GlyphMeshKey meshKey, TextLayer.GlyphMaterial material) { - private Model into() { - return new SingleMeshModel(MESH_CACHE.get(meshKey), material.create(font)); + return key(glyphWidth, glyphHeight, glyphExtension.flywheel$texture(), pattern, bold, bold ? glyphInfo.getBoldOffset() : 0, glyphInfo.getShadowOffset()); } - } - private record GlyphMeshKey(TextLayer.GlyphMeshStyle style, boolean bold) { - public GlyphMesh into() { - List out = new ArrayList<>(); + private GlyphInstanceKey key(float glyphWidth, float glyphHeight, ResourceLocation texture, TextLayer.GlyphPattern pattern, boolean bold, float boldOffset, float shadowOffset) { + var meshKey = new GlyphMeshKey(glyphWidth, glyphHeight, pattern, bold, boldOffset, shadowOffset); + var modelKey = new GlyphModelKey(meshKey, layer.material(), texture); + return new GlyphInstanceKey(modelKey, layer.bias()); + } - style.addQuads(quad -> { - out.add(quad); - if (bold) { - out.add(new Vector3f(quad.x + TextLayer.ONE_PIXEL, quad.y, quad.z)); - } - }); + private GlyphInstanceKey effectKey(BakedGlyph glyph) { + var glyphExtension = FlwLibLink.INSTANCE.getBakedGlyphExtension(glyph); + return effectKey(glyphExtension.flywheel$texture()); + } - return new GlyphMesh(out.toArray(new Vector3f[0])); + private GlyphInstanceKey effectKey(ResourceLocation texture) { + var modelKey = new GlyphModelKey(null, layer.material(), texture); + return new GlyphInstanceKey(modelKey, layer.bias()); } } /** - * A mesh that represents a single glyph. Expects to be drawn with the glyph instance type. + * A mesh that represents a pattern of a glyph with a certain width and height. Expects to be drawn with the glyph + * instance type. * - * @param quads Each quad will be expanded into 4 vertices. + * @param offsets Each offset will be expanded into a glyph quad. */ - private record GlyphMesh(Vector3f[] quads) implements QuadMesh { - private static final float[] X = new float[]{0, 0, 1, 1}; - private static final float[] Y = new float[]{0, 1, 1, 0}; + private record GlyphMesh(float glyphWidth, float glyphHeight, Vector2fc[] offsets, Vector4fc boundingSphere) implements QuadMesh { + private static final float[] X = new float[] { 0, 0, 1, 1 }; + private static final float[] Y = new float[] { 0, 1, 1, 0 }; + + // FIXME: what is the actual bounding sphere?? + public GlyphMesh(float glyphWidth, float glyphHeight, Vector2fc[] offsets) { + this(glyphWidth, glyphHeight, offsets, new Vector4f(0, 0, 0, Math.max(glyphWidth, glyphHeight) * 2 * Mth.SQRT_OF_TWO)); + } @Override public int vertexCount() { - return 4 * quads.length; + return 4 * offsets.length; } @Override public void write(MutableVertexList vertexList) { - for (int i = 0; i < quads.length; i++) { - Vector3f quad = quads[i]; - var quadStart = i * 4; + for (int i = 0; i < offsets.length; i++) { + Vector2fc offset = offsets[i]; + var startVertex = i * 4; for (int j = 0; j < 4; j++) { - vertexList.x(quadStart + j, quad.x + X[j]); - vertexList.y(quadStart + j, quad.y + Y[j]); - vertexList.z(quadStart + j, quad.z); - vertexList.normalX(quadStart + j, 0); - vertexList.normalY(quadStart + j, 0); - vertexList.normalZ(quadStart + j, 1); - vertexList.overlay(quadStart + j, OverlayTexture.NO_OVERLAY); - vertexList.r(quadStart + j, 1); - vertexList.g(quadStart + j, 1); - vertexList.b(quadStart + j, 1); - vertexList.a(quadStart + j, 1); + vertexList.x(startVertex + j, offset.x() + (glyphWidth * X[j])); + vertexList.y(startVertex + j, offset.y() + (glyphHeight * Y[j])); + vertexList.z(startVertex + j, 0); + vertexList.r(startVertex + j, 1); + vertexList.g(startVertex + j, 1); + vertexList.b(startVertex + j, 1); + vertexList.a(startVertex + j, 1); + vertexList.overlay(startVertex + j, OverlayTexture.NO_OVERLAY); + vertexList.normalX(startVertex + j, 0); + vertexList.normalY(startVertex + j, 0); + vertexList.normalZ(startVertex + j, 1); } } } @Override public Vector4fc boundingSphere() { - // FIXME: what is the actual bounding sphere?? - return new Vector4f(0, 0, 0, 2); + return boundingSphere; + } + } + + private record GlyphEffectMesh() implements QuadMesh { + private static final float[] X = new float[] { 0, 1, 1, 0 }; + private static final float[] Y = new float[] { 0, 0, 1, 1 }; + private static final Vector4fc BOUNDING_SPHERE = new Vector4f(0.5f, 0.5f, 0, Mth.SQRT_OF_TWO * 0.5f); + + public static final GlyphEffectMesh INSTANCE = new GlyphEffectMesh(); + + @Override + public int vertexCount() { + return 4; + } + + @Override + public void write(MutableVertexList vertexList) { + for (int i = 0; i < 4; i++) { + vertexList.x(i, X[i]); + vertexList.y(i, Y[i]); + vertexList.z(i, 0); + vertexList.r(i, 1); + vertexList.g(i, 1); + vertexList.b(i, 1); + vertexList.a(i, 1); + vertexList.normalX(i, 0); + vertexList.normalY(i, 0); + vertexList.normalZ(i, 1); + } + } + + @Override + public Vector4fc boundingSphere() { + return BOUNDING_SPHERE; } } } diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/glyph.vert b/common/src/lib/resources/assets/flywheel/flywheel/instance/glyph.vert index 3f7d2a303..6592d5e8c 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/instance/glyph.vert +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/glyph.vert @@ -8,7 +8,7 @@ void flw_instanceVertex(in FlwInstance i) { flw_vertexTexCoord.s = i.u0u1v0v1[(vertexInGlyph & 2u) >> 1u]; flw_vertexTexCoord.t = i.u0u1v0v1[2u + yIndex]; - flw_vertexColor = i.color; + flw_vertexColor *= i.color; // Some drivers have a bug where uint over float division is invalid, so use an explicit cast. flw_vertexLight = vec2(i.light) / 256.0; diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java index f3abee51b..c156f5668 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java @@ -12,8 +12,8 @@ import dev.engine_room.flywheel.impl.extension.PoseStackExtension; import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor; import dev.engine_room.flywheel.impl.mixin.PoseStackAccessor; import dev.engine_room.flywheel.impl.mixin.text.FontAccessor; +import dev.engine_room.flywheel.lib.internal.BakedGlyphExtension; import dev.engine_room.flywheel.lib.internal.FlwLibLink; -import dev.engine_room.flywheel.lib.internal.GlyphExtension; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.font.FontSet; @@ -47,13 +47,18 @@ public class FlwLibLinkImpl implements FlwLibLink { return ((PoseStackAccessor) stack).flywheel$getPoseStack(); } - @Override - public GlyphExtension getGlyphExtension(BakedGlyph glyph) { - return (GlyphExtension) glyph; - } - @Override public FontSet getFontSet(Font font, ResourceLocation loc) { return ((FontAccessor) font).flywheel$getFontSet(loc); } + + @Override + public boolean getFilterFishyGlyphs(Font font) { + return ((FontAccessor) font).flywheel$getFilterFishyGlyphs(); + } + + @Override + public BakedGlyphExtension getBakedGlyphExtension(BakedGlyph glyph) { + return (BakedGlyphExtension) glyph; + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/extension/FontTextureExtension.java b/common/src/main/java/dev/engine_room/flywheel/impl/extension/FontTextureExtension.java new file mode 100644 index 000000000..2db5e5462 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/extension/FontTextureExtension.java @@ -0,0 +1,7 @@ +package dev.engine_room.flywheel.impl.extension; + +import net.minecraft.resources.ResourceLocation; + +public interface FontTextureExtension { + void flywheel$setName(ResourceLocation name); +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/BakedGlyphMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/BakedGlyphMixin.java index 2d3c63f24..e18ffb5c5 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/BakedGlyphMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/BakedGlyphMixin.java @@ -5,12 +5,12 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; -import dev.engine_room.flywheel.lib.internal.GlyphExtension; +import dev.engine_room.flywheel.lib.internal.BakedGlyphExtension; import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.resources.ResourceLocation; @Mixin(BakedGlyph.class) -public class BakedGlyphMixin implements GlyphExtension { +public class BakedGlyphMixin implements BakedGlyphExtension { @Shadow @Final private float u0; diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontAccessor.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontAccessor.java index db9cbe0c4..bb37a7212 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontAccessor.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontAccessor.java @@ -1,6 +1,7 @@ package dev.engine_room.flywheel.impl.mixin.text; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; import net.minecraft.client.gui.Font; @@ -9,6 +10,9 @@ import net.minecraft.resources.ResourceLocation; @Mixin(Font.class) public interface FontAccessor { + @Accessor("filterFishyGlyphs") + boolean flywheel$getFilterFishyGlyphs(); + @Invoker("getFontSet") FontSet flywheel$getFontSet(ResourceLocation fontLocation); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontSetMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontSetMixin.java index d31f79a41..8970ae216 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontSetMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontSetMixin.java @@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.At; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; -import dev.engine_room.flywheel.lib.internal.FontTextureExtension; +import dev.engine_room.flywheel.impl.extension.FontTextureExtension; import net.minecraft.client.gui.font.FontSet; import net.minecraft.client.gui.font.FontTexture; import net.minecraft.resources.ResourceLocation; diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontTextureMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontTextureMixin.java index 358d9e36b..bd00aac1c 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontTextureMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/text/FontTextureMixin.java @@ -21,8 +21,8 @@ import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import dev.engine_room.flywheel.impl.FontTextureUpload; -import dev.engine_room.flywheel.lib.internal.FontTextureExtension; -import dev.engine_room.flywheel.lib.internal.GlyphExtension; +import dev.engine_room.flywheel.impl.extension.FontTextureExtension; +import dev.engine_room.flywheel.lib.internal.BakedGlyphExtension; import net.minecraft.client.gui.font.FontTexture; import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.renderer.texture.AbstractTexture; @@ -90,7 +90,7 @@ public abstract class FontTextureMixin extends AbstractTexture implements FontTe @ModifyExpressionValue(method = "add", at = @At(value = "NEW", target = "net/minecraft/client/gui/font/glyphs/BakedGlyph")) private BakedGlyph flywheel$setGlyphExtensionName(BakedGlyph original) { - ((GlyphExtension) original).flywheel$texture(flywheel$name); + ((BakedGlyphExtension) original).flywheel$texture(flywheel$name); return original; } @@ -108,7 +108,7 @@ public abstract class FontTextureMixin extends AbstractTexture implements FontTe } @Override - public void flywheel$setName(ResourceLocation value) { - flywheel$name = value; + public void flywheel$setName(ResourceLocation name) { + flywheel$name = name; } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/SignVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/SignVisual.java index 531760ef3..b4dc3e192 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/SignVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/SignVisual.java @@ -18,11 +18,13 @@ import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.util.ResourceReloadCache; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; -import dev.engine_room.flywheel.lib.visual.text.SimpleTextLayer; import dev.engine_room.flywheel.lib.visual.text.TextLayer; +import dev.engine_room.flywheel.lib.visual.text.TextLayers; import dev.engine_room.flywheel.lib.visual.text.TextVisual; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.Sheets; import net.minecraft.util.FastColor; import net.minecraft.util.FormattedCharSequence; @@ -36,8 +38,8 @@ import net.minecraft.world.level.block.state.properties.WoodType; import net.minecraft.world.phys.Vec3; public class SignVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { - private static final Vec3 TEXT_OFFSET = new Vec3(0.0, 0.3333333432674408, 0.046666666865348816); + private static final Font FONT = Minecraft.getInstance().font; private static final ResourceReloadCache SIGN_MODELS = new ResourceReloadCache<>(SignVisual::createSignModel); @@ -47,12 +49,12 @@ public class SignVisual extends AbstractBlockEntityVisual imple .backfaceCulling(false) .build(); - private final Matrix4f pose = new Matrix4f(); private final InstanceTree instances; + private final Matrix4f initialPose; // The 8 lines of text we render - private final TextVisual[] frontText = new TextVisual[4]; - private final TextVisual[] backText = new TextVisual[4]; + private final TextVisual[] frontTextVisuals = new TextVisual[4]; + private final TextVisual[] backTextVisuals = new TextVisual[4]; // Need to update these every frame, so just remember which ones are obfuscated // Most of the time this will be empty. @@ -65,8 +67,8 @@ public class SignVisual extends AbstractBlockEntityVisual imple super(ctx, blockEntity, partialTick); for (int i = 0; i < 4; i++) { - frontText[i] = new TextVisual(ctx.instancerProvider()); - backText[i] = new TextVisual(ctx.instancerProvider()); + frontTextVisuals[i] = new TextVisual(ctx.instancerProvider()); + backTextVisuals[i] = new TextVisual(ctx.instancerProvider()); } var block = (SignBlock) blockState.getBlock(); @@ -79,46 +81,48 @@ public class SignVisual extends AbstractBlockEntityVisual imple instances.childOrThrow("stick") .visible(isStanding); - var rotation = -block.getYRotationDegrees(blockState); - var visualPosition = getVisualPosition(); - var signModelRenderScale = this.getSignModelRenderScale(); - pose.translate(visualPosition.getX() + 0.5f, visualPosition.getY() + 0.75f * signModelRenderScale, visualPosition.getZ() + 0.5f) + var signModelRenderScale = getSignModelRenderScale(); + var rotation = -block.getYRotationDegrees(blockState); + initialPose = new Matrix4f().translate(visualPosition.getX() + 0.5f, visualPosition.getY() + 0.75f * signModelRenderScale, visualPosition.getZ() + 0.5f) .rotateY(Mth.DEG_TO_RAD * rotation); - if (!(isStanding)) { - pose.translate(0.0f, -0.3125f, -0.4375f); + if (!isStanding) { + initialPose.translate(0.0f, -0.3125f, -0.4375f); } // Only apply this to the instances because text gets a separate scaling. - instances.scale(signModelRenderScale, -signModelRenderScale, -signModelRenderScale); - - instances.updateInstancesStatic(pose); + Matrix4f initialModelPose = new Matrix4f(initialPose).scale(signModelRenderScale, -signModelRenderScale, -signModelRenderScale); + instances.updateInstancesStatic(initialModelPose); lastFrontText = blockEntity.getFrontText(); lastBackText = blockEntity.getBackText(); - this.setupText(lastFrontText, true); - this.setupText(lastBackText, false); + setupText(lastFrontText, true); + setupText(lastBackText, false); + } + + private static ModelTree createSignModel(WoodType woodType) { + return ModelTrees.of(ModelLayers.createSignModelName(woodType), Sheets.getSignMaterial(woodType), MATERIAL); } @Override public void beginFrame(Context ctx) { - boolean setup = false; + boolean doSetup = false; if (lastFrontText != blockEntity.getFrontText()) { lastFrontText = blockEntity.getFrontText(); - setup = true; + doSetup = true; } if (lastBackText != blockEntity.getBackText()) { lastBackText = blockEntity.getBackText(); - setup = true; + doSetup = true; } - if (setup) { + if (doSetup) { // Setup both to make it easier to track obfuscation obfuscated.clear(); - this.setupText(lastBackText, false); - this.setupText(lastFrontText, true); + setupText(lastFrontText, true); + setupText(lastBackText, false); } else { // The is visible check is relatively expensive compared to the boolean checks above, // so only do it when it'll actually save some work in obfuscating. @@ -128,11 +132,6 @@ public class SignVisual extends AbstractBlockEntityVisual imple } } - @Override - public void collectCrumblingInstances(Consumer<@Nullable Instance> consumer) { - instances.traverse(consumer); - } - @Override public void updateLight(float partialTick) { int packedLight = computePackedLight(); @@ -141,120 +140,123 @@ public class SignVisual extends AbstractBlockEntityVisual imple .setChanged(); }); - for (var text : frontText) { - text.light(packedLight); + if (!lastFrontText.hasGlowingText()) { + for (var text : frontTextVisuals) { + text.light(packedLight); + text.setup(); + } } - for (var text : backText) { - text.light(packedLight); + + if (!lastBackText.hasGlowingText()) { + for (var text : backTextVisuals) { + text.light(packedLight); + text.setup(); + } } } + @Override + public void collectCrumblingInstances(Consumer<@Nullable Instance> consumer) { + instances.traverse(consumer); + } + @Override protected void _delete() { instances.delete(); - for (var text : frontText) { + for (var text : frontTextVisuals) { text.delete(); } - for (var text : backText) { + for (var text : backTextVisuals) { text.delete(); } } - public float getSignModelRenderScale() { + protected float getSignModelRenderScale() { return 0.6666667f; } - public float getSignTextRenderScale() { + protected float getSignTextRenderScale() { return 0.6666667f; } - public Vec3 getTextOffset() { + protected Vec3 getTextOffset() { return TEXT_OFFSET; } - protected void setupText(SignText text, boolean isFrontText) { - FormattedCharSequence[] formattedCharSequences = text.getRenderMessages(Minecraft.getInstance() + private void setupText(SignText text, boolean isFrontText) { + FormattedCharSequence[] textLines = text.getRenderMessages(Minecraft.getInstance() .isTextFilteringEnabled(), component -> { - List list = Minecraft.getInstance().font.split(component, blockEntity.getMaxTextLineWidth()); + List list = FONT.split(component, blockEntity.getMaxTextLineWidth()); return list.isEmpty() ? FormattedCharSequence.EMPTY : list.get(0); }); List layers = new ArrayList<>(); - int darkColor = TextLayer.GlyphColor.adjustColor(getDarkColor(text)); + int darkColor = getDarkColor(text); int textColor; if (text.hasGlowingText()) { - textColor = TextLayer.GlyphColor.adjustColor(text.getColor() - .getTextColor()); + textColor = text.getColor() + .getTextColor(); - layers.add(new SimpleTextLayer.Builder().style(TextLayer.GlyphMeshStyle.OUTLINE) - .material(TextLayer.GlyphMaterial.SIMPLE) - .color(TextLayer.GlyphColor.always(darkColor)) - .build()); + layers.add(TextLayers.outline(darkColor)); } else { textColor = darkColor; } - layers.add(new SimpleTextLayer.Builder().style(TextLayer.GlyphMeshStyle.SIMPLE) - .material(TextLayer.GlyphMaterial.POLYGON_OFFSET) - .color(TextLayer.GlyphColor.defaultTo(textColor)) - .bias(1) - .build()); + layers.add(TextLayers.normal(textColor, Font.DisplayMode.POLYGON_OFFSET, 1)); - var dst = isFrontText ? frontText : backText; + var textVisuals = isFrontText ? frontTextVisuals : backTextVisuals; int lineHeight = blockEntity.getTextLineHeight(); int lineDelta = 4 * lineHeight / 2; - for (int m = 0; m < 4; ++m) { - FormattedCharSequence formattedCharSequence = formattedCharSequences[m]; - float f = (float) -Minecraft.getInstance().font.width(formattedCharSequence) / 2; + for (int i = 0; i < 4; ++i) { + FormattedCharSequence textLine = textLines[i]; + float x = (float) (-FONT.width(textLine) / 2); + float y = i * lineHeight - lineDelta; - var textVisual = dst[m].content(formattedCharSequence) - .layers(layers) - .fullBright(text.hasGlowingText()) - .backgroundColor(0) - .x(f) - .y(m * lineHeight - lineDelta); - - var textPose = textVisual.pose(); - - textPose.set(pose); + var textVisual = textVisuals[i].layers(layers) + .text(textLine) + .pos(x, y) + .backgroundColor(0); + var pose = textVisual.pose().set(initialPose); if (!isFrontText) { - textPose.rotateY(Mth.PI); + pose.rotateY(Mth.PI); } - var offset = getTextOffset(); - float scale = 0.015625f * this.getSignTextRenderScale(); - textPose.translate((float) offset.x, (float) offset.y, (float) offset.z); - textPose.scale(scale, -scale, scale); + float scale = 0.015625f * getSignTextRenderScale(); + var textOffset = getTextOffset(); + pose.translate((float) textOffset.x, (float) textOffset.y, (float) textOffset.z); + pose.scale(scale, -scale, scale); + + if (text.hasGlowingText()) { + textVisual.light(LightTexture.FULL_BRIGHT); + } + // FIXME: incorrect light when going from glowing to non-glowing textVisual.setup(); - if (hasObfuscation(formattedCharSequence)) { - this.obfuscated.add(textVisual); + if (hasObfuscation(textLine)) { + obfuscated.add(textVisual); } } } - public static boolean hasObfuscation(FormattedCharSequence text) { + private static int getDarkColor(SignText signText) { + int colorArgb = signText.getColor() + .getTextColor(); + if (colorArgb == DyeColor.BLACK.getTextColor() && signText.hasGlowingText()) { + return 0xFFF0EBCC; + } + + int r = (int) ((double) FastColor.ARGB32.red(colorArgb) * 0.4); + int g = (int) ((double) FastColor.ARGB32.green(colorArgb) * 0.4); + int b = (int) ((double) FastColor.ARGB32.blue(colorArgb) * 0.4); + return FastColor.ARGB32.color(0, r, g, b); + } + + private static boolean hasObfuscation(FormattedCharSequence text) { return text.accept((i, s, j) -> s.isObfuscated()); } - - public static int getDarkColor(SignText signText) { - int i = signText.getColor() - .getTextColor(); - if (i == DyeColor.BLACK.getTextColor() && signText.hasGlowingText()) { - return -988212; - } - int j = (int) ((double) FastColor.ARGB32.red(i) * 0.4); - int k = (int) ((double) FastColor.ARGB32.green(i) * 0.4); - int l = (int) ((double) FastColor.ARGB32.blue(i) * 0.4); - return FastColor.ARGB32.color(0, j, k, l); - } - - private static ModelTree createSignModel(WoodType woodType) { - return ModelTrees.of(ModelLayers.createSignModelName(woodType), Sheets.getSignMaterial(woodType), MATERIAL); - } } diff --git a/gradle.properties b/gradle.properties index 013c0181a..418aaff91 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ parchment_version = 2023.09.03 # Minecraft build dependency versions minecraft_version = 1.20.1 forge_version = 47.2.19 -fabric_loader_version=0.16.5 +fabric_loader_version = 0.16.5 fabric_api_version = 0.92.1+1.20.1 # Build dependency mod versions