Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,31 @@ public class AbstractConnectGlassModel implements UnbakedModel, BakedModel, Fabr
protected static final Mesh[][][] END_MESHES = new Mesh[VARIANT_COUNT][STAINED_GLASS_PATTERN_COUNT][DIRECTION_COUNT];
protected static final Mesh[][][][][] QUARTER_MESHES = new Mesh[VARIANT_COUNT][SLAB_PATTERN_COUNT][DIRECTION_COUNT][QUARTER_COUNT][UV_AXIS_COUNT];

// 生成済みかどうか。メッシュ配列の特定の要素が null かどうかで判定すると、その面を
// そもそも生成しない場合(例:横のハーフの上下の面)に毎回作り直してしまうため、別に持つ。
protected static final boolean[][] SIDE_POSITIVE_BUILT = new boolean[AXIS_COUNT][VARIANT_COUNT];
protected static final boolean[][] SIDE_NEGATIVE_BUILT = new boolean[AXIS_COUNT][VARIANT_COUNT];
protected static final boolean[][] END_POSITIVE_BUILT = new boolean[AXIS_COUNT][VARIANT_COUNT];
protected static final boolean[][] END_NEGATIVE_BUILT = new boolean[AXIS_COUNT][VARIANT_COUNT];
protected static final boolean[] QUARTER_BUILT = new boolean[VARIANT_COUNT];

/**
* 作り置きのメッシュをすべて捨てる。メッシュはテクスチャの貼り付け位置(UV)を焼き込んでいるため、
* リソースの再読み込み(リソースパックの変更など)でテクスチャの配置が変わったら作り直す必要がある。
* モデルの読み込みのたびに呼ぶ。
*/
public static synchronized void clearMeshCaches() {
for (Mesh[][][][] a : new Mesh[][][][][]{SIDE_POSITIVE_MESHES, SIDE_NEGATIVE_MESHES, END_POSITIVE_MESHES, END_NEGATIVE_MESHES}) {
for (Mesh[][][] b : a) for (Mesh[][] c : b) for (Mesh[] d : c) java.util.Arrays.fill(d, null);
}
for (Mesh[][] b : END_MESHES) for (Mesh[] c : b) java.util.Arrays.fill(c, null);
for (Mesh[][][][] b : QUARTER_MESHES) for (Mesh[][][] c : b) for (Mesh[][] d : c) for (Mesh[] e : d) java.util.Arrays.fill(e, null);
for (boolean[][] f : new boolean[][][]{SIDE_POSITIVE_BUILT, SIDE_NEGATIVE_BUILT, END_POSITIVE_BUILT, END_NEGATIVE_BUILT}) {
for (boolean[] g : f) java.util.Arrays.fill(g, false);
}
java.util.Arrays.fill(QUARTER_BUILT, false);
}

protected static final SpriteIdentifier nullSpriteIdentifier = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier.ofVanilla("block/stone"));

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ protected void setUV(QuadEmitter emitter, float u0, float u1, float v0, float v1

@Override
public @Nullable BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer) {
if (this.positiveId != null && SIDE_POSITIVE_MESHES[axis][positiveVariantIndex][0][0] == null) {
if (this.positiveId != null && !SIDE_POSITIVE_BUILT[axis][positiveVariantIndex]) {
SIDE_POSITIVE_BUILT[axis][positiveVariantIndex] = true;
for (int patternIndex = 0; patternIndex < SLAB_PATTERN_COUNT; patternIndex++) {
for (Direction dir : Direction.values()) {
if (isEndFace(dir)) {
Expand All @@ -155,7 +156,8 @@ protected void setUV(QuadEmitter emitter, float u0, float u1, float v0, float v1
}
}

if (this.negativeId != null && SIDE_NEGATIVE_MESHES[axis][negativeVariantIndex][0][0] == null) {
if (this.negativeId != null && !SIDE_NEGATIVE_BUILT[axis][negativeVariantIndex]) {
SIDE_NEGATIVE_BUILT[axis][negativeVariantIndex] = true;
for (int patternIndex = 0; patternIndex < SLAB_PATTERN_COUNT; patternIndex++) {
for (Direction dir : Direction.values()) {
if (isEndFace(dir)) {
Expand All @@ -172,7 +174,8 @@ protected void setUV(QuadEmitter emitter, float u0, float u1, float v0, float v1
}
}

if (this.positiveId != null && END_POSITIVE_MESHES[axis][positiveVariantIndex][0][0] == null) {
if (this.positiveId != null && !END_POSITIVE_BUILT[axis][positiveVariantIndex]) {
END_POSITIVE_BUILT[axis][positiveVariantIndex] = true;
for (int patternIndex = 0; patternIndex < (isGlassPositive ? GLASS_PATTERN_COUNT : STAINED_GLASS_PATTERN_COUNT); patternIndex++) {
for (Direction dir : Direction.values()) {
if (!isEndFace(dir)) {
Expand All @@ -192,7 +195,8 @@ protected void setUV(QuadEmitter emitter, float u0, float u1, float v0, float v1
}
}

if (this.negativeId != null && END_NEGATIVE_MESHES[axis][negativeVariantIndex][0][0] == null) {
if (this.negativeId != null && !END_NEGATIVE_BUILT[axis][negativeVariantIndex]) {
END_NEGATIVE_BUILT[axis][negativeVariantIndex] = true;
for (int patternIndex = 0; patternIndex < (isGlassNegative ? GLASS_PATTERN_COUNT : STAINED_GLASS_PATTERN_COUNT); patternIndex++) {
for (Direction dir : Direction.values()) {
if (!isEndFace(dir)) {
Expand Down Expand Up @@ -229,6 +233,11 @@ protected void setUV(QuadEmitter emitter, float u0, float u1, float v0, float v1

@Override
public void emitBlockQuads(BlockRenderView blockRenderView, BlockState blockState, BlockPos blockPos, Supplier<Random> supplier, RenderContext renderContext) {
// 描くガラスのハーフがない(両側とも普通のハーフ)なら、周りのガラスを調べる必要はない
if (this.positiveId == null && this.negativeId == null) {
return;
}

NeighborState ns = new NeighborState(blockRenderView, blockPos, positiveSlab, negativeSlab, getDoubleSlabType());

if (this.positiveId != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ protected DoubleSlabType getDoubleSlabType() {

@Override
public @Nullable BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer) {
if (this.positiveId != null && QUARTER_MESHES[positiveVariantIndex][0][0][0][0] == null) {
if (this.positiveId != null && !QUARTER_BUILT[positiveVariantIndex]) {
QUARTER_BUILT[positiveVariantIndex] = true;
for (int patternIndex = 0; patternIndex < SLAB_PATTERN_COUNT; patternIndex++) {
for (Direction dir : Direction.values()) {
for (int quarterIndex = 0; quarterIndex < QUARTER_COUNT; quarterIndex++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ record SlabVariantKey(Block positiveSlab, Block negativeSlab, int axis) {}

@Override
public void onInitializeModelLoader(Context context) {
// モデルの読み込み(起動時・リソースの再読み込み時)のたびに、古いテクスチャ配置のメッシュを捨てる
AbstractConnectGlassModel.clearMeshCaches();

// 【描画の試作】焼き上がったダブルハーフのモデルを包み、普通のハーフもチャンクの描画に焼き込む
context.modifyModelAfterBake().register((model, ctx) -> {
ModelIdentifier topId = ctx.topLevelId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,42 @@ static void buildReference(ServerWorld world, int y, List<Block> glassSlabs) {
}
}

static final int PAIR_Z = 75;

/** 素材の組み合わせごとの比較用(上, 下)。葉・光るブロック・ガラスなど、面の省略が分かれやすいもの */
static final String[][] PAIRS = {
{"slabee:oak_leaf_slab", "slabee:glowstone_slab"},
{"slabee:glowstone_slab", "slabee:oak_leaf_slab"},
{"slabee:oak_leaf_slab", "minecraft:stone_slab"},
{"slabee:oak_leaf_slab", "slabee:birch_leaf_slab"},
{"slabee:glass_slab", "slabee:glowstone_slab"},
{"slabee:glass_slab", "slabee:oak_leaf_slab"},
{"slabee:magma_block_slab", "slabee:oak_leaf_slab"},
};

/**
* 組み合わせごとに、左に通常ブロック(下・上の2段)、右に同じ組み合わせのダブルハーフを、
* それぞれ 2x2 の広さで並べる。上から見下ろすと、境目の面が見えるかを比べられる。
*/
static void buildPairs(ServerWorld world, int y) {
for (int k = 0; k < PAIRS.length; k++) {
Block top = Registries.BLOCK.get(net.minecraft.util.Identifier.of(PAIRS[k][0]));
Block bottom = Registries.BLOCK.get(net.minecraft.util.Identifier.of(PAIRS[k][1]));
Block topFull = ModBlockMap.slabToOriginal(top);
Block bottomFull = ModBlockMap.slabToOriginal(bottom);
if (topFull == null) topFull = Blocks.STONE;
if (bottomFull == null) bottomFull = Blocks.STONE;
int x0 = k * 8;
for (int dx = 0; dx < 2; dx++) {
for (int dz = 0; dz < 2; dz++) {
world.setBlockState(new BlockPos(x0 + dx, y, PAIR_Z + dz), bottomFull.getDefaultState(), 3);
world.setBlockState(new BlockPos(x0 + dx, y + 1, PAIR_Z + dz), topFull.getDefaultState(), 3);
placeDouble(world, new BlockPos(x0 + 4 + dx, y, PAIR_Z + dz), top, bottom);
}
}
}
}

/** 重さの測定用:Slabee のダブルハーフを敷き詰めた 100x100 */
static void buildSlabeeField(ServerWorld world, int y) {
for (int x = 0; x < FIELD_SIZE; x++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ private record View(String name, double x, double y, double z, float yaw, float
private int stepTicks = 0;
private int stableTicks = 0;
private boolean teleported = false;
private int lastCompletedChunks = -1;

private boolean recording = false;
private long lastFrameNanos = 0;
Expand Down Expand Up @@ -155,7 +156,9 @@ private static void configureOptions(GameOptions options) {
options.pauseOnLostFocus = false;
options.getViewDistance().setValue(8);
options.getSimulationDistance().setValue(8);
options.getMaxFps().setValue(260);
// 環境変数 SLABEE_SCENE_MAX_FPS=60(または -Dslabee.scene.maxFps=60)で、フレーム数の上限を固定して測れる
// (描画が速くなるとフレーム数が増えてCPUを多く使い、同じマシンのサーバー側の測定に影響するため)
options.getMaxFps().setValue(maxFps());
options.getEnableVsync().setValue(false);
options.getCloudRenderMode().setValue(CloudRenderMode.OFF);
options.getBobView().setValue(false);
Expand All @@ -165,6 +168,17 @@ private static void configureOptions(GameOptions options) {
options.getAo().setValue(true);
}

private static int maxFps() {
String env = System.getenv("SLABEE_SCENE_MAX_FPS");
if (env != null) {
try {
return Integer.parseInt(env.trim());
} catch (NumberFormatException ignored) {
}
}
return Integer.getInteger("slabee.scene.maxFps", 260);
}

private static void createWorld(MinecraftClient client) {
GameRules rules = new GameRules();
rules.get(GameRules.DO_DAYLIGHT_CYCLE).set(false, null);
Expand Down Expand Up @@ -197,6 +211,7 @@ private void buildScene(IntegratedServer server) {
SceneBuilder.buildCatalog(world, baseY, slabs);
SceneBuilder.buildAdjacent(world, baseY, glass);
SceneBuilder.buildReference(world, baseY, glass);
SceneBuilder.buildPairs(world, baseY);
SceneBuilder.buildSlabeeField(world, baseY);
SceneBuilder.buildVanillaField(world, baseY);
LOGGER.info("Built scene with {} slab types ({} glass) at y={} in {} ms",
Expand Down Expand Up @@ -234,6 +249,12 @@ private void planViews(List<Block> slabs, List<Block> glass) {
shots.add(new View(String.format("reference_%02d", k), centerX, baseY + 3.5, SceneBuilder.REFERENCE_Z - 6, 0f, 18f, note));
}

for (int k = 0; k < SceneBuilder.PAIRS.length; k++) {
double centerX = k * 8 + 3.0;
String note = "上=" + SceneBuilder.PAIRS[k][0] + "、下=" + SceneBuilder.PAIRS[k][1] + ":左=通常ブロック(2段)、右=ダブルハーフ";
shots.add(new View(String.format("pair_%02d", k), centerX, baseY + 4.5, SceneBuilder.PAIR_Z - 3, 0f, 40f, note));
}

double fieldCenterX = SceneBuilder.FIELD_SIZE / 2.0;
perfStages.add(new View("empty", fieldCenterX, baseY + 25, SceneBuilder.EMPTY_Z - 15, 0f, 40f, "何も置いていない場所"));
perfStages.add(new View("slabee_field", fieldCenterX, baseY + 25, SceneBuilder.SLABEE_FIELD_Z - 15, 0f, 40f,
Expand Down Expand Up @@ -266,16 +287,23 @@ private boolean moveAndSettle(MinecraftClient client, View view, int minTicks) {
}
boolean arrived = client.player != null
&& client.player.squaredDistanceTo(view.x(), view.y(), view.z()) < 0.25;
if (arrived && client.worldRenderer.isTerrainRenderComplete()) {
// 描画範囲のチャンクが読み込み・組み立てを終えて、完成数が増えなくなるまで待つ
// (実機では遠くの地形が読み込み途中のまま測ってしまうことがあったため)
int completed = client.worldRenderer.getCompletedChunkCount();
// 光の計算も終わるまで待つ(ブロックを置いたあと遅れて進むため、終わる前に撮ると地面の陰影が毎回ずれる)
boolean lightQuiet = client.world != null && !client.world.getLightingProvider().hasUpdates()
&& (client.getServer() == null || !client.getServer().getOverworld().getLightingProvider().hasUpdates());
if (arrived && lightQuiet && client.worldRenderer.isTerrainRenderComplete() && completed == lastCompletedChunks) {
stableTicks++;
} else {
stableTicks = 0;
}
lastCompletedChunks = completed;
if (stepTicks > 20 * 60) {
LOGGER.warn("View {} did not settle in time; continuing anyway", view.name());
return true;
}
return stepTicks >= minTicks && stableTicks >= 20;
return stepTicks >= minTicks && stableTicks >= 40;
}

private void nextStep() {
Expand Down Expand Up @@ -326,6 +354,7 @@ private void stepPerf(MinecraftClient client) {
long[] sorted = frameNanos.stream().mapToLong(Long::longValue).sorted().toArray();
double avg = Arrays.stream(sorted).average().orElse(0) / 1e6;
result.addProperty("frames", sorted.length);
result.addProperty("completedChunks", client.worldRenderer.getCompletedChunkCount());
result.addProperty("frameMsAvg", round(avg));
result.addProperty("frameMsP50", round(percentile(sorted, 0.50) / 1e6));
result.addProperty("frameMsP95", round(percentile(sorted, 0.95) / 1e6));
Expand Down Expand Up @@ -360,6 +389,7 @@ private void writeResults(MinecraftClient client) {
root.addProperty("glVersion", GlDebugInfo.getVersion());
root.addProperty("window", client.getWindow().getFramebufferWidth() + "x" + client.getWindow().getFramebufferHeight());
root.addProperty("baseY", baseY);
root.addProperty("maxFps", client.options.getMaxFps().getValue());
root.add("shots", shotResults);
root.add("perf", perfResults);
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.forestotzka.yurufu.slabee.gametest;

import com.forestotzka.yurufu.slabee.block.AbstractDoubleSlabBlockEntity;
import com.forestotzka.yurufu.slabee.block.ModBlockMap;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.registry.Registries;
import net.minecraft.test.GameTest;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;

import java.util.ArrayList;
import java.util.List;

import static com.forestotzka.yurufu.slabee.gametest.SlabeeTestUtil.placeDouble;

/**
* ダブルハーフの内側の面(上下のハーフが接している面)を描くかどうかが、
* 「同じ並びを通常ブロックで作ったとき」と同じになることを確かめる(MODの基本方針)。
* 正解は、その場で通常ブロックを実際に置いて Block#shouldDrawSide で求める。
*/
public class InnerFaceGameTests implements FabricGameTest {
private static final Block[] FULL_BLOCKS = {
Blocks.STONE, Blocks.OAK_PLANKS, Blocks.GLOWSTONE, Blocks.OAK_LEAVES, Blocks.BIRCH_LEAVES,
Blocks.GLASS, Blocks.RED_STAINED_GLASS, Blocks.BLUE_STAINED_GLASS, Blocks.TINTED_GLASS, Blocks.MAGMA_BLOCK,
};

@GameTest(templateName = EMPTY_STRUCTURE)
public void innerFacesMatchFullBlocks(TestContext context) {
BlockPos full = new BlockPos(1, 1, 1);
BlockPos dbl = new BlockPos(3, 1, 1);
BlockPos fullAbs = context.getAbsolutePos(full);
BlockPos dblAbs = context.getAbsolutePos(dbl);
List<String> mismatches = new ArrayList<>();
int checked = 0;

for (Block bottomFull : FULL_BLOCKS) {
for (Block topFull : FULL_BLOCKS) {
Block bottomSlab = ModBlockMap.originalToSlab(bottomFull);
Block topSlab = ModBlockMap.originalToSlab(topFull);
if (bottomSlab == null || topSlab == null) continue;

// 正解:通常ブロックを上下に置いたときの、接している面
context.setBlockState(full, bottomFull);
context.setBlockState(full.up(), topFull);
boolean expectBottomUp = Block.shouldDrawSide(bottomFull.getDefaultState(), context.getWorld(), fullAbs, Direction.UP, fullAbs.up());
boolean expectTopDown = Block.shouldDrawSide(topFull.getDefaultState(), context.getWorld(), fullAbs.up(), Direction.DOWN, fullAbs);

// ダブルハーフ
placeDouble(context, dbl, topSlab, bottomSlab);
if (!(context.getWorld().getBlockEntity(dblAbs) instanceof AbstractDoubleSlabBlockEntity entity)) {
mismatches.add("ダブルハーフを置けない: " + id(topSlab) + " / " + id(bottomSlab));
continue;
}
BlockState bottomHalf = entity.getNegativeSlabState();
BlockState topHalf = entity.getPositiveSlabState();
boolean actualBottomUp = Block.shouldDrawSide(bottomHalf, context.getWorld(), dblAbs, Direction.UP, dblAbs.up());
boolean actualTopDown = Block.shouldDrawSide(topHalf, context.getWorld(), dblAbs, Direction.DOWN, dblAbs.down());

checked++;
if (actualBottomUp != expectBottomUp) {
mismatches.add("上=" + id(topFull) + " 下=" + id(bottomFull) + ":下の上面 通常=" + expectBottomUp + " ダブル=" + actualBottomUp);
}
if (actualTopDown != expectTopDown) {
mismatches.add("上=" + id(topFull) + " 下=" + id(bottomFull) + ":上の下面 通常=" + expectTopDown + " ダブル=" + actualTopDown);
}
}
}

context.assertTrue(checked > 50, "比べられた組み合わせが少なすぎる: " + checked);
context.assertTrue(mismatches.isEmpty(),
"通常ブロックと違う面 " + mismatches.size() + " 件(" + checked + " 組中):\n" + String.join("\n", mismatches));
context.complete();
}

private static String id(Block block) {
return Registries.BLOCK.getId(block).getPath();
}
}
3 changes: 2 additions & 1 deletion src/gametest/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"com.forestotzka.yurufu.slabee.gametest.StackingGameTests",
"com.forestotzka.yurufu.slabee.gametest.HalfBreakGameTests",
"com.forestotzka.yurufu.slabee.gametest.MixinGameTests",
"com.forestotzka.yurufu.slabee.gametest.LightingGameTests"
"com.forestotzka.yurufu.slabee.gametest.LightingGameTests",
"com.forestotzka.yurufu.slabee.gametest.InnerFaceGameTests"
]
},
"depends": {
Expand Down
Loading
Loading