Improve structure rendering performance and change lighting

This commit is contained in:
Sollace 2024-02-05 00:34:49 +00:00
parent 3b0a64326e
commit e48b7bd244
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
4 changed files with 131 additions and 141 deletions

View file

@ -0,0 +1,45 @@
package com.minelittlepony.unicopia.block.state;
import java.util.Arrays;
import java.util.function.Supplier;
class ExpandableList<T> {
private final Supplier<T> defaultValue;
private Object[] values;
private int size;
public ExpandableList(int initialCapacity, Supplier<T> defaultValue) {
this.defaultValue = defaultValue;
values = new Object[Math.max(0, initialCapacity)];
}
public int size() {
return size;
}
@SuppressWarnings("unchecked")
public T get(int index) {
T t = (T)values[index];
if (t == null) {
values[index] = t = defaultValue.get();
}
return t;
}
public T getOrExpand(int index) {
resize(index);
return get(index);
}
public void set(int index, T value) {
resize(index);
values[index] = value;
}
private void resize(int index) {
if (index >= values.length) {
values = Arrays.copyOf(values, Math.max(index + 1, (values.length + 1) * 2));
}
size = Math.max(index + 1, size);
}
}

View file

@ -0,0 +1,72 @@
package com.minelittlepony.unicopia.block.state;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.network.PacketByteBuf;
public record Schematic(int dx, int dy, int dz, Entry[] states) {
public static Schematic fromPacket(PacketByteBuf buffer) {
Builder builder = new Builder();
buffer.readCollection(ArrayList::new, buf -> {
byte op = buf.readByte();
return switch (op) {
case 1 -> builder.set(buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt()));
case 2 -> builder.fill(buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt()));
default -> builder;
};
});
return builder.build();
}
public int volume() {
return states.length;
}
public static final class Builder {
private static final BlockState AIR = Blocks.AIR.getDefaultState();
private int dx = -1;
private int dy = -1;
private int dz = -1;
private final ExpandableList<ExpandableList<ExpandableList<BlockState>>> layers = new ExpandableList<>(0, () -> new ExpandableList<>(dz, () -> new ExpandableList<>(dx, () -> AIR)));
public Builder set(int x, int y, int z, BlockState state) {
dx = Math.max(dx, x);
dy = Math.max(dy, y);
dz = Math.max(dz, z);
layers.getOrExpand(y).getOrExpand(z).set(x, state);
return this;
}
public Builder fill(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, BlockState state) {
dx = Math.max(dx, maxX);
dy = Math.max(dy, maxY);
dz = Math.max(dz, maxZ);
for (int x = minX; x <= maxX; x++)
for (int y = minY; y <= maxY; y++)
for (int z = minZ; z <= maxZ; z++)
set(x, y, z, state);
return this;
}
public Schematic build() {
List<Entry> states = new LinkedList<>();
for (int y = 0; y <= dy && y < layers.size(); y++)
for (int z = 0; z <= dz && z < layers.get(y).size(); z++)
for (int x = 0; x <= dx && x < layers.get(y).get(z).size(); x++) {
BlockState state = layers.get(y).get(z).get(x);
if (!state.isAir()) {
states.add(new Entry(x, y, z, state));
}
}
return new Schematic(dx, dy, dz, states.toArray(Entry[]::new));
}
}
public record Entry(int x, int y, int z, BlockState state) {}
}

View file

@ -10,6 +10,7 @@ import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.IngredientWithSpell; import com.minelittlepony.unicopia.ability.magic.spell.crafting.IngredientWithSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.block.state.Schematic;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Drawable; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.Drawable;
import com.minelittlepony.unicopia.container.SpellbookChapterLoader.Flow; import com.minelittlepony.unicopia.container.SpellbookChapterLoader.Flow;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
@ -59,7 +60,7 @@ public interface PageElement extends Drawable {
default -> throw new IllegalArgumentException("Unexpected value: " + t); default -> throw new IllegalArgumentException("Unexpected value: " + t);
}; };
})); }));
case 5 -> new Structure.Builder().fromBuffer(buffer).build(); case 5 -> new Structure(Bounds.empty(), Schematic.fromPacket(buffer));
default -> throw new IllegalArgumentException("Unexpected value: " + type); default -> throw new IllegalArgumentException("Unexpected value: " + type);
}; };
} }

