- 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
This commit is contained in:
Jozufozu 2024-04-08 22:50:04 -07:00
parent 09cb15e2ed
commit 163649e792
31 changed files with 1698 additions and 1 deletions

View file

@ -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

View file

@ -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);

View file

@ -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) {

View file

@ -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) {

View file

@ -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() {
}
}

View file

@ -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;

View file

@ -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() {
}

View file

@ -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<Mesh> meshes) {
int vertexCount = 0;
for (Mesh mesh : meshes) {

View file

@ -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<RenderType, MeshEmitter> emitters = new HashMap<>();
private final Set<MeshEmitter> active = new LinkedHashSet<>();
// Hack: we want glint to render after everything else so track it separately here
private final Set<MeshEmitter> 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<MeshEmitter> 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;
}
}
}

View file

@ -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<RenderType, Boolean, Material> 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<RenderType, Boolean, Material> materialFunc) {
this.materialFunc = materialFunc;
return this;
}
public SimpleModel build() {
if (displayContext == null) {
displayContext = ItemDisplayContext.GROUND;
}
if (materialFunc == null) {
materialFunc = ModelUtil::getItemMaterial;
}
var out = ImmutableList.<Model.ConfiguredMesh>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());
}
}

View file

@ -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();
}
}

View file

@ -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<InstanceTree> headParts();
protected abstract Iterable<InstanceTree> bodyParts();
public record Config(boolean scaleHead, float babyYHeadOffset, float babyZHeadOffset, float babyHeadScale,
float babyBodyScale, float bodyYOffset) {
}
}

View file

@ -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<Cow> {
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();
}
}

View file

@ -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<ItemEntity> {
private static final ThreadLocal<RandomSource> RANDOM = ThreadLocal.withInitial(RandomSource::createNewThreadLocalInstance);
public static final ModelCache<ItemKey> MODEL_CACHE = new ModelCache<>(stack -> {
return new ItemModelBuilder(stack.stack()).build();
});
private final PoseStack pPoseStack = new PoseStack();
private final BakedModel model;
private final InstanceRecycler<TransformedInstance> 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;
}
}
}

View file

@ -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<InstanceTree> headParts() {
return List.of(head);
}
@Override
protected Iterable<InstanceTree> bodyParts() {
return List.of(body, rightHindLeg, leftHindLeg, rightFrontLeg, leftFrontLeg);
}
}

View file

@ -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();
}
}

View file

@ -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<Direction> vanillin$visibleFaces();
}

View file

@ -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();
}

View file

@ -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<ModelLayerLocation, LayerDefinition> vanillin$roots();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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<CubeDefinition> vanillin$cubes();
@Accessor("partPose")
PartPose vanillin$partPose();
@Accessor("children")
Map<String, PartDefinition> vanillin$children();
}

View file

@ -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);
}
}

View file

@ -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<Entry> 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<String, InstanceTree> children;
private InstanceTree(InstancerProvider provider, Entry entry, Map<String, InstanceTree> 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<String, InstanceTree> children = new HashMap<>();
for (Map.Entry<String, MeshTree> 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<TransformedInstance> consumer) {
if (instance != null) {
consumer.accept(instance);
}
for (InstanceTree child : children.values()) {
child.walkInstances(consumer);
}
}
public void walkInstances(int i, ObjIntConsumer<TransformedInstance> 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<TransformedInstance> consumer) {
if (instance != null) {
consumer.accept(instance, i, j);
}
for (InstanceTree child : children.values()) {
child.walkInstances(i, j, consumer);
}
}
@FunctionalInterface
public interface ObjIntIntConsumer<T> {
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) {
}
}

View file

@ -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<String, MeshTree> children;
private MeshTree(PartPose initialPose, @Nullable Mesh mesh, Map<String, MeshTree> children) {
this.initialPose = initialPose;
this.mesh = mesh;
this.children = children;
}
public PartPose initialPose() {
return initialPose;
}
@Nullable
public Mesh mesh() {
return mesh;
}
public Map<String, MeshTree> 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<String, MeshTree> children = new HashMap<>();
for (Map.Entry<String, PartDefinition> 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<CubeDefinition> 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<CubeDefinition> 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);
}
}
}

View file

@ -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<ModelLayerLocation, MeshTree> 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() {
}
}

View file

@ -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() {
}
}

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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.);
}

View file

@ -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
}
}