93 days late

- Add BlazeBurnerVisual
- Offload blaze burner's tickAnimation to flywheel when enabled
- Add generic scrolling instance type for the flame, to be re-used for
  belts
- Mark flame model json as cutout
This commit is contained in:
Jozufozu 2024-07-22 15:26:26 -07:00
parent 3071e46ddc
commit b3aba93665
9 changed files with 373 additions and 15 deletions

View file

@ -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<BlazeBurnerBlockEntity> HEATER = REGISTRATE
.blockEntity("blaze_heater", BlazeBurnerBlockEntity::new)
.visual(() -> BlazeBurnerVisual::new, false)
.validBlocks(AllBlocks.BLAZE_BURNER)
.renderer(() -> BlazeBurnerRenderer::new)
.register();

View file

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

View file

@ -124,15 +124,7 @@ public class BlazeBurnerRenderer extends SafeBlockEntityRenderer<BlazeBurnerBloc
draw(flameBuffer, horizontalAngle, ms, cutout);
}
PartialModel blazeModel;
if (heatLevel.isAtLeast(HeatLevel.SEETHING)) {
blazeModel = blockAbove ? AllPartialModels.BLAZE_SUPER_ACTIVE : AllPartialModels.BLAZE_SUPER;
} else if (heatLevel.isAtLeast(HeatLevel.FADING)) {
blazeModel = blockAbove && heatLevel.isAtLeast(HeatLevel.KINDLED) ? AllPartialModels.BLAZE_ACTIVE
: AllPartialModels.BLAZE_IDLE;
} else {
blazeModel = AllPartialModels.BLAZE_INERT;
}
var blazeModel = getBlazeModel(heatLevel, blockAbove);
SuperByteBuffer blazeBuffer = CachedBufferer.partial(blazeModel, blockState);
if (modelTransform != null)
@ -195,6 +187,17 @@ public class BlazeBurnerRenderer extends SafeBlockEntityRenderer<BlazeBurnerBloc
ms.popPose();
}
public static PartialModel getBlazeModel(HeatLevel heatLevel, boolean blockAbove) {
if (heatLevel.isAtLeast(HeatLevel.SEETHING)) {
return blockAbove ? AllPartialModels.BLAZE_SUPER_ACTIVE : AllPartialModels.BLAZE_SUPER;
} else if (heatLevel.isAtLeast(HeatLevel.FADING)) {
return blockAbove && heatLevel.isAtLeast(HeatLevel.KINDLED) ? AllPartialModels.BLAZE_ACTIVE
: AllPartialModels.BLAZE_IDLE;
} else {
return AllPartialModels.BLAZE_INERT;
}
}
private static void draw(SuperByteBuffer buffer, float horizontalAngle, PoseStack ms, VertexConsumer vc) {
buffer.rotateCentered(horizontalAngle, Direction.UP)
.light(LightTexture.FULL_BRIGHT)

View file

@ -0,0 +1,256 @@
package com.simibubi.create.content.processing.burner;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.AllPartialModels;
import com.simibubi.create.AllSpriteShifts;
import com.simibubi.create.foundation.block.render.SpriteShiftEntry;
import com.simibubi.create.foundation.render.AllInstanceTypes;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.visual.DynamicVisual;
import dev.engine_room.flywheel.api.visual.TickableVisual;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.model.Models;
import dev.engine_room.flywheel.lib.model.baked.PartialModel;
import dev.engine_room.flywheel.lib.transform.Translate;
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
public class BlazeBurnerVisual extends AbstractBlockEntityVisual<BlazeBurnerBlockEntity> 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();
}
}
}

View file

@ -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<? extends ColoredLitInstance> 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;
}
}

View file

@ -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<ScrollInstance> 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<ActorInstance> ACTOR = SimpleInstanceType.builder(ActorInstance::new)
.cullShader(asResource("instance/cull/actor.glsl"))
.vertexShader(asResource("instance/actor.vert"))

View file

@ -0,0 +1,4 @@
void flw_transformBoundingSphere(in FlwInstance instance, inout vec3 center, inout float radius) {
radius += length(center - 0.5);
center += instance.pos;
}

View file

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

View file

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