mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-30 23:04:57 +01:00
Laying it on
- TextVisual renders an arbitrary number of layers - Layers have relatively fine control over how the glyph instances are set up - Encapsulate all fields in TextVisual - Move Sinks to a thread local
This commit is contained in:
parent
7aefbee9c1
commit
c0b19a2f01
5 changed files with 593 additions and 327 deletions
|
@ -1,299 +0,0 @@
|
||||||
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 static final float ONE_PIXEL = 0.125f;
|
|
||||||
|
|
||||||
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.pose = pose;
|
|
||||||
sink.light = light;
|
|
||||||
|
|
||||||
if (with8xOutline) {
|
|
||||||
sink.x = x;
|
|
||||||
sink.y = y;
|
|
||||||
sink.setup(GlyphMode.OUTLINE, this.backgroundColor);
|
|
||||||
content.accept(sink);
|
|
||||||
}
|
|
||||||
|
|
||||||
sink.setup(GlyphMode.SIMPLE, this.color);
|
|
||||||
sink.x = x;
|
|
||||||
sink.y = y;
|
|
||||||
content.accept(sink);
|
|
||||||
|
|
||||||
sink.recycler.discardExtra();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete() {
|
|
||||||
sink.recycler.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Sink implements FormattedCharSink {
|
|
||||||
private final SmartRecycler<GlyphModelKey, GlyphInstance> recycler;
|
|
||||||
|
|
||||||
private final Font font;
|
|
||||||
private int light;
|
|
||||||
private Matrix4f pose;
|
|
||||||
private GlyphMode mode = GlyphMode.SIMPLE;
|
|
||||||
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
|
|
||||||
private float x;
|
|
||||||
private float y;
|
|
||||||
|
|
||||||
private Sink(InstancerProvider instancerProvider) {
|
|
||||||
recycler = new SmartRecycler<>(key -> instancerProvider.instancer(InstanceTypes.GLYPH, GLYPH_CACHE.get(key), key.settings.glyphMode.bias)
|
|
||||||
.createInstance());
|
|
||||||
font = Minecraft.getInstance().font;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setup(GlyphMode mode, int color) {
|
|
||||||
this.mode = mode;
|
|
||||||
r = (float) (color >> 16 & 0xFF) / 255.0f * mode.dimFactor;
|
|
||||||
g = (float) (color >> 8 & 0xFF) / 255.0f * mode.dimFactor;
|
|
||||||
b = (float) (color & 0xFF) / 255.0f * mode.dimFactor;
|
|
||||||
a = (float) (color >> 24 & 0xFF) / 255.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 * mode.dimFactor;
|
|
||||||
g = (float) (color >> 8 & 0xFF) / 255.0f * mode.dimFactor;
|
|
||||||
b = (float) (color & 0xFF) / 255.0f * mode.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(mode, bold)));
|
|
||||||
|
|
||||||
glyph.pose.set(pose);
|
|
||||||
glyph.setGlyph(bakedGlyph, this.x, this.y, style.isItalic());
|
|
||||||
glyph.color(r, g, b, this.a);
|
|
||||||
glyph.light = light;
|
|
||||||
glyph.setChanged();
|
|
||||||
}
|
|
||||||
float advance = glyphInfo.getAdvance(bold);
|
|
||||||
float o = mode.effectShift;
|
|
||||||
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(GlyphMode.SIMPLE, false)));
|
|
||||||
|
|
||||||
glyph.pose.set(pose);
|
|
||||||
glyph.setEffect(bakedGlyph, x0, y0, x1, y1, depth);
|
|
||||||
glyph.color(r, g, b, this.a);
|
|
||||||
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)
|
|
||||||
.diffuse(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)
|
|
||||||
.polygonOffset(settings.glyphMode.polygonOffset)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This could probably be made a public interface and a TextVisual could render an arbitrary number of layers
|
|
||||||
private enum GlyphMode {
|
|
||||||
SIMPLE(1, 1, 0, true),
|
|
||||||
OUTLINE(1, 0, 0, false),
|
|
||||||
SHADOW(0.25f, 0, 1, false),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final float dimFactor;
|
|
||||||
private final int bias;
|
|
||||||
private final float effectShift;
|
|
||||||
private final boolean polygonOffset;
|
|
||||||
|
|
||||||
GlyphMode(float dimFactor, int bias, float effectShift, boolean polygonOffset) {
|
|
||||||
this.dimFactor = dimFactor;
|
|
||||||
this.bias = bias;
|
|
||||||
this.effectShift = effectShift;
|
|
||||||
this.polygonOffset = polygonOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record GlyphSettings(GlyphMode glyphMode, boolean bold) {
|
|
||||||
public GlyphMesh into() {
|
|
||||||
List<Vector3f> out = new ArrayList<>();
|
|
||||||
|
|
||||||
switch (glyphMode) {
|
|
||||||
case SIMPLE:
|
|
||||||
add(out, 0, 0);
|
|
||||||
break;
|
|
||||||
case OUTLINE:
|
|
||||||
for (int x = -1; x <= 1; ++x) {
|
|
||||||
for (int y = -1; y <= 1; ++y) {
|
|
||||||
if (x == 0 && y == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(out, x * ONE_PIXEL, y * ONE_PIXEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SHADOW:
|
|
||||||
add(out, ONE_PIXEL, ONE_PIXEL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GlyphMesh(out.toArray(new Vector3f[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add(List<Vector3f> out, float x, float y) {
|
|
||||||
out.add(new Vector3f(x, y, 0));
|
|
||||||
if (bold) {
|
|
||||||
out.add(new Vector3f(x + ONE_PIXEL, y, 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 {
|
|
||||||
private static final float[] X = new float[]{0, 0, 1, 1};
|
|
||||||
private static final float[] Y = new float[]{0, 1, 1, 0};
|
|
||||||
|
|
||||||
@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 + 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Vector4fc boundingSphere() {
|
|
||||||
// FIXME: what is the actual bounding sphere??
|
|
||||||
return new Vector4f(0, 0, 0, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package dev.engine_room.flywheel.lib.visual.text;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public record SimpleTextLayer(GlyphMeshStyle style, GlyphMaterial material, GlyphColor color, int bias, float offsetX,
|
||||||
|
float offsetY, float effectOffsetX, float effectOffsetY) implements TextLayer {
|
||||||
|
public static class Builder {
|
||||||
|
@Nullable
|
||||||
|
private GlyphMeshStyle style;
|
||||||
|
@Nullable
|
||||||
|
private GlyphMaterial material;
|
||||||
|
@Nullable
|
||||||
|
private GlyphColor color;
|
||||||
|
|
||||||
|
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;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder material(GlyphMaterial material) {
|
||||||
|
this.material = material;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder color(GlyphColor color) {
|
||||||
|
this.color = color;
|
||||||
|
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(material);
|
||||||
|
Objects.requireNonNull(color);
|
||||||
|
|
||||||
|
return new SimpleTextLayer(style, material, color, bias, offsetX, offsetY, effectOffsetX, effectOffsetY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
package dev.engine_room.flywheel.lib.visual.text;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import dev.engine_room.flywheel.api.material.Material;
|
||||||
|
import dev.engine_room.flywheel.lib.material.CutoutShaders;
|
||||||
|
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
|
||||||
|
import net.minecraft.network.chat.Style;
|
||||||
|
import net.minecraft.network.chat.TextColor;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public interface TextLayer {
|
||||||
|
float ONE_PIXEL = 0.125f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The style of individual glyphs.
|
||||||
|
*
|
||||||
|
* @return A GlyphMeshStyle.
|
||||||
|
*/
|
||||||
|
GlyphMeshStyle style();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping from texture ResourceLocations to Flywheel materials.
|
||||||
|
*
|
||||||
|
* @return A GlyphMaterial.
|
||||||
|
*/
|
||||||
|
GlyphMaterial material();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping from text styles to ARGB colors.
|
||||||
|
*
|
||||||
|
* @return A GlyphColor.
|
||||||
|
*/
|
||||||
|
GlyphColor color();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instancer bias for this layer.
|
||||||
|
*
|
||||||
|
* @return The bias.
|
||||||
|
*/
|
||||||
|
int bias();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x offset of text content in this layer.
|
||||||
|
*
|
||||||
|
* @return The x offset.
|
||||||
|
*/
|
||||||
|
float offsetX();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y offset of text content in this layer.
|
||||||
|
*
|
||||||
|
* @return The y offset.
|
||||||
|
*/
|
||||||
|
float offsetY();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The x offset of text effects such as strikethrough or underline in this layer.
|
||||||
|
*
|
||||||
|
* @return The x offset.
|
||||||
|
*/
|
||||||
|
float effectOffsetX();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The y offset of text effects such as strikethrough or underline in this layer.
|
||||||
|
*
|
||||||
|
* @return The y offset.
|
||||||
|
*/
|
||||||
|
float effectOffsetY();
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface GlyphColor {
|
||||||
|
/**
|
||||||
|
* 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 style -> {
|
||||||
|
TextColor textColor = style.getColor();
|
||||||
|
if (textColor != null) {
|
||||||
|
return adjustColor(textColor.getValue());
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always use the given color, regardless of the style.
|
||||||
|
*
|
||||||
|
* @param color The ARGB color to use.
|
||||||
|
* @return A new GlyphColor.
|
||||||
|
*/
|
||||||
|
static GlyphColor always(int color) {
|
||||||
|
return style -> adjustColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust the color to be fully opaque if it's very close to having 0 alpha.
|
||||||
|
*
|
||||||
|
* @param color The ARGB color to adjust.
|
||||||
|
* @return The adjusted color.
|
||||||
|
*/
|
||||||
|
static int adjustColor(int color) {
|
||||||
|
if ((color & 0xFC000000) == 0) {
|
||||||
|
return color | 0xFF000000;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a style to a color.
|
||||||
|
*
|
||||||
|
* @param style The style 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<Vector3f> out);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,310 @@
|
||||||
|
package dev.engine_room.flywheel.lib.visual.text;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
|
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.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.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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A visual that renders a single line of text.
|
||||||
|
*/
|
||||||
|
public class TextVisual {
|
||||||
|
private static final ThreadLocal<Sink> SINKS = ThreadLocal.withInitial(Sink::new);
|
||||||
|
|
||||||
|
private final SmartRecycler<GlyphInstancerKey, GlyphInstance> recycler;
|
||||||
|
|
||||||
|
private FormattedCharSequence content = FormattedCharSequence.EMPTY;
|
||||||
|
private float x;
|
||||||
|
private float y;
|
||||||
|
private int backgroundColor = 0;
|
||||||
|
private int light;
|
||||||
|
private boolean fullBright;
|
||||||
|
|
||||||
|
private final List<TextLayer> 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)
|
||||||
|
.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 layers(Collection<TextLayer> layers) {
|
||||||
|
this.layers.clear();
|
||||||
|
this.layers.addAll(layers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextVisual pos(float x, float y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextVisual x(float x) {
|
||||||
|
this.x = x;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextVisual y(float y) {
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextVisual backgroundColor(int backgroundColor) {
|
||||||
|
this.backgroundColor = backgroundColor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextVisual light(int light) {
|
||||||
|
this.light = light;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextVisual fullBright(boolean fullBright) {
|
||||||
|
this.fullBright = fullBright;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: method to just update pose or light without recalculating text
|
||||||
|
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);
|
||||||
|
maxX = Math.max(maxX, (int) sink.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.addBackground(backgroundColor, x, maxX);
|
||||||
|
|
||||||
|
sink.clear();
|
||||||
|
|
||||||
|
recycler.discardExtra();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete() {
|
||||||
|
recycler.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Sink implements FormattedCharSink {
|
||||||
|
private final Font font;
|
||||||
|
|
||||||
|
@UnknownNullability
|
||||||
|
private SmartRecycler<GlyphInstancerKey, GlyphInstance> recycler;
|
||||||
|
@UnknownNullability
|
||||||
|
private Matrix4f pose;
|
||||||
|
@UnknownNullability
|
||||||
|
private TextLayer layer;
|
||||||
|
|
||||||
|
private int light;
|
||||||
|
|
||||||
|
private float x;
|
||||||
|
private float y;
|
||||||
|
|
||||||
|
private Sink() {
|
||||||
|
font = Minecraft.getInstance().font;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepare(SmartRecycler<GlyphInstancerKey, GlyphInstance> recycler, Matrix4f pose, int light) {
|
||||||
|
this.recycler = recycler;
|
||||||
|
this.pose = pose;
|
||||||
|
this.light = light;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
boolean bold = style.isBold();
|
||||||
|
|
||||||
|
int color = layer.color()
|
||||||
|
.color(style);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
float advance = glyphInfo.getAdvance(bold);
|
||||||
|
float effectX = layer.effectOffsetX();
|
||||||
|
float effectY = layer.effectOffsetY();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
this.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 record GlyphInstancerKey(GlyphModelKey modelKey, int bias) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ResourceReloadCache<GlyphModelKey, Model> GLYPH_CACHE = new ResourceReloadCache<>(GlyphModelKey::into);
|
||||||
|
private static final ResourceReloadCache<GlyphMeshKey, GlyphMesh> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record GlyphMeshKey(TextLayer.GlyphMeshStyle style, boolean bold) {
|
||||||
|
public GlyphMesh into() {
|
||||||
|
List<Vector3f> out = new ArrayList<>();
|
||||||
|
|
||||||
|
style.addQuads(quad -> {
|
||||||
|
out.add(quad);
|
||||||
|
if (bold) {
|
||||||
|
out.add(new Vector3f(quad.x + TextLayer.ONE_PIXEL, quad.y, quad.z));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 {
|
||||||
|
private static final float[] X = new float[]{0, 0, 1, 1};
|
||||||
|
private static final float[] Y = new float[]{0, 1, 1, 0};
|
||||||
|
|
||||||
|
@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 + 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Vector4fc boundingSphere() {
|
||||||
|
// FIXME: what is the actual bounding sphere??
|
||||||
|
return new Vector4f(0, 0, 0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.engine_room.flywheel.vanilla;
|
package dev.engine_room.flywheel.vanilla;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@ -17,10 +18,11 @@ import dev.engine_room.flywheel.lib.model.part.ModelTrees;
|
||||||
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
|
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
|
||||||
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
|
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
|
||||||
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
|
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
|
||||||
import dev.engine_room.flywheel.lib.visual.TextVisual;
|
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.TextVisual;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.model.geom.ModelLayers;
|
import net.minecraft.client.model.geom.ModelLayers;
|
||||||
import net.minecraft.client.renderer.LightTexture;
|
|
||||||
import net.minecraft.client.renderer.Sheets;
|
import net.minecraft.client.renderer.Sheets;
|
||||||
import net.minecraft.util.FastColor;
|
import net.minecraft.util.FastColor;
|
||||||
import net.minecraft.util.FormattedCharSequence;
|
import net.minecraft.util.FormattedCharSequence;
|
||||||
|
@ -123,12 +125,11 @@ public class SignVisual extends AbstractBlockEntityVisual<SignBlockEntity> imple
|
||||||
.setChanged();
|
.setChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME: fullbright
|
|
||||||
for (var text : frontText) {
|
for (var text : frontText) {
|
||||||
text.light = packedLight;
|
text.light(packedLight);
|
||||||
}
|
}
|
||||||
for (var text : backText) {
|
for (var text : backText) {
|
||||||
text.light = packedLight;
|
text.light(packedLight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,54 +159,58 @@ public class SignVisual extends AbstractBlockEntityVisual<SignBlockEntity> imple
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupText(SignText text, TextVisual[] dst, boolean isFrontText) {
|
void setupText(SignText text, TextVisual[] dst, boolean isFrontText) {
|
||||||
var font = Minecraft.getInstance().font;
|
|
||||||
FormattedCharSequence[] formattedCharSequences = text.getRenderMessages(Minecraft.getInstance()
|
FormattedCharSequence[] formattedCharSequences = text.getRenderMessages(Minecraft.getInstance()
|
||||||
.isTextFilteringEnabled(), component -> {
|
.isTextFilteringEnabled(), component -> {
|
||||||
List<FormattedCharSequence> list = font.split(component, blockEntity.getMaxTextLineWidth());
|
List<FormattedCharSequence> list = Minecraft.getInstance().font.split(component, blockEntity.getMaxTextLineWidth());
|
||||||
return list.isEmpty() ? FormattedCharSequence.EMPTY : list.get(0);
|
return list.isEmpty() ? FormattedCharSequence.EMPTY : list.get(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
int light;
|
List<TextLayer> layers = new ArrayList<>();
|
||||||
boolean outline;
|
|
||||||
int darkColor = adjustColor(getDarkColor(text));
|
int darkColor = adjustColor(getDarkColor(text));
|
||||||
int textColor;
|
int textColor;
|
||||||
if (text.hasGlowingText()) {
|
if (text.hasGlowingText()) {
|
||||||
textColor = adjustColor(text.getColor()
|
textColor = adjustColor(text.getColor()
|
||||||
.getTextColor());
|
.getTextColor());
|
||||||
outline = true;
|
|
||||||
light = LightTexture.FULL_BRIGHT;
|
layers.add(new SimpleTextLayer.Builder().style(TextLayer.GlyphMeshStyle.OUTLINE)
|
||||||
|
.material(TextLayer.GlyphMaterial.SIMPLE)
|
||||||
|
.color(TextLayer.GlyphColor.always(darkColor))
|
||||||
|
.build());
|
||||||
} else {
|
} else {
|
||||||
textColor = darkColor;
|
textColor = darkColor;
|
||||||
outline = false;
|
|
||||||
light = LightTexture.FULL_BLOCK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layers.add(new SimpleTextLayer.Builder().style(TextLayer.GlyphMeshStyle.SIMPLE)
|
||||||
|
.material(TextLayer.GlyphMaterial.POLYGON_OFFSET)
|
||||||
|
.color(TextLayer.GlyphColor.defaultTo(textColor))
|
||||||
|
.bias(1)
|
||||||
|
.build());
|
||||||
|
|
||||||
int lineHeight = blockEntity.getTextLineHeight();
|
int lineHeight = blockEntity.getTextLineHeight();
|
||||||
int lineDelta = 4 * lineHeight / 2;
|
int lineDelta = 4 * lineHeight / 2;
|
||||||
for (int m = 0; m < 4; ++m) {
|
for (int m = 0; m < 4; ++m) {
|
||||||
FormattedCharSequence formattedCharSequence = formattedCharSequences[m];
|
FormattedCharSequence formattedCharSequence = formattedCharSequences[m];
|
||||||
float f = (float) -font.width(formattedCharSequence) / 2;
|
float f = (float) -Minecraft.getInstance().font.width(formattedCharSequence) / 2;
|
||||||
|
|
||||||
var textVisual = dst[m];
|
var textVisual = dst[m].content(formattedCharSequence)
|
||||||
textVisual.color = textColor;
|
.layers(layers)
|
||||||
textVisual.dropShadow = false;
|
.fullBright(text.hasGlowingText())
|
||||||
textVisual.with8xOutline = outline;
|
.backgroundColor(0)
|
||||||
textVisual.backgroundColor = darkColor;
|
.x(f)
|
||||||
textVisual.x = f;
|
.y(m * lineHeight - lineDelta);
|
||||||
textVisual.y = m * lineHeight - lineDelta;
|
|
||||||
textVisual.content = formattedCharSequence;
|
|
||||||
// FIXME: separate flag for full bright?
|
|
||||||
textVisual.light = light;
|
|
||||||
|
|
||||||
textVisual.pose.set(pose);
|
var textPose = textVisual.pose();
|
||||||
|
|
||||||
|
textPose.set(pose);
|
||||||
|
|
||||||
if (!isFrontText) {
|
if (!isFrontText) {
|
||||||
textVisual.pose.rotateY(Mth.PI);
|
textPose.rotateY(Mth.PI);
|
||||||
}
|
}
|
||||||
var offset = getTextOffset();
|
var offset = getTextOffset();
|
||||||
float scale = 0.015625f * this.getSignTextRenderScale();
|
float scale = 0.015625f * this.getSignTextRenderScale();
|
||||||
textVisual.pose.translate((float) offset.x, (float) offset.y, (float) offset.z);
|
textPose.translate((float) offset.x, (float) offset.y, (float) offset.z);
|
||||||
textVisual.pose.scale(scale, -scale, scale);
|
textPose.scale(scale, -scale, scale);
|
||||||
|
|
||||||
textVisual.setup();
|
textVisual.setup();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue