v1.5 new shapes + self culling + caching + coding tools/cleanup #10

Merged
Adrien1106 merged 11 commits from dev into master 2024-03-14 22:30:30 +01:00
15 changed files with 188 additions and 33 deletions
Showing only changes of commit e6c9ee63cd - Show all commits

View File

@ -76,4 +76,15 @@ public record QuadPosBounds(float min_x, float max_x, float min_y, float max_y,
quad.pos(i, pos);
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof QuadPosBounds other)) return false;
return MathHelper.approximatelyEquals(min_x, other.min_x)
&& MathHelper.approximatelyEquals(min_y, other.min_y)
&& MathHelper.approximatelyEquals(min_z, other.min_z)
&& MathHelper.approximatelyEquals(max_x, other.max_x)
&& MathHelper.approximatelyEquals(max_y, other.max_y)
&& MathHelper.approximatelyEquals(max_z, other.max_z);
}
}

View File

@ -9,10 +9,7 @@ import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager;
import fr.adrien1106.reframed.client.model.apperance.WeightedComputedAppearance;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.mesh.*;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState;
@ -27,6 +24,7 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public abstract class RetexturingBakedModel extends ForwardingBakedModel {
@ -167,12 +165,15 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder();
QuadEmitter emitter = builder.getEmitter();
AtomicInteger quad_index = new AtomicInteger();
getBaseMesh(key.state_key, state).forEach(quad -> {
int i = -1;
do {
emitter.copyFrom(quad);
i = key.appearance.transformQuad(emitter, i, key.model_id, ao, uv_lock);
i = key.appearance.transformQuad(emitter, i, quad_index.get(), key.model_id, ao, uv_lock);
} while (i > 0);
// kinda weird to do it like that but other directions don't use the quad_index so it doesn't matter
if (quad.cullFace() == null) quad_index.getAndIncrement();
});
return builder.build();
@ -191,9 +192,10 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
@Override
public boolean transform(MutableQuadView quad) {
if(quad.tag() == 0) return true;
int camo_quad_index = quad.tag() - ((quad.tag() >>> 8) << 8);
if(camo_quad_index == 0) return true;
if(appearance.hasColor(quad.nominalFace(), model_id, quad.tag())) quad.color(tint, tint, tint, tint);
if(appearance.hasColor(quad.nominalFace(), model_id, camo_quad_index)) quad.color(tint, tint, tint, tint);
return true;
}

View File

@ -28,7 +28,7 @@ public abstract class CamoAppearance {
return ao && ao_material != null? ao_material : material;
}
public int transformQuad(QuadEmitter quad, int i, int model_id, boolean ao, boolean uv_lock) {
public int transformQuad(QuadEmitter quad, int i, int quad_index, int model_id, boolean ao, boolean uv_lock) {
if(quad.tag() == 0) return 0; // Pass the quad through unmodified.
Direction direction = quad.nominalFace();
@ -36,6 +36,7 @@ public abstract class CamoAppearance {
if (i == -1) i = sprites.size();
SpriteProperties properties = sprites.get(sprites.size() - i);
int tag = i + (quad_index << 8);
i--;
QuadPosBounds bounds = properties.bounds();
@ -47,7 +48,7 @@ public abstract class CamoAppearance {
| properties.flags()
| (uv_lock ? MutableQuadView.BAKE_LOCK_UV : 0)
);
quad.tag(i+1);
quad.tag(tag);
quad.emit();
return i;
}
@ -64,7 +65,7 @@ public abstract class CamoAppearance {
MutableQuadView.BAKE_NORMALIZED
| MutableQuadView.BAKE_LOCK_UV
);
quad.tag(i+1);
quad.tag(tag);
quad.emit();
return i;
}

View File

@ -11,9 +11,7 @@ import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtHelper;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

View File

@ -21,9 +21,11 @@ public class CompatMixinPlugin implements IMixinConfigPlugin {
"fr.adrien1106.reframed.mixin.compat.AthenaBakedModelMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)),
"fr.adrien1106.reframed.mixin.compat.AthenaWrappedGetterMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(0)),
"fr.adrien1106.reframed.mixin.render.TerrainRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumTerrainRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.render.BlockRenderInfoMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.render.AbstractBlockRenderContextMixin", () -> !LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumTerrainRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumTerrainBlockRenderInfoMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.IndiumAbstractBlockRenderContextMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(1)),
"fr.adrien1106.reframed.mixin.compat.SodiumBlockOcclusionCacheMixin", () -> LOADER.isModLoaded(COMPAT_MOD.get(2))
);

View File

@ -0,0 +1,29 @@
package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import link.infra.indium.renderer.mesh.MutableQuadViewImpl;
import link.infra.indium.renderer.render.AbstractBlockRenderContext;
import link.infra.indium.renderer.render.BlockRenderInfo;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(AbstractBlockRenderContext.class)
public abstract class IndiumAbstractBlockRenderContextMixin {
@Shadow(remap = false) protected BlockRenderInfo blockInfo;
@Shadow public abstract boolean isFaceCulled(@Nullable Direction face);
@Redirect(method = "renderQuad", at = @At(value = "INVOKE", target = "Llink/infra/indium/renderer/render/AbstractBlockRenderContext;isFaceCulled(Lnet/minecraft/util/math/Direction;)Z"))
private boolean shouldDrawInnerQuad(AbstractBlockRenderContext instance, Direction face, @Local(argsOnly = true) MutableQuadViewImpl quad) {
if (face != null || quad.tag() == 0 || !(blockInfo instanceof IBlockRenderInfoMixin info) || info.getThemeIndex() == 0) return isFaceCulled(face);
return !BlockHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
}
}

View File

@ -18,7 +18,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TerrainBlockRenderInfo.class)
public abstract class IndiumTerrainBlockRenderInfoMixin extends BlockRenderInfo implements IBlockRenderInfoMixin {
@Unique private int theme_index = 1;
@Unique private int theme_index = 0;
@Redirect(
method = "shouldDrawFaceInner",
@ -40,4 +40,9 @@ public abstract class IndiumTerrainBlockRenderInfoMixin extends BlockRenderInfo
this.theme_index = theme_index;
prepareForBlock(blockState, blockPos, seed, modelAo);
}
@Override
public int getThemeIndex() {
return theme_index;
}
}

