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
This commit is contained in:
Jozufozu 2024-09-24 21:00:45 -07:00
parent 9227631c73
commit 71cf582e97
6 changed files with 513 additions and 213 deletions

View file

@ -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();

View file

@ -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;

View file

@ -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<GlyphModelKey, GlyphInstance> 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<BakedGlyph.Effect> 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<GlyphModelKey, Model> 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);
}
}
}

View file

@ -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<GlyphModelKey, GlyphInstance> 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<GlyphModelKey, Model> GLYPH_CACHE = new ResourceReloadCache<>(GlyphModelKey::into);
private static final ResourceReloadCache<GlyphSettings, GlyphMesh> 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<Vector3f> 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);
}
}
}

View file

@ -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<SignBlockEntity> implements SimpleDynamicVisual {
private static final Vec3 TEXT_OFFSET = new Vec3(0.0, 0.3333333432674408, 0.046666666865348816);
private static final ResourceReloadCache<WoodType, ModelTree> 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<FormattedCharSequence> 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);
}
}

View file

@ -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)