From 71cf582e971080c0c88ad3a61c34143940dba830 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Tue, 24 Sep 2024 21:00:45 -0700 Subject: [PATCH] Sign of life - Move all text handling into the TextVisual class - Add SignVisual - Flesh out the glyph model cache - Render effect glyphs - Clean up variable names --- .../flywheel/backend/engine/EngineImpl.java | 1 + .../flywheel/lib/instance/GlyphInstance.java | 4 + .../flywheel/lib/visual/NameplateVisual.java | 213 -------------- .../flywheel/lib/visual/TextVisual.java | 269 ++++++++++++++++++ .../flywheel/vanilla/SignVisual.java | 236 +++++++++++++++ .../flywheel/vanilla/VanillaVisuals.java | 3 + 6 files changed, 513 insertions(+), 213 deletions(-) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/visual/NameplateVisual.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/visual/TextVisual.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/vanilla/SignVisual.java diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index 338a63991..47b58fae5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -92,6 +92,7 @@ public class EngineImpl implements Engine { @Override public void setupRender(RenderContext context) { try (var state = GlStateTracker.getRestoreState()) { + // Process the render queue for font updates RenderSystem.replayQueue(); Uniforms.update(context); environmentStorage.flush(); 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 902ece1a1..cd33b677a 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 @@ -83,6 +83,10 @@ public class GlyphInstance extends AbstractInstance { 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; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/NameplateVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/NameplateVisual.java deleted file mode 100644 index 3fb42a933..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/NameplateVisual.java +++ /dev/null @@ -1,213 +0,0 @@ -package dev.engine_room.flywheel.lib.visual; - -import java.util.List; - -import org.jetbrains.annotations.Nullable; -import org.joml.Matrix4f; -import org.joml.Vector3f; -import org.joml.Vector4f; -import org.joml.Vector4fc; - -import com.google.common.collect.Lists; -import com.mojang.blaze3d.font.GlyphInfo; - -import dev.engine_room.flywheel.api.instance.InstancerProvider; -import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Model; -import dev.engine_room.flywheel.api.vertex.MutableVertexList; -import dev.engine_room.flywheel.lib.instance.GlyphInstance; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.internal.FlwLibLink; -import dev.engine_room.flywheel.lib.material.CutoutShaders; -import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.QuadMesh; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.util.ResourceReloadCache; -import dev.engine_room.flywheel.lib.visual.util.SmartRecycler; -import net.minecraft.client.Minecraft; -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.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.network.chat.TextColor; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.FormattedCharSink; - -public class NameplateVisual { - - public final Matrix4f pose = new Matrix4f(); - private final InstancerProvider provider; - private final InstanceEmitter instanceEmitter; - - @Nullable - private Component name; - - public NameplateVisual(InstancerProvider provider) { - this.provider = provider; - - instanceEmitter = new InstanceEmitter(provider); - instanceEmitter.font = Minecraft.getInstance().font; - } - - public void name(Component name) { - this.name = name; - } - - public void update() { - if (name == null) { - return; - } - - instanceEmitter.recycler.resetCount(); - instanceEmitter.x = 0; - instanceEmitter.y = 0; - name.getVisualOrderText() - .accept(instanceEmitter); - instanceEmitter.recycler.discardExtra(); - - } - - private class InstanceEmitter implements FormattedCharSink { - private final SmartRecycler recycler; - - Font font; - private boolean dropShadow; - private float dimFactor; - private byte r; - private byte g; - private byte b; - private byte a; - private Font.DisplayMode mode; - private int packedLightCoords; - float x; - float y; - - private InstanceEmitter(InstancerProvider instancerProvider) { - recycler = new SmartRecycler<>(key -> instancerProvider.instancer(InstanceTypes.GLYPH, GLYPH_CACHE.get(key)) - .createInstance()); - } - - @Nullable - private List effects; - - private void addEffect(BakedGlyph.Effect effect) { - if (this.effects == null) { - this.effects = Lists.newArrayList(); - } - this.effects.add(effect); - } - - @Override - public boolean accept(int i, Style style, int j) { - float n; - float l; - float h; - float g; - 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); - boolean bl = style.isBold(); - float f = this.a; - TextColor textColor = style.getColor(); - if (textColor != null) { - int k = textColor.getValue(); - g = (float) (k >> 16 & 0xFF) / 255.0f * this.dimFactor; - h = (float) (k >> 8 & 0xFF) / 255.0f * this.dimFactor; - l = (float) (k & 0xFF) / 255.0f * this.dimFactor; - } else { - g = this.r; - h = this.g; - l = this.b; - } - if (!(bakedGlyph instanceof EmptyGlyph)) { - // var renderType = bakedGlyph.renderType(Font.DisplayMode.NORMAL); - - var glyphExtension = FlwLibLink.INSTANCE.getGlyphExtension(bakedGlyph); - - GlyphInstance glyph = recycler.get(new GlyphModelKey(glyphExtension.flywheel$texture(), bl, dropShadow)); - - glyph.setGlyph(bakedGlyph, this.x, this.y, style.isItalic()); - glyph.pose.set(pose); - glyph.light = LightTexture.FULL_BRIGHT; - glyph.setChanged(); - } - float m = glyphInfo.getAdvance(bl); - float f2 = n = this.dropShadow ? 1.0f : 0.0f; - // if (style.isStrikethrough()) { - // this.addEffect(new BakedGlyph.Effect(this.x + n - 1.0f, this.y + n + 4.5f, this.x + n + m, this.y + n + 4.5f - 1.0f, 0.01f, g, h, l, f)); - // } - // if (style.isUnderlined()) { - // this.addEffect(new BakedGlyph.Effect(this.x + n - 1.0f, this.y + n + 9.0f, this.x + n + m, this.y + n + 9.0f - 1.0f, 0.01f, g, h, l, f)); - // } - this.x += m; - return true; - } - - public float finish(int backgroundColor, float x) { - if (backgroundColor != 0) { - float f = (float) (backgroundColor >> 24 & 0xFF) / 255.0f; - float g = (float) (backgroundColor >> 16 & 0xFF) / 255.0f; - float h = (float) (backgroundColor >> 8 & 0xFF) / 255.0f; - float i = (float) (backgroundColor & 0xFF) / 255.0f; - this.addEffect(new BakedGlyph.Effect(x - 1.0f, this.y + 9.0f, this.x + 1.0f, this.y - 1.0f, 0.01f, g, h, i, f)); - } - // if (this.effects != null) { - // BakedGlyph bakedGlyph = font.getFontSet(Style.DEFAULT_FONT).whiteGlyph(); - // VertexConsumer vertexConsumer = this.bufferSource.getBuffer(bakedGlyph.renderType(this.mode)); - // for (BakedGlyph.Effect effect : this.effects) { - // bakedGlyph.renderEffect(effect, this.pose, vertexConsumer, this.packedLightCoords); - // } - // } - return this.x; - } - } - - private static final ResourceReloadCache GLYPH_CACHE = new ResourceReloadCache<>(GlyphModelKey::into); - - private static final Material GLYPH_MATERIAL = SimpleMaterial.builder() - .build(); - - private record GlyphModelKey(ResourceLocation font, boolean bold, boolean shadow) { - private Model into() { - // bold -> x + 1 - // shadow -> x + 1, y + 1 - return new SingleMeshModel(new GlyphMesh(new Vector3f[]{new Vector3f(0, 0, 0),}), SimpleMaterial.builderOf(GLYPH_MATERIAL) - .texture(font) - .cutout(CutoutShaders.ONE_TENTH) - .build()); - } - } - - public record GlyphMesh(Vector3f[] quads) implements QuadMesh { - - @Override - public int vertexCount() { - return 4 * quads.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 j = 0; j < 4; j++) { - vertexList.x(quadStart + j, quad.x); - vertexList.y(quadStart + j, quad.y); - vertexList.z(quadStart + j, quad.z); - vertexList.normalX(quadStart + j, 0); - vertexList.normalY(quadStart + j, 0); - vertexList.normalZ(quadStart + j, 1); - } - } - } - - @Override - public Vector4fc boundingSphere() { - return new Vector4f(0, 0, 0, 2); - } - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/TextVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/TextVisual.java new file mode 100644 index 000000000..4ecc07c1b --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/TextVisual.java @@ -0,0 +1,269 @@ +package dev.engine_room.flywheel.lib.visual; + +import java.util.ArrayList; +import java.util.List; + +import org.joml.Matrix4f; +import org.joml.Vector3f; +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.material.Material; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.api.vertex.MutableVertexList; +import dev.engine_room.flywheel.lib.instance.GlyphInstance; +import dev.engine_room.flywheel.lib.instance.InstanceTypes; +import dev.engine_room.flywheel.lib.internal.FlwLibLink; +import dev.engine_room.flywheel.lib.material.CutoutShaders; +import dev.engine_room.flywheel.lib.material.SimpleMaterial; +import dev.engine_room.flywheel.lib.model.QuadMesh; +import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; +import dev.engine_room.flywheel.lib.visual.util.SmartRecycler; +import net.minecraft.client.Minecraft; +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.texture.OverlayTexture; +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextColor; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.FormattedCharSink; + +/** + * A visual that renders a single line of text. + */ +public class TextVisual { + public boolean dropShadow; + public boolean with8xOutline; + public int backgroundColor = 0; + public int color; + public FormattedCharSequence content = FormattedCharSequence.EMPTY; + public float x; + public float y; + public int light; + + public final Matrix4f pose = new Matrix4f(); + + private final Sink sink; + + public TextVisual(InstancerProvider provider) { + sink = new Sink(provider); + } + + public void setup() { + sink.recycler.resetCount(); + sink.x = x; + sink.y = y; + sink.dimFactor = dropShadow ? 0.25f : 1.0f; + sink.r = (float) (color >> 16 & 0xFF) / 255.0f * sink.dimFactor; + sink.g = (float) (color >> 8 & 0xFF) / 255.0f * sink.dimFactor; + sink.b = (float) (color & 0xFF) / 255.0f * sink.dimFactor; + sink.a = (float) (color >> 24 & 0xFF) / 255.0f; + // FIXME: Need separate instances for the 8x outline and the center. + // Right now we just show the outline. + content.accept(sink); + sink.recycler.discardExtra(); + } + + public void delete() { + sink.recycler.delete(); + } + + private class Sink implements FormattedCharSink { + private final SmartRecycler recycler; + + Font font; + private float dimFactor; + private float r; + private float g; + private float b; + private float a; + + // Separate x and y from TextVisual because these advance as we accept glyphs + float x; + float y; + + private Sink(InstancerProvider instancerProvider) { + recycler = new SmartRecycler<>(key -> instancerProvider.instancer(InstanceTypes.GLYPH, GLYPH_CACHE.get(key)) + .createInstance()); + font = Minecraft.getInstance().font; + } + + @Override + public boolean accept(int i, Style style, int j) { + float b; + float g; + float r; + 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); + boolean bold = style.isBold(); + TextColor textColor = style.getColor(); + if (textColor != null) { + int color = textColor.getValue(); + r = (float) (color >> 16 & 0xFF) / 255.0f * this.dimFactor; + g = (float) (color >> 8 & 0xFF) / 255.0f * this.dimFactor; + b = (float) (color & 0xFF) / 255.0f * this.dimFactor; + } else { + r = this.r; + g = this.g; + b = this.b; + } + if (!(bakedGlyph instanceof EmptyGlyph)) { + var glyphExtension = FlwLibLink.INSTANCE.getGlyphExtension(bakedGlyph); + + GlyphInstance glyph = recycler.get(new GlyphModelKey(glyphExtension.flywheel$texture(), new GlyphSettings(bold, dropShadow, with8xOutline))); + + glyph.setGlyph(bakedGlyph, this.x, this.y, style.isItalic()); + glyph.color(r, g, b, this.a); + glyph.pose.set(pose); + glyph.light = light; + glyph.setChanged(); + } + float advance = glyphInfo.getAdvance(bold); + float o = dropShadow ? 1.0f : 0.0f; + if (style.isStrikethrough()) { + this.addEffect(this.x + o - 1.0f, this.y + o + 4.5f, this.x + o + advance, this.y + o + 4.5f - 1.0f, 0.01f, r, g, b, this.a); + } + if (style.isUnderlined()) { + this.addEffect(this.x + o - 1.0f, this.y + o + 9.0f, this.x + o + advance, this.y + o + 9.0f - 1.0f, 0.01f, r, g, b, this.a); + } + this.x += advance; + return true; + } + + private void addEffect(float x0, float y0, float x1, float y1, float depth, float r, float g, float b, float a) { + BakedGlyph bakedGlyph = FlwLibLink.INSTANCE.getFontSet(font, Style.DEFAULT_FONT) + .whiteGlyph(); + + var glyphExtension = FlwLibLink.INSTANCE.getGlyphExtension(bakedGlyph); + + GlyphInstance glyph = recycler.get(new GlyphModelKey(glyphExtension.flywheel$texture(), new GlyphSettings(false, dropShadow, with8xOutline))); + + glyph.u0 = glyphExtension.flywheel$u0(); + glyph.u1 = glyphExtension.flywheel$u1(); + glyph.v0 = glyphExtension.flywheel$v0(); + glyph.v1 = glyphExtension.flywheel$v1(); + + glyph.y0 = y0; + glyph.y1 = y1; + + glyph.x0 = x0; + glyph.x1 = x1; + glyph.x2 = x1; + glyph.x3 = x0; + glyph.color(r, g, b, this.a); + glyph.pose.set(pose); + glyph.light = light; + glyph.setChanged(); + } + + public float finish(int backgroundColor, float x) { + if (backgroundColor != 0) { + float f = (float) (backgroundColor >> 24 & 0xFF) / 255.0f; + float g = (float) (backgroundColor >> 16 & 0xFF) / 255.0f; + float h = (float) (backgroundColor >> 8 & 0xFF) / 255.0f; + float i = (float) (backgroundColor & 0xFF) / 255.0f; + this.addEffect(x - 1.0f, this.y + 9.0f, this.x + 1.0f, this.y - 1.0f, 0.01f, g, h, i, f); + } + return this.x; + } + } + + private static final ResourceReloadCache GLYPH_CACHE = new ResourceReloadCache<>(GlyphModelKey::into); + private static final ResourceReloadCache MESH_CACHE = new ResourceReloadCache<>(GlyphSettings::into); + + private static final Material GLYPH_MATERIAL = SimpleMaterial.builder() + .cutout(CutoutShaders.ONE_TENTH) + .backfaceCulling(false) + .build(); + + private record GlyphModelKey(ResourceLocation font, GlyphSettings settings) { + private Model into() { + return new SingleMeshModel(MESH_CACHE.get(settings), SimpleMaterial.builderOf(GLYPH_MATERIAL) + .texture(font) + .build()); + } + } + + // FIXME: probably replace with an enum + private record GlyphSettings(boolean bold, boolean dropShadow, boolean with8xOutline) { + public GlyphMesh into() { + // bold -> x + 1 + // shadow -> x + 1, y + 1 + + List out = new ArrayList<>(); + + if (with8xOutline) { + for (int x = -1; x <= 1; ++x) { + for (int y = -1; y <= 1; ++y) { + if (x == 0 && y == 0) { + continue; + } + + out.add(new Vector3f(x, y, 0)); + } + } + } else { + out.add(new Vector3f(0, 0, 0)); + } + + if (bold) { + out.add(new Vector3f(1, 0, 0)); + } + + if (dropShadow) { + out.add(new Vector3f(1, 1, 0)); + } + + return new GlyphMesh(out.toArray(new Vector3f[0])); + } + } + + /** + * A mesh that represents a single glyph. Expects to be drawn with the glyph instance type. + * + * @param quads Each quad will be expanded into 4 vertices. + */ + private record GlyphMesh(Vector3f[] quads) implements QuadMesh { + + @Override + public int vertexCount() { + return 4 * quads.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 j = 0; j < 4; j++) { + vertexList.x(quadStart + j, quad.x); + vertexList.y(quadStart + j, quad.y); + 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); + } + } + } + + @Override + public Vector4fc boundingSphere() { + // FIXME: what is the actual bounding sphere?? + return new Vector4f(0, 0, 0, 2); + } + } +} 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 new file mode 100644 index 000000000..1691591c3 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/SignVisual.java @@ -0,0 +1,236 @@ +package dev.engine_room.flywheel.vanilla; + +import java.util.List; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; + +import dev.engine_room.flywheel.api.instance.Instance; +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.visualization.VisualizationContext; +import dev.engine_room.flywheel.lib.material.CutoutShaders; +import dev.engine_room.flywheel.lib.material.SimpleMaterial; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.ModelTree; +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.TextVisual; +import net.minecraft.client.Minecraft; +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; +import net.minecraft.util.Mth; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.level.block.SignBlock; +import net.minecraft.world.level.block.StandingSignBlock; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.level.block.entity.SignText; +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 ResourceReloadCache SIGN_MODELS = new ResourceReloadCache<>(SignVisual::createSignModel); + + private static final Material MATERIAL = SimpleMaterial.builder() + .cutout(CutoutShaders.ONE_TENTH) + .texture(Sheets.SIGN_SHEET) + .backfaceCulling(false) + .build(); + + private final Matrix4f pose = new Matrix4f(); + private final InstanceTree instances; + + // The 8 lines of text we render + private final TextVisual[] frontText = new TextVisual[4]; + private final TextVisual[] backText = new TextVisual[4]; + + private SignText lastFrontText; + private SignText lastBackText; + + public SignVisual(VisualizationContext ctx, SignBlockEntity blockEntity, float partialTick) { + super(ctx, blockEntity, partialTick); + + for (int i = 0; i < 4; i++) { + frontText[i] = new TextVisual(ctx.instancerProvider()); + backText[i] = new TextVisual(ctx.instancerProvider()); + } + + var block = (SignBlock) blockState.getBlock(); + WoodType woodType = SignBlock.getWoodType(block); + var isStanding = block instanceof StandingSignBlock; + + instances = InstanceTree.create(ctx.instancerProvider(), SIGN_MODELS.get(woodType)); + + // Maybe use a separate model tree? + 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) + .rotateY(Mth.DEG_TO_RAD * rotation); + + if (!(isStanding)) { + pose.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); + + lastFrontText = blockEntity.getFrontText(); + lastBackText = blockEntity.getBackText(); + this.setupText(lastFrontText, frontText, true); + this.setupText(lastBackText, backText, false); + } + + @Override + public void beginFrame(Context ctx) { + // Need to update every frame if the text is obfuscated + + if (lastFrontText != blockEntity.getFrontText()) { + lastFrontText = blockEntity.getFrontText(); + this.setupText(lastFrontText, frontText, true); + } + + if (lastBackText != blockEntity.getBackText()) { + lastBackText = blockEntity.getBackText(); + this.setupText(lastBackText, backText, false); + } + } + + @Override + public void collectCrumblingInstances(Consumer<@Nullable Instance> consumer) { + instances.traverse(consumer); + } + + @Override + public void updateLight(float partialTick) { + int packedLight = computePackedLight(); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); + + // FIXME: fullbright + for (var text : frontText) { + text.light = packedLight; + } + for (var text : backText) { + text.light = packedLight; + } + } + + @Override + protected void _delete() { + instances.delete(); + + for (var text : frontText) { + text.delete(); + } + + for (var text : backText) { + text.delete(); + } + } + + public float getSignModelRenderScale() { + return 0.6666667f; + } + + public float getSignTextRenderScale() { + return 0.6666667f; + } + + public Vec3 getTextOffset() { + return TEXT_OFFSET; + } + + void setupText(SignText text, TextVisual[] dst, boolean isFrontText) { + var font = Minecraft.getInstance().font; + FormattedCharSequence[] formattedCharSequences = text.getRenderMessages(Minecraft.getInstance() + .isTextFilteringEnabled(), component -> { + List list = font.split(component, blockEntity.getMaxTextLineWidth()); + return list.isEmpty() ? FormattedCharSequence.EMPTY : list.get(0); + }); + + int light; + boolean outline; + int darkColor = adjustColor(getDarkColor(text)); + int textColor; + if (text.hasGlowingText()) { + textColor = adjustColor(text.getColor() + .getTextColor()); + outline = true; + light = LightTexture.FULL_BRIGHT; + } else { + textColor = darkColor; + outline = false; + light = LightTexture.FULL_BLOCK; + } + + int lineHeight = blockEntity.getTextLineHeight(); + int lineDelta = 4 * lineHeight / 2; + for (int m = 0; m < 4; ++m) { + FormattedCharSequence formattedCharSequence = formattedCharSequences[m]; + float f = (float) -font.width(formattedCharSequence) / 2; + + var textVisual = dst[m]; + textVisual.color = textColor; + textVisual.dropShadow = false; + textVisual.with8xOutline = outline; + textVisual.backgroundColor = darkColor; + textVisual.x = f; + textVisual.y = m * lineHeight - lineDelta; + textVisual.content = formattedCharSequence; + // FIXME: separate flag for full bright? + textVisual.light = light; + + textVisual.pose.set(pose); + + if (!isFrontText) { + textVisual.pose.rotateY(Mth.PI); + } + var offset = getTextOffset(); + float scale = 0.015625f * this.getSignTextRenderScale(); + textVisual.pose.translate((float) offset.x, (float) offset.y, (float) offset.z); + textVisual.pose.scale(scale, -scale, scale); + + textVisual.setup(); + } + } + + private static int adjustColor(int color) { + if ((color & 0xFC000000) == 0) { + return color | 0xFF000000; + } + return color; + } + + 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/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java index 53efc4562..cadda5114 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java @@ -47,6 +47,9 @@ public class VanillaVisuals { .factory(ShulkerBoxVisual::new) .apply(); + builder(BlockEntityType.SIGN).factory(SignVisual::new) + .apply(); + builder(EntityType.CHEST_MINECART) .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.CHEST_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender)