diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java index 158c12924..fc0ae8dfe 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java @@ -1,11 +1,15 @@ package dev.engine_room.flywheel.lib.internal; +import java.util.Map; + import org.slf4j.Logger; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; import dev.engine_room.flywheel.api.internal.DependencyInjection; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; +import net.minecraft.client.model.geom.ModelPart; public interface FlwLibLink { FlwLibLink INSTANCE = DependencyInjection.load(FlwLibLink.class, "dev.engine_room.flywheel.impl.FlwLibLinkImpl"); @@ -13,4 +17,8 @@ public interface FlwLibLink { Logger getLogger(); PoseTransformStack getPoseTransformStackOf(PoseStack stack); + + Map getModelPartChildren(ModelPart part); + + void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java new file mode 100644 index 000000000..2aa476891 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java @@ -0,0 +1,38 @@ +package dev.engine_room.flywheel.lib.model; + +import org.joml.Vector4fc; + +import dev.engine_room.flywheel.api.model.IndexSequence; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.vertex.MutableVertexList; +import dev.engine_room.flywheel.lib.vertex.VertexTransformations; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public record RetexturedMesh(Mesh mesh, TextureAtlasSprite sprite) implements Mesh { + @Override + public int vertexCount() { + return mesh.vertexCount(); + } + + @Override + public void write(MutableVertexList vertexList) { + mesh.write(vertexList); + VertexTransformations.retexture(vertexList, sprite); + } + + @Override + public IndexSequence indexSequence() { + return mesh.indexSequence(); + } + + @Override + public int indexCount() { + return mesh.indexCount(); + } + + @Override + public Vector4fc boundingSphere() { + return mesh.boundingSphere(); + } +} + diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java new file mode 100644 index 000000000..ab1ded3bc --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -0,0 +1,295 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.ObjIntConsumer; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.joml.Quaternionf; +import org.joml.Vector3fc; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.instance.InstancerProvider; +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.instance.InstanceTypes; +import dev.engine_room.flywheel.lib.instance.TransformedInstance; +import dev.engine_room.flywheel.lib.model.ModelCache; +import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; + +public final class InstanceTree { + private static final ModelCache MODEL_CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material())); + + @Nullable + private final TransformedInstance instance; + private final PartPose initialPose; + @Unmodifiable + private final Map children; + + private final Quaternionf rotation = new Quaternionf(); + + public float x; + public float y; + public float z; + public float xRot; + public float yRot; + public float zRot; + public float xScale; + public float yScale; + public float zScale; + public boolean visible = true; + public boolean skipDraw; + + private InstanceTree(@Nullable TransformedInstance instance, PartPose initialPose, @Unmodifiable Map children) { + this.instance = instance; + this.initialPose = initialPose; + this.children = children; + resetPose(); + } + + private static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc, String path) { + Map children = new HashMap<>(); + String pathSlash = path + "/"; + + meshTree.children().forEach((name, meshTreeChild) -> { + children.put(name, InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name)); + }); + + Mesh mesh = meshTree.mesh(); + TransformedInstance instance; + if (mesh != null) { + Model.ConfiguredMesh configuredMesh = meshFinalizerFunc.apply(path, mesh); + instance = provider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(configuredMesh)) + .createInstance(); + } else { + instance = null; + } + + return new InstanceTree(instance, meshTree.initialPose(), Collections.unmodifiableMap(children)); + } + + public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc) { + return create(provider, meshTree, meshFinalizerFunc, ""); + } + + public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, BiFunction meshFinalizerFunc) { + return create(provider, MeshTree.of(layer), meshFinalizerFunc); + } + + public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, Material material) { + return create(provider, meshTree, (path, mesh) -> new Model.ConfiguredMesh(material, mesh)); + } + + public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, Material material) { + return create(provider, MeshTree.of(layer), material); + } + + @Nullable + public TransformedInstance instance() { + return instance; + } + + public PartPose initialPose() { + return initialPose; + } + + @Unmodifiable + public Map children() { + return children; + } + + public boolean hasChild(String name) { + return children.containsKey(name); + } + + @Nullable + public InstanceTree child(String name) { + return children.get(name); + } + + public InstanceTree childOrThrow(String name) { + InstanceTree child = child(name); + + if (child == null) { + throw new NoSuchElementException("Can't find part " + name); + } + + return child; + } + + public void traverse(Consumer consumer) { + if (instance != null) { + consumer.accept(instance); + } + for (InstanceTree child : children.values()) { + child.traverse(consumer); + } + } + + @ApiStatus.Experimental + public void traverse(int i, ObjIntConsumer consumer) { + if (instance != null) { + consumer.accept(instance, i); + } + for (InstanceTree child : children.values()) { + child.traverse(i, consumer); + } + } + + @ApiStatus.Experimental + public void traverse(int i, int j, ObjIntIntConsumer consumer) { + if (instance != null) { + consumer.accept(instance, i, j); + } + for (InstanceTree child : children.values()) { + child.traverse(i, j, consumer); + } + } + + public void translateAndRotate(PoseStack poseStack) { + poseStack.translate(x / 16.0F, y / 16.0F, z / 16.0F); + + if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) { + poseStack.mulPose(rotation.rotationZYX(zRot, yRot, xRot)); + } + + if (xScale != 1.0F || yScale != 1.0F || zScale != 1.0F) { + poseStack.scale(xScale, yScale, zScale); + } + } + + public void updateInstances(PoseStack poseStack) { + if (visible) { + poseStack.pushPose(); + translateAndRotate(poseStack); + + if (instance != null && !skipDraw) { + instance.setTransform(poseStack.last()) + .setChanged(); + } + + for (InstanceTree child : children.values()) { + child.updateInstances(poseStack); + } + + poseStack.popPose(); + } + } + + public void pos(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void rotation(float xRot, float yRot, float zRot) { + this.xRot = xRot; + this.yRot = yRot; + this.zRot = zRot; + } + + public void scale(float xScale, float yScale, float zScale) { + this.xScale = xScale; + this.yScale = yScale; + this.zScale = zScale; + } + + public void offsetPos(float xOffset, float yOffset, float zOffset) { + x += xOffset; + y += yOffset; + z += zOffset; + } + + public void offsetRotation(float xOffset, float yOffset, float zOffset) { + xRot += xOffset; + yRot += yOffset; + zRot += zOffset; + } + + public void offsetScale(float xOffset, float yOffset, float zOffset) { + xScale += xOffset; + yScale += yOffset; + zScale += zOffset; + } + + public void offsetPos(Vector3fc offset) { + offsetPos(offset.x(), offset.y(), offset.z()); + } + + public void offsetRotation(Vector3fc offset) { + offsetRotation(offset.x(), offset.y(), offset.z()); + } + + public void offsetScale(Vector3fc offset) { + offsetScale(offset.x(), offset.y(), offset.z()); + } + + public PartPose storePose() { + return PartPose.offsetAndRotation(x, y, z, xRot, yRot, zRot); + } + + public void loadPose(PartPose pose) { + x = pose.x; + y = pose.y; + z = pose.z; + xRot = pose.xRot; + yRot = pose.yRot; + zRot = pose.zRot; + xScale = ModelPart.DEFAULT_SCALE; + yScale = ModelPart.DEFAULT_SCALE; + zScale = ModelPart.DEFAULT_SCALE; + } + + public void resetPose() { + loadPose(initialPose); + } + + public void copyTransform(InstanceTree tree) { + x = tree.x; + y = tree.y; + z = tree.z; + xRot = tree.xRot; + yRot = tree.yRot; + zRot = tree.zRot; + xScale = tree.xScale; + yScale = tree.yScale; + zScale = tree.zScale; + } + + public void copyTransform(ModelPart modelPart) { + x = modelPart.x; + y = modelPart.y; + z = modelPart.z; + xRot = modelPart.xRot; + yRot = modelPart.yRot; + zRot = modelPart.zRot; + xScale = modelPart.xScale; + yScale = modelPart.yScale; + zScale = modelPart.zScale; + } + + public void delete() { + if (instance != null) { + instance.delete(); + } + children.values() + .forEach(InstanceTree::delete); + } + + @ApiStatus.Experimental + @FunctionalInterface + public interface ObjIntIntConsumer { + void accept(T t, int i, int j); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java new file mode 100644 index 000000000..d097266d3 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -0,0 +1,136 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.lib.internal.FlwLibLink; +import dev.engine_room.flywheel.lib.memory.MemoryBlock; +import dev.engine_room.flywheel.lib.model.SimpleQuadMesh; +import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView; +import dev.engine_room.flywheel.lib.vertex.VertexView; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.geom.EntityModelSet; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.texture.OverlayTexture; + +public final class MeshTree { + private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); + private static final PoseStack.Pose IDENTITY_POSE = new PoseStack().last(); + private static final Map CACHE = new ConcurrentHashMap<>(); + + @Nullable + private final Mesh mesh; + private final PartPose initialPose; + @Unmodifiable + private final Map children; + + private MeshTree(@Nullable Mesh mesh, PartPose initialPose, @Unmodifiable Map children) { + this.mesh = mesh; + this.initialPose = initialPose; + this.children = children; + } + + public static MeshTree of(ModelLayerLocation layer) { + return CACHE.computeIfAbsent(layer, MeshTree::convert); + } + + private static MeshTree convert(ModelLayerLocation layer) { + EntityModelSet entityModels = Minecraft.getInstance() + .getEntityModels(); + ModelPart modelPart = entityModels.bakeLayer(layer); + + return convert(modelPart, THREAD_LOCAL_OBJECTS.get()); + } + + private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) { + var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart); + Map children = new HashMap<>(); + + modelPartChildren.forEach((name, modelPartChild) -> { + children.put(name, convert(modelPartChild, objects)); + }); + + return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), Collections.unmodifiableMap(children)); + } + + @Nullable + private static Mesh compile(ModelPart modelPart, ThreadLocalObjects objects) { + if (modelPart.isEmpty()) { + return null; + } + + VertexWriter vertexWriter = objects.vertexWriter; + FlwLibLink.INSTANCE.compileModelPart(modelPart, IDENTITY_POSE, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F); + MemoryBlock data = vertexWriter.copyDataAndReset(); + + VertexView vertexView = new PosTexNormalVertexView(); + vertexView.load(data); + return new SimpleQuadMesh(vertexView, "source=MeshTree"); + } + + @Nullable + public Mesh mesh() { + return mesh; + } + + public PartPose initialPose() { + return initialPose; + } + + @Unmodifiable + public Map children() { + return children; + } + + public boolean hasChild(String name) { + return children.containsKey(name); + } + + @Nullable + public MeshTree child(String name) { + return children.get(name); + } + + public MeshTree childOrThrow(String name) { + MeshTree child = child(name); + + if (child == null) { + throw new NoSuchElementException("Can't find part " + name); + } + + return child; + } + + public void traverse(Consumer consumer) { + if (mesh != null) { + consumer.accept(mesh); + } + for (MeshTree child : children.values()) { + child.traverse(consumer); + } + } + + @ApiStatus.Internal + public static void onEndClientResourceReload() { + CACHE.clear(); + } + + private static class ThreadLocalObjects { + public final VertexWriter vertexWriter = new VertexWriter(); + } +} + diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java index 1e4a17d27..b5999cdac 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java @@ -18,6 +18,7 @@ import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +@Deprecated(forRemoval = true) public final class ModelPartConverter { private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java new file mode 100644 index 000000000..bf06084a2 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java @@ -0,0 +1,20 @@ +package dev.engine_room.flywheel.lib.vertex; + +import dev.engine_room.flywheel.api.vertex.MutableVertexList; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public final class VertexTransformations { + private VertexTransformations() { + } + + public static void retexture(MutableVertexList vertexList, int index, TextureAtlasSprite sprite) { + vertexList.u(index, sprite.getU(vertexList.u(index) * 16)); + vertexList.v(index, sprite.getV(vertexList.v(index) * 16)); + } + + public static void retexture(MutableVertexList vertexList, TextureAtlasSprite sprite) { + for (int i = 0; i < vertexList.vertexCount(); i++) { + retexture(vertexList, i, sprite); + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java index 7b0e0e682..003037264 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java @@ -1,12 +1,17 @@ package dev.engine_room.flywheel.impl; +import java.util.Map; + import org.slf4j.Logger; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; import dev.engine_room.flywheel.impl.extension.PoseStackExtension; +import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor; import dev.engine_room.flywheel.lib.internal.FlwLibLink; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; +import net.minecraft.client.model.geom.ModelPart; public class FlwLibLinkImpl implements FlwLibLink { @Override @@ -18,4 +23,14 @@ public class FlwLibLinkImpl implements FlwLibLink { public PoseTransformStack getPoseTransformStackOf(PoseStack stack) { return ((PoseStackExtension) stack).flywheel$transformStack(); } + + @Override + public Map getModelPartChildren(ModelPart part) { + return ((ModelPartAccessor) (Object) part).flywheel$children(); + } + + @Override + public void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha) { + ((ModelPartAccessor) (Object) part).flywheel$compile(pose, consumer, light, overlay, red, green, blue, alpha); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java new file mode 100644 index 000000000..274e7a829 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java @@ -0,0 +1,21 @@ +package dev.engine_room.flywheel.impl.mixin; + +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.model.geom.ModelPart; + +@Mixin(ModelPart.class) +public interface ModelPartAccessor { + @Accessor("children") + Map flywheel$children(); + + @Invoker("compile") + void flywheel$compile(PoseStack.Pose pose, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha); +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 086a11841..16c3849b2 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -5,26 +5,30 @@ import java.util.EnumMap; import java.util.Map; import java.util.function.Consumer; -import org.joml.Quaternionf; +import org.jetbrains.annotations.Nullable; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; import dev.engine_room.flywheel.api.instance.Instance; +import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.visualization.VisualizationContext; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.instance.OrientedInstance; -import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; -import dev.engine_room.flywheel.lib.util.Pair; +import dev.engine_room.flywheel.lib.model.RetexturedMesh; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.transform.TransformStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.resources.model.Material; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.SectionPos; import net.minecraft.world.level.block.AbstractChestBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.ChestBlock; @@ -35,7 +39,7 @@ import net.minecraft.world.level.block.entity.LidBlockEntity; import net.minecraft.world.level.block.state.properties.ChestType; public class ChestVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { - public static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() + private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() .cutout(CutoutShaders.ONE_TENTH) .texture(Sheets.CHEST_SHEET) .mipmap(false) @@ -48,68 +52,55 @@ public class ChestVisual extends Abstrac LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT); } - private static final ModelCache> BOTTOM_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "bottom"), MATERIAL); - }); - private static final ModelCache> LID_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lid"), MATERIAL); - }); - private static final ModelCache> LOCK_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lock"), MATERIAL); - }); + @Nullable + private final InstanceTree instances; + @Nullable + private final InstanceTree lid; + @Nullable + private final InstanceTree lock; - private final OrientedInstance bottom; - private final TransformedInstance lid; - private final TransformedInstance lock; - - private final ChestType chestType; + private final PoseStack poseStack = new PoseStack(); + private final BrightnessCombiner brightnessCombiner = new BrightnessCombiner(); + @Nullable + private final DoubleBlockCombiner.NeighborCombineResult neighborCombineResult; + @Nullable private final Float2FloatFunction lidProgress; - private final Quaternionf baseRotation = new Quaternionf(); - private float lastProgress = Float.NaN; public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { super(ctx, blockEntity, partialTick); - chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; - Material texture = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()); - - bottom = createBottomInstance(texture).position(getVisualPosition()); - lid = createLidInstance(texture); - lock = createLockInstance(texture); - Block block = blockState.getBlock(); if (block instanceof AbstractChestBlock chestBlock) { + ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; + TextureAtlasSprite sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()).sprite(); + + instances = InstanceTree.create(instancerProvider, LAYER_LOCATIONS.get(chestType), (path, mesh) -> { + return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite)); + }); + lid = instances.childOrThrow("lid"); + lock = instances.childOrThrow("lock"); + + poseStack.pushPose(); + TransformStack.of(poseStack).translate(getVisualPosition()); float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); - baseRotation.setAngleAxis(Math.toRadians(-horizontalAngle), 0, 1, 0); + poseStack.translate(0.5F, 0.5F, 0.5F); + poseStack.mulPose(Axis.YP.rotationDegrees(-horizontalAngle)); + poseStack.translate(-0.5F, -0.5F, -0.5F); - DoubleBlockCombiner.NeighborCombineResult wrapper = chestBlock.combine(blockState, level, pos, true); - lidProgress = wrapper.apply(ChestBlock.opennessCombiner(blockEntity)); + neighborCombineResult = chestBlock.combine(blockState, level, pos, true); + lidProgress = neighborCombineResult.apply(ChestBlock.opennessCombiner(blockEntity)); + + lastProgress = lidProgress.get(partialTick); + applyLidTransform(lastProgress); } else { - baseRotation.identity(); - lidProgress = $ -> 0f; + instances = null; + lid = null; + lock = null; + neighborCombineResult = null; + lidProgress = null; } - - bottom.rotation(baseRotation); - bottom.setChanged(); - - applyLidTransform(lidProgress.get(partialTick)); - } - - private OrientedInstance createBottomInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.ORIENTED, BOTTOM_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); - } - - private TransformedInstance createLidInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); - } - - private TransformedInstance createLockInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LOCK_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); } private static boolean isChristmas() { @@ -117,8 +108,23 @@ public class ChestVisual extends Abstrac return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26; } + @Override + public void setSectionCollector(SectionCollector sectionCollector) { + this.lightSections = sectionCollector; + + if (neighborCombineResult != null) { + lightSections.sections(neighborCombineResult.apply(new SectionPosCombiner())); + } else { + lightSections.sections(LongSet.of(SectionPos.asLong(pos))); + } + } + @Override public void beginFrame(Context context) { + if (instances == null) { + return; + } + if (doDistanceLimitThisFrame(context) || !isVisible(context.frustum())) { return; } @@ -136,41 +142,80 @@ public class ChestVisual extends Abstrac progress = 1.0F - progress; progress = 1.0F - progress * progress * progress; - float angleX = -(progress * ((float) Math.PI / 2F)); - - lid.setIdentityTransform() - .translate(getVisualPosition()) - .rotateCentered(baseRotation) - .translate(0, 9f / 16f, 1f / 16f) - .rotateX(angleX) - .translate(0, -9f / 16f, -1f / 16f) - .setChanged(); - - lock.setIdentityTransform() - .translate(getVisualPosition()) - .rotateCentered(baseRotation) - .translate(0, 8f / 16f, 0) - .rotateX(angleX) - .translate(0, -8f / 16f, 0) - .setChanged(); + lid.xRot = -(progress * ((float) Math.PI / 2F)); + lock.xRot = lid.xRot; + instances.updateInstances(poseStack); } @Override public void updateLight(float partialTick) { - relight(bottom, lid, lock); + if (instances != null) { + int packedLight = neighborCombineResult.apply(brightnessCombiner); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); + } } @Override public void collectCrumblingInstances(Consumer consumer) { - consumer.accept(bottom); - consumer.accept(lid); - consumer.accept(lock); + if (instances != null) { + instances.traverse(consumer); + } } @Override protected void _delete() { - bottom.delete(); - lid.delete(); - lock.delete(); + if (instances != null) { + instances.delete(); + } + } + + private class SectionPosCombiner implements DoubleBlockCombiner.Combiner { + @Override + public LongSet acceptDouble(BlockEntity first, BlockEntity second) { + long firstSection = SectionPos.asLong(first.getBlockPos()); + long secondSection = SectionPos.asLong(second.getBlockPos()); + + if (firstSection == secondSection) { + return LongSet.of(firstSection); + } else { + return LongSet.of(firstSection, secondSection); + } + } + + @Override + public LongSet acceptSingle(BlockEntity single) { + return LongSet.of(SectionPos.asLong(single.getBlockPos())); + } + + @Override + public LongSet acceptNone() { + return LongSet.of(SectionPos.asLong(pos)); + } + } + + private class BrightnessCombiner implements DoubleBlockCombiner.Combiner { + @Override + public Integer acceptDouble(BlockEntity first, BlockEntity second) { + int firstLight = LevelRenderer.getLightColor(first.getLevel(), first.getBlockPos()); + int secondLight = LevelRenderer.getLightColor(second.getLevel(), second.getBlockPos()); + int firstBlockLight = LightTexture.block(firstLight); + int secondBlockLight = LightTexture.block(secondLight); + int firstSkyLight = LightTexture.sky(firstLight); + int secondSkyLight = LightTexture.sky(secondLight); + return LightTexture.pack(Math.max(firstBlockLight, secondBlockLight), Math.max(firstSkyLight, secondSkyLight)); + } + + @Override + public Integer acceptSingle(BlockEntity single) { + return LevelRenderer.getLightColor(single.getLevel(), single.getBlockPos()); + } + + @Override + public Integer acceptNone() { + return LevelRenderer.getLightColor(level, pos); + } } } diff --git a/common/src/main/resources/flywheel.impl.mixins.json b/common/src/main/resources/flywheel.impl.mixins.json index c8d878e85..0f914a73c 100644 --- a/common/src/main/resources/flywheel.impl.mixins.json +++ b/common/src/main/resources/flywheel.impl.mixins.json @@ -12,6 +12,7 @@ "LevelMixin", "LevelRendererMixin", "MinecraftMixin", + "ModelPartAccessor", "PoseStackMixin", "fix.FixFabulousDepthMixin", "fix.FixNormalScalingMixin", diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java index 5e8ab76ac..d05ebb115 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java @@ -12,6 +12,7 @@ import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; import dev.engine_room.flywheel.lib.model.ModelCache; import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; +import dev.engine_room.flywheel.lib.model.part.MeshTree; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; @@ -71,6 +72,8 @@ public final class FlywheelFabric implements ClientModInitializer { ModelCache.onEndClientResourceReload()); EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ModelHolder.onEndClientResourceReload()); + EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> + MeshTree.onEndClientResourceReload()); ModelLoadingPlugin.register(ctx -> { ctx.addModels(PartialModelEventHandler.onRegisterAdditional()); diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java index f8ba8d83b..8fb860d6a 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java @@ -13,6 +13,7 @@ import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; import dev.engine_room.flywheel.lib.model.ModelCache; import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; +import dev.engine_room.flywheel.lib.model.part.MeshTree; import dev.engine_room.flywheel.lib.util.LevelAttached; import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.ArgumentTypeInfos; @@ -111,6 +112,7 @@ public final class FlywheelForge { modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelCache.onEndClientResourceReload()); modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelHolder.onEndClientResourceReload()); + modEventBus.addListener((EndClientResourceReloadEvent e) -> MeshTree.onEndClientResourceReload()); modEventBus.addListener(PartialModelEventHandler::onRegisterAdditional); modEventBus.addListener(PartialModelEventHandler::onBakingCompleted); diff --git a/gradle.properties b/gradle.properties index 86a12b351..d8c4ff106 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ forge_version_range = [47.0.0,) # General build dependency versions java_version = 17 -arch_loom_version=1.7.412 +arch_loom_version = 1.7.412 cursegradle_version = 1.4.0 parchment_version = 2023.09.03