diff --git a/src/main/java/com/simibubi/create/AllBlockEntityTypes.java b/src/main/java/com/simibubi/create/AllBlockEntityTypes.java index 790445320b..d0cccbc369 100644 --- a/src/main/java/com/simibubi/create/AllBlockEntityTypes.java +++ b/src/main/java/com/simibubi/create/AllBlockEntityTypes.java @@ -175,6 +175,7 @@ import com.simibubi.create.content.processing.basin.BasinBlockEntity; import com.simibubi.create.content.processing.basin.BasinRenderer; import com.simibubi.create.content.processing.burner.BlazeBurnerBlockEntity; import com.simibubi.create.content.processing.burner.BlazeBurnerRenderer; +import com.simibubi.create.content.processing.burner.BlazeBurnerVisual; import com.simibubi.create.content.redstone.analogLever.AnalogLeverBlockEntity; import com.simibubi.create.content.redstone.analogLever.AnalogLeverRenderer; import com.simibubi.create.content.redstone.analogLever.AnalogLeverVisual; @@ -662,6 +663,7 @@ public class AllBlockEntityTypes { public static final BlockEntityEntry HEATER = REGISTRATE .blockEntity("blaze_heater", BlazeBurnerBlockEntity::new) + .visual(() -> BlazeBurnerVisual::new, false) .validBlocks(AllBlocks.BLAZE_BURNER) .renderer(() -> BlazeBurnerRenderer::new) .register(); diff --git a/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java b/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java index 64873b435c..88395a783f 100644 --- a/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerBlockEntity.java @@ -14,6 +14,7 @@ import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.animation.LerpedFloat; import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser; +import dev.engine_room.flywheel.api.backend.BackendManager; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; @@ -75,7 +76,8 @@ public class BlazeBurnerBlockEntity extends SmartBlockEntity { super.tick(); if (level.isClientSide) { - tickAnimation(); + if (shouldTickAnimation()) + tickAnimation(); if (!isVirtual()) spawnParticles(getHeatLevelFromBlock(), 1); return; @@ -102,7 +104,13 @@ public class BlazeBurnerBlockEntity extends SmartBlockEntity { } @OnlyIn(Dist.CLIENT) - private void tickAnimation() { + private boolean shouldTickAnimation() { + // Offload the animation tick to the visual when flywheel in enabled + return !BackendManager.isBackendOn(); + } + + @OnlyIn(Dist.CLIENT) + void tickAnimation() { boolean active = getHeatLevelFromBlock().isAtLeast(HeatLevel.FADING) && isValidBlockAbove(); if (!active) { @@ -305,14 +313,14 @@ public class BlazeBurnerBlockEntity extends SmartBlockEntity { Vec3 c = VecHelper.getCenterOf(worldPosition); Vec3 v = c.add(VecHelper.offsetRandomly(Vec3.ZERO, r, .125f) .multiply(1, 0, 1)); - + if (r.nextInt(4) != 0) return; boolean empty = level.getBlockState(worldPosition.above()) .getCollisionShape(level, worldPosition.above()) .isEmpty(); - + if (empty || r.nextInt(8) == 0) level.addParticle(ParticleTypes.LARGE_SMOKE, v.x, v.y, v.z, 0, 0, 0); diff --git a/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerRenderer.java b/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerRenderer.java index 5dba7cca20..002b4c9366 100644 --- a/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerRenderer.java +++ b/src/main/java/com/simibubi/create/content/processing/burner/BlazeBurnerRenderer.java @@ -124,15 +124,7 @@ public class BlazeBurnerRenderer extends SafeBlockEntityRenderer implements SimpleDynamicVisual, SimpleTickableVisual { + + private final BlazeBurnerBlock.HeatLevel heatLevel; + + private final TransformedInstance head; + private final TransformedInstance smallRods; + private final TransformedInstance largeRods; + + private final boolean isInert; + + @Nullable + private ScrollInstance flame; + @Nullable + private TransformedInstance goggles; + @Nullable + private TransformedInstance hat; + + private boolean validBlockAbove; + + public BlazeBurnerVisual(VisualizationContext ctx, BlazeBurnerBlockEntity blockEntity, float partialTick) { + super(ctx, blockEntity, partialTick); + + heatLevel = blockEntity.getHeatLevelFromBlock(); + validBlockAbove = blockEntity.isValidBlockAbove(); + + PartialModel blazeModel = BlazeBurnerRenderer.getBlazeModel(heatLevel, validBlockAbove); + isInert = blazeModel == AllPartialModels.BLAZE_INERT; + + head = instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.partial(blazeModel)) + .createInstance(); + + head.light(LightTexture.FULL_BRIGHT); + + if (heatLevel.isAtLeast(BlazeBurnerBlock.HeatLevel.FADING)) { + PartialModel rodsModel = heatLevel == BlazeBurnerBlock.HeatLevel.SEETHING ? AllPartialModels.BLAZE_BURNER_SUPER_RODS + : AllPartialModels.BLAZE_BURNER_RODS; + PartialModel rodsModel2 = heatLevel == BlazeBurnerBlock.HeatLevel.SEETHING ? AllPartialModels.BLAZE_BURNER_SUPER_RODS_2 + : AllPartialModels.BLAZE_BURNER_RODS_2; + + smallRods = instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.partial(rodsModel)) + .createInstance(); + largeRods = instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.partial(rodsModel2)) + .createInstance(); + + smallRods.light(LightTexture.FULL_BRIGHT); + largeRods.light(LightTexture.FULL_BRIGHT); + } else { + smallRods = null; + largeRods = null; + } + } + + @Override + public void tick(TickableVisual.Context context) { + blockEntity.tickAnimation(); + } + + @Override + public void beginFrame(DynamicVisual.Context ctx) { + if (!isVisible(ctx.frustum()) || doDistanceLimitThisFrame(ctx)) { + return; + } + + float animation = blockEntity.headAnimation.getValue(ctx.partialTick()) * .175f; + + boolean validBlockAbove = animation > 0.125f; + + if (validBlockAbove != this.validBlockAbove) { + this.validBlockAbove = validBlockAbove; + + PartialModel blazeModel = BlazeBurnerRenderer.getBlazeModel(heatLevel, validBlockAbove); + instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.partial(blazeModel)) + .stealInstance(head); + } + + // Switch between showing/hiding the flame + if (validBlockAbove && flame == null) { + setupFlameInstance(); + } else if (!validBlockAbove && flame != null) { + flame.delete(); + flame = null; + } + + if (blockEntity.goggles && goggles == null) { + goggles = instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.partial(isInert ? AllPartialModels.BLAZE_GOGGLES_SMALL : AllPartialModels.BLAZE_GOGGLES)) + .createInstance(); + goggles.light(LightTexture.FULL_BRIGHT); + } else if (!blockEntity.goggles && goggles != null) { + goggles.delete(); + goggles = null; + } + + if (blockEntity.hat && hat == null) { + hat = instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.partial(AllPartialModels.TRAIN_HAT)) + .createInstance(); + hat.light(LightTexture.FULL_BRIGHT); + } else if (!blockEntity.hat && hat != null) { + hat.delete(); + hat = null; + } + + var hashCode = blockEntity.hashCode(); + float time = AnimationTickHolder.getRenderTime(level); + float renderTick = time + (hashCode % 13) * 16f; + float offsetMult = heatLevel.isAtLeast(BlazeBurnerBlock.HeatLevel.FADING) ? 64 : 16; + float offset = Mth.sin((float) ((renderTick / 16f) % (2 * Math.PI))) / offsetMult; + float headY = offset - (animation * .75f); + + float horizontalAngle = AngleHelper.rad(blockEntity.headAngle.getValue(ctx.partialTick())); + + head.loadIdentity() + .translate(getVisualPosition()) + .translateY(headY) + .translate(Translate.CENTER) + .rotateY(horizontalAngle) + .translateBack(Translate.CENTER) + .setChanged(); + + if (goggles != null) { + goggles.loadIdentity() + .translate(getVisualPosition()) + .translateY(headY + 8 / 16f) + .translate(Translate.CENTER) + .rotateY(horizontalAngle) + .translateBack(Translate.CENTER) + .setChanged(); + } + + if (hat != null) { + hat.loadIdentity() + .translate(getVisualPosition()) + .translateY(headY); + if (isInert) { + hat.translateY(0.5f) + .center() + .scale(0.75f) + .uncenter(); + } else { + hat.translateY(0.75f); + } + hat.rotateCentered(horizontalAngle + Mth.PI, Direction.UP) + .translate(0.5f, 0, 0.5f) + .light(LightTexture.FULL_BRIGHT); + + hat.setChanged(); + } + + if (smallRods != null) { + float offset1 = Mth.sin((float) ((renderTick / 16f + Math.PI) % (2 * Math.PI))) / offsetMult; + + smallRods.loadIdentity() + .translate(getVisualPosition()) + .translateY(offset1 + animation + .125f) + .setChanged(); + } + + if (largeRods != null) { + float offset2 = Mth.sin((float) ((renderTick / 16f + Math.PI / 2) % (2 * Math.PI))) / offsetMult; + + largeRods.loadIdentity() + .translate(getVisualPosition()) + .translateY(offset2 + animation - 3 / 16f) + .setChanged(); + } + } + + private void setupFlameInstance() { + flame = instancerProvider.instancer(AllInstanceTypes.SCROLLING, Models.partial(AllPartialModels.BLAZE_BURNER_FLAME)) + .createInstance(); + + flame.position(getVisualPosition()) + .light(LightTexture.FULL_BRIGHT); + + SpriteShiftEntry spriteShift = + heatLevel == BlazeBurnerBlock.HeatLevel.SEETHING ? AllSpriteShifts.SUPER_BURNER_FLAME : AllSpriteShifts.BURNER_FLAME; + + float spriteWidth = spriteShift.getTarget() + .getU1() + - spriteShift.getTarget() + .getU0(); + + float spriteHeight = spriteShift.getTarget() + .getV1() + - spriteShift.getTarget() + .getV0(); + + float speed = 1 / 32f + 1 / 64f * heatLevel.ordinal(); + + flame.speedU = speed / 2; + flame.speedV = speed; + + flame.scaleU = spriteWidth / 2; + flame.scaleV = spriteHeight / 2; + + flame.diffU = spriteShift.getTarget().getU0() - spriteShift.getOriginal().getU0(); + flame.diffV = spriteShift.getTarget().getV0() - spriteShift.getOriginal().getV0(); + } + + @Override + public void updateLight(float partialTick) { + } + + @Override + public void collectCrumblingInstances(Consumer<@Nullable Instance> consumer) { + + } + + @Override + protected void _delete() { + head.delete(); + if (smallRods != null) { + smallRods.delete(); + } + if (largeRods != null) { + largeRods.delete(); + } + if (flame != null) { + flame.delete(); + } + if (goggles != null) { + goggles.delete(); + } + if (hat != null) { + hat.delete(); + } + } +} diff --git a/src/main/java/com/simibubi/create/content/processing/burner/ScrollInstance.java b/src/main/java/com/simibubi/create/content/processing/burner/ScrollInstance.java new file mode 100644 index 0000000000..12bcc25e1a --- /dev/null +++ b/src/main/java/com/simibubi/create/content/processing/burner/ScrollInstance.java @@ -0,0 +1,35 @@ +package com.simibubi.create.content.processing.burner; + +import org.joml.Quaternionf; + +import dev.engine_room.flywheel.api.instance.InstanceHandle; +import dev.engine_room.flywheel.api.instance.InstanceType; +import dev.engine_room.flywheel.lib.instance.ColoredLitInstance; +import net.minecraft.core.Vec3i; + +public class ScrollInstance extends ColoredLitInstance { + public float x; + public float y; + public float z; + public final Quaternionf rotation = new Quaternionf(); + + public float speedU; + public float speedV; + + public float diffU; + public float diffV; + + public float scaleU; + public float scaleV; + + public ScrollInstance(InstanceType type, InstanceHandle handle) { + super(type, handle); + } + + public ScrollInstance position(Vec3i position) { + this.x = position.getX(); + this.y = position.getY(); + this.z = position.getZ(); + return this; + } +} diff --git a/src/main/java/com/simibubi/create/foundation/render/AllInstanceTypes.java b/src/main/java/com/simibubi/create/foundation/render/AllInstanceTypes.java index 44424329c1..dde9646d15 100644 --- a/src/main/java/com/simibubi/create/foundation/render/AllInstanceTypes.java +++ b/src/main/java/com/simibubi/create/foundation/render/AllInstanceTypes.java @@ -8,6 +8,7 @@ import com.simibubi.create.content.contraptions.actors.ActorInstance; import com.simibubi.create.content.kinetics.base.RotatingInstance; import com.simibubi.create.content.kinetics.belt.BeltInstance; import com.simibubi.create.content.logistics.flwdata.FlapInstance; +import com.simibubi.create.content.processing.burner.ScrollInstance; import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.api.layout.FloatRepr; @@ -88,6 +89,40 @@ public class AllInstanceTypes { }) .register(); + // TODO: use this for belts too + public static final InstanceType SCROLLING = SimpleInstanceType.builder(ScrollInstance::new) + .cullShader(asResource("instance/cull/scrolling.glsl")) + .vertexShader(asResource("instance/scrolling.vert")) + .layout(LayoutBuilder.create() + .vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4) + .vector("light", IntegerRepr.SHORT, 2) + .vector("overlay", IntegerRepr.SHORT, 2) + .vector("pos", FloatRepr.FLOAT, 3) + .vector("rotation", FloatRepr.FLOAT, 4) + .vector("speed", FloatRepr.FLOAT, 2) + .vector("diff", FloatRepr.FLOAT, 2) + .vector("scale", FloatRepr.FLOAT, 2) + .build()) + .writer((ptr, instance) -> { + MemoryUtil.memPutByte(ptr, instance.r); + MemoryUtil.memPutByte(ptr + 1, instance.g); + MemoryUtil.memPutByte(ptr + 2, instance.b); + MemoryUtil.memPutByte(ptr + 3, instance.a); + ExtraMemoryOps.put2x16(ptr + 4, instance.light); + ExtraMemoryOps.put2x16(ptr + 8, instance.overlay); + MemoryUtil.memPutFloat(ptr + 12, instance.x); + MemoryUtil.memPutFloat(ptr + 16, instance.y); + MemoryUtil.memPutFloat(ptr + 20, instance.z); + ExtraMemoryOps.putQuaternionf(ptr + 24, instance.rotation); + MemoryUtil.memPutFloat(ptr + 40, instance.speedU); + MemoryUtil.memPutFloat(ptr + 44, instance.speedV); + MemoryUtil.memPutFloat(ptr + 48, instance.diffU); + MemoryUtil.memPutFloat(ptr + 52, instance.diffV); + MemoryUtil.memPutFloat(ptr + 56, instance.scaleU); + MemoryUtil.memPutFloat(ptr + 60, instance.scaleV); + }) + .register(); + public static final InstanceType ACTOR = SimpleInstanceType.builder(ActorInstance::new) .cullShader(asResource("instance/cull/actor.glsl")) .vertexShader(asResource("instance/actor.vert")) diff --git a/src/main/resources/assets/create/flywheel/instance/cull/scrolling.glsl b/src/main/resources/assets/create/flywheel/instance/cull/scrolling.glsl new file mode 100644 index 0000000000..f16aeef0f9 --- /dev/null +++ b/src/main/resources/assets/create/flywheel/instance/cull/scrolling.glsl @@ -0,0 +1,4 @@ +void flw_transformBoundingSphere(in FlwInstance instance, inout vec3 center, inout float radius) { + radius += length(center - 0.5); + center += instance.pos; +} diff --git a/src/main/resources/assets/create/flywheel/instance/scrolling.vert b/src/main/resources/assets/create/flywheel/instance/scrolling.vert new file mode 100644 index 0000000000..73a97f002e --- /dev/null +++ b/src/main/resources/assets/create/flywheel/instance/scrolling.vert @@ -0,0 +1,14 @@ +#include "flywheel:util/quaternion.glsl" +#include "flywheel:util/matrix.glsl" + +void flw_instanceVertex(in FlwInstance instance) { + flw_vertexPos = vec4(rotateByQuaternion(flw_vertexPos.xyz - .5, instance.rotation) + instance.pos + .5, 1.); + + flw_vertexNormal = rotateByQuaternion(flw_vertexNormal, instance.rotation); + + vec2 scroll = fract(instance.speed * flw_renderTicks) * instance.scale; + + flw_vertexTexCoord = flw_vertexTexCoord + instance.diff + scroll; + flw_vertexLight = vec2(instance.light) / 256.; + flw_vertexOverlay = instance.overlay; +} diff --git a/src/main/resources/assets/create/models/block/blaze_burner/flame.json b/src/main/resources/assets/create/models/block/blaze_burner/flame.json index c111c8b3a1..209ec4d796 100644 --- a/src/main/resources/assets/create/models/block/blaze_burner/flame.json +++ b/src/main/resources/assets/create/models/block/blaze_burner/flame.json @@ -3,7 +3,8 @@ "loader": "forge:obj", "flip_v": true, "model": "create:models/block/blaze_burner/blaze_flame.obj", - "textures": { + "render_type": "minecraft:cutout", + "textures": { "0": "create:block/blaze_burner_flame" } -} \ No newline at end of file +}