Flywheel ECS

- Add SimpleEntityVisual which is composed of many EntityComponents.
- Would be nice to expand on this system, particularly to add components
  when registering visualizers.
- Misc. doc updates.
This commit is contained in:
Jozufozu 2024-02-01 17:40:36 -08:00
parent cdfe144131
commit 82470debb4
9 changed files with 102 additions and 46 deletions

View File

@ -5,28 +5,30 @@ import java.util.function.LongConsumer;
import net.minecraft.core.SectionPos; import net.minecraft.core.SectionPos;
/** /**
* A non-moving visual that listens to light updates. * A visual that listens to light updates.
* <br> *
* If your visual moves around in the world at all, you should use {@link TickableVisual} or {@link DynamicVisual}, * <p>If your visual moves around in the world at all, you should use {@link TickableVisual} or {@link DynamicVisual},
* and poll for light yourself rather than listening for updates. * and poll for light yourself along with listening for updates. When your visual moves to a different section, call
* {@link Notifier#notifySectionsChanged}.</p>
*/ */
public interface LitVisual extends Visual { public interface LitVisual extends Visual {
/** /**
* Called when a section this visual is contained in receives a light update. * Called when a section this visual is contained in receives a light update.
* <br> *
* Even if multiple sections are updated at the same time, this method will only be called once. * <p>Even if multiple sections are updated at the same time, this method will only be called once.</p>
* <br> *
* The implementation is free to parallelize calls to this method, as well as call into * <p>The implementation is free to parallelize calls to this method, as well as call into
* {@link DynamicVisual#beginFrame} simultaneously. It is safe to query/update light here, * {@link DynamicVisual#beginFrame} simultaneously. It is safe to query/update light here,
* but you must ensure proper synchronization if you want to mutate anything outside this * but you must ensure proper synchronization if you want to mutate anything outside this
* visual or anything that is also mutated by {@link DynamicVisual#beginFrame}. * visual or anything that is also mutated by {@link DynamicVisual#beginFrame}.</p>
*/ */
void updateLight(); void updateLight();
/** /**
* Collect the sections that this visual is contained in. * Collect the sections that this visual is contained in.
* <br> *
* This method is called upon visual creation, and the frame after {@link Notifier#notifySectionsChanged} is called. * <p>This method is called upon visual creation, and the frame after
* {@link Notifier#notifySectionsChanged} is called.</p>
* *
* @param consumer The consumer to provide the sections to. * @param consumer The consumer to provide the sections to.
* @see SectionPos#asLong * @see SectionPos#asLong
@ -35,9 +37,9 @@ public interface LitVisual extends Visual {
/** /**
* Set the notifier object. * Set the notifier object.
* <br> *
* This method is only called once, upon visual creation, * <p>This method is only called once, upon visual creation,
* after {@link #init} and before {@link #collectLightSections}. * after {@link #init} and before {@link #collectLightSections}.</p>
* *
* @param notifier The notifier. * @param notifier The notifier.
*/ */

View File

@ -11,13 +11,17 @@ package com.jozufozu.flywheel.api.visual;
public interface Visual { public interface Visual {
/** /**
* Initialize instances here. * Initialize instances here.
*
* <p>This method will be called exactly once upon visual creation.</p>
*/ */
void init(float partialTick); void init(float partialTick);
/** /**
* Update instances here. Good for when instances don't change very often and when animations are GPU based. * Update instances here.
* <br> *
* <br> If your animations are complex or more CPU driven, see {@link DynamicVisual} or {@link TickableVisual}. * <p>Good for when instances don't change very often and when animations are GPU based.
*
* <br>If your animations are complex or more CPU driven, see {@link DynamicVisual} or {@link TickableVisual}.</p>
*/ */
void update(float partialTick); void update(float partialTick);

View File

@ -33,17 +33,11 @@ import net.minecraft.world.phys.Vec3;
public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVisual implements EntityVisual<T> { public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVisual implements EntityVisual<T> {
protected final T entity; protected final T entity;
protected final EntityVisibilityTester visibilityTester; protected final EntityVisibilityTester visibilityTester;
protected final ShadowComponent shadow;
protected final FireComponent fire;
protected final BoundingBoxComponent boundingBox;
public AbstractEntityVisual(VisualizationContext ctx, T entity) { public AbstractEntityVisual(VisualizationContext ctx, T entity) {
super(ctx, entity.level()); super(ctx, entity.level());
this.entity = entity; this.entity = entity;
visibilityTester = new EntityVisibilityTester(entity, ctx.renderOrigin(), 1.5f); visibilityTester = new EntityVisibilityTester(entity, ctx.renderOrigin(), 1.5f);
shadow = new ShadowComponent(ctx, entity);
fire = new FireComponent(ctx, entity);
boundingBox = new BoundingBoxComponent(ctx, entity);
} }
@Override @Override
@ -93,11 +87,4 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
public boolean isVisible(FrustumIntersection frustum) { public boolean isVisible(FrustumIntersection frustum) {
return entity.noCulling || visibilityTester.check(frustum); return entity.noCulling || visibilityTester.check(frustum);
} }
@Override
protected void _delete() {
shadow.delete();
fire.delete();
boundingBox.delete();
}
} }

View File

@ -0,0 +1,9 @@
package com.jozufozu.flywheel.lib.visual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
public interface EntityComponent {
void beginFrame(VisualFrameContext context);
void delete();
}

View File

@ -0,0 +1,36 @@
package com.jozufozu.flywheel.lib.visual;
import java.util.ArrayList;
import java.util.List;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import net.minecraft.world.entity.Entity;
public class SimpleEntityVisual<T extends Entity> extends AbstractEntityVisual<T> implements DynamicVisual {
protected final List<EntityComponent> components = new ArrayList<>();
public SimpleEntityVisual(VisualizationContext ctx, T entity) {
super(ctx, entity);
}
public void addComponent(EntityComponent component) {
components.add(component);
}
@Override
public void beginFrame(VisualFrameContext ctx) {
for (EntityComponent component : components) {
component.beginFrame(ctx);
}
}
@Override
protected void _delete() {
for (EntityComponent component : components) {
component.delete();
}
}
}

View File

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.lib.visual; package com.jozufozu.flywheel.lib.visual.components;
import org.joml.Vector4f; import org.joml.Vector4f;
import org.joml.Vector4fc; import org.joml.Vector4fc;
@ -15,6 +15,8 @@ import com.jozufozu.flywheel.lib.material.StandardMaterialShaders;
import com.jozufozu.flywheel.lib.math.MoreMath; import com.jozufozu.flywheel.lib.math.MoreMath;
import com.jozufozu.flywheel.lib.model.QuadMesh; import com.jozufozu.flywheel.lib.model.QuadMesh;
import com.jozufozu.flywheel.lib.model.SingleMeshModel; import com.jozufozu.flywheel.lib.model.SingleMeshModel;
import com.jozufozu.flywheel.lib.visual.EntityComponent;
import com.jozufozu.flywheel.lib.visual.InstanceRecycler;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.LightTexture;
@ -22,7 +24,7 @@ import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
public class BoundingBoxComponent { public class BoundingBoxComponent implements EntityComponent {
private static final Material MATERIAL = SimpleMaterial.builder() private static final Material MATERIAL = SimpleMaterial.builder()
.shaders(StandardMaterialShaders.WIREFRAME) .shaders(StandardMaterialShaders.WIREFRAME)
.backfaceCulling(false) .backfaceCulling(false)
@ -53,10 +55,12 @@ public class BoundingBoxComponent {
return instance; return instance;
} }
public void showEyeBox(boolean renderEyeBox) { public BoundingBoxComponent showEyeBox(boolean renderEyeBox) {
this.showEyeBox = renderEyeBox; this.showEyeBox = renderEyeBox;
return this;
} }
@Override
public void beginFrame(VisualFrameContext context) { public void beginFrame(VisualFrameContext context) {
recycler.resetCount(); recycler.resetCount();
@ -93,6 +97,7 @@ public class BoundingBoxComponent {
recycler.discardExtra(); recycler.discardExtra();
} }
@Override
public void delete() { public void delete() {
recycler.delete(); recycler.delete();
} }

View File

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.lib.visual; package com.jozufozu.flywheel.lib.visual.components;
import org.joml.Vector4f; import org.joml.Vector4f;
import org.joml.Vector4fc; import org.joml.Vector4fc;
@ -14,6 +14,8 @@ import com.jozufozu.flywheel.lib.material.SimpleMaterial;
import com.jozufozu.flywheel.lib.model.ModelCache; import com.jozufozu.flywheel.lib.model.ModelCache;
import com.jozufozu.flywheel.lib.model.QuadMesh; import com.jozufozu.flywheel.lib.model.QuadMesh;
import com.jozufozu.flywheel.lib.model.SingleMeshModel; import com.jozufozu.flywheel.lib.model.SingleMeshModel;
import com.jozufozu.flywheel.lib.visual.EntityComponent;
import com.jozufozu.flywheel.lib.visual.InstanceRecycler;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis; import com.mojang.math.Axis;
@ -26,7 +28,7 @@ import net.minecraft.world.entity.Entity;
/** /**
* A component that uses instances to render the fire animation on an entity. * A component that uses instances to render the fire animation on an entity.
*/ */
public class FireComponent { public class FireComponent implements EntityComponent {
private static final Material FIRE_MATERIAL = SimpleMaterial.builderOf(Materials.CHUNK_CUTOUT_UNSHADED) private static final Material FIRE_MATERIAL = SimpleMaterial.builderOf(Materials.CHUNK_CUTOUT_UNSHADED)
.backfaceCulling(false) // Disable backface because we want to be able to flip the model. .backfaceCulling(false) // Disable backface because we want to be able to flip the model.
.build(); .build();
@ -66,6 +68,7 @@ public class FireComponent {
* *
* @param context The frame context. * @param context The frame context.
*/ */
@Override
public void beginFrame(VisualFrameContext context) { public void beginFrame(VisualFrameContext context) {
fire0.resetCount(); fire0.resetCount();
fire1.resetCount(); fire1.resetCount();
@ -118,6 +121,7 @@ public class FireComponent {
} }
} }
@Override
public void delete() { public void delete() {
fire0.delete(); fire0.delete();
fire1.delete(); fire1.delete();

View File

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.lib.visual; package com.jozufozu.flywheel.lib.visual.components;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Vector4f; import org.joml.Vector4f;
@ -16,6 +16,8 @@ import com.jozufozu.flywheel.lib.instance.ShadowInstance;
import com.jozufozu.flywheel.lib.material.SimpleMaterial; import com.jozufozu.flywheel.lib.material.SimpleMaterial;
import com.jozufozu.flywheel.lib.model.QuadMesh; import com.jozufozu.flywheel.lib.model.QuadMesh;
import com.jozufozu.flywheel.lib.model.SingleMeshModel; import com.jozufozu.flywheel.lib.model.SingleMeshModel;
import com.jozufozu.flywheel.lib.visual.EntityComponent;
import com.jozufozu.flywheel.lib.visual.InstanceRecycler;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.LightTexture;
@ -40,7 +42,7 @@ import net.minecraft.world.phys.shapes.VoxelShape;
* <br> * <br>
* The shadow will be cast on blocks at most {@code min(radius, 2 * strength)} blocks below the entity.</p> * The shadow will be cast on blocks at most {@code min(radius, 2 * strength)} blocks below the entity.</p>
*/ */
public class ShadowComponent { public class ShadowComponent implements EntityComponent {
private static final Material SHADOW_MATERIAL = SimpleMaterial.builder() private static final Material SHADOW_MATERIAL = SimpleMaterial.builder()
.texture(new ResourceLocation("textures/misc/shadow.png")) .texture(new ResourceLocation("textures/misc/shadow.png"))
.mipmap(false) .mipmap(false)
@ -88,8 +90,9 @@ public class ShadowComponent {
* *
* @param radius The radius of the shadow, in blocks. * @param radius The radius of the shadow, in blocks.
*/ */
public void radius(float radius) { public ShadowComponent radius(float radius) {
this.radius = Math.min(radius, 32); this.radius = Math.min(radius, 32);
return this;
} }
/** /**
@ -97,8 +100,9 @@ public class ShadowComponent {
* *
* @param strength The strength of the shadow. * @param strength The strength of the shadow.
*/ */
public void strength(float strength) { public ShadowComponent strength(float strength) {
this.strength = strength; this.strength = strength;
return this;
} }
/** /**
@ -107,6 +111,7 @@ public class ShadowComponent {
* *
* @param context The frame context. * @param context The frame context.
*/ */
@Override
public void beginFrame(VisualFrameContext context) { public void beginFrame(VisualFrameContext context) {
instances.resetCount(); instances.resetCount();
@ -210,6 +215,7 @@ public class ShadowComponent {
return shape; return shape;
} }
@Override
public void delete() { public void delete() {
instances.delete(); instances.delete();
} }

View File

@ -14,7 +14,10 @@ import com.jozufozu.flywheel.lib.model.ModelHolder;
import com.jozufozu.flywheel.lib.model.Models; import com.jozufozu.flywheel.lib.model.Models;
import com.jozufozu.flywheel.lib.model.SingleMeshModel; import com.jozufozu.flywheel.lib.model.SingleMeshModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartConverter; import com.jozufozu.flywheel.lib.model.part.ModelPartConverter;
import com.jozufozu.flywheel.lib.visual.AbstractEntityVisual; import com.jozufozu.flywheel.lib.visual.SimpleEntityVisual;
import com.jozufozu.flywheel.lib.visual.components.BoundingBoxComponent;
import com.jozufozu.flywheel.lib.visual.components.FireComponent;
import com.jozufozu.flywheel.lib.visual.components.ShadowComponent;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis; import com.mojang.math.Axis;
@ -26,7 +29,7 @@ import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVisual<T> implements TickableVisual, DynamicVisual { public class MinecartVisual<T extends AbstractMinecart> extends SimpleEntityVisual<T> implements TickableVisual, DynamicVisual {
public static final ModelHolder CHEST_BODY_MODEL = createBodyModelHolder(ModelLayers.CHEST_MINECART); public static final ModelHolder CHEST_BODY_MODEL = createBodyModelHolder(ModelLayers.CHEST_MINECART);
public static final ModelHolder COMMAND_BLOCK_BODY_MODEL = createBodyModelHolder(ModelLayers.COMMAND_BLOCK_MINECART); public static final ModelHolder COMMAND_BLOCK_BODY_MODEL = createBodyModelHolder(ModelLayers.COMMAND_BLOCK_MINECART);
public static final ModelHolder FURNACE_BODY_MODEL = createBodyModelHolder(ModelLayers.FURNACE_MINECART); public static final ModelHolder FURNACE_BODY_MODEL = createBodyModelHolder(ModelLayers.FURNACE_MINECART);
@ -48,7 +51,6 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
public MinecartVisual(VisualizationContext ctx, T entity, ModelHolder bodyModel) { public MinecartVisual(VisualizationContext ctx, T entity, ModelHolder bodyModel) {
super(ctx, entity); super(ctx, entity);
this.bodyModel = bodyModel; this.bodyModel = bodyModel;
shadow.radius(0.7f);
} }
private static ModelHolder createBodyModelHolder(ModelLayerLocation layer) { private static ModelHolder createBodyModelHolder(ModelLayerLocation layer) {
@ -59,6 +61,10 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
@Override @Override
public void init(float partialTick) { public void init(float partialTick) {
addComponent(new ShadowComponent(visualizationContext, entity).radius(0.7f));
addComponent(new FireComponent(visualizationContext, entity));
addComponent(new BoundingBoxComponent(visualizationContext, entity));
body = createBodyInstance(); body = createBodyInstance();
blockState = entity.getDisplayBlockState(); blockState = entity.getDisplayBlockState();
contents = createContentsInstance(); contents = createContentsInstance();
@ -109,9 +115,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
@Override @Override
public void beginFrame(VisualFrameContext context) { public void beginFrame(VisualFrameContext context) {
shadow.beginFrame(context); super.beginFrame(context);
fire.beginFrame(context);
boundingBox.beginFrame(context);
if (!isVisible(context.frustum())) { if (!isVisible(context.frustum())) {
return; return;
@ -210,7 +214,6 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
if (contents != null) { if (contents != null) {
contents.delete(); contents.delete();
} }
super._delete();
} }
public static boolean shouldSkipRender(AbstractMinecart minecart) { public static boolean shouldSkipRender(AbstractMinecart minecart) {