View file

@ -1,15 +1,8 @@
package com.minelittlepony.unicopia.client.gui.spellbook.element; package com.minelittlepony.unicopia.client.gui.spellbook.element;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.dimension.Bounds; import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.block.state.Schematic;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.DiffuseLighting; import net.minecraft.client.render.DiffuseLighting;
@ -17,48 +10,13 @@ import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider.Immediate; import net.minecraft.client.render.VertexConsumerProvider.Immediate;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.state.property.Properties;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.RotationAxis;
public record Structure(Bounds bounds, List<List<List<BlockState>>> states) implements PageElement { public record Structure(Bounds bounds, Schematic schematic) implements PageElement {
static final BlockState DIAMOND = Blocks.DIAMOND_BLOCK.getDefaultState();
static final BlockState AIR = Blocks.AIR.getDefaultState();
static final BlockState OBS = Blocks.OBSIDIAN.getDefaultState();
static final BlockState SOU = Blocks.SOUL_SAND.getDefaultState();
public static final Structure CRYSTAL_HEART_ALTAR = new Structure.Builder()
.fill(0, 0, 0, 2, 0, 2, DIAMOND)
.set(1, 1, 1, DIAMOND)
.set(1, 2, 1, Blocks.END_ROD.getDefaultState().with(Properties.FACING, Direction.UP))
.set(1, 4, 1, Blocks.END_ROD.getDefaultState().with(Properties.FACING, Direction.DOWN))
.set(1, 5, 1, DIAMOND)
.fill(0, 6, 0, 2, 6, 2, DIAMOND)
.build();
public static final Structure ALTAR_STRUCTURE = new Structure.Builder()
.fill(0, 0, 0, 8, 0, 8, SOU)
.fill(3, 1, 3, 5, 1, 5, OBS)
.set(4, 1, 4, SOU)
.set(4, 1, 6, Blocks.LODESTONE.getDefaultState())
.fill(0, 1, 2, 0, 4, 2, OBS).fill(0, 1, 6, 0, 4, 6, OBS)
.fill(2, 1, 0, 2, 4, 0, OBS).fill(6, 1, 0, 6, 4, 0, OBS)
.fill(8, 1, 2, 8, 4, 2, OBS).fill(8, 1, 6, 8, 4, 6, OBS)
.fill(2, 1, 8, 2, 4, 8, OBS).fill(6, 1, 8, 6, 4, 8, OBS)
.build();
@Override @Override
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
int height = states.size(); if (schematic.volume() == 0) {
if (height == 0) {
return;
}
int depth = states.get(0).size();
if (depth == 0) {
return;
}
int width = states.get(0).get(0).size();
if (width == 0) {
return; return;
} }
MatrixStack matrices = context.getMatrices(); MatrixStack matrices = context.getMatrices();
@ -72,110 +30,24 @@ public record Structure(Bounds bounds, List<List<List<BlockState>>> states) impl
if (container != null) { if (container != null) {
matrices.translate(container.getBounds().width / 2, container.getBounds().height / 2, 100); matrices.translate(container.getBounds().width / 2, container.getBounds().height / 2, 100);
float minDimensions = Math.min(container.getBounds().width, container.getBounds().height) - 30; float minDimensions = Math.min(container.getBounds().width, container.getBounds().height) - 30;
int minSize = Math.max(height, Math.max(width, depth)) * 16; int minSize = (Math.max(schematic.dx(), Math.max(schematic.dy(), schematic.dz())) + 1) * 16;
float scale = minDimensions / minSize; float scale = minDimensions / minSize;
matrices.scale(scale, scale, 1); matrices.scale(scale, scale, 1);
} }
matrices.scale(-16, -16, -16); matrices.scale(16, -16, 16);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-20 + MathHelper.sin(tickDelta / 10F) * 2)); matrices.peek().getNormalMatrix().scale(1, -1, 1);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(20 + MathHelper.sin(tickDelta / 10F) * 2));
matrices.peek().getPositionMatrix().rotate(RotationAxis.POSITIVE_Y.rotationDegrees(age)); matrices.peek().getPositionMatrix().rotate(RotationAxis.POSITIVE_Y.rotationDegrees(age));
DiffuseLighting.enableForLevel(matrices.peek().getPositionMatrix()); matrices.translate((-schematic.dx() - 1) / 2F, (-schematic.dy() - 1) / 2F, (-schematic.dz() - 1) / 2F);
DiffuseLighting.disableGuiDepthLighting();
matrices.translate(-width / 2F, -height / 2F, -depth / 2F); for (var entry : schematic.states()) {
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
for (int z = 0; z < depth; z++) {
BlockState state = states.get(y).get(z).get(x);
matrices.push(); matrices.push();
matrices.translate(x, y, z); matrices.translate(entry.x(), entry.y(), entry.z());
client.getBlockRenderManager().renderBlockAsEntity(state, matrices, immediate, LightmapTextureManager.MAX_LIGHT_COORDINATE, OverlayTexture.DEFAULT_UV); client.getBlockRenderManager().renderBlockAsEntity(entry.state(), matrices, immediate, LightmapTextureManager.MAX_LIGHT_COORDINATE, OverlayTexture.DEFAULT_UV);
matrices.pop(); matrices.pop();
} }
matrices.pop(); matrices.pop();
} }
public static class Builder {
private int dx = -1;
private int dy = -1;
private int dz = -1;
private final List<List<List<BlockState>>> layers = new ArrayList<>();
private void resize(int x, int y, int z) {
int ddx = Math.max(dx, x);
int ddy = Math.max(dy, y);
int ddz = Math.max(dz, z);
if (ddx <= dx && ddy <= dy && ddz <= dz) {
return;
}
if (ddy > dy) {
dy = ddy;
while (layers.size() <= ddy) {
layers.add(createFilledList(ddz, () -> createFilledList(ddx, () -> AIR)));
}
}
if (ddz > dz || ddx > dx) {
layers.forEach(layer -> {
if (ddz > dz) {
while (layer.size() <= ddz) {
layer.add(createFilledList(ddx, () -> AIR));
}
}
if (ddx > dx) {
layer.forEach(row -> {
while (row.size() <= ddx) {
row.add(AIR);
}
});
}
});
dz = ddz;
dx = ddx;
}
}
private <T> List<T> createFilledList(int length, Supplier<T> contentSupplier) {
List<T> list = new ArrayList<>();
while (list.size() <= length) {
list.add(contentSupplier.get());
}
return list;
}
public Builder set(int x, int y, int z, BlockState state) {
resize(x, y, z);
layers.get(y).get(z).set(x, state);
return this;
}
public Builder fill(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, BlockState state) {
resize(maxX, maxY, maxZ);
for (int x = minX; x <= maxX; x++)
for (int y = minY; y <= maxY; y++)
for (int z = minZ; z <= maxZ; z++)
layers.get(y).get(z).set(x, state);
return this;
}
public Builder fromBuffer(PacketByteBuf buffer) {
buffer.readCollection(ArrayList::new, buf -> {
byte op = buf.readByte();
return switch (op) {
case 1 -> set(buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt()));
case 2 -> fill(buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt()));
default -> this;
};
});
return this;
}
public Structure build() {
return new Structure(Bounds.empty(), layers);
}
}
} }