From 163649e792fa15a2f8d5044d9a5e78ea4eb97ea4 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Mon, 8 Apr 2024 22:50:04 -0700 Subject: [PATCH] Cramming - Implement cow and item visuals (very buggy!) - Mostly copy vanilla's LivingEntityRenderer and ItemRenderer to implement the #beginFrame for CowVisual and ItemVisual - Cache translations of vanilla LayerDefinition into flywheel MeshTrees - Create models for individual parts in a second layer of cache - Add so many accessors to get into LayerDefinition and friends - Add glint material and material shader - Add flw_systemSeconds and flw_systemMillis uniforms - Create models from items via ItemModelBuilder - Shove MeshEmitter into vanilla's item renderer via a hacky implementation of MultiBufferSource - Fix options uniforms getting zeroed on a renderer reload --- build.gradle | 1 + .../java/com/jozufozu/flywheel/Flywheel.java | 2 + .../backend/engine/uniform/FrameUniforms.java | 6 +- .../backend/engine/uniform/Uniforms.java | 2 + .../flywheel/lib/material/Materials.java | 18 ++ .../flywheel/lib/material/SimpleMaterial.java | 32 ++ .../lib/material/StandardMaterialShaders.java | 2 + .../flywheel/lib/model/ModelUtil.java | 30 ++ .../lib/model/baked/BakedModelBufferer.java | 76 +++++ .../lib/model/baked/ItemModelBuilder.java | 92 ++++++ .../lib/vertex/WrappedVertexList.java | 156 ++++++++++ .../vanilla/AgeableListComponent.java | 55 ++++ .../jozufozu/flywheel/vanilla/CowVisual.java | 224 ++++++++++++++ .../jozufozu/flywheel/vanilla/ItemVisual.java | 197 ++++++++++++ .../flywheel/vanilla/QuadrupedComponent.java | 60 ++++ .../flywheel/vanilla/VanillaVisuals.java | 6 + .../vanilla/mixin/CubeDefinitionAccessor.java | 36 +++ .../mixin/CubeDeformationAccessor.java | 18 ++ .../vanilla/mixin/EntityModelSetAccessor.java | 16 + .../mixin/LayerDefinitionAccessor.java | 17 ++ .../mixin/MaterialDefinitionAccessor.java | 15 + .../vanilla/mixin/PartDefinitionAccessor.java | 23 ++ .../mixin/VertexMultiConsumerDoubleMixin.java | 37 +++ .../flywheel/vanilla/model/InstanceTree.java | 170 +++++++++++ .../flywheel/vanilla/model/MeshTree.java | 281 ++++++++++++++++++ .../flywheel/vanilla/model/MeshTreeCache.java | 28 ++ .../vanilla/model/RetexturedMesh.java | 41 +++ .../vanilla/model/RetexturingVertexList.java | 29 ++ .../flywheel/internal/uniforms/frame.glsl | 2 + .../flywheel/flywheel/material/glint.vert | 8 + .../resources/flywheel.vanilla.mixins.json | 19 ++ 31 files changed, 1698 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/jozufozu/flywheel/lib/model/baked/ItemModelBuilder.java create mode 100644 src/main/java/com/jozufozu/flywheel/lib/vertex/WrappedVertexList.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/AgeableListComponent.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/CowVisual.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/ItemVisual.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/QuadrupedComponent.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDefinitionAccessor.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDeformationAccessor.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/mixin/EntityModelSetAccessor.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/mixin/LayerDefinitionAccessor.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/mixin/MaterialDefinitionAccessor.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/mixin/PartDefinitionAccessor.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/mixin/VertexMultiConsumerDoubleMixin.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/model/InstanceTree.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTree.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTreeCache.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturedMesh.java create mode 100644 src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturingVertexList.java create mode 100644 src/main/resources/assets/flywheel/flywheel/material/glint.vert create mode 100644 src/main/resources/flywheel.vanilla.mixins.json diff --git a/build.gradle b/build.gradle index 6caf08ab3..c7e81a148 100644 --- a/build.gradle +++ b/build.gradle @@ -98,6 +98,7 @@ mixin { config 'flywheel.backend.mixins.json' config 'flywheel.impl.mixins.json' config 'flywheel.impl.sodium.mixins.json' + config 'flywheel.vanilla.mixins.json' debug.verbose = true debug.export = true diff --git a/src/main/java/com/jozufozu/flywheel/Flywheel.java b/src/main/java/com/jozufozu/flywheel/Flywheel.java index 864f616db..0982c6ca9 100644 --- a/src/main/java/com/jozufozu/flywheel/Flywheel.java +++ b/src/main/java/com/jozufozu/flywheel/Flywheel.java @@ -31,6 +31,7 @@ import com.jozufozu.flywheel.lib.util.LevelAttached; import com.jozufozu.flywheel.lib.util.ShadersModHandler; import com.jozufozu.flywheel.lib.util.StringUtil; import com.jozufozu.flywheel.vanilla.VanillaVisuals; +import com.jozufozu.flywheel.vanilla.model.MeshTreeCache; import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.ArgumentTypeInfos; @@ -106,6 +107,7 @@ public class Flywheel { modEventBus.addListener(BackendManagerImpl::onEndClientResourceReload); modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelCache.onEndClientResourceReload(e)); + modEventBus.addListener(MeshTreeCache::onEndClientResourceReload); modEventBus.addListener(ModelHolder::onEndClientResourceReload); modEventBus.addListener(PartialModel::onModelRegistry); diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java b/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java index 6505e185b..e3ceeefe0 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/FrameUniforms.java @@ -10,6 +10,7 @@ import com.jozufozu.flywheel.api.event.RenderContext; import com.jozufozu.flywheel.api.visualization.VisualizationManager; import com.jozufozu.flywheel.lib.math.MatrixMath; +import net.minecraft.Util; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; @@ -155,12 +156,15 @@ public class FrameUniforms implements UniformProvider { float partialTick = context.partialTick(); float renderTicks = ticks + partialTick; float renderSeconds = renderTicks / 20f; + float systemSeconds = Util.getMillis() / 1000f; MemoryUtil.memPutInt(ptr, ticks); MemoryUtil.memPutFloat(ptr + 4, partialTick); MemoryUtil.memPutFloat(ptr + 8, renderTicks); MemoryUtil.memPutFloat(ptr + 12, renderSeconds); - return ptr + 16; + MemoryUtil.memPutFloat(ptr + 16, systemSeconds); + MemoryUtil.memPutInt(ptr + 20, (int) (Util.getMillis() % Integer.MAX_VALUE)); + return ptr + 24; } private long writeCameraIn(long ptr) { diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/Uniforms.java b/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/Uniforms.java index 988869125..5e64edf19 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/Uniforms.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/uniform/Uniforms.java @@ -163,6 +163,8 @@ public class Uniforms { level.delete(); level = null; } + + optionsRequiresUpdate = true; } static long writeVec4(long ptr, float x, float y, float z, float w) { diff --git a/src/main/java/com/jozufozu/flywheel/lib/material/Materials.java b/src/main/java/com/jozufozu/flywheel/lib/material/Materials.java index d3a921e66..2ee5ba9da 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/material/Materials.java +++ b/src/main/java/com/jozufozu/flywheel/lib/material/Materials.java @@ -1,9 +1,12 @@ package com.jozufozu.flywheel.lib.material; +import com.jozufozu.flywheel.api.material.DepthTest; import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Transparency; +import com.jozufozu.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 { @@ -70,6 +73,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/src/main/java/com/jozufozu/flywheel/lib/material/SimpleMaterial.java b/src/main/java/com/jozufozu/flywheel/lib/material/SimpleMaterial.java index 0de829d5d..5f6eafaa6 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/material/SimpleMaterial.java +++ b/src/main/java/com/jozufozu/flywheel/lib/material/SimpleMaterial.java @@ -125,6 +125,38 @@ public class SimpleMaterial implements Material { 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/src/main/java/com/jozufozu/flywheel/lib/material/StandardMaterialShaders.java b/src/main/java/com/jozufozu/flywheel/lib/material/StandardMaterialShaders.java index bfea555bd..f0054d000 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/material/StandardMaterialShaders.java +++ b/src/main/java/com/jozufozu/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/src/main/java/com/jozufozu/flywheel/lib/model/ModelUtil.java b/src/main/java/com/jozufozu/flywheel/lib/model/ModelUtil.java index dc2a155de..c4fd5e6d2 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/model/ModelUtil.java +++ b/src/main/java/com/jozufozu/flywheel/lib/model/ModelUtil.java @@ -26,6 +26,7 @@ import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.renderer.block.ModelBlockRenderer; import net.minecraftforge.fml.util.ObfuscationReflectionHelper; @@ -100,6 +101,35 @@ public final class ModelUtil { 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/src/main/java/com/jozufozu/flywheel/lib/model/baked/BakedModelBufferer.java b/src/main/java/com/jozufozu/flywheel/lib/model/baked/BakedModelBufferer.java index de638d654..8ffec312d 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/model/baked/BakedModelBufferer.java +++ b/src/main/java/com/jozufozu/flywheel/lib/model/baked/BakedModelBufferer.java @@ -1,6 +1,10 @@ package com.jozufozu.flywheel.lib.model.baked; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; import java.util.function.Function; import org.jetbrains.annotations.Nullable; @@ -8,8 +12,11 @@ import org.jetbrains.annotations.Nullable; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +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; @@ -17,6 +24,8 @@ import net.minecraft.client.renderer.texture.OverlayTexture; 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; @@ -132,6 +141,23 @@ final class BakedModelBufferer { transformingWrapper.clear(); } + 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); + + var itemRenderer = Minecraft.getInstance() + .getItemRenderer(); + + itemRenderer.render(stack, displayContext, leftHand, poseStack, emitterSource, 0, OverlayTexture.NO_OVERLAY, model); + + emitterSource.end(); + } + public interface ResultConsumer { void accept(RenderType renderType, boolean shaded, RenderedBuffer data); } @@ -142,6 +168,8 @@ final class BakedModelBufferer { public final TransformingVertexConsumer transformingWrapper = new TransformingVertexConsumer(); + public final MeshEmitterSource emitterSource = new MeshEmitterSource(); + public final MeshEmitter[] emitters = new MeshEmitter[CHUNK_LAYER_AMOUNT]; { @@ -152,4 +180,52 @@ final class BakedModelBufferer { } } } + + private static class MeshEmitterSource implements MultiBufferSource { + private final Map emitters = new HashMap<>(); + + private final Set active = new LinkedHashSet<>(); + // Hack: we want glint to render after everything else so track it separately here + private final Set activeGlint = new LinkedHashSet<>(); + + @Nullable + private ResultConsumer resultConsumer; + + @Override + public VertexConsumer getBuffer(RenderType renderType) { + var out = emitters.computeIfAbsent(renderType, type -> new MeshEmitter(new BufferBuilder(type.bufferSize()), type)); + + Set active; + if (renderType == RenderType.glint() || renderType == RenderType.glintDirect() || renderType == RenderType.entityGlint() || renderType == RenderType.entityGlintDirect()) { + active = this.activeGlint; + } else { + active = this.active; + } + + if (active.add(out)) { + out.begin(resultConsumer); + } + + return out; + } + + public void end() { + for (var emitter : active) { + emitter.end(); + } + + for (var emitter : activeGlint) { + emitter.end(); + } + + active.clear(); + activeGlint.clear(); + + resultConsumer = null; + } + + public void resultConsumer(ResultConsumer resultConsumer) { + this.resultConsumer = resultConsumer; + } + } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/model/baked/ItemModelBuilder.java b/src/main/java/com/jozufozu/flywheel/lib/model/baked/ItemModelBuilder.java new file mode 100644 index 000000000..ca83f1290 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/model/baked/ItemModelBuilder.java @@ -0,0 +1,92 @@ +package com.jozufozu.flywheel.lib.model.baked; + +import java.util.function.BiFunction; + +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.ImmutableList; +import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.api.model.Model; +import com.jozufozu.flywheel.api.vertex.VertexView; +import com.jozufozu.flywheel.lib.memory.MemoryBlock; +import com.jozufozu.flywheel.lib.model.ModelUtil; +import com.jozufozu.flywheel.lib.model.SimpleMesh; +import com.jozufozu.flywheel.lib.model.SimpleModel; +import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer; +import com.jozufozu.flywheel.lib.vertex.NoOverlayVertexView; +import com.mojang.blaze3d.vertex.PoseStack; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; + +public class ItemModelBuilder { + private final ItemStack itemStack; + @Nullable + private PoseStack poseStack; + @Nullable + private ItemDisplayContext displayContext; + private boolean leftHand; + private int seed = 42; + @Nullable + private BiFunction materialFunc; + + public ItemModelBuilder(ItemStack itemStack) { + this.itemStack = itemStack; + } + + 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 seed(int seed) { + this.seed = seed; + return this; + } + + public ItemModelBuilder materialFunc(BiFunction materialFunc) { + this.materialFunc = materialFunc; + return this; + } + + public SimpleModel build() { + if (displayContext == null) { + displayContext = ItemDisplayContext.GROUND; + } + if (materialFunc == null) { + materialFunc = ModelUtil::getItemMaterial; + } + + var out = ImmutableList.builder(); + + ResultConsumer resultConsumer = (renderType, shaded, data) -> { + Material material = materialFunc.apply(renderType, shaded); + if (material != null) { + VertexView vertexView = new NoOverlayVertexView(); + MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, vertexView); + var mesh = new SimpleMesh(vertexView, meshData, "source=ItemModelBuilder," + "itemStack=" + itemStack + ",renderType=" + renderType + ",shaded=" + shaded); + out.add(new Model.ConfiguredMesh(material, mesh)); + } + }; + + var model = Minecraft.getInstance() + .getItemRenderer() + .getModel(itemStack, null, null, seed); + + BakedModelBufferer.bufferItem(model, itemStack, displayContext, leftHand, poseStack, resultConsumer); + + return new SimpleModel(out.build()); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/lib/vertex/WrappedVertexList.java b/src/main/java/com/jozufozu/flywheel/lib/vertex/WrappedVertexList.java new file mode 100644 index 000000000..1cc27d93f --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/lib/vertex/WrappedVertexList.java @@ -0,0 +1,156 @@ +package com.jozufozu.flywheel.lib.vertex; + +import com.jozufozu.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/src/main/java/com/jozufozu/flywheel/vanilla/AgeableListComponent.java b/src/main/java/com/jozufozu/flywheel/vanilla/AgeableListComponent.java new file mode 100644 index 000000000..39a1dda1c --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/AgeableListComponent.java @@ -0,0 +1,55 @@ +package com.jozufozu.flywheel.vanilla; + +import com.jozufozu.flywheel.vanilla.model.InstanceTree; +import com.mojang.blaze3d.vertex.PoseStack; + +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 p_102081_ : this.headParts()) { + p_102081_.render(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 p_102071_ : this.bodyParts()) { + p_102071_.render(pPoseStack); + } + pPoseStack.popPose(); + } else { + for (InstanceTree p_102061_ : this.headParts()) { + p_102061_.render(pPoseStack); + } + for (InstanceTree p_102051_ : this.bodyParts()) { + p_102051_.render(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/src/main/java/com/jozufozu/flywheel/vanilla/CowVisual.java b/src/main/java/com/jozufozu/flywheel/vanilla/CowVisual.java new file mode 100644 index 000000000..734aa0b6e --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/CowVisual.java @@ -0,0 +1,224 @@ +package com.jozufozu.flywheel.vanilla; + +import static net.minecraft.client.renderer.entity.LivingEntityRenderer.getOverlayCoords; +import static net.minecraft.client.renderer.entity.LivingEntityRenderer.isEntityUpsideDown; + +import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.api.visualization.VisualizationContext; +import com.jozufozu.flywheel.lib.material.SimpleMaterial; +import com.jozufozu.flywheel.lib.transform.TransformStack; +import com.jozufozu.flywheel.lib.visual.SimpleEntityVisual; +import com.jozufozu.flywheel.lib.visual.components.FireComponent; +import com.jozufozu.flywheel.lib.visual.components.HitboxComponent; +import com.jozufozu.flywheel.lib.visual.components.ShadowComponent; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; + +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 QuadrupedComponent cowQuadrupedComponent; + + private final PoseStack stack = new PoseStack(); + + public CowVisual(VisualizationContext ctx, Cow entity) { + super(ctx, entity); + } + + @Override + public void init(float 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)); + + super.init(partialTick); + } + + @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.setOverlay(o); + i.light(l); + i.setChanged(); + }); + + 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/src/main/java/com/jozufozu/flywheel/vanilla/ItemVisual.java b/src/main/java/com/jozufozu/flywheel/vanilla/ItemVisual.java new file mode 100644 index 000000000..4365c450c --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/ItemVisual.java @@ -0,0 +1,197 @@ +package com.jozufozu.flywheel.vanilla; + +import java.util.Objects; + +import com.jozufozu.flywheel.api.visualization.VisualizationContext; +import com.jozufozu.flywheel.lib.instance.InstanceTypes; +import com.jozufozu.flywheel.lib.instance.TransformedInstance; +import com.jozufozu.flywheel.lib.model.ModelCache; +import com.jozufozu.flywheel.lib.model.baked.ItemModelBuilder; +import com.jozufozu.flywheel.lib.transform.TransformStack; +import com.jozufozu.flywheel.lib.visual.InstanceRecycler; +import com.jozufozu.flywheel.lib.visual.SimpleEntityVisual; +import com.jozufozu.flywheel.lib.visual.components.FireComponent; +import com.jozufozu.flywheel.lib.visual.components.HitboxComponent; +import com.jozufozu.flywheel.lib.visual.components.ShadowComponent; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.resources.model.BakedModel; +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 new ItemModelBuilder(stack.stack()).build(); + }); + + private final PoseStack pPoseStack = new PoseStack(); + private final BakedModel model; + + private final InstanceRecycler instances; + + public ItemVisual(VisualizationContext ctx, ItemEntity entity) { + super(ctx, entity); + + model = Minecraft.getInstance() + .getItemRenderer() + .getModel(entity.getItem(), entity.level(), null, entity.getId()); + + instances = new InstanceRecycler<>(() -> instancerProvider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(new ItemKey(entity.getItem()))) + .createInstance()); + } + + @Override + public void init(float partialTick) { + super.init(partialTick); + + addComponent(new ShadowComponent(visualizationContext, entity).radius(0.15f) + .strength(0.75f)); + addComponent(new HitboxComponent(visualizationContext, entity)); + addComponent(new FireComponent(visualizationContext, entity)); + } + + @Override + public void beginFrame(Context ctx) { + if (!isVisible(ctx.frustum())) { + return; + } + + super.beginFrame(ctx); + + pPoseStack.setIdentity(); + TransformStack.of(pPoseStack) + .translate(getVisualPosition(ctx.partialTick())); + + instances.resetCount(); + pPoseStack.pushPose(); + 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); + } + } + + pPoseStack.popPose(); + 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) { + public ItemKey(ItemStack stack) { + this.stack = stack.copy(); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ItemKey itemKey = (ItemKey) o; + if (stack.isEmpty()) { + return itemKey.stack.isEmpty(); + } else { + return !itemKey.stack.isEmpty() && stack.getItem() == itemKey.stack.getItem() && Objects.equals(stack.getTag(), itemKey.stack.getTag()); + } + } + + @Override + public int hashCode() { + int out = stack.getItem() + .hashCode(); + out = 31 * out + (stack.getTag() != null ? stack.getTag() + .hashCode() : 0); + return out; + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/QuadrupedComponent.java b/src/main/java/com/jozufozu/flywheel/vanilla/QuadrupedComponent.java new file mode 100644 index 000000000..7a280ff5c --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/QuadrupedComponent.java @@ -0,0 +1,60 @@ +package com.jozufozu.flywheel.vanilla; + +import java.util.List; + +import com.jozufozu.flywheel.api.instance.InstancerProvider; +import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.vanilla.model.InstanceTree; +import com.jozufozu.flywheel.vanilla.model.MeshTreeCache; + +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 = MeshTreeCache.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/src/main/java/com/jozufozu/flywheel/vanilla/VanillaVisuals.java b/src/main/java/com/jozufozu/flywheel/vanilla/VanillaVisuals.java index 3e123d1dd..c1354dc90 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/VanillaVisuals.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/VanillaVisuals.java @@ -74,5 +74,11 @@ public class VanillaVisuals { .factory(TntMinecartVisual::new) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); + + builder(EntityType.COW).factory(CowVisual::new) + .apply(); + + builder(EntityType.ITEM).factory(ItemVisual::new) + .apply(); } } diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDefinitionAccessor.java b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDefinitionAccessor.java new file mode 100644 index 000000000..6c8f337fa --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDefinitionAccessor.java @@ -0,0 +1,36 @@ +package com.jozufozu.flywheel.vanilla.mixin; + +import java.util.Set; + +import org.joml.Vector3f; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.model.geom.builders.CubeDefinition; +import net.minecraft.client.model.geom.builders.CubeDeformation; +import net.minecraft.client.model.geom.builders.UVPair; +import net.minecraft.core.Direction; + +@Mixin(CubeDefinition.class) +public interface CubeDefinitionAccessor { + @Accessor("origin") + Vector3f vanillin$origin(); + + @Accessor("dimensions") + Vector3f vanillin$dimensions(); + + @Accessor("grow") + CubeDeformation vanillin$grow(); + + @Accessor("mirror") + boolean vanillin$mirror(); + + @Accessor("texCoord") + UVPair vanillin$texCoord(); + + @Accessor("texScale") + UVPair vanillin$texScale(); + + @Accessor("visibleFaces") + Set vanillin$visibleFaces(); +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDeformationAccessor.java b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDeformationAccessor.java new file mode 100644 index 000000000..775324fde --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/CubeDeformationAccessor.java @@ -0,0 +1,18 @@ +package com.jozufozu.flywheel.vanilla.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.model.geom.builders.CubeDeformation; + +@Mixin(CubeDeformation.class) +public interface CubeDeformationAccessor { + @Accessor("growX") + float vanillin$growX(); + + @Accessor("growY") + float vanillin$growY(); + + @Accessor("growZ") + float vanillin$growZ(); +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/mixin/EntityModelSetAccessor.java b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/EntityModelSetAccessor.java new file mode 100644 index 000000000..9010f084a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/EntityModelSetAccessor.java @@ -0,0 +1,16 @@ +package com.jozufozu.flywheel.vanilla.mixin; + +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.model.geom.EntityModelSet; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.builders.LayerDefinition; + +@Mixin(EntityModelSet.class) +public interface EntityModelSetAccessor { + @Accessor("roots") + Map vanillin$roots(); +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/mixin/LayerDefinitionAccessor.java b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/LayerDefinitionAccessor.java new file mode 100644 index 000000000..367266270 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/LayerDefinitionAccessor.java @@ -0,0 +1,17 @@ +package com.jozufozu.flywheel.vanilla.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.model.geom.builders.LayerDefinition; +import net.minecraft.client.model.geom.builders.MaterialDefinition; +import net.minecraft.client.model.geom.builders.MeshDefinition; + +@Mixin(LayerDefinition.class) +public interface LayerDefinitionAccessor { + @Accessor("mesh") + MeshDefinition vanillin$mesh(); + + @Accessor("material") + MaterialDefinition vanillin$material(); +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/mixin/MaterialDefinitionAccessor.java b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/MaterialDefinitionAccessor.java new file mode 100644 index 000000000..e0140a777 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/MaterialDefinitionAccessor.java @@ -0,0 +1,15 @@ +package com.jozufozu.flywheel.vanilla.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.model.geom.builders.MaterialDefinition; + +@Mixin(MaterialDefinition.class) +public interface MaterialDefinitionAccessor { + @Accessor("xTexSize") + int vanillin$xTexSize(); + + @Accessor("yTexSize") + int vanillin$yTexSize(); +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/mixin/PartDefinitionAccessor.java b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/PartDefinitionAccessor.java new file mode 100644 index 000000000..72ab1469d --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/PartDefinitionAccessor.java @@ -0,0 +1,23 @@ +package com.jozufozu.flywheel.vanilla.mixin; + +import java.util.List; +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.CubeDefinition; +import net.minecraft.client.model.geom.builders.PartDefinition; + +@Mixin(PartDefinition.class) +public interface PartDefinitionAccessor { + @Accessor("cubes") + List vanillin$cubes(); + + @Accessor("partPose") + PartPose vanillin$partPose(); + + @Accessor("children") + Map vanillin$children(); +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/mixin/VertexMultiConsumerDoubleMixin.java b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/VertexMultiConsumerDoubleMixin.java new file mode 100644 index 000000000..3bee7eaed --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/mixin/VertexMultiConsumerDoubleMixin.java @@ -0,0 +1,37 @@ +package com.jozufozu.flywheel.vanilla.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/src/main/java/com/jozufozu/flywheel/vanilla/model/InstanceTree.java b/src/main/java/com/jozufozu/flywheel/vanilla/model/InstanceTree.java new file mode 100644 index 000000000..ffb50a31a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/model/InstanceTree.java @@ -0,0 +1,170 @@ +package com.jozufozu.flywheel.vanilla.model; + +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.jozufozu.flywheel.api.instance.InstancerProvider; +import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.api.model.Mesh; +import com.jozufozu.flywheel.lib.instance.InstanceTypes; +import com.jozufozu.flywheel.lib.instance.TransformedInstance; +import com.jozufozu.flywheel.lib.model.ModelCache; +import com.jozufozu.flywheel.lib.model.SingleMeshModel; +import com.mojang.blaze3d.vertex.PoseStack; + +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 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 render(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.render(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((new Quaternionf()).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/src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTree.java b/src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTree.java new file mode 100644 index 000000000..3968b881f --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTree.java @@ -0,0 +1,281 @@ +package com.jozufozu.flywheel.vanilla.model; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.jozufozu.flywheel.api.model.Mesh; +import com.jozufozu.flywheel.lib.memory.MemoryBlock; +import com.jozufozu.flywheel.lib.model.SimpleMesh; +import com.jozufozu.flywheel.lib.vertex.PosTexNormalVertexView; +import com.jozufozu.flywheel.vanilla.mixin.CubeDefinitionAccessor; +import com.jozufozu.flywheel.vanilla.mixin.CubeDeformationAccessor; +import com.jozufozu.flywheel.vanilla.mixin.EntityModelSetAccessor; +import com.jozufozu.flywheel.vanilla.mixin.LayerDefinitionAccessor; +import com.jozufozu.flywheel.vanilla.mixin.MaterialDefinitionAccessor; +import com.jozufozu.flywheel.vanilla.mixin.PartDefinitionAccessor; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.CubeDefinition; +import net.minecraft.client.model.geom.builders.LayerDefinition; +import net.minecraft.client.model.geom.builders.PartDefinition; +import net.minecraft.core.Direction; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public class MeshTree { + 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 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) { + var entityModels = (EntityModelSetAccessor) Minecraft.getInstance() + .getEntityModels(); + + return convert(entityModels.vanillin$roots() + .get(location)); + } + + public static MeshTree convert(LayerDefinition layerDefinition) { + var accessor = (LayerDefinitionAccessor) layerDefinition; + var root = accessor.vanillin$mesh() + .getRoot(); + + var material = (MaterialDefinitionAccessor) accessor.vanillin$material(); + + int xTexSize = material.vanillin$xTexSize(); + int yTexSize = material.vanillin$yTexSize(); + + return convert(root, xTexSize, yTexSize); + } + + private static MeshTree convert(PartDefinition part, int xTexSize, int yTexSize) { + var accessor = (PartDefinitionAccessor) part; + + var cubes = accessor.vanillin$cubes(); + var initialPose = accessor.vanillin$partPose(); + var childDefinitions = accessor.vanillin$children(); + + Map children = new HashMap<>(); + + for (Map.Entry entry : childDefinitions.entrySet()) { + children.put(entry.getKey(), convert(entry.getValue(), xTexSize, yTexSize)); + } + + return new MeshTree(initialPose, convertCubes(cubes, xTexSize, yTexSize), children); + } + + private static Mesh convertCubes(List cubes, int xTexSize, int yTexSize) { + if (cubes.isEmpty()) { + return null; + } + var totalVisibleFaces = countVisibleFaces(cubes); + + if (totalVisibleFaces == 0) { + return null; + } + + var totalVertices = totalVisibleFaces * 4; + + var block = MemoryBlock.malloc(totalVertices * PosTexNormalVertexView.STRIDE); + var view = new PosTexNormalVertexView(); + + view.ptr(block.ptr()); + view.vertexCount(totalVertices); + + int vertexIndex = 0; + for (CubeDefinition cube : cubes) { + CubeDefinitionAccessor accessor = cast(cube); + + var origin = accessor.vanillin$origin(); + var dimensions = accessor.vanillin$dimensions(); + var grow = (CubeDeformationAccessor) accessor.vanillin$grow(); + var pMirror = accessor.vanillin$mirror(); + var texCoord = accessor.vanillin$texCoord(); + var texScale = accessor.vanillin$texScale(); + + var visibleFaces = accessor.vanillin$visibleFaces(); + + float pOriginX = origin.x(); + float pOriginY = origin.y(); + float pOriginZ = origin.z(); + + float pDimensionX = dimensions.x(); + float pDimensionY = dimensions.y(); + float pDimensionZ = dimensions.z(); + + float pGrowX = grow.vanillin$growX(); + float pGrowY = grow.vanillin$growY(); + float pGrowZ = grow.vanillin$growZ(); + + float pTexCoordU = texCoord.u(); + float pTexCoordV = texCoord.v(); + + float pTexScaleU = xTexSize * texScale.u(); + float pTexScaleV = yTexSize * texScale.v(); + + float f = pOriginX + pDimensionX; + float f1 = pOriginY + pDimensionY; + float f2 = pOriginZ + pDimensionZ; + pOriginX -= pGrowX; + pOriginY -= pGrowY; + pOriginZ -= pGrowZ; + f += pGrowX; + f1 += pGrowY; + f2 += pGrowZ; + if (pMirror) { + float f3 = f; + f = pOriginX; + pOriginX = f3; + } + + Vertex modelpart$vertex7 = new Vertex(pOriginX, pOriginY, pOriginZ, 0.0F, 0.0F); + Vertex modelpart$vertex = new Vertex(f, pOriginY, pOriginZ, 0.0F, 8.0F); + Vertex modelpart$vertex1 = new Vertex(f, f1, pOriginZ, 8.0F, 8.0F); + Vertex modelpart$vertex2 = new Vertex(pOriginX, f1, pOriginZ, 8.0F, 0.0F); + Vertex modelpart$vertex3 = new Vertex(pOriginX, pOriginY, f2, 0.0F, 0.0F); + Vertex modelpart$vertex4 = new Vertex(f, pOriginY, f2, 0.0F, 8.0F); + Vertex modelpart$vertex5 = new Vertex(f, f1, f2, 8.0F, 8.0F); + Vertex modelpart$vertex6 = new Vertex(pOriginX, f1, f2, 8.0F, 0.0F); + float f4 = pTexCoordU; + float f5 = pTexCoordU + pDimensionZ; + float f6 = pTexCoordU + pDimensionZ + pDimensionX; + float f7 = pTexCoordU + pDimensionZ + pDimensionX + pDimensionX; + float f8 = pTexCoordU + pDimensionZ + pDimensionX + pDimensionZ; + float f9 = pTexCoordU + pDimensionZ + pDimensionX + pDimensionZ + pDimensionX; + float f10 = pTexCoordV; + float f11 = pTexCoordV + pDimensionZ; + float f12 = pTexCoordV + pDimensionZ + pDimensionY; + if (visibleFaces.contains(Direction.DOWN)) { + addFace(view, vertexIndex, new Vertex[]{modelpart$vertex4, modelpart$vertex3, modelpart$vertex7, modelpart$vertex}, f5, f10, f6, f11, pTexScaleU, pTexScaleV, pMirror, Direction.DOWN); + vertexIndex += 4; + } + + if (visibleFaces.contains(Direction.UP)) { + addFace(view, vertexIndex, new Vertex[]{modelpart$vertex1, modelpart$vertex2, modelpart$vertex6, modelpart$vertex5}, f6, f11, f7, f10, pTexScaleU, pTexScaleV, pMirror, Direction.UP); + vertexIndex += 4; + } + + if (visibleFaces.contains(Direction.WEST)) { + addFace(view, vertexIndex, new Vertex[]{modelpart$vertex7, modelpart$vertex3, modelpart$vertex6, modelpart$vertex2}, f4, f11, f5, f12, pTexScaleU, pTexScaleV, pMirror, Direction.WEST); + vertexIndex += 4; + } + + if (visibleFaces.contains(Direction.NORTH)) { + addFace(view, vertexIndex, new Vertex[]{modelpart$vertex, modelpart$vertex7, modelpart$vertex2, modelpart$vertex1}, f5, f11, f6, f12, pTexScaleU, pTexScaleV, pMirror, Direction.NORTH); + vertexIndex += 4; + } + + if (visibleFaces.contains(Direction.EAST)) { + addFace(view, vertexIndex, new Vertex[]{modelpart$vertex4, modelpart$vertex, modelpart$vertex1, modelpart$vertex5}, f6, f11, f8, f12, pTexScaleU, pTexScaleV, pMirror, Direction.EAST); + vertexIndex += 4; + } + + if (visibleFaces.contains(Direction.SOUTH)) { + addFace(view, vertexIndex, new Vertex[]{modelpart$vertex3, modelpart$vertex4, modelpart$vertex5, modelpart$vertex6}, f8, f11, f9, f12, pTexScaleU, pTexScaleV, pMirror, Direction.SOUTH); + vertexIndex += 4; + } + } + + return new SimpleMesh(view, block); + } + + private static void addFace(PosTexNormalVertexView view, int index, Vertex[] pVertices, float pU1, float pV1, float pU2, float pV2, float pTextureWidth, float pTextureHeight, boolean pMirror, Direction pDirection) { + float f = 0.0F / pTextureWidth; + float f1 = 0.0F / pTextureHeight; + pVertices[0] = pVertices[0].remap(pU2 / pTextureWidth - f, pV1 / pTextureHeight + f1); + pVertices[1] = pVertices[1].remap(pU1 / pTextureWidth + f, pV1 / pTextureHeight + f1); + pVertices[2] = pVertices[2].remap(pU1 / pTextureWidth + f, pV2 / pTextureHeight - f1); + pVertices[3] = pVertices[3].remap(pU2 / pTextureWidth - f, pV2 / pTextureHeight - f1); + if (pMirror) { + int i = pVertices.length; + + for (int j = 0; j < i / 2; ++j) { + Vertex modelpart$vertex = pVertices[j]; + pVertices[j] = pVertices[i - 1 - j]; + pVertices[i - 1 - j] = modelpart$vertex; + } + } + + var normal = pDirection.step(); + if (pMirror) { + normal.mul(-1.0F, 1.0F, 1.0F); + } + + int i = index; + for (Vertex modelpart$vertex : pVertices) { + float f3 = modelpart$vertex.x() / 16.0F; + float f4 = modelpart$vertex.y() / 16.0F; + float f5 = modelpart$vertex.z() / 16.0F; + view.x(i, f3); + view.y(i, f4); + view.z(i, f5); + view.u(i, modelpart$vertex.u()); + view.v(i, modelpart$vertex.v()); + view.normalX(i, normal.x()); + view.normalY(i, normal.y()); + view.normalZ(i, normal.z()); + + ++i; + } + } + + private static int countVisibleFaces(List cubes) { + int totalVisibleFaces = 0; + for (CubeDefinition cube : cubes) { + totalVisibleFaces += cast(cube).vanillin$visibleFaces() + .size(); + } + return totalVisibleFaces; + } + + @NotNull + private static CubeDefinitionAccessor cast(CubeDefinition cube) { + return (CubeDefinitionAccessor) (Object) cube; + } + + @OnlyIn(Dist.CLIENT) + record Vertex(float x, float y, float z, float u, float v) { + public Vertex remap(float pU, float pV) { + return new Vertex(x, y, z, pU, pV); + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTreeCache.java b/src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTreeCache.java new file mode 100644 index 000000000..cb95b3966 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/model/MeshTreeCache.java @@ -0,0 +1,28 @@ +package com.jozufozu.flywheel.vanilla.model; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jetbrains.annotations.ApiStatus; + +import com.jozufozu.flywheel.api.event.EndClientResourceReloadEvent; + +import net.minecraft.client.model.geom.ModelLayerLocation; + +public class MeshTreeCache { + private static final Map MESH_TREES = new ConcurrentHashMap<>(); + + public static MeshTree get(ModelLayerLocation key) { + return MESH_TREES.computeIfAbsent(key, MeshTree::convert); + } + + @ApiStatus.Internal + public static void onEndClientResourceReload(EndClientResourceReloadEvent event) { + MESH_TREES.values() + .forEach(MeshTree::delete); + MESH_TREES.clear(); + } + + private MeshTreeCache() { + } +} diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturedMesh.java b/src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturedMesh.java new file mode 100644 index 000000000..8a751d0e2 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturedMesh.java @@ -0,0 +1,41 @@ +package com.jozufozu.flywheel.vanilla.model; + +import org.joml.Vector4fc; + +import com.jozufozu.flywheel.api.model.IndexSequence; +import com.jozufozu.flywheel.api.model.Mesh; +import com.jozufozu.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/src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturingVertexList.java b/src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturingVertexList.java new file mode 100644 index 000000000..7d82b1e5b --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/vanilla/model/RetexturingVertexList.java @@ -0,0 +1,29 @@ +package com.jozufozu.flywheel.vanilla.model; + +import com.jozufozu.flywheel.api.vertex.MutableVertexList; +import com.jozufozu.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)); + } + + @Override + public void v(int index, float v) { + super.v(index, sprite.getV(v)); + } +} diff --git a/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl b/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl index 7e5ead7eb..ed0bb80d2 100644 --- a/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl +++ b/src/main/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl @@ -39,6 +39,8 @@ layout(std140) uniform _FlwFrameUniforms { 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/src/main/resources/assets/flywheel/flywheel/material/glint.vert b/src/main/resources/assets/flywheel/flywheel/material/glint.vert new file mode 100644 index 000000000..eca6ddef7 --- /dev/null +++ b/src/main/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/src/main/resources/flywheel.vanilla.mixins.json b/src/main/resources/flywheel.vanilla.mixins.json new file mode 100644 index 000000000..cf90f7ddf --- /dev/null +++ b/src/main/resources/flywheel.vanilla.mixins.json @@ -0,0 +1,19 @@ +{ + "required" : true, + "minVersion" : "0.8", + "package" : "com.jozufozu.flywheel.vanilla.mixin", + "compatibilityLevel" : "JAVA_17", + "refmap" : "flywheel.refmap.json", + "client" : [ + "CubeDefinitionAccessor", + "CubeDeformationAccessor", + "EntityModelSetAccessor", + "LayerDefinitionAccessor", + "MaterialDefinitionAccessor", + "PartDefinitionAccessor", + "VertexMultiConsumerDoubleMixin" + ], + "injectors" : { + "defaultRequire" : 1 + } +}