diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java index 3ae15cb5b..d702e9638 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java @@ -8,6 +8,7 @@ import dev.engine_room.flywheel.api.visualization.VisualizationManager; import dev.engine_room.flywheel.backend.mixin.LevelRendererAccessor; import dev.engine_room.flywheel.lib.math.MatrixMath; +import net.minecraft.Util; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; @@ -16,7 +17,7 @@ import net.minecraft.world.phys.Vec3; public final class FrameUniforms extends UniformWriter { - private static final int SIZE = 96 + 64 * 9 + 16 * 4 + 8 * 2 + 8 + 4 * 10; + private static final int SIZE = 96 + 64 * 9 + 16 * 4 + 8 * 2 + 8 + 4 * 12; static final UniformBuffer BUFFER = new UniformBuffer(Uniforms.FRAME_INDEX, SIZE); private static final Matrix4f VIEW = new Matrix4f(); @@ -150,11 +151,15 @@ private static long writeTime(long ptr, RenderContext context) { float partialTick = context.partialTick(); float renderTicks = ticks + partialTick; float renderSeconds = renderTicks / 20f; + float systemSeconds = Util.getMillis() / 1000f; + int systemMillis = (int) (Util.getMillis() % Integer.MAX_VALUE); ptr = writeInt(ptr, ticks); ptr = writeFloat(ptr, partialTick); ptr = writeFloat(ptr, renderTicks); ptr = writeFloat(ptr, renderSeconds); + ptr = writeFloat(ptr, systemSeconds); + ptr = writeInt(ptr, systemMillis); return ptr; } diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl index 5131f8ab0..d133b7de3 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl @@ -38,6 +38,8 @@ layout(std140) uniform _FlwFrameUniforms { float flw_partialTick; float flw_renderTicks; float flw_renderSeconds; + float flw_systemSeconds; + uint flw_systemMillis; /** 0 means no fluid. Use FLW_CAMERA_IN_FLUID_* defines to detect fluid type. */ uint flw_cameraInFluid; 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/internal/FlwLibXplat.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibXplat.java index 0fe92a997..316571f73 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibXplat.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibXplat.java @@ -5,11 +5,13 @@ import dev.engine_room.flywheel.api.internal.DependencyInjection; import dev.engine_room.flywheel.lib.model.baked.BakedModelBuilder; import dev.engine_room.flywheel.lib.model.baked.BlockModelBuilder; +import dev.engine_room.flywheel.lib.model.baked.ItemModelBuilder; import dev.engine_room.flywheel.lib.model.baked.MultiBlockModelBuilder; import dev.engine_room.flywheel.lib.util.ShadersModHandler; import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; @@ -26,4 +28,6 @@ public interface FlwLibXplat { @Nullable ShadersModHandler.InternalHandler createIrisHandler(); + + ItemModelBuilder createItemModelBuilder(ItemStack stack, BakedModel model); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java index 2cab44864..72b3980b4 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/Materials.java @@ -1,8 +1,11 @@ package dev.engine_room.flywheel.lib.material; +import dev.engine_room.flywheel.api.material.DepthTest; import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.material.Transparency; +import dev.engine_room.flywheel.api.material.WriteMask; import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.renderer.entity.ItemRenderer; import net.minecraft.resources.ResourceLocation; public final class Materials { @@ -69,6 +72,21 @@ public final class Materials { .mipmap(false) .build(); + public static final Material GLINT = SimpleMaterial.builder() + .texture(ItemRenderer.ENCHANTED_GLINT_ITEM) + .shaders(StandardMaterialShaders.GLINT) + .transparency(Transparency.GLINT) + .writeMask(WriteMask.COLOR) + .depthTest(DepthTest.EQUAL) + .backfaceCulling(false) + .blur(true) + .mipmap(false) + .build(); + + public static final Material GLINT_ENTITY = SimpleMaterial.builderOf(GLINT) + .texture(ItemRenderer.ENCHANTED_GLINT_ENTITY) + .build(); + private Materials() { } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java index 4fe1daa11..073e63c18 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java @@ -124,6 +124,38 @@ public boolean diffuse() { return diffuse; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SimpleMaterial that = (SimpleMaterial) o; + return blur == that.blur && mipmap == that.mipmap && backfaceCulling == that.backfaceCulling && polygonOffset == that.polygonOffset && useOverlay == that.useOverlay && useLight == that.useLight && diffuse == that.diffuse && shaders.equals(that.shaders) && fog.equals(that.fog) && cutout.equals(that.cutout) && texture.equals(that.texture) && depthTest == that.depthTest && transparency == that.transparency && writeMask == that.writeMask; + } + + @Override + public int hashCode() { + int result = shaders.hashCode(); + result = 31 * result + fog.hashCode(); + result = 31 * result + cutout.hashCode(); + result = 31 * result + texture.hashCode(); + result = 31 * result + Boolean.hashCode(blur); + result = 31 * result + Boolean.hashCode(mipmap); + result = 31 * result + Boolean.hashCode(backfaceCulling); + result = 31 * result + Boolean.hashCode(polygonOffset); + result = 31 * result + depthTest.hashCode(); + result = 31 * result + transparency.hashCode(); + result = 31 * result + writeMask.hashCode(); + result = 31 * result + Boolean.hashCode(useOverlay); + result = 31 * result + Boolean.hashCode(useLight); + result = 31 * result + Boolean.hashCode(diffuse); + return result; + } + public static class Builder implements Material { protected MaterialShaders shaders; protected FogShader fog; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/StandardMaterialShaders.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/StandardMaterialShaders.java index 97ae60b97..80aa8a36b 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/StandardMaterialShaders.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/StandardMaterialShaders.java @@ -14,6 +14,8 @@ public final class StandardMaterialShaders { public static final MaterialShaders LINE = MaterialShaders.REGISTRY.registerAndGet(new SimpleMaterialShaders(Flywheel.rl("material/lines.vert"), Flywheel.rl("material/lines.frag"))); + public static final MaterialShaders GLINT = MaterialShaders.REGISTRY.registerAndGet(new SimpleMaterialShaders(Flywheel.rl("material/glint.vert"), Flywheel.rl("material/default.frag"))); + private StandardMaterialShaders() { } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java index 0f9816e7f..72664d464 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelUtil.java @@ -15,6 +15,7 @@ import dev.engine_room.flywheel.lib.memory.MemoryBlock; import dev.engine_room.flywheel.lib.vertex.PosVertexView; import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.block.BlockRenderDispatcher; public final class ModelUtil { @@ -48,6 +49,35 @@ public static Material getMaterial(RenderType chunkRenderType, boolean shaded) { return null; } + @Nullable + public static Material getItemMaterial(RenderType renderType, boolean shaded) { + if (renderType == RenderType.solid()) { + return shaded ? Materials.CHUNK_SOLID_SHADED : Materials.CHUNK_SOLID_UNSHADED; + } + if (renderType == RenderType.cutoutMipped()) { + return shaded ? Materials.CHUNK_CUTOUT_MIPPED_SHADED : Materials.CHUNK_CUTOUT_MIPPED_UNSHADED; + } + if (renderType == RenderType.cutout() || renderType == Sheets.cutoutBlockSheet()) { + return shaded ? Materials.CHUNK_CUTOUT_SHADED : Materials.CHUNK_CUTOUT_UNSHADED; + } + if (renderType == RenderType.translucent()) { + return shaded ? Materials.CHUNK_TRANSLUCENT_SHADED : Materials.CHUNK_TRANSLUCENT_UNSHADED; + } + if (renderType == RenderType.tripwire()) { + return shaded ? Materials.CHUNK_TRIPWIRE_SHADED : Materials.CHUNK_TRIPWIRE_UNSHADED; + } + if (renderType == Sheets.translucentCullBlockSheet() || renderType == Sheets.translucentItemSheet()) { + return shaded ? Materials.CHUNK_CUTOUT_SHADED : Materials.CHUNK_CUTOUT_UNSHADED; + } + if (renderType == RenderType.glint() || renderType == RenderType.glintDirect()) { + return Materials.GLINT; + } + if (renderType == RenderType.entityGlint() || renderType == RenderType.entityGlintDirect()) { + return Materials.GLINT_ENTITY; + } + return null; + } + public static int computeTotalVertexCount(Iterable meshes) { int vertexCount = 0; for (Mesh mesh : meshes) { 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..9f05636b6 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java @@ -0,0 +1,40 @@ +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 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(new RetexturingVertexList(vertexList, sprite)); + } + + @Override + public IndexSequence indexSequence() { + return mesh.indexSequence(); + } + + @Override + public int indexCount() { + return mesh.indexCount(); + } + + @Override + public Vector4fc boundingSphere() { + return mesh.boundingSphere(); + } + + @Override + public void delete() { + + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturingVertexList.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturingVertexList.java new file mode 100644 index 000000000..e890d0406 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturingVertexList.java @@ -0,0 +1,28 @@ +package dev.engine_room.flywheel.lib.model; + +import dev.engine_room.flywheel.api.vertex.MutableVertexList; +import dev.engine_room.flywheel.lib.vertex.WrappedVertexList; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +/** + * A wrapper so that differently textured models from the same mesh tree can share the same backing memory + */ +public class RetexturingVertexList extends WrappedVertexList { + private final TextureAtlasSprite sprite; + + public RetexturingVertexList(MutableVertexList delegate, TextureAtlasSprite sprite) { + super(delegate); + + this.sprite = sprite; + } + + @Override + public void u(int index, float u) { + super.u(index, sprite.getU(u * 16)); + } + + @Override + public void v(int index, float v) { + super.v(index, sprite.getV(v * 16)); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ItemModelBuilder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ItemModelBuilder.java new file mode 100644 index 000000000..477c7a186 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ItemModelBuilder.java @@ -0,0 +1,60 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.function.BiFunction; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.lib.internal.FlwLibXplat; +import dev.engine_room.flywheel.lib.model.SimpleModel; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; + +@ApiStatus.NonExtendable +public abstract class ItemModelBuilder { + final ItemStack itemStack; + final BakedModel model; + @Nullable + PoseStack poseStack; + @Nullable + ItemDisplayContext displayContext; + boolean leftHand; + @Nullable + BiFunction materialFunc; + + ItemModelBuilder(ItemStack itemStack, BakedModel model) { + this.itemStack = itemStack; + this.model = model; + } + + public static ItemModelBuilder create(ItemStack stack, BakedModel model) { + return FlwLibXplat.INSTANCE.createItemModelBuilder(stack, model); + } + + public ItemModelBuilder poseStack(PoseStack poseStack) { + this.poseStack = poseStack; + return this; + } + + public ItemModelBuilder displayContext(ItemDisplayContext displayContext) { + this.displayContext = displayContext; + return this; + } + + public ItemModelBuilder leftHand(boolean leftHand) { + this.leftHand = leftHand; + return this; + } + + public ItemModelBuilder materialFunc(BiFunction materialFunc) { + this.materialFunc = materialFunc; + return this; + } + + public abstract SimpleModel build(); +} 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..df4d6f023 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -0,0 +1,171 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.ObjIntConsumer; + +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +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.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.ModelPart; +import net.minecraft.client.model.geom.PartPose; + +public class InstanceTree { + private static final ModelCache CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material())); + + public float x; + public float y; + public float z; + public float xRot; + public float yRot; + public float zRot; + public float xScale = ModelPart.DEFAULT_SCALE; + public float yScale = ModelPart.DEFAULT_SCALE; + public float zScale = ModelPart.DEFAULT_SCALE; + public boolean visible = true; + public boolean skipDraw; + + @Nullable + public TransformedInstance instance; + + private final Quaternionf rotation = new Quaternionf(); + private final Entry entry; + private final PartPose initialPose; + + private final Map children; + + private InstanceTree(InstancerProvider provider, Entry entry, Map children, PartPose initialPose) { + this.entry = entry; + this.children = children; + this.initialPose = initialPose; + + if (entry.mesh() != null) { + this.instance = provider.instancer(InstanceTypes.TRANSFORMED, CACHE.get(entry)) + .createInstance(); + } + + this.x = initialPose.x; + this.y = initialPose.y; + this.z = initialPose.z; + this.xRot = initialPose.xRot; + this.yRot = initialPose.yRot; + this.zRot = initialPose.zRot; + } + + public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, Material material) { + Map children = new HashMap<>(); + for (Map.Entry child : meshTree.children() + .entrySet()) { + var childTree = InstanceTree.create(provider, child.getValue(), material); + + children.put(child.getKey(), childTree); + } + + return new InstanceTree(provider, new Entry(meshTree.mesh(), material), children, meshTree.initialPose()); + } + + public void offsetPos(Vector3f pOffset) { + this.x += pOffset.x(); + this.y += pOffset.y(); + this.z += pOffset.z(); + } + + public void offsetRotation(Vector3f pOffset) { + this.xRot += pOffset.x(); + this.yRot += pOffset.y(); + this.zRot += pOffset.z(); + } + + public void offsetScale(Vector3f pOffset) { + this.xScale += pOffset.x(); + this.yScale += pOffset.y(); + this.zScale += pOffset.z(); + } + + public void delete() { + if (instance != null) { + instance.delete(); + } + children.values() + .forEach(InstanceTree::delete); + } + + public InstanceTree child(String name) { + return children.get(name); + } + + public void walkInstances(Consumer consumer) { + if (instance != null) { + consumer.accept(instance); + } + for (InstanceTree child : children.values()) { + child.walkInstances(consumer); + } + } + + public void walkInstances(int i, ObjIntConsumer consumer) { + if (instance != null) { + consumer.accept(instance, i); + } + for (InstanceTree child : children.values()) { + child.walkInstances(i, consumer); + } + } + + public void walkInstances(int i, int j, ObjIntIntConsumer consumer) { + if (instance != null) { + consumer.accept(instance, i, j); + } + for (InstanceTree child : children.values()) { + child.walkInstances(i, j, consumer); + } + } + + @FunctionalInterface + public interface ObjIntIntConsumer { + void accept(T t, int i, int j); + } + + public void updateInstances(PoseStack pPoseStack) { + if (this.visible) { + pPoseStack.pushPose(); + this.translateAndRotate(pPoseStack); + if (!this.skipDraw && instance != null) { + instance.setTransform(pPoseStack.last()) + .setChanged(); + } + + for (InstanceTree modelpart : this.children.values()) { + modelpart.updateInstances(pPoseStack); + } + + pPoseStack.popPose(); + } + } + + public void translateAndRotate(PoseStack pPoseStack) { + pPoseStack.translate(this.x / 16.0F, this.y / 16.0F, this.z / 16.0F); + if (this.xRot != 0.0F || this.yRot != 0.0F || this.zRot != 0.0F) { + pPoseStack.mulPose(rotation.rotationZYX(this.zRot, this.yRot, this.xRot)); + } + + if (this.xScale != 1.0F || this.yScale != 1.0F || this.zScale != 1.0F) { + pPoseStack.scale(this.xScale, this.yScale, this.zScale); + } + + } + + private record Entry(@Nullable Mesh mesh, Material material) { + } +} 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..4f2776a1d --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -0,0 +1,108 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +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.model.SimpleMesh; +import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView; +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; + +public class MeshTree { + private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); + private static final PoseStack.Pose IDENTITY = new PoseStack().last(); + private static final Map MESH_TREES = new ConcurrentHashMap<>(); + + private final PartPose initialPose; + @Nullable + private final Mesh mesh; + private final Map children; + + private MeshTree(PartPose initialPose, @Nullable Mesh mesh, Map children) { + this.initialPose = initialPose; + this.mesh = mesh; + this.children = children; + } + + public static MeshTree get(ModelLayerLocation key) { + return MESH_TREES.computeIfAbsent(key, MeshTree::convert); + } + + @ApiStatus.Internal + public static void onEndClientResourceReload() { + MESH_TREES.values() + .forEach(MeshTree::delete); + MESH_TREES.clear(); + } + + public PartPose initialPose() { + return initialPose; + } + + @Nullable + public Mesh mesh() { + return mesh; + } + + public Map children() { + return children; + } + + @Nullable + public MeshTree child(String name) { + return children.get(name); + } + + public void delete() { + if (mesh != null) { + mesh.delete(); + } + children.values() + .forEach(MeshTree::delete); + } + + public static MeshTree convert(ModelLayerLocation location) { + EntityModelSet entityModels = Minecraft.getInstance() + .getEntityModels(); + ModelPart modelPart = entityModels.bakeLayer(location); + + return convert(modelPart, THREAD_LOCAL_OBJECTS.get()); + } + + private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) { + var childModelParts = FlwLibLink.INSTANCE.getModelPartChildren(modelPart); + + Map children = new HashMap<>(); + + for (Map.Entry entry : childModelParts.entrySet()) { + children.put(entry.getKey(), convert(entry.getValue(), objects)); + } + + return new MeshTree(modelPart.getInitialPose(), compile(modelPart, objects), children); + } + + private static Mesh compile(ModelPart modelPart, ThreadLocalObjects objects) { + var vertexWriter = objects.vertexWriter; + + FlwLibLink.INSTANCE.compileModelPart(modelPart, IDENTITY, vertexWriter, 0, 0, 1.0F, 1.0F, 1.0F, 1.0F); + + var data = vertexWriter.copyDataAndReset(); + + return new SimpleMesh(new PosTexNormalVertexView(), data, "source=ModelPartConverter"); + } + + private static class ThreadLocalObjects { + public final VertexWriter vertexWriter = new VertexWriter(); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/WrappedVertexList.java b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/WrappedVertexList.java new file mode 100644 index 000000000..ae772f04d --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/WrappedVertexList.java @@ -0,0 +1,156 @@ +package dev.engine_room.flywheel.lib.vertex; + +import dev.engine_room.flywheel.api.vertex.MutableVertexList; + +public class WrappedVertexList implements MutableVertexList { + protected final MutableVertexList delegate; + + public WrappedVertexList(MutableVertexList delegate) { + this.delegate = delegate; + } + + @Override + public void x(int index, float x) { + delegate.x(index, x); + } + + @Override + public void y(int index, float y) { + delegate.y(index, y); + } + + @Override + public void z(int index, float z) { + delegate.z(index, z); + } + + @Override + public void r(int index, float r) { + delegate.r(index, r); + } + + @Override + public void g(int index, float g) { + delegate.g(index, g); + } + + @Override + public void b(int index, float b) { + delegate.b(index, b); + } + + @Override + public void a(int index, float a) { + delegate.a(index, a); + } + + @Override + public void u(int index, float u) { + delegate.u(index, u); + } + + @Override + public void v(int index, float v) { + delegate.v(index, v); + } + + @Override + public void overlay(int index, int overlay) { + delegate.overlay(index, overlay); + } + + @Override + public void light(int index, int light) { + delegate.light(index, light); + } + + @Override + public void normalX(int index, float normalX) { + delegate.normalX(index, normalX); + } + + @Override + public void normalY(int index, float normalY) { + delegate.normalY(index, normalY); + } + + @Override + public void normalZ(int index, float normalZ) { + delegate.normalZ(index, normalZ); + } + + @Override + public float x(int index) { + return delegate.x(index); + } + + @Override + public float y(int index) { + return delegate.y(index); + } + + @Override + public float z(int index) { + return delegate.z(index); + } + + @Override + public float r(int index) { + return delegate.r(index); + } + + @Override + public float g(int index) { + return delegate.g(index); + } + + @Override + public float b(int index) { + return delegate.b(index); + } + + @Override + public float a(int index) { + return delegate.a(index); + } + + @Override + public float u(int index) { + return delegate.u(index); + } + + @Override + public float v(int index) { + return delegate.v(index); + } + + @Override + public int overlay(int index) { + return delegate.overlay(index); + } + + @Override + public int light(int index) { + return delegate.light(index); + } + + @Override + public float normalX(int index) { + return delegate.normalX(index); + } + + @Override + public float normalY(int index) { + return delegate.normalY(index); + } + + @Override + public float normalZ(int index) { + return delegate.normalZ(index); + } + + @Override + public int vertexCount() { + return delegate.vertexCount(); + } +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/material/glint.vert b/common/src/lib/resources/assets/flywheel/flywheel/material/glint.vert new file mode 100644 index 000000000..eca6ddef7 --- /dev/null +++ b/common/src/lib/resources/assets/flywheel/flywheel/material/glint.vert @@ -0,0 +1,8 @@ +void flw_materialVertex() { + float p = flw_glintSpeedOption * flw_systemSeconds * 8.; + + flw_vertexTexCoord *= 8.; + // Rotate by 0.17453292 radians + flw_vertexTexCoord *= mat2(0.98480775, 0.17364817, -0.17364817, 0.98480775); + flw_vertexTexCoord += vec2(-p / 110., p / 30.); +} 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 Logger getLogger() { 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/ClientLevelMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientLevelMixin.java deleted file mode 100644 index 556e4869f..000000000 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientLevelMixin.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.engine_room.flywheel.impl.mixin; - -import java.util.ArrayList; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import com.google.common.collect.Lists; - -import dev.engine_room.flywheel.api.visualization.VisualizationManager; -import dev.engine_room.flywheel.lib.visual.VisualizationHelper; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.world.entity.Entity; - -@Mixin(ClientLevel.class) -abstract class ClientLevelMixin { - @Inject(method = "entitiesForRendering()Ljava/lang/Iterable;", at = @At("RETURN"), cancellable = true) - private void flywheel$filterEntities(CallbackInfoReturnable> cir) { - if (!VisualizationManager.supportsVisualization((ClientLevel) (Object) this)) { - return; - } - - Iterable entities = cir.getReturnValue(); - ArrayList filtered = Lists.newArrayList(entities); - - filtered.removeIf(VisualizationHelper::shouldSkipRender); - - cir.setReturnValue(filtered); - } -} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java index 9cbcf2790..23e982bc3 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java @@ -18,17 +18,21 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.event.RenderStage; +import dev.engine_room.flywheel.api.visualization.VisualizationManager; import dev.engine_room.flywheel.impl.FlwImplXplat; import dev.engine_room.flywheel.impl.event.RenderContextImpl; import dev.engine_room.flywheel.impl.visualization.VisualizationManagerImpl; +import dev.engine_room.flywheel.lib.visual.VisualizationHelper; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import net.minecraft.client.Camera; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderBuffers; import net.minecraft.server.level.BlockDestructionProgress; +import net.minecraft.world.entity.Entity; @Mixin(value = LevelRenderer.class, priority = 1001) // Higher priority to go after Sodium abstract class LevelRendererMixin { @@ -78,6 +82,14 @@ abstract class LevelRendererMixin { } } + // ENTITY CANCELLING + @Inject(method = "renderEntity", at = @At("HEAD"), cancellable = true) + private void flywheel$decideNotToRenderEntity(Entity pEntity, double pCamX, double pCamY, double pCamZ, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBufferSource, CallbackInfo ci) { + if (VisualizationManager.supportsVisualization(pEntity.level()) && VisualizationHelper.shouldSkipRender(pEntity)) { + ci.cancel(); + } + } + // STAGE DISPATCHING @Unique 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..8a4af327e --- /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 pPose, VertexConsumer pVertexConsumer, int pPackedLight, int pPackedOverlay, float pRed, float pGreen, float pBlue, float pAlpha); +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/AgeableListComponent.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/AgeableListComponent.java new file mode 100644 index 000000000..d872b70f7 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/AgeableListComponent.java @@ -0,0 +1,56 @@ +package dev.engine_room.flywheel.vanilla; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.lib.model.part.InstanceTree; + +public abstract class AgeableListComponent { + public float attackTime; + public boolean riding; + public boolean young = true; + protected final Config config; + + public AgeableListComponent(Config config) { + this.config = config; + } + + public void updateInstances(PoseStack pPoseStack) { + if (this.young) { + pPoseStack.pushPose(); + if (this.config.scaleHead) { + float f = 1.5F / this.config.babyHeadScale; + pPoseStack.scale(f, f, f); + } + + pPoseStack.translate(0.0F, this.config.babyYHeadOffset / 16.0F, this.config.babyZHeadOffset / 16.0F); + for (InstanceTree headPart : this.headParts()) { + headPart.updateInstances(pPoseStack); + } + pPoseStack.popPose(); + pPoseStack.pushPose(); + float f1 = 1.0F / this.config.babyBodyScale; + pPoseStack.scale(f1, f1, f1); + pPoseStack.translate(0.0F, this.config.bodyYOffset / 16.0F, 0.0F); + for (InstanceTree bodyPart : this.bodyParts()) { + bodyPart.updateInstances(pPoseStack); + } + pPoseStack.popPose(); + } else { + for (InstanceTree headPart : this.headParts()) { + headPart.updateInstances(pPoseStack); + } + for (InstanceTree bodyPart : this.bodyParts()) { + bodyPart.updateInstances(pPoseStack); + } + } + + } + + protected abstract Iterable headParts(); + + protected abstract Iterable bodyParts(); + + public record Config(boolean scaleHead, float babyYHeadOffset, float babyZHeadOffset, float babyHeadScale, + float babyBodyScale, float bodyYOffset) { + } +} 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 32c77be61..cdfcfbb1e 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 @@ -8,14 +8,16 @@ import org.joml.Quaternionf; 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.Materials; import dev.engine_room.flywheel.lib.model.ModelCache; +import dev.engine_room.flywheel.lib.model.RetexturedMesh; import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; +import dev.engine_room.flywheel.lib.model.part.MeshTree; import dev.engine_room.flywheel.lib.util.Pair; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; @@ -42,13 +44,13 @@ public class ChestVisual extends Abstrac } private static final ModelCache> BOTTOM_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "bottom"), Materials.CHEST); + return chestModel(key, "bottom"); }); private static final ModelCache> LID_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lid"), Materials.CHEST); + return chestModel(key, "lid"); }); private static final ModelCache> LOCK_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lock"), Materials.CHEST); + return chestModel(key, "lock"); }); private final OrientedInstance bottom; @@ -137,15 +139,13 @@ private void applyLidTransform(float progress) { .rotateCentered(baseRotation) .translate(0, 9f / 16f, 1f / 16f) .rotateX(angleX) - .translate(0, -9f / 16f, -1f / 16f) .setChanged(); lock.loadIdentity() .translate(getVisualPosition()) .rotateCentered(baseRotation) - .translate(0, 8f / 16f, 0) + .translate(0, 9f / 16f, 1f / 16f) .rotateX(angleX) - .translate(0, -8f / 16f, 0) .setChanged(); } @@ -167,4 +167,13 @@ protected void _delete() { lid.delete(); lock.delete(); } + + public static Model chestModel(Pair key, String child) { + var mesh = MeshTree.get(LAYER_LOCATIONS.get(key.first())) + .child(child) + .mesh(); + var sprite = key.second() + .sprite(); + return new SingleMeshModel(new RetexturedMesh(mesh, sprite), Materials.CHEST); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/CowVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/CowVisual.java new file mode 100644 index 000000000..e2a4d815b --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/CowVisual.java @@ -0,0 +1,218 @@ +package dev.engine_room.flywheel.vanilla; + +import static net.minecraft.client.renderer.entity.LivingEntityRenderer.getOverlayCoords; +import static net.minecraft.client.renderer.entity.LivingEntityRenderer.isEntityUpsideDown; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.visualization.VisualizationContext; +import dev.engine_room.flywheel.lib.material.SimpleMaterial; +import dev.engine_room.flywheel.lib.transform.TransformStack; +import dev.engine_room.flywheel.lib.visual.SimpleEntityVisual; +import dev.engine_room.flywheel.lib.visual.component.FireComponent; +import dev.engine_room.flywheel.lib.visual.component.HitboxComponent; +import dev.engine_room.flywheel.lib.visual.component.ShadowComponent; +import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.animal.Cow; +import net.minecraft.world.level.LightLayer; + +public class CowVisual extends SimpleEntityVisual { + private static final ResourceLocation COW_LOCATION = new ResourceLocation("textures/entity/cow/cow.png"); + + public static final AgeableListComponent.Config COW_CONFIG = new AgeableListComponent.Config(false, 10.0F, 4.0F, 2.0F, 2.0F, 24); + + public static final Material COW_MATERIAL = SimpleMaterial.builder() + .texture(COW_LOCATION) + .build(); + + private final QuadrupedComponent cowQuadrupedComponent; + + private final PoseStack stack = new PoseStack(); + + public CowVisual(VisualizationContext ctx, Cow entity, float partialTick) { + super(ctx, entity, partialTick); + + cowQuadrupedComponent = new QuadrupedComponent(instancerProvider, ModelLayers.COW, COW_MATERIAL, COW_CONFIG); + + addComponent(new ShadowComponent(visualizationContext, entity).radius(0.7f)); + addComponent(new HitboxComponent(visualizationContext, entity)); + addComponent(new FireComponent(visualizationContext, entity)); + } + + @Override + public void beginFrame(Context ctx) { + if (!isVisible(ctx.frustum())) { + return; + } + + super.beginFrame(ctx); + + int overlay = getOverlayCoords(entity, this.getWhiteOverlayProgress(ctx.partialTick())); + int light = LightTexture.pack(level.getBrightness(LightLayer.BLOCK, entity.blockPosition()), level.getBrightness(LightLayer.SKY, entity.blockPosition())); + cowQuadrupedComponent.root.walkInstances(overlay, light, (i, o, l) -> { + i.overlay(o); + i.light(l); + // We'll #setChanged later + }); + + stack.setIdentity(); + TransformStack.of(stack) + .translate(getVisualPosition(ctx.partialTick())); + + boolean shouldSit = entity.isPassenger(); // && (entity.getVehicle() != null && entity.getVehicle().shouldRiderSit()); + this.cowQuadrupedComponent.riding = shouldSit; + this.cowQuadrupedComponent.young = entity.isBaby(); + + float yBodyRot = Mth.rotLerp(ctx.partialTick(), entity.yBodyRotO, entity.yBodyRot); + float yHeadRot = Mth.rotLerp(ctx.partialTick(), entity.yHeadRotO, entity.yHeadRot); + float diffRot = yHeadRot - yBodyRot; + if (shouldSit && entity.getVehicle() instanceof LivingEntity livingentity) { + yBodyRot = Mth.rotLerp(ctx.partialTick(), livingentity.yBodyRotO, livingentity.yBodyRot); + diffRot = yHeadRot - yBodyRot; + float f3 = Mth.wrapDegrees(diffRot); + if (f3 < -85.0F) { + f3 = -85.0F; + } + + if (f3 >= 85.0F) { + f3 = 85.0F; + } + + yBodyRot = yHeadRot - f3; + if (f3 * f3 > 2500.0F) { + yBodyRot += f3 * 0.2F; + } + + diffRot = yHeadRot - yBodyRot; + } + + float xRot = Mth.lerp(ctx.partialTick(), entity.xRotO, entity.getXRot()); + if (isEntityUpsideDown(entity)) { + xRot *= -1.0F; + diffRot *= -1.0F; + } + + if (entity.hasPose(Pose.SLEEPING)) { + Direction direction = entity.getBedOrientation(); + if (direction != null) { + float f4 = entity.getEyeHeight(Pose.STANDING) - 0.1F; + stack.translate((float) (-direction.getStepX()) * f4, 0.0F, (float) (-direction.getStepZ()) * f4); + } + } + + float bob = this.getBob(ctx.partialTick()); + this.setupRotations(stack, bob, yBodyRot, ctx.partialTick()); + stack.scale(-1.0F, -1.0F, 1.0F); + this.scale(stack, ctx.partialTick()); + stack.translate(0.0F, -1.501F, 0.0F); + float walkSpeed = 0.0F; + float walkPos = 0.0F; + if (!shouldSit && entity.isAlive()) { + walkSpeed = entity.walkAnimation.speed(ctx.partialTick()); + walkPos = entity.walkAnimation.position(ctx.partialTick()); + if (entity.isBaby()) { + walkPos *= 3.0F; + } + + if (walkSpeed > 1.0F) { + walkSpeed = 1.0F; + } + } + + cowQuadrupedComponent.setupAnim(walkPos, walkSpeed, entity.tickCount, diffRot, xRot); + cowQuadrupedComponent.updateInstances(stack); + } + + private static float sleepDirectionToRotation(Direction pFacing) { + switch (pFacing) { + case SOUTH: + return 90.0F; + case WEST: + return 0.0F; + case NORTH: + return 270.0F; + case EAST: + return 180.0F; + default: + return 0.0F; + } + } + + + /** + * Returns where in the swing animation the living entity is (from 0 to 1). Args : entity, partialTickTime + */ + protected float getAttackAnim(float pPartialTickTime) { + return entity.getAttackAnim(pPartialTickTime); + } + + /** + * Defines what float the third param in setRotationAngles of ModelBase is + */ + protected float getBob(float pPartialTick) { + return (float) entity.tickCount + pPartialTick; + } + + protected float getFlipDegrees() { + return 90.0F; + } + + protected float getWhiteOverlayProgress(float pPartialTicks) { + return 0.0F; + } + + protected boolean isShaking() { + return entity.isFullyFrozen(); + } + + protected void setupRotations(PoseStack pPoseStack, float pAgeInTicks, float pRotationYaw, float pPartialTicks) { + if (this.isShaking()) { + pRotationYaw += (float) (Math.cos((double) entity.tickCount * 3.25D) * Math.PI * (double) 0.4F); + } + + if (!entity.hasPose(Pose.SLEEPING)) { + pPoseStack.mulPose(Axis.YP.rotationDegrees(180.0F - pRotationYaw)); + } + + if (entity.deathTime > 0) { + float f = ((float) entity.deathTime + pPartialTicks - 1.0F) / 20.0F * 1.6F; + f = Mth.sqrt(f); + if (f > 1.0F) { + f = 1.0F; + } + + pPoseStack.mulPose(Axis.ZP.rotationDegrees(f * this.getFlipDegrees())); + } else if (entity.isAutoSpinAttack()) { + pPoseStack.mulPose(Axis.XP.rotationDegrees(-90.0F - entity.getXRot())); + pPoseStack.mulPose(Axis.YP.rotationDegrees(((float) entity.tickCount + pPartialTicks) * -75.0F)); + } else if (entity.hasPose(Pose.SLEEPING)) { + Direction direction = entity.getBedOrientation(); + float f1 = direction != null ? sleepDirectionToRotation(direction) : pRotationYaw; + pPoseStack.mulPose(Axis.YP.rotationDegrees(f1)); + pPoseStack.mulPose(Axis.ZP.rotationDegrees(this.getFlipDegrees())); + pPoseStack.mulPose(Axis.YP.rotationDegrees(270.0F)); + } else if (isEntityUpsideDown(entity)) { + pPoseStack.translate(0.0F, entity.getBbHeight() + 0.1F, 0.0F); + pPoseStack.mulPose(Axis.ZP.rotationDegrees(180.0F)); + } + + } + + protected void scale(PoseStack pPoseStack, float pPartialTickTime) { + } + + @Override + protected void _delete() { + super._delete(); + + cowQuadrupedComponent.delete(); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ItemVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ItemVisual.java new file mode 100644 index 000000000..4dff87e43 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ItemVisual.java @@ -0,0 +1,216 @@ +package dev.engine_room.flywheel.vanilla; + +import java.util.Objects; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; + +import dev.engine_room.flywheel.api.visualization.VisualizationContext; +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.baked.ItemModelBuilder; +import dev.engine_room.flywheel.lib.transform.TransformStack; +import dev.engine_room.flywheel.lib.visual.InstanceRecycler; +import dev.engine_room.flywheel.lib.visual.SimpleEntityVisual; +import dev.engine_room.flywheel.lib.visual.component.FireComponent; +import dev.engine_room.flywheel.lib.visual.component.HitboxComponent; +import dev.engine_room.flywheel.lib.visual.component.ShadowComponent; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.MultiPartBakedModel; +import net.minecraft.client.resources.model.SimpleBakedModel; +import net.minecraft.client.resources.model.WeightedBakedModel; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.LightLayer; + +public class ItemVisual extends SimpleEntityVisual { + + private static final ThreadLocal RANDOM = ThreadLocal.withInitial(RandomSource::createNewThreadLocalInstance); + + public static final ModelCache MODEL_CACHE = new ModelCache<>(stack -> { + return ItemModelBuilder.create(stack.stack(), stack.model()) + .build(); + }); + + private final PoseStack pPoseStack = new PoseStack(); + private final BakedModel model; + private final boolean isSupported; + + private final InstanceRecycler instances; + + public ItemVisual(VisualizationContext ctx, ItemEntity entity, float partialTick) { + super(ctx, entity, partialTick); + + addComponent(new ShadowComponent(visualizationContext, entity).radius(0.15f) + .strength(0.75f)); + addComponent(new HitboxComponent(visualizationContext, entity)); + addComponent(new FireComponent(visualizationContext, entity)); + + var item = entity.getItem(); + model = getModel(item); + + isSupported = isSupported(model); + + var key = new ItemKey(item.copy(), model); + + instances = new InstanceRecycler<>(() -> instancerProvider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(key)) + .createInstance()); + } + + public static boolean isSupported(ItemEntity entity) { + return isSupported(getModel(entity.getItem())); + } + + public static BakedModel getModel(ItemStack stack) { + return Minecraft.getInstance() + .getItemRenderer() + .getItemModelShaper() + .getItemModel(stack); + } + + public static boolean isSupported(BakedModel model) { + if (model.isCustomRenderer()) { + return false; + } + + if (model.getOverrides() != ItemOverrides.EMPTY) { + return false; + } + + Class c = model.getClass(); + if (!(c == SimpleBakedModel.class || c == MultiPartBakedModel.class || c == WeightedBakedModel.class)) { + return false; + } + + return true; + } + + @Override + public void beginFrame(Context ctx) { + if (!isSupported || !isVisible(ctx.frustum())) { + return; + } + + super.beginFrame(ctx); + + pPoseStack.setIdentity(); + TransformStack.of(pPoseStack) + .translate(getVisualPosition(ctx.partialTick())); + + instances.resetCount(); + ItemStack itemstack = entity.getItem(); + int i = itemstack.isEmpty() ? 187 : Item.getId(itemstack.getItem()) + itemstack.getDamageValue(); + var random = RANDOM.get(); + random.setSeed(i); + boolean flag = model.isGui3d(); + int j = this.getRenderAmount(itemstack); + float f = 0.25F; + float f1 = shouldBob() ? Mth.sin(((float) entity.getAge() + ctx.partialTick()) / 10.0F + entity.bobOffs) * 0.1F + 0.1F : 0; + float f2 = model.getTransforms() + .getTransform(ItemDisplayContext.GROUND).scale.y(); + pPoseStack.translate(0.0F, f1 + 0.25F * f2, 0.0F); + float f3 = entity.getSpin(ctx.partialTick()); + pPoseStack.mulPose(Axis.YP.rotation(f3)); + if (!flag) { + float f7 = -0.0F * (float) (j - 1) * 0.5F; + float f8 = -0.0F * (float) (j - 1) * 0.5F; + float f9 = -0.09375F * (float) (j - 1) * 0.5F; + pPoseStack.translate(f7, f8, f9); + } + + int light = LightTexture.pack(level.getBrightness(LightLayer.BLOCK, entity.blockPosition()), level.getBrightness(LightLayer.SKY, entity.blockPosition())); + + for (int k = 0; k < j; ++k) { + pPoseStack.pushPose(); + if (k > 0) { + if (flag) { + float f11 = (random.nextFloat() * 2.0F - 1.0F) * 0.15F; + float f13 = (random.nextFloat() * 2.0F - 1.0F) * 0.15F; + float f10 = (random.nextFloat() * 2.0F - 1.0F) * 0.15F; + pPoseStack.translate(shouldSpreadItems() ? f11 : 0, shouldSpreadItems() ? f13 : 0, shouldSpreadItems() ? f10 : 0); + } else { + float f12 = (random.nextFloat() * 2.0F - 1.0F) * 0.15F * 0.5F; + float f14 = (random.nextFloat() * 2.0F - 1.0F) * 0.15F * 0.5F; + pPoseStack.translate(shouldSpreadItems() ? f12 : 0, shouldSpreadItems() ? f14 : 0, 0.0D); + } + } + + instances.get() + .setTransform(pPoseStack.last()) + .light(light) + .setChanged(); + pPoseStack.popPose(); + if (!flag) { + pPoseStack.translate(0.0, 0.0, 0.09375F); + } + } + + instances.discardExtra(); + } + + protected int getRenderAmount(ItemStack pStack) { + int i = 1; + if (pStack.getCount() > 48) { + i = 5; + } else if (pStack.getCount() > 32) { + i = 4; + } else if (pStack.getCount() > 16) { + i = 3; + } else if (pStack.getCount() > 1) { + i = 2; + } + + return i; + } + + /** + * @return If items should spread out when rendered in 3D + */ + public boolean shouldSpreadItems() { + return true; + } + + /** + * @return If items should have a bob effect + */ + public boolean shouldBob() { + return true; + } + + @Override + protected void _delete() { + super._delete(); + + instances.delete(); + } + + public record ItemKey(ItemStack stack, BakedModel model) { + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + var o1 = (ItemKey) o; + return Objects.equals(model, o1.model) && stack.hasFoil() == o1.stack.hasFoil(); + } + + @Override + public int hashCode() { + int out = model.hashCode(); + out = 31 * out + Boolean.hashCode(stack.hasFoil()); + return out; + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/QuadrupedComponent.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/QuadrupedComponent.java new file mode 100644 index 000000000..86ec46864 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/QuadrupedComponent.java @@ -0,0 +1,59 @@ +package dev.engine_room.flywheel.vanilla; + +import java.util.List; + +import dev.engine_room.flywheel.api.instance.InstancerProvider; +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.MeshTree; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.util.Mth; + +public class QuadrupedComponent extends AgeableListComponent { + public final InstanceTree root; + + public final InstanceTree head; + public final InstanceTree body; + public final InstanceTree rightHindLeg; + public final InstanceTree leftHindLeg; + public final InstanceTree rightFrontLeg; + public final InstanceTree leftFrontLeg; + + public QuadrupedComponent(InstancerProvider instancerProvider, ModelLayerLocation layer, Material material, Config config) { + super(config); + + var meshTree = MeshTree.get(layer); + + this.root = InstanceTree.create(instancerProvider, meshTree, material); + + this.head = root.child("head"); + this.body = root.child("body"); + this.rightHindLeg = root.child("right_hind_leg"); + this.leftHindLeg = root.child("left_hind_leg"); + this.rightFrontLeg = root.child("right_front_leg"); + this.leftFrontLeg = root.child("left_front_leg"); + } + + public void setupAnim(float pLimbSwing, float pLimbSwingAmount, float pAgeInTicks, float pNetHeadYaw, float pHeadPitch) { + this.head.xRot = pHeadPitch * ((float) Math.PI / 180F); + this.head.yRot = pNetHeadYaw * ((float) Math.PI / 180F); + this.rightHindLeg.xRot = Mth.cos(pLimbSwing * 0.6662F) * 1.4F * pLimbSwingAmount; + this.leftHindLeg.xRot = Mth.cos(pLimbSwing * 0.6662F + (float) Math.PI) * 1.4F * pLimbSwingAmount; + this.rightFrontLeg.xRot = Mth.cos(pLimbSwing * 0.6662F + (float) Math.PI) * 1.4F * pLimbSwingAmount; + this.leftFrontLeg.xRot = Mth.cos(pLimbSwing * 0.6662F) * 1.4F * pLimbSwingAmount; + } + + public void delete() { + root.delete(); + } + + @Override + protected Iterable headParts() { + return List.of(head); + } + + @Override + protected Iterable bodyParts() { + return List.of(body, rightHindLeg, leftHindLeg, rightFrontLeg, leftFrontLeg); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java index 5b648c532..ffe72bb15 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java @@ -8,13 +8,15 @@ 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.TransformedInstance; import dev.engine_room.flywheel.lib.material.Materials; import dev.engine_room.flywheel.lib.model.ModelCache; +import dev.engine_room.flywheel.lib.model.RetexturedMesh; import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; +import dev.engine_room.flywheel.lib.model.part.MeshTree; import dev.engine_room.flywheel.lib.transform.TransformStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; @@ -28,10 +30,10 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { private static final ModelCache BASE_MODELS = new ModelCache<>(texture -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "base"), Materials.SHULKER); + return shulkerModel(texture, "base"); }); private static final ModelCache LID_MODELS = new ModelCache<>(texture -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "lid"), Materials.SHULKER); + return shulkerModel(texture, "lid"); }); private final TransformedInstance base; @@ -58,10 +60,10 @@ public ShulkerBoxVisual(VisualizationContext ctx, ShulkerBoxBlockEntity blockEnt TransformStack.of(stack) .translate(getVisualPosition()) .translate(0.5) - .scale(0.9995f) .rotate(rotation) - .scale(1, -1, -1) - .translateY(-1); + .scale(0.9995f) + .translate(0, -0.5, 0) + .scale(1, -1, -1); base = createBaseInstance(texture).setTransform(stack); base.setChanged(); @@ -130,4 +132,11 @@ protected void _delete() { base.delete(); lid.delete(); } + + private static Model shulkerModel(Material texture, String child) { + var mesh = MeshTree.get(ModelLayers.SHULKER) + .child(child) + .mesh(); + return new SingleMeshModel(new RetexturedMesh(mesh, texture.sprite()), Materials.SHULKER); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java index fb7bc7a10..dd33f953f 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java @@ -74,5 +74,12 @@ public static void init() { .factory(TntMinecartVisual::new) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); + + builder(EntityType.COW).factory(CowVisual::new) + .apply(); + + builder(EntityType.ITEM).factory(ItemVisual::new) + .skipVanillaRender(ItemVisual::isSupported) + .apply(); } } diff --git a/common/src/main/resources/flywheel.impl.mixins.json b/common/src/main/resources/flywheel.impl.mixins.json index c8d878e85..438b5991b 100644 --- a/common/src/main/resources/flywheel.impl.mixins.json +++ b/common/src/main/resources/flywheel.impl.mixins.json @@ -7,12 +7,14 @@ "client": [ "BlockEntityTypeMixin", "ClientChunkCacheMixin", - "ClientLevelMixin", "EntityTypeMixin", "LevelMixin", "LevelRendererMixin", "MinecraftMixin", + "ModelPartAccessor", "PoseStackMixin", + "VertexMultiConsumerDoubleMixin", + "VertexMultiConsumerMultipleMixin", "fix.FixFabulousDepthMixin", "fix.FixNormalScalingMixin", "visualmanage.BlockEntityMixin", diff --git a/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricItemModelBuilder.java b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricItemModelBuilder.java new file mode 100644 index 000000000..0f1c6079d --- /dev/null +++ b/fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/FabricItemModelBuilder.java @@ -0,0 +1,49 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.List; +import java.util.function.BiFunction; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.lib.model.SimpleModel; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; + +public class FabricItemModelBuilder extends ItemModelBuilder { + public FabricItemModelBuilder(ItemStack itemStack, BakedModel model) { + super(itemStack, model); + } + + @Override + public FabricItemModelBuilder poseStack(PoseStack poseStack) { + super.poseStack(poseStack); + return this; + } + + @Override + public FabricItemModelBuilder displayContext(ItemDisplayContext displayContext) { + super.displayContext(displayContext); + return this; + } + + @Override + public FabricItemModelBuilder leftHand(boolean leftHand) { + super.leftHand(leftHand); + return this; + } + + @Override + public FabricItemModelBuilder materialFunc(BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public SimpleModel build() { + // TODO + return new SimpleModel(List.of()); + } +} diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java index 8770f0abe..e2cde918a 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java @@ -7,7 +7,9 @@ import dev.engine_room.flywheel.lib.model.baked.BlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.FabricBakedModelBuilder; import dev.engine_room.flywheel.lib.model.baked.FabricBlockModelBuilder; +import dev.engine_room.flywheel.lib.model.baked.FabricItemModelBuilder; import dev.engine_room.flywheel.lib.model.baked.FabricMultiBlockModelBuilder; +import dev.engine_room.flywheel.lib.model.baked.ItemModelBuilder; import dev.engine_room.flywheel.lib.model.baked.MultiBlockModelBuilder; import dev.engine_room.flywheel.lib.util.ShadersModHandler; import net.fabricmc.loader.api.FabricLoader; @@ -16,6 +18,7 @@ import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; @@ -62,4 +65,9 @@ public boolean isRenderingShadowPass() { } }; } + + @Override + public ItemModelBuilder createItemModelBuilder(ItemStack stack, BakedModel model) { + return new FabricItemModelBuilder(stack, model); + } } 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 d72f235f7..ef923ffa6 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 @@ -13,6 +13,7 @@ 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; @@ -73,6 +74,7 @@ private static void setupLib() { 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/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java index 50162d4b9..68a5201b5 100644 --- a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/BakedModelBufferer.java @@ -1,14 +1,22 @@ package dev.engine_room.flywheel.lib.model.baked; import java.util.Iterator; +import java.util.Map; +import java.util.Set; import java.util.function.Function; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArraySet; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ItemBlockRenderTypes; +import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.renderer.block.ModelBlockRenderer; @@ -16,6 +24,8 @@ import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; import net.minecraft.util.RandomSource; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.RenderShape; import net.minecraft.world.level.block.state.BlockState; @@ -129,6 +139,22 @@ public static void bufferMultiBlock(BlockRenderDispatcher renderDispatcher, Iter } } + public static void bufferItem(BakedModel model, ItemStack stack, ItemDisplayContext displayContext, boolean leftHand, @Nullable PoseStack poseStack, ResultConsumer consumer) { + ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); + if (poseStack == null) { + poseStack = objects.identityPoseStack; + } + + var emitterSource = objects.emitterSource; + emitterSource.resultConsumer(consumer); + + Minecraft.getInstance() + .getItemRenderer() + .render(stack, displayContext, leftHand, poseStack, emitterSource, 0, OverlayTexture.NO_OVERLAY, model); + + emitterSource.end(); + } + public interface ResultConsumer { void accept(RenderType renderType, boolean shaded, RenderedBuffer data); } @@ -140,6 +166,8 @@ private static class ThreadLocalObjects { public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT]; public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); + public final MeshEmitterSource emitterSource = new MeshEmitterSource(); + { for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) { RenderType renderType = CHUNK_LAYERS[layerIndex]; @@ -147,4 +175,37 @@ private static class ThreadLocalObjects { } } } + + private static class MeshEmitterSource implements MultiBufferSource { + private final Map emitters = new Reference2ReferenceOpenHashMap<>(); + private final Set active = new ReferenceArraySet<>(); + + @UnknownNullability + private ResultConsumer resultConsumer; + + @Override + public VertexConsumer getBuffer(RenderType renderType) { + var out = emitters.computeIfAbsent(renderType, MeshEmitter::new); + + if (active.add(out)) { + out.prepare(resultConsumer); + } + + return out; + } + + public void end() { + for (var emitter : active) { + emitter.end(); + } + + active.clear(); + + resultConsumer = null; + } + + public void resultConsumer(ResultConsumer resultConsumer) { + this.resultConsumer = resultConsumer; + } + } } diff --git a/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeItemModelBuilder.java b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeItemModelBuilder.java new file mode 100644 index 000000000..c4b6e913b --- /dev/null +++ b/forge/src/lib/java/dev/engine_room/flywheel/lib/model/baked/ForgeItemModelBuilder.java @@ -0,0 +1,88 @@ +package dev.engine_room.flywheel.lib.model.baked; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.function.BiFunction; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.material.Transparency; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.model.ModelUtil; +import dev.engine_room.flywheel.lib.model.SimpleModel; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; + +public class ForgeItemModelBuilder extends ItemModelBuilder { + public static final Comparator GLINT_LAST = (a, b) -> { + if (a.material() + .transparency() == b.material() + .transparency()) { + return 0; + } + return a.material() + .transparency() == Transparency.GLINT ? 1 : -1; + }; + + public ForgeItemModelBuilder(ItemStack itemStack, BakedModel model) { + super(itemStack, model); + } + + @Override + public ForgeItemModelBuilder poseStack(PoseStack poseStack) { + super.poseStack(poseStack); + return this; + } + + @Override + public ForgeItemModelBuilder displayContext(ItemDisplayContext displayContext) { + super.displayContext(displayContext); + return this; + } + + @Override + public ForgeItemModelBuilder leftHand(boolean leftHand) { + super.leftHand(leftHand); + return this; + } + + @Override + public ForgeItemModelBuilder materialFunc(BiFunction materialFunc) { + super.materialFunc(materialFunc); + return this; + } + + @Override + public SimpleModel build() { + if (displayContext == null) { + displayContext = ItemDisplayContext.GROUND; + } + if (materialFunc == null) { + materialFunc = ModelUtil::getItemMaterial; + } + + ArrayList out = new ArrayList<>(); + + BakedModelBufferer.ResultConsumer resultConsumer = (renderType, shaded, data) -> { + Material material = materialFunc.apply(renderType, shaded); + if (material != null) { + var mesh = MeshHelper.blockVerticesToMesh(data, "source=ItemModelBuilder," + "itemStack=" + itemStack + ",renderType=" + renderType + ",shaded=" + shaded); + if (mesh.vertexCount() == 0) { + mesh.delete(); + } else { + out.add(new Model.ConfiguredMesh(material, mesh)); + } + } + }; + + BakedModelBufferer.bufferItem(model, itemStack, displayContext, leftHand, poseStack, resultConsumer); + + out.sort(GLINT_LAST); + + return new SimpleModel(ImmutableList.copyOf(out)); + } +} diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java index 7aa29a9b0..a2133ab90 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java @@ -9,7 +9,9 @@ import dev.engine_room.flywheel.lib.model.baked.BlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.ForgeBakedModelBuilder; import dev.engine_room.flywheel.lib.model.baked.ForgeBlockModelBuilder; +import dev.engine_room.flywheel.lib.model.baked.ForgeItemModelBuilder; import dev.engine_room.flywheel.lib.model.baked.ForgeMultiBlockModelBuilder; +import dev.engine_room.flywheel.lib.model.baked.ItemModelBuilder; import dev.engine_room.flywheel.lib.model.baked.MultiBlockModelBuilder; import dev.engine_room.flywheel.lib.util.ShadersModHandler; import net.irisshaders.iris.api.v0.IrisApi; @@ -18,6 +20,7 @@ import net.minecraft.client.renderer.block.ModelBlockRenderer; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.fml.ModList; @@ -78,4 +81,9 @@ public boolean isRenderingShadowPass() { } }; } + + @Override + public ItemModelBuilder createItemModelBuilder(ItemStack stack, BakedModel model) { + return new ForgeItemModelBuilder(stack, model); + } } 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 72e5ada1f..a52197d05 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 @@ -14,6 +14,7 @@ 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; @@ -112,6 +113,7 @@ private static void registerLibEventListeners(IEventBus forgeEventBus, IEventBus 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/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/VertexMultiConsumerDoubleMixin.java b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/VertexMultiConsumerDoubleMixin.java new file mode 100644 index 000000000..618ab744d --- /dev/null +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/VertexMultiConsumerDoubleMixin.java @@ -0,0 +1,36 @@ +package dev.engine_room.flywheel.impl.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.block.model.BakedQuad; + +/** + * blaze3d doesn't forward #putBulkData, but we want that for our MeshEmitter + */ +@Mixin(targets = "com.mojang.blaze3d.vertex.VertexMultiConsumer$Double") +public abstract class VertexMultiConsumerDoubleMixin implements VertexConsumer { + @Shadow + @Final + private VertexConsumer first; + + @Shadow + @Final + private VertexConsumer second; + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, float red, float green, float blue, float alpha, int packedLight, int packedOverlay, boolean readExistingColor) { + first.putBulkData(pose, bakedQuad, red, green, blue, alpha, packedLight, packedOverlay, readExistingColor); + second.putBulkData(pose, bakedQuad, red, green, blue, alpha, packedLight, packedOverlay, readExistingColor); + } + + @Override + public void putBulkData(PoseStack.Pose pPoseEntry, BakedQuad pQuad, float[] pColorMuls, float pRed, float pGreen, float pBlue, float alpha, int[] pCombinedLights, int pCombinedOverlay, boolean pMulColor) { + first.putBulkData(pPoseEntry, pQuad, pColorMuls, pRed, pGreen, pBlue, alpha, pCombinedLights, pCombinedOverlay, pMulColor); + second.putBulkData(pPoseEntry, pQuad, pColorMuls, pRed, pGreen, pBlue, alpha, pCombinedLights, pCombinedOverlay, pMulColor); + } +} diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/VertexMultiConsumerMultipleMixin.java b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/VertexMultiConsumerMultipleMixin.java new file mode 100644 index 000000000..e41868da1 --- /dev/null +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/VertexMultiConsumerMultipleMixin.java @@ -0,0 +1,31 @@ +package dev.engine_room.flywheel.impl.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.renderer.block.model.BakedQuad; + +@Mixin(targets = "com.mojang.blaze3d.vertex.VertexMultiConsumer$Multiple") +public abstract class VertexMultiConsumerMultipleMixin implements VertexConsumer { + @Shadow + @Final + private VertexConsumer[] delegates; + + @Override + public void putBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, float red, float green, float blue, float alpha, int packedLight, int packedOverlay, boolean readExistingColor) { + for (VertexConsumer delegate : this.delegates) { + delegate.putBulkData(pose, bakedQuad, red, green, blue, alpha, packedLight, packedOverlay, readExistingColor); + } + } + + @Override + public void putBulkData(PoseStack.Pose pPoseEntry, BakedQuad pQuad, float[] pColorMuls, float pRed, float pGreen, float pBlue, float alpha, int[] pCombinedLights, int pCombinedOverlay, boolean pMulColor) { + for (VertexConsumer delegate : this.delegates) { + delegate.putBulkData(pPoseEntry, pQuad, pColorMuls, pRed, pGreen, pBlue, alpha, pCombinedLights, pCombinedOverlay, pMulColor); + } + } +}