What a view!

- Add entity view vectors to BoundingBoxComponent.
- Add "centerline" material, similar to wireframe.
- Add SmartRecycler to create recyclers based on a parameter.
This commit is contained in:
Jozufozu 2024-02-01 20:31:08 -08:00
parent b0bc3d3145
commit 31148ae9b5
6 changed files with 91 additions and 24 deletions

View file

@ -12,6 +12,8 @@ public final class StandardMaterialShaders {
public static final MaterialShaders WIREFRAME = MaterialShaders.REGISTRY.registerAndGet(new SimpleMaterialShaders(Flywheel.rl("material/wireframe.vert"), Flywheel.rl("material/wireframe.frag"))); public static final MaterialShaders WIREFRAME = MaterialShaders.REGISTRY.registerAndGet(new SimpleMaterialShaders(Flywheel.rl("material/wireframe.vert"), Flywheel.rl("material/wireframe.frag")));
public static final MaterialShaders CENTERLINE = MaterialShaders.REGISTRY.registerAndGet(new SimpleMaterialShaders(Flywheel.rl("material/centerline.vert"), Flywheel.rl("material/centerline.frag")));
private StandardMaterialShaders() { private StandardMaterialShaders() {
} }

View file

@ -0,0 +1,37 @@
package com.jozufozu.flywheel.lib.visual;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import com.jozufozu.flywheel.api.instance.Instance;
public class SmartRecycler<K, I extends Instance> {
private final Function<K, I> factory;
private final Map<K, InstanceRecycler<I>> recyclers = new HashMap<>();
public SmartRecycler(Function<K, I> factory) {
this.factory = factory;
}
public void resetCount() {
recyclers.values()
.forEach(InstanceRecycler::resetCount);
}
public I get(K key) {
return recyclers.computeIfAbsent(key, k -> new InstanceRecycler<>(() -> factory.apply(k)))
.get();
}
public void discardExtra() {
recyclers.values()
.forEach(InstanceRecycler::discardExtra);
}
public void delete() {
recyclers.values()
.forEach(InstanceRecycler::delete);
recyclers.clear();
}
}

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.lib.visual.components; package com.jozufozu.flywheel.lib.visual.components;
import org.joml.Quaternionf;
import org.joml.Vector4f; import org.joml.Vector4f;
import org.joml.Vector4fc; import org.joml.Vector4fc;
@ -16,7 +17,7 @@ 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.EntityComponent;
import com.jozufozu.flywheel.lib.visual.InstanceRecycler; import com.jozufozu.flywheel.lib.visual.SmartRecycler;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.LightTexture;
@ -25,30 +26,38 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
public class BoundingBoxComponent implements EntityComponent { public class BoundingBoxComponent implements EntityComponent {
private static final Material MATERIAL = SimpleMaterial.builder() private static final Material WIREFRAME = SimpleMaterial.builder()
.shaders(StandardMaterialShaders.WIREFRAME) .shaders(StandardMaterialShaders.WIREFRAME)
.backfaceCulling(false) .backfaceCulling(false)
.build(); .build();
private static final Model MODEL = new SingleMeshModel(BoundingBoxMesh.INSTANCE, MATERIAL);
private static final Material CENTERLINE = SimpleMaterial.builder()
.shaders(StandardMaterialShaders.CENTERLINE)
.backfaceCulling(false)
.build();
private static final Model BOX = new SingleMeshModel(BoundingBoxMesh.INSTANCE, WIREFRAME);
// Should we try a single quad oriented to face the camera instead?
private static final Model LINE = new SingleMeshModel(BoundingBoxMesh.INSTANCE, CENTERLINE);
private final VisualizationContext context; private final VisualizationContext context;
private final Entity entity; private final Entity entity;
private boolean showEyeBox; private boolean showEyeBox;
private final InstanceRecycler<TransformedInstance> recycler; private final SmartRecycler<Model, TransformedInstance> recycler;
public BoundingBoxComponent(VisualizationContext context, Entity entity) { public BoundingBoxComponent(VisualizationContext context, Entity entity) {
this.context = context; this.context = context;
this.entity = entity; this.entity = entity;
this.showEyeBox = entity instanceof LivingEntity; this.showEyeBox = entity instanceof LivingEntity;
this.recycler = new InstanceRecycler<>(this::createInstance); this.recycler = new SmartRecycler<>(this::createInstance);
} }
private TransformedInstance createInstance() { private TransformedInstance createInstance(Model model) {
TransformedInstance instance = context.instancerProvider() TransformedInstance instance = context.instancerProvider()
.instancer(InstanceTypes.TRANSFORMED, MODEL) .instancer(InstanceTypes.TRANSFORMED, model)
.createInstance(); .createInstance();
instance.setBlockLight(LightTexture.block(LightTexture.FULL_BLOCK)); instance.setBlockLight(LightTexture.block(LightTexture.FULL_BLOCK));
instance.setChanged(); instance.setChanged();
@ -76,22 +85,33 @@ public class BoundingBoxComponent implements EntityComponent {
var bbWidth = entity.getBbWidth(); var bbWidth = entity.getBbWidth();
var bbHeight = entity.getBbHeight(); var bbHeight = entity.getBbHeight();
var bbWidthHalf = bbWidth * 0.5; var bbWidthHalf = bbWidth * 0.5;
recycler.get() recycler.get(BOX)
.loadIdentity() .loadIdentity()
.translate(entityX - bbWidthHalf, entityY, entityZ - bbWidthHalf) .translate(entityX - bbWidthHalf, entityY, entityZ - bbWidthHalf)
.scale(bbWidth, bbHeight, bbWidth) .scale(bbWidth, bbHeight, bbWidth)
.setChanged(); .setChanged();
// TODO: multipart entities and view vectors // TODO: multipart entities, but forge seems to have an
// injection for them so we'll need platform specific code.
if (showEyeBox) { if (showEyeBox) {
recycler.get() recycler.get(BOX)
.loadIdentity() .loadIdentity()
.translate(entityX - bbWidthHalf, entityY + entity.getEyeHeight() - 0.01, entityZ - bbWidthHalf) .translate(entityX - bbWidthHalf, entityY + entity.getEyeHeight() - 0.01, entityZ - bbWidthHalf)
.scale(bbWidth, 0.02f, bbWidth) .scale(bbWidth, 0.02f, bbWidth)
.setColor(255, 0, 0) .setColor(255, 0, 0)
.setChanged(); .setChanged();
} }
var viewVector = entity.getViewVector(context.partialTick());
recycler.get(LINE)
.loadIdentity()
.translate(entityX, entityY + entity.getEyeHeight(), entityZ)
.rotate(new Quaternionf().rotateTo(0, 1, 0, (float) viewVector.x, (float) viewVector.y, (float) viewVector.z))
.scale(0.02f, 2f, 0.02f)
.setColor(0, 0, 255)
.setChanged();
} }
recycler.discardExtra(); recycler.discardExtra();

View file

@ -4,6 +4,7 @@ import org.joml.Vector4f;
import org.joml.Vector4fc; import org.joml.Vector4fc;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.vertex.MutableVertexList; import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.api.visual.VisualFrameContext; import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.api.visualization.VisualizationContext;
@ -15,7 +16,7 @@ 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.EntityComponent;
import com.jozufozu.flywheel.lib.visual.InstanceRecycler; import com.jozufozu.flywheel.lib.visual.SmartRecycler;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis; import com.mojang.math.Axis;
@ -42,20 +43,18 @@ public class FireComponent implements EntityComponent {
private final Entity entity; private final Entity entity;
private final PoseStack stack = new PoseStack(); private final PoseStack stack = new PoseStack();
private final InstanceRecycler<TransformedInstance> fire0; private final SmartRecycler<Model, TransformedInstance> recycler;
private final InstanceRecycler<TransformedInstance> fire1;
public FireComponent(VisualizationContext context, Entity entity) { public FireComponent(VisualizationContext context, Entity entity) {
this.context = context; this.context = context;
this.entity = entity; this.entity = entity;
fire0 = new InstanceRecycler<>(() -> createInstance(ModelBakery.FIRE_0)); recycler = new SmartRecycler<>(this::createInstance);
fire1 = new InstanceRecycler<>(() -> createInstance(ModelBakery.FIRE_1));
} }
private TransformedInstance createInstance(net.minecraft.client.resources.model.Material texture) { private TransformedInstance createInstance(Model model) {
TransformedInstance instance = context.instancerProvider() TransformedInstance instance = context.instancerProvider()
.instancer(InstanceTypes.TRANSFORMED, FIRE_MODELS.get(texture)) .instancer(InstanceTypes.TRANSFORMED, model)
.createInstance(); .createInstance();
instance.setBlockLight(LightTexture.block(LightTexture.FULL_BLOCK)); instance.setBlockLight(LightTexture.block(LightTexture.FULL_BLOCK));
instance.setChanged(); instance.setChanged();
@ -70,15 +69,13 @@ public class FireComponent implements EntityComponent {
*/ */
@Override @Override
public void beginFrame(VisualFrameContext context) { public void beginFrame(VisualFrameContext context) {
fire0.resetCount(); recycler.resetCount();
fire1.resetCount();
if (entity.displayFireAnimation()) { if (entity.displayFireAnimation()) {
setupInstances(context); setupInstances(context);
} }
fire0.discardExtra(); recycler.discardExtra();
fire1.discardExtra();
} }
private void setupInstances(VisualFrameContext context) { private void setupInstances(VisualFrameContext context) {
@ -101,7 +98,7 @@ public class FireComponent implements EntityComponent {
stack.translate(0.0F, 0.0F, -0.3F + (float) ((int) maxHeight) * 0.02F); stack.translate(0.0F, 0.0F, -0.3F + (float) ((int) maxHeight) * 0.02F);
for (int i = 0; y < maxHeight; ++i) { for (int i = 0; y < maxHeight; ++i) {
var instance = (i % 2 == 0 ? this.fire0 : this.fire1).get() var instance = recycler.get(FIRE_MODELS.get(i % 2 == 0 ? ModelBakery.FIRE_0 : ModelBakery.FIRE_1))
.setTransform(stack) .setTransform(stack)
.scaleX(width) .scaleX(width)
.translate(0, y, z); .translate(0, y, z);
@ -123,8 +120,7 @@ public class FireComponent implements EntityComponent {
@Override @Override
public void delete() { public void delete() {
fire0.delete(); recycler.delete();
fire1.delete();
} }
private record FireMesh(TextureAtlasSprite sprite) implements QuadMesh { private record FireMesh(TextureAtlasSprite sprite) implements QuadMesh {

View file

@ -0,0 +1,10 @@
void flw_materialFragment() {
float toCenter = abs(flw_vertexTexCoord.s - 0.5);
// multiply by fwidth to get the width of the edge in screen space
if (flw_defaultLineWidth * fwidth(toCenter) < toCenter) {
discard;
}
flw_fragColor = flw_vertexColor;
}

View file

@ -0,0 +1,2 @@
void flw_materialVertex() {
}