@@ -15,23 +15,25 @@
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* Renders the terminal texture on the front face of the terminal block.
*
* Performance: the texture is uploaded ONCE per controller per frame
* (not once per block). A frame-local set tracks which controllers
* have already been uploaded this frame to avoid redundant work.
* Supports multi-block screens: extension blocks render their sub-region
* of the controller's texture.
*/
public class TerminalBlockRenderer implements BlockEntityRenderer<TerminalBlockEntity> {
private final Map<Long, TerminalTexture> textures = new HashMap<>();
// Matrix rain state for idle screens
private static final int RAIN_W = 48, RAIN_H = 32;
private static final Random RAIN_RNG = new Random();
private final Map<Long, int[]> rainColumns = new HashMap<>(); // per-block column positions
// Track which textures have been uploaded THIS frame to avoid re-uploading
// for every block in a multi-block group. Reset each frame via frame counter.
private long lastFrameTime = 0;
private final Set<Long> uploadedThisFrame = new HashSet<>();
private long lastRainTick = 0;
public TerminalBlockRenderer(BlockEntityRendererProvider.Context context) {
}
@@ -41,8 +43,5 @@
MultiBufferSource bufferSource, int packedLight, int packedOverlay) {
Direction facing = entity.getBlockState().getValue(TerminalBlock.FACING);
long posKey = entity.getBlockPos().asLong();
// Determine the actual data source (controller or self)
TerminalBlockEntity dataSource = entity.getController();
if (dataSource == null) dataSource = entity;
@@ -52,15 +51,16 @@
Matrix4f mat = poseStack.last().pose();
int light = LightTexture.FULL_BRIGHT;
float x0 = 0f, x1 = 1f, y0 = 0f, y1 = 1f;
// No margin — terminal fills the entire block face
float z = 0.001f;
ScreenGroup group = entity.getScreenGroup();
boolean inGroup = group != null && group.getMembers().size() > 1;
float x0 = 0f, x1 = 1f, y0 = 0f, y1 = 1f;
float z = 0.001f;
if (dataSource.isTerminalRunning() && dataSource.getPixelWidth() > 0 && dataSource.getPixelHeight() > 0) {
// Use the controller's position as the texture key (shared across group)
long texKey = dataSource.getBlockPos().asLong();
// Get or create shared texture for this controller
TerminalTexture termTex = textures.get(texKey);
if (termTex == null || termTex.getWidth() != dataSource.getPixelWidth() ||
termTex.getHeight() != dataSource.getPixelHeight()) {
@@ -69,68 +69,54 @@
textures.put(texKey, termTex);
}
// Upload ONCE per controller per frame — the first block in the
// group that renders triggers the upload, subsequent blocks skip it.
// This matters for multi-block: N blocks share 1 texture.
long frameTime = System.nanoTime() / 1_000_000;
if (frameTime != lastFrameTime) {
uploadedThisFrame.clear();
lastFrameTime = frameTime;
}
if (!uploadedThisFrame.contains(texKey)) {
if (dataSource.getPixelBuffer() != null) {
termTex.upload(dataSource.getPixelBuffer(), dataSource.getPixelWidth(), dataSource.getPixelHeight());
dataSource.clearTextureUpdateFlag();
ByteBuffer buf = dataSource.getPixelBuffer();
if (buf != null) {
termTex.upload(buf, dataSource.getPixelWidth(), dataSource.getPixelHeight());
dataSource.clearTextureUpdateFlag();
}
uploadedThisFrame.add(texKey);
}
// Calculate UV sub-region for multi-block
// ALL blocks in a group use sub-region UVs (controller AND extensions)
// UV sub-region for multi-block
float u0 = 0f, v0 = 0f, u1 = 1f, v1 = 1f;
if (inGroup) {
float[] uv = group.getSubRegion(entity.getBlockPos());
u0 = uv[0]; v0 = uv[1]; u1 = uv[2]; v1 = uv[3];
}
// Vertex UV mapping: x0 (world left = viewer right) gets u1,
// x1 (world right = viewer left) gets u0. This is the same for both
// single and multi-block because getSubRegion returns UVs already
// oriented for the viewing direction (blockCol accounts for facing).
VertexConsumer vc = bufferSource.getBuffer(termTex.getRenderType());
vc.vertex(mat, x0, y1, z).color(255, 255, 255, 255).uv(u1, v0).uv2(light).endVertex();
vc.vertex(mat, x1, y1, z).color(255, 255, 255, 255).uv(u0, v0).uv2(light).endVertex();
vc.vertex(mat, x1, y0, z).color(255, 255, 255, 255).uv(u0, v1).uv2(light).endVertex();
vc.vertex(mat, x0, y0, z).color(255, 255, 255, 255).uv(u1, v1).uv2(light).endVertex();
} else {
// Terminal is off — render Matrix-style falling code rain
// Terminal off — Matrix rain
long posKey = entity.getBlockPos().asLong();
TerminalTexture rainTex = textures.computeIfAbsent(posKey,
k -> new TerminalTexture(RAIN_W, RAIN_H));
int[] cols = rainColumns.computeIfAbsent(posKey, k -> {
int[] c = new int[RAIN_W];
for (int i = 0; i < RAIN_W; i++) c[i] = RAIN_RNG.nextInt(RAIN_H);
return c;
TerminalTexture rainTex = textures.computeIfAbsent(posKey, k -> {
TerminalTexture t = new TerminalTexture(48, 32);
return t;
});
// Animate rain at ~10fps (every 100ms)
// Animate: advance columns every ~100ms (2 ticks)
long tick = System.currentTimeMillis() / 100;
if (tick % 2 == 0) { // Only update every other tick to save CPU
ByteBuffer buf = ByteBuffer.allocateDirect(48 * 32 * 4);
if (tick != lastRainTick) {
lastRainTick = tick;
java.util.Random rng = new java.util.Random(entity.getBlockPos().asLong() + tick);
for (int i = 0; i < 48 * 32; i++) {
int g = rng.nextInt(40);
buf.put((byte) 255).put((byte) 0).put((byte) g).put((byte) 0);
ByteBuffer buf = ByteBuffer.allocateDirect(RAIN_W * RAIN_H * 4);
for (int ry = 0; ry < RAIN_H; ry++) {
for (int rx = 0; rx < RAIN_W; rx++) {
int colHead = cols[rx];
int dist = (colHead - ry + RAIN_H) % RAIN_H;
int g; // green intensity
if (dist == 0) {
g = 255; // bright head
} else if (dist < 8) {
g = 180 - dist * 20; // fading trail
} else {
g = RAIN_RNG.nextInt(10); // dim random flicker
}
// ABGR format
buf.put((byte) 255); // A
buf.put((byte) 0); // B
buf.put((byte) g); // G
buf.put((byte) 0); // R
}
}
buf.flip();
rainTex.upload(buf, RAIN_W, RAIN_H);
// Advance columns
for (int i = 0; i < RAIN_W; i++) {
cols[i] = (cols[i] + (RAIN_RNG.nextInt(3) == 0 ? 1 : 0)) % RAIN_H;
}
rainTex.upload(buf, 48, 32);
}
VertexConsumer vc = bufferSource.getBuffer(rainTex.getRenderType());