View File

@ -1,6 +1,7 @@
package fr.adrien1106.reframed.mixin.compat;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import link.infra.indium.renderer.render.AbstractBlockRenderContext;
@ -31,6 +32,7 @@ public abstract class IndiumTerrainRenderContextMixin extends AbstractBlockRende
|| !(wrapped.getModel(ctx.state()) instanceof MultiRetexturableModel retexturing_model)) return;
List<ForwardingBakedModel> models = retexturing_model.models();
BlockHelper.computeInnerCull(ctx.state(), models);
int i = 0;
for (BakedModel model : models) {
i++;

View File

@ -0,0 +1,36 @@
package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractBlockRenderContext;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(AbstractBlockRenderContext.class)
public abstract class AbstractBlockRenderContextMixin {
@Shadow public abstract boolean isFaceCulled(@Nullable Direction face);
@Shadow(remap = false) @Final protected BlockRenderInfo blockInfo;
@Redirect(
method = "renderQuad",
at = @At(
value = "INVOKE",
target = "Lnet/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractBlockRenderContext;isFaceCulled(Lnet/minecraft/util/math/Direction;)Z"
)
)
private boolean shouldDrawInnerQuad(AbstractBlockRenderContext instance, Direction face, @Local(argsOnly = true) MutableQuadViewImpl quad) {
if (face != null || quad.tag() == 0 || !(blockInfo instanceof IBlockRenderInfoMixin info) || info.getThemeIndex() == 0) return isFaceCulled(face);
return !BlockHelper.shouldDrawInnerFace(blockInfo.blockState, blockInfo.blockView, blockInfo.blockPos, quad.tag() >>> 8, info.getThemeIndex());
}
}

View File

@ -24,8 +24,7 @@ public abstract class BlockRenderInfoMixin implements IBlockRenderInfoMixin {
@Shadow public abstract void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAo);
@Shadow public BlockRenderView blockView;
@Unique
private int theme_index = 1;
@Unique private int theme_index = 0;
@ModifyArg(method = "prepareForBlock",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayers;" +
@ -47,4 +46,9 @@ public abstract class BlockRenderInfoMixin implements IBlockRenderInfoMixin {
this.theme_index = theme_index;
prepareForBlock(state, pos, ao);
}
@Override
public int getThemeIndex() {
return theme_index;
}
}

View File

@ -1,6 +1,7 @@
package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
@ -30,10 +31,7 @@ public abstract class TerrainRenderContextMixin extends AbstractBlockRenderConte
|| !(wrapped.getModel(state) instanceof MultiRetexturableModel retexturing_model)) return;
List<ForwardingBakedModel> models = retexturing_model.models();
// models.forEach(model -> // TODO self culling here
// model.getWrappedModel().getQuads(state_key, null, blockInfo.randomSupplier.get())
// .forEach(quad -> QuadPosBounds.read(getEmitter().fromVanilla(quad.getVertexData(), 0)).min_x())
// );
BlockHelper.computeInnerCull(state, models);
int i = 0;
for (BakedModel model : models) {
i++;

View File

@ -1,8 +1,15 @@
package fr.adrien1106.reframed.util.blocks;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.block.ReFramedEntity;
import it.unimi.dsi.fastutil.objects.Object2ByteLinkedOpenHashMap;
import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.QuadPosBounds;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
@ -18,14 +25,17 @@ import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -36,17 +46,10 @@ import static net.minecraft.util.shape.VoxelShapes.combine;
public class BlockHelper {
// self culling cache TODO move
private static final ThreadLocal<Object2ByteLinkedOpenHashMap<CullElement>> INNER_FACE_CULL_MAP = ThreadLocal.withInitial(() -> {
Object2ByteLinkedOpenHashMap<CullElement> object2ByteLinkedOpenHashMap = new Object2ByteLinkedOpenHashMap<>(2048, 0.25F) {
protected void rehash(int newN) {
}
};
object2ByteLinkedOpenHashMap.defaultReturnValue((byte)0);
return object2ByteLinkedOpenHashMap;
});
// self culling cache of the models not made thread local so that it is only computed once
private static final Cache<CullElement, Integer[]> INNER_CULL_MAP = CacheBuilder.newBuilder().maximumSize(1024).concurrencyLevel().build();
private record CullElement(BlockState state, int model, Direction side) {}
private record CullElement(Object state_key, int model) {}
public static Corner getPlacementCorner(ItemPlacementContext ctx) {
Direction side = ctx.getSide().getOpposite();
@ -69,7 +72,7 @@ public class BlockHelper {
Math.abs(axis_1.choose(pos.x, pos.y, pos.z)) > Math.abs(axis_2.choose(pos.x, pos.y, pos.z))
? axis_1
: axis_2
).get();
).orElse(null);
}
public static Vec3d getRelativePos(Vec3d pos, BlockPos block_pos) {
@ -216,6 +219,66 @@ public class BlockHelper {
return ActionResult.PASS;
}
/**
* compute which quad might cull with another model quad
* @param state - the state of the model
* @param models - list of models on the same block
*/
public static void computeInnerCull(BlockState state, List<ForwardingBakedModel> models) {
if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
Object key = frame_block.getModelCacheKey(state);
if (INNER_CULL_MAP.asMap().containsKey(new CullElement(key, 1))) return;
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
QuadEmitter quad_emitter = r.meshBuilder().getEmitter();
RenderMaterial material = r.materialFinder().clear().find();
Random random = Random.create();
List<List<QuadPosBounds>> model_bounds = models.stream()
.map(ForwardingBakedModel::getWrappedModel)
.filter(Objects::nonNull)
.map(wrapped -> wrapped.getQuads(state, null, random))
.map(quads -> quads.stream().map(quad -> {
quad_emitter.fromVanilla(quad, material, null);
return QuadPosBounds.read(quad_emitter, false);
}).toList()).toList();
Integer[] cull_array;
for(int self_id = 1; self_id <= model_bounds.size(); self_id++) {
List<QuadPosBounds> self_bounds = model_bounds.get(self_id - 1);
cull_array = new Integer[self_bounds.size()];
for (int self_quad = 0; self_quad < cull_array.length; self_quad++) {
QuadPosBounds self_bound = self_bounds.get(self_quad);
for(int other_id = 1; other_id <= model_bounds.size(); other_id++) {
if (other_id == self_id) continue;
if (model_bounds.get(other_id - 1).stream().anyMatch(other_bound -> other_bound.equals(self_bound))) {
cull_array[self_quad] = other_id;
break;
}
}
}
INNER_CULL_MAP.put(new CullElement(key, self_id), cull_array);
}
}
public static boolean shouldDrawInnerFace(BlockState state, BlockRenderView view, BlockPos pos, int quad_index, int theme_index) {
if ( !(state.getBlock() instanceof ReFramedBlock frame_block)
|| !(view.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity)
) return true;
CullElement key = new CullElement(frame_block.getModelCacheKey(state), theme_index);
if (!INNER_CULL_MAP.asMap().containsKey(key)) return true;
// needs to be Integer object because array is initialized with null not 0
Integer cull_theme = Objects.requireNonNull(INNER_CULL_MAP.getIfPresent(key))[quad_index];
if (cull_theme == null) return true; // no culling possible
BlockState self_theme = frame_entity.getTheme(theme_index);
BlockState other_theme = frame_entity.getTheme(cull_theme);
if (self_theme.isSideInvisible(other_theme, null)) return false;
return !self_theme.isOpaque() || !other_theme.isOpaque();
}
// Doing this method from scratch as it is simpler to do than injecting everywhere
public static boolean shouldDrawSide(BlockState self_state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos, int theme_index) {
ThemeableBlockEntity self = world.getBlockEntity(pos) instanceof ThemeableBlockEntity e ? e : null;

View File

@ -8,4 +8,6 @@ public interface IBlockRenderInfoMixin {
void prepareForBlock(BlockState state, BlockPos pos, boolean ao, int theme_index);
void prepareForBlock(BlockState state, BlockPos pos, long seed, boolean ao, int theme_index);
int getThemeIndex();
}

View File

@ -30,7 +30,7 @@
"fabricloader": "^${loader_version}",
"fabric-api": "*"
},
"suggest": {
"suggests": {
"athena": "^${athena_version}",
"sodium": "^${sodium_version}",
"indium": "^${indium_version}"

View File

@ -14,6 +14,7 @@
"compat.AthenaBakedModelMixin",
"compat.AthenaConnectedBlockModelMixin",
"compat.AthenaWrappedGetterMixin",
"compat.IndiumAbstractBlockRenderContextMixin",
"compat.IndiumTerrainBlockRenderInfoMixin",
"compat.IndiumTerrainRenderContextMixin",
"compat.SodiumBlockOcclusionCacheMixin",
@ -21,6 +22,7 @@
"particles.AccessorParticle",
"particles.AccessorSpriteBillboardParticle",
"particles.MixinBlockDustParticle",
"render.AbstractBlockRenderContextMixin",
"render.BlockRenderInfoMixin",
"render.MultipartBakedModelMixin",
"render.TerrainRenderContextMixin",