mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-03 16:54:57 +01:00
Treefactors
- Add MeshTree and InstanceTree - Deprecate ModelPartConverter for removal - Refactor ChestVisual to use InstanceTree - Combine double chest light in ChestVisual
This commit is contained in:
parent
ddb0450105
commit
295ebc7573
13 changed files with 668 additions and 83 deletions
|
@ -1,11 +1,15 @@
|
|||
package dev.engine_room.flywheel.lib.internal;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import dev.engine_room.flywheel.api.internal.DependencyInjection;
|
||||
import dev.engine_room.flywheel.lib.transform.PoseTransformStack;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
|
||||
public interface FlwLibLink {
|
||||
FlwLibLink INSTANCE = DependencyInjection.load(FlwLibLink.class, "dev.engine_room.flywheel.impl.FlwLibLinkImpl");
|
||||
|
@ -13,4 +17,8 @@ public interface FlwLibLink {
|
|||
Logger getLogger();
|
||||
|
||||
PoseTransformStack getPoseTransformStackOf(PoseStack stack);
|
||||
|
||||
Map<String, ModelPart> getModelPartChildren(ModelPart part);
|
||||
|
||||
void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package dev.engine_room.flywheel.lib.model;
|
||||
|
||||
import org.joml.Vector4fc;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.IndexSequence;
|
||||
import dev.engine_room.flywheel.api.model.Mesh;
|
||||
import dev.engine_room.flywheel.api.vertex.MutableVertexList;
|
||||
import dev.engine_room.flywheel.lib.vertex.VertexTransformations;
|
||||
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(vertexList);
|
||||
VertexTransformations.retexture(vertexList, sprite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexSequence indexSequence() {
|
||||
return mesh.indexSequence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexCount() {
|
||||
return mesh.indexCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector4fc boundingSphere() {
|
||||
return mesh.boundingSphere();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.ObjIntConsumer;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3fc;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.InstancerProvider;
|
||||
import dev.engine_room.flywheel.api.material.Material;
|
||||
import dev.engine_room.flywheel.api.model.Mesh;
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
|
||||
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
|
||||
import dev.engine_room.flywheel.lib.model.ModelCache;
|
||||
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
|
||||
public final class InstanceTree {
|
||||
private static final ModelCache<Model.ConfiguredMesh> MODEL_CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material()));
|
||||
|
||||
@Nullable
|
||||
private final TransformedInstance instance;
|
||||
private final PartPose initialPose;
|
||||
@Unmodifiable
|
||||
private final Map<String, InstanceTree> children;
|
||||
|
||||
private final Quaternionf rotation = new Quaternionf();
|
||||
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
public float xRot;
|
||||
public float yRot;
|
||||
public float zRot;
|
||||
public float xScale;
|
||||
public float yScale;
|
||||
public float zScale;
|
||||
public boolean visible = true;
|
||||
public boolean skipDraw;
|
||||
|
||||
private InstanceTree(@Nullable TransformedInstance instance, PartPose initialPose, @Unmodifiable Map<String, InstanceTree> children) {
|
||||
this.instance = instance;
|
||||
this.initialPose = initialPose;
|
||||
this.children = children;
|
||||
resetPose();
|
||||
}
|
||||
|
||||
private static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction<String, Mesh, Model.ConfiguredMesh> meshFinalizerFunc, String path) {
|
||||
Map<String, InstanceTree> children = new HashMap<>();
|
||||
String pathSlash = path + "/";
|
||||
|
||||
meshTree.children().forEach((name, meshTreeChild) -> {
|
||||
children.put(name, InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name));
|
||||
});
|
||||
|
||||
Mesh mesh = meshTree.mesh();
|
||||
TransformedInstance instance;
|
||||
if (mesh != null) {
|
||||
Model.ConfiguredMesh configuredMesh = meshFinalizerFunc.apply(path, mesh);
|
||||
instance = provider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(configuredMesh))
|
||||
.createInstance();
|
||||
} else {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
return new InstanceTree(instance, meshTree.initialPose(), Collections.unmodifiableMap(children));
|
||||
}
|
||||
|
||||
public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction<String, Mesh, Model.ConfiguredMesh> meshFinalizerFunc) {
|
||||
return create(provider, meshTree, meshFinalizerFunc, "");
|
||||
}
|
||||
|
||||
public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, BiFunction<String, Mesh, Model.ConfiguredMesh> meshFinalizerFunc) {
|
||||
return create(provider, MeshTree.of(layer), meshFinalizerFunc);
|
||||
}
|
||||
|
||||
public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, Material material) {
|
||||
return create(provider, meshTree, (path, mesh) -> new Model.ConfiguredMesh(material, mesh));
|
||||
}
|
||||
|
||||
public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, Material material) {
|
||||
return create(provider, MeshTree.of(layer), material);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TransformedInstance instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public PartPose initialPose() {
|
||||
return initialPose;
|
||||
}
|
||||
|
||||
@Unmodifiable
|
||||
public Map<String, InstanceTree> children() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
return children.containsKey(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InstanceTree child(String name) {
|
||||
return children.get(name);
|
||||
}
|
||||
|
||||
public InstanceTree childOrThrow(String name) {
|
||||
InstanceTree child = child(name);
|
||||
|
||||
if (child == null) {
|
||||
throw new NoSuchElementException("Can't find part " + name);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
public void traverse(Consumer<? super TransformedInstance> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance);
|
||||
}
|
||||
for (InstanceTree child : children.values()) {
|
||||
child.traverse(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public void traverse(int i, ObjIntConsumer<? super TransformedInstance> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance, i);
|
||||
}
|
||||
for (InstanceTree child : children.values()) {
|
||||
child.traverse(i, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public void traverse(int i, int j, ObjIntIntConsumer<? super TransformedInstance> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance, i, j);
|
||||
}
|
||||
for (InstanceTree child : children.values()) {
|
||||
child.traverse(i, j, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
public void translateAndRotate(PoseStack poseStack) {
|
||||
poseStack.translate(x / 16.0F, y / 16.0F, z / 16.0F);
|
||||
|
||||
if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) {
|
||||
poseStack.mulPose(rotation.rotationZYX(zRot, yRot, xRot));
|
||||
}
|
||||
|
||||
if (xScale != 1.0F || yScale != 1.0F || zScale != 1.0F) {
|
||||
poseStack.scale(xScale, yScale, zScale);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateInstances(PoseStack poseStack) {
|
||||
if (visible) {
|
||||
poseStack.pushPose();
|
||||
translateAndRotate(poseStack);
|
||||
|
||||
if (instance != null && !skipDraw) {
|
||||
instance.setTransform(poseStack.last())
|
||||
.setChanged();
|
||||
}
|
||||
|
||||
for (InstanceTree child : children.values()) {
|
||||
child.updateInstances(poseStack);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
public void pos(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public void rotation(float xRot, float yRot, float zRot) {
|
||||
this.xRot = xRot;
|
||||
this.yRot = yRot;
|
||||
this.zRot = zRot;
|
||||
}
|
||||
|
||||
public void scale(float xScale, float yScale, float zScale) {
|
||||
this.xScale = xScale;
|
||||
this.yScale = yScale;
|
||||
this.zScale = zScale;
|
||||
}
|
||||
|
||||
public void offsetPos(float xOffset, float yOffset, float zOffset) {
|
||||
x += xOffset;
|
||||
y += yOffset;
|
||||
z += zOffset;
|
||||
}
|
||||
|
||||
public void offsetRotation(float xOffset, float yOffset, float zOffset) {
|
||||
xRot += xOffset;
|
||||
yRot += yOffset;
|
||||
zRot += zOffset;
|
||||
}
|
||||
|
||||
public void offsetScale(float xOffset, float yOffset, float zOffset) {
|
||||
xScale += xOffset;
|
||||
yScale += yOffset;
|
||||
zScale += zOffset;
|
||||
}
|
||||
|
||||
public void offsetPos(Vector3fc offset) {
|
||||
offsetPos(offset.x(), offset.y(), offset.z());
|
||||
}
|
||||
|
||||
public void offsetRotation(Vector3fc offset) {
|
||||
offsetRotation(offset.x(), offset.y(), offset.z());
|
||||
}
|
||||
|
||||
public void offsetScale(Vector3fc offset) {
|
||||
offsetScale(offset.x(), offset.y(), offset.z());
|
||||
}
|
||||
|
||||
public PartPose storePose() {
|
||||
return PartPose.offsetAndRotation(x, y, z, xRot, yRot, zRot);
|
||||
}
|
||||
|
||||
public void loadPose(PartPose pose) {
|
||||
x = pose.x;
|
||||
y = pose.y;
|
||||
z = pose.z;
|
||||
xRot = pose.xRot;
|
||||
yRot = pose.yRot;
|
||||
zRot = pose.zRot;
|
||||
xScale = ModelPart.DEFAULT_SCALE;
|
||||
yScale = ModelPart.DEFAULT_SCALE;
|
||||
zScale = ModelPart.DEFAULT_SCALE;
|
||||
}
|
||||
|
||||
public void resetPose() {
|
||||
loadPose(initialPose);
|
||||
}
|
||||
|
||||
public void copyTransform(InstanceTree tree) {
|
||||
x = tree.x;
|
||||
y = tree.y;
|
||||
z = tree.z;
|
||||
xRot = tree.xRot;
|
||||
yRot = tree.yRot;
|
||||
zRot = tree.zRot;
|
||||
xScale = tree.xScale;
|
||||
yScale = tree.yScale;
|
||||
zScale = tree.zScale;
|
||||
}
|
||||
|
||||
public void copyTransform(ModelPart modelPart) {
|
||||
x = modelPart.x;
|
||||
y = modelPart.y;
|
||||
z = modelPart.z;
|
||||
xRot = modelPart.xRot;
|
||||
yRot = modelPart.yRot;
|
||||
zRot = modelPart.zRot;
|
||||
xScale = modelPart.xScale;
|
||||
yScale = modelPart.yScale;
|
||||
zScale = modelPart.zScale;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (instance != null) {
|
||||
instance.delete();
|
||||
}
|
||||
children.values()
|
||||
.forEach(InstanceTree::delete);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@FunctionalInterface
|
||||
public interface ObjIntIntConsumer<T> {
|
||||
void accept(T t, int i, int j);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.Mesh;
|
||||
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
import dev.engine_room.flywheel.lib.model.SimpleQuadMesh;
|
||||
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
|
||||
import dev.engine_room.flywheel.lib.vertex.VertexView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.model.geom.EntityModelSet;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
|
||||
public final class MeshTree {
|
||||
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
|
||||
private static final PoseStack.Pose IDENTITY_POSE = new PoseStack().last();
|
||||
private static final Map<ModelLayerLocation, MeshTree> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@Nullable
|
||||
private final Mesh mesh;
|
||||
private final PartPose initialPose;
|
||||
@Unmodifiable
|
||||
private final Map<String, MeshTree> children;
|
||||
|
||||
private MeshTree(@Nullable Mesh mesh, PartPose initialPose, @Unmodifiable Map<String, MeshTree> children) {
|
||||
this.mesh = mesh;
|
||||
this.initialPose = initialPose;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public static MeshTree of(ModelLayerLocation layer) {
|
||||
return CACHE.computeIfAbsent(layer, MeshTree::convert);
|
||||
}
|
||||
|
||||
private static MeshTree convert(ModelLayerLocation layer) {
|
||||
EntityModelSet entityModels = Minecraft.getInstance()
|
||||
.getEntityModels();
|
||||
ModelPart modelPart = entityModels.bakeLayer(layer);
|
||||
|
||||
return convert(modelPart, THREAD_LOCAL_OBJECTS.get());
|
||||
}
|
||||
|
||||
private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) {
|
||||
var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart);
|
||||
Map<String, MeshTree> children = new HashMap<>();
|
||||
|
||||
modelPartChildren.forEach((name, modelPartChild) -> {
|
||||
children.put(name, convert(modelPartChild, objects));
|
||||
});
|
||||
|
||||
return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), Collections.unmodifiableMap(children));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Mesh compile(ModelPart modelPart, ThreadLocalObjects objects) {
|
||||
if (modelPart.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
VertexWriter vertexWriter = objects.vertexWriter;
|
||||
FlwLibLink.INSTANCE.compileModelPart(modelPart, IDENTITY_POSE, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
|
||||
MemoryBlock data = vertexWriter.copyDataAndReset();
|
||||
|
||||
VertexView vertexView = new PosTexNormalVertexView();
|
||||
vertexView.load(data);
|
||||
return new SimpleQuadMesh(vertexView, "source=MeshTree");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Mesh mesh() {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public PartPose initialPose() {
|
||||
return initialPose;
|
||||
}
|
||||
|
||||
@Unmodifiable
|
||||
public Map<String, MeshTree> children() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
return children.containsKey(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MeshTree child(String name) {
|
||||
return children.get(name);
|
||||
}
|
||||
|
||||
public MeshTree childOrThrow(String name) {
|
||||
MeshTree child = child(name);
|
||||
|
||||
if (child == null) {
|
||||
throw new NoSuchElementException("Can't find part " + name);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
public void traverse(Consumer<Mesh> consumer) {
|
||||
if (mesh != null) {
|
||||
consumer.accept(mesh);
|
||||
}
|
||||
for (MeshTree child : children.values()) {
|
||||
child.traverse(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void onEndClientResourceReload() {
|
||||
CACHE.clear();
|
||||
}
|
||||
|
||||
private static class ThreadLocalObjects {
|
||||
public final VertexWriter vertexWriter = new VertexWriter();
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import net.minecraft.client.renderer.LightTexture;
|
|||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public final class ModelPartConverter {
|
||||
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package dev.engine_room.flywheel.lib.vertex;
|
||||
|
||||
import dev.engine_room.flywheel.api.vertex.MutableVertexList;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
|
||||
public final class VertexTransformations {
|
||||
private VertexTransformations() {
|
||||
}
|
||||
|
||||
public static void retexture(MutableVertexList vertexList, int index, TextureAtlasSprite sprite) {
|
||||
vertexList.u(index, sprite.getU(vertexList.u(index) * 16));
|
||||
vertexList.v(index, sprite.getV(vertexList.v(index) * 16));
|
||||
}
|
||||
|
||||
public static void retexture(MutableVertexList vertexList, TextureAtlasSprite sprite) {
|
||||
for (int i = 0; i < vertexList.vertexCount(); i++) {
|
||||
retexture(vertexList, i, sprite);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
package dev.engine_room.flywheel.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import dev.engine_room.flywheel.impl.extension.PoseStackExtension;
|
||||
import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor;
|
||||
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
|
||||
import dev.engine_room.flywheel.lib.transform.PoseTransformStack;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
|
||||
public class FlwLibLinkImpl implements FlwLibLink {
|
||||
@Override
|
||||
|
@ -18,4 +23,14 @@ public class FlwLibLinkImpl implements FlwLibLink {
|
|||
public PoseTransformStack getPoseTransformStackOf(PoseStack stack) {
|
||||
return ((PoseStackExtension) stack).flywheel$transformStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ModelPart> getModelPartChildren(ModelPart part) {
|
||||
return ((ModelPartAccessor) (Object) part).flywheel$children();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha) {
|
||||
((ModelPartAccessor) (Object) part).flywheel$compile(pose, consumer, light, overlay, red, green, blue, alpha);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package dev.engine_room.flywheel.impl.mixin;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
|
||||
@Mixin(ModelPart.class)
|
||||
public interface ModelPartAccessor {
|
||||
@Accessor("children")
|
||||
Map<String, ModelPart> flywheel$children();
|
||||
|
||||
@Invoker("compile")
|
||||
void flywheel$compile(PoseStack.Pose pose, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha);
|
||||
}
|
|
@ -5,26 +5,30 @@ import java.util.EnumMap;
|
|||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.Instance;
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
|
||||
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
|
||||
import dev.engine_room.flywheel.lib.instance.OrientedInstance;
|
||||
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
|
||||
import dev.engine_room.flywheel.lib.material.CutoutShaders;
|
||||
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
|
||||
import dev.engine_room.flywheel.lib.model.ModelCache;
|
||||
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
|
||||
import dev.engine_room.flywheel.lib.model.part.ModelPartConverter;
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import dev.engine_room.flywheel.lib.model.RetexturedMesh;
|
||||
import dev.engine_room.flywheel.lib.model.part.InstanceTree;
|
||||
import dev.engine_room.flywheel.lib.transform.TransformStack;
|
||||
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
|
||||
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
|
||||
import it.unimi.dsi.fastutil.floats.Float2FloatFunction;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelLayers;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.Sheets;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.block.AbstractChestBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.ChestBlock;
|
||||
|
@ -35,7 +39,7 @@ import net.minecraft.world.level.block.entity.LidBlockEntity;
|
|||
import net.minecraft.world.level.block.state.properties.ChestType;
|
||||
|
||||
public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends AbstractBlockEntityVisual<T> implements SimpleDynamicVisual {
|
||||
public static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder()
|
||||
private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder()
|
||||
.cutout(CutoutShaders.ONE_TENTH)
|
||||
.texture(Sheets.CHEST_SHEET)
|
||||
.mipmap(false)
|
||||
|
@ -48,68 +52,55 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
|
|||
LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT);
|
||||
}
|
||||
|
||||
private static final ModelCache<Pair<ChestType, Material>> BOTTOM_MODELS = new ModelCache<>(key -> {
|
||||
return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "bottom"), MATERIAL);
|
||||
});
|
||||
private static final ModelCache<Pair<ChestType, Material>> LID_MODELS = new ModelCache<>(key -> {
|
||||
return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lid"), MATERIAL);
|
||||
});
|
||||
private static final ModelCache<Pair<ChestType, Material>> LOCK_MODELS = new ModelCache<>(key -> {
|
||||
return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lock"), MATERIAL);
|
||||
});
|
||||
@Nullable
|
||||
private final InstanceTree instances;
|
||||
@Nullable
|
||||
private final InstanceTree lid;
|
||||
@Nullable
|
||||
private final InstanceTree lock;
|
||||
|
||||
private final OrientedInstance bottom;
|
||||
private final TransformedInstance lid;
|
||||
private final TransformedInstance lock;
|
||||
|
||||
private final ChestType chestType;
|
||||
private final PoseStack poseStack = new PoseStack();
|
||||
private final BrightnessCombiner brightnessCombiner = new BrightnessCombiner();
|
||||
@Nullable
|
||||
private final DoubleBlockCombiner.NeighborCombineResult<? extends ChestBlockEntity> neighborCombineResult;
|
||||
@Nullable
|
||||
private final Float2FloatFunction lidProgress;
|
||||
|
||||
private final Quaternionf baseRotation = new Quaternionf();
|
||||
|
||||
private float lastProgress = Float.NaN;
|
||||
|
||||
public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) {
|
||||
super(ctx, blockEntity, partialTick);
|
||||
|
||||
chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE;
|
||||
Material texture = Sheets.chooseMaterial(blockEntity, chestType, isChristmas());
|
||||
|
||||
bottom = createBottomInstance(texture).position(getVisualPosition());
|
||||
lid = createLidInstance(texture);
|
||||
lock = createLockInstance(texture);
|
||||
|
||||
Block block = blockState.getBlock();
|
||||
if (block instanceof AbstractChestBlock<?> chestBlock) {
|
||||
ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE;
|
||||
TextureAtlasSprite sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()).sprite();
|
||||
|
||||
instances = InstanceTree.create(instancerProvider, LAYER_LOCATIONS.get(chestType), (path, mesh) -> {
|
||||
return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite));
|
||||
});
|
||||
lid = instances.childOrThrow("lid");
|
||||
lock = instances.childOrThrow("lock");
|
||||
|
||||
poseStack.pushPose();
|
||||
TransformStack.of(poseStack).translate(getVisualPosition());
|
||||
float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot();
|
||||
baseRotation.setAngleAxis(Math.toRadians(-horizontalAngle), 0, 1, 0);
|
||||
poseStack.translate(0.5F, 0.5F, 0.5F);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(-horizontalAngle));
|
||||
poseStack.translate(-0.5F, -0.5F, -0.5F);
|
||||
|
||||
DoubleBlockCombiner.NeighborCombineResult<? extends ChestBlockEntity> wrapper = chestBlock.combine(blockState, level, pos, true);
|
||||
lidProgress = wrapper.apply(ChestBlock.opennessCombiner(blockEntity));
|
||||
neighborCombineResult = chestBlock.combine(blockState, level, pos, true);
|
||||
lidProgress = neighborCombineResult.apply(ChestBlock.opennessCombiner(blockEntity));
|
||||
|
||||
lastProgress = lidProgress.get(partialTick);
|
||||
applyLidTransform(lastProgress);
|
||||
} else {
|
||||
baseRotation.identity();
|
||||
lidProgress = $ -> 0f;
|
||||
instances = null;
|
||||
lid = null;
|
||||
lock = null;
|
||||
neighborCombineResult = null;
|
||||
lidProgress = null;
|
||||
}
|
||||
|
||||
bottom.rotation(baseRotation);
|
||||
bottom.setChanged();
|
||||
|
||||
applyLidTransform(lidProgress.get(partialTick));
|
||||
}
|
||||
|
||||
private OrientedInstance createBottomInstance(Material texture) {
|
||||
return instancerProvider.instancer(InstanceTypes.ORIENTED, BOTTOM_MODELS.get(Pair.of(chestType, texture)))
|
||||
.createInstance();
|
||||
}
|
||||
|
||||
private TransformedInstance createLidInstance(Material texture) {
|
||||
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(Pair.of(chestType, texture)))
|
||||
.createInstance();
|
||||
}
|
||||
|
||||
private TransformedInstance createLockInstance(Material texture) {
|
||||
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LOCK_MODELS.get(Pair.of(chestType, texture)))
|
||||
.createInstance();
|
||||
}
|
||||
|
||||
private static boolean isChristmas() {
|
||||
|
@ -117,8 +108,23 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
|
|||
return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSectionCollector(SectionCollector sectionCollector) {
|
||||
this.lightSections = sectionCollector;
|
||||
|
||||
if (neighborCombineResult != null) {
|
||||
lightSections.sections(neighborCombineResult.apply(new SectionPosCombiner()));
|
||||
} else {
|
||||
lightSections.sections(LongSet.of(SectionPos.asLong(pos)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginFrame(Context context) {
|
||||
if (instances == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (doDistanceLimitThisFrame(context) || !isVisible(context.frustum())) {
|
||||
return;
|
||||
}
|
||||
|
@ -136,41 +142,80 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
|
|||
progress = 1.0F - progress;
|
||||
progress = 1.0F - progress * progress * progress;
|
||||
|
||||
float angleX = -(progress * ((float) Math.PI / 2F));
|
||||
|
||||
lid.setIdentityTransform()
|
||||
.translate(getVisualPosition())
|
||||
.rotateCentered(baseRotation)
|
||||
.translate(0, 9f / 16f, 1f / 16f)
|
||||
.rotateX(angleX)
|
||||
.translate(0, -9f / 16f, -1f / 16f)
|
||||
.setChanged();
|
||||
|
||||
lock.setIdentityTransform()
|
||||
.translate(getVisualPosition())
|
||||
.rotateCentered(baseRotation)
|
||||
.translate(0, 8f / 16f, 0)
|
||||
.rotateX(angleX)
|
||||
.translate(0, -8f / 16f, 0)
|
||||
.setChanged();
|
||||
lid.xRot = -(progress * ((float) Math.PI / 2F));
|
||||
lock.xRot = lid.xRot;
|
||||
instances.updateInstances(poseStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLight(float partialTick) {
|
||||
relight(bottom, lid, lock);
|
||||
if (instances != null) {
|
||||
int packedLight = neighborCombineResult.apply(brightnessCombiner);
|
||||
instances.traverse(instance -> {
|
||||
instance.light(packedLight)
|
||||
.setChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collectCrumblingInstances(Consumer<Instance> consumer) {
|
||||
consumer.accept(bottom);
|
||||
consumer.accept(lid);
|
||||
consumer.accept(lock);
|
||||
if (instances != null) {
|
||||
instances.traverse(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _delete() {
|
||||
bottom.delete();
|
||||
lid.delete();
|
||||
lock.delete();
|
||||
if (instances != null) {
|
||||
instances.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private class SectionPosCombiner implements DoubleBlockCombiner.Combiner<BlockEntity, LongSet> {
|
||||
@Override
|
||||
public LongSet acceptDouble(BlockEntity first, BlockEntity second) {
|
||||
long firstSection = SectionPos.asLong(first.getBlockPos());
|
||||
long secondSection = SectionPos.asLong(second.getBlockPos());
|
||||
|
||||
if (firstSection == secondSection) {
|
||||
return LongSet.of(firstSection);
|
||||
} else {
|
||||
return LongSet.of(firstSection, secondSection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongSet acceptSingle(BlockEntity single) {
|
||||
return LongSet.of(SectionPos.asLong(single.getBlockPos()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LongSet acceptNone() {
|
||||
return LongSet.of(SectionPos.asLong(pos));
|
||||
}
|
||||
}
|
||||
|
||||
private class BrightnessCombiner implements DoubleBlockCombiner.Combiner<BlockEntity, Integer> {
|
||||
@Override
|
||||
public Integer acceptDouble(BlockEntity first, BlockEntity second) {
|
||||
int firstLight = LevelRenderer.getLightColor(first.getLevel(), first.getBlockPos());
|
||||
int secondLight = LevelRenderer.getLightColor(second.getLevel(), second.getBlockPos());
|
||||
int firstBlockLight = LightTexture.block(firstLight);
|
||||
int secondBlockLight = LightTexture.block(secondLight);
|
||||
int firstSkyLight = LightTexture.sky(firstLight);
|
||||
int secondSkyLight = LightTexture.sky(secondLight);
|
||||
return LightTexture.pack(Math.max(firstBlockLight, secondBlockLight), Math.max(firstSkyLight, secondSkyLight));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer acceptSingle(BlockEntity single) {
|
||||
return LevelRenderer.getLightColor(single.getLevel(), single.getBlockPos());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer acceptNone() {
|
||||
return LevelRenderer.getLightColor(level, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"LevelMixin",
|
||||
"LevelRendererMixin",
|
||||
"MinecraftMixin",
|
||||
"ModelPartAccessor",
|
||||
"PoseStackMixin",
|
||||
"fix.FixFabulousDepthMixin",
|
||||
"fix.FixNormalScalingMixin",
|
||||
|
|
|
@ -12,6 +12,7 @@ import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler;
|
|||
import dev.engine_room.flywheel.lib.model.ModelCache;
|
||||
import dev.engine_room.flywheel.lib.model.ModelHolder;
|
||||
import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler;
|
||||
import dev.engine_room.flywheel.lib.model.part.MeshTree;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
|
||||
|
@ -71,6 +72,8 @@ public final class FlywheelFabric implements ClientModInitializer {
|
|||
ModelCache.onEndClientResourceReload());
|
||||
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) ->
|
||||
ModelHolder.onEndClientResourceReload());
|
||||
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) ->
|
||||
MeshTree.onEndClientResourceReload());
|
||||
|
||||
ModelLoadingPlugin.register(ctx -> {
|
||||
ctx.addModels(PartialModelEventHandler.onRegisterAdditional());
|
||||
|
|
|
@ -13,6 +13,7 @@ import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler;
|
|||
import dev.engine_room.flywheel.lib.model.ModelCache;
|
||||
import dev.engine_room.flywheel.lib.model.ModelHolder;
|
||||
import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler;
|
||||
import dev.engine_room.flywheel.lib.model.part.MeshTree;
|
||||
import dev.engine_room.flywheel.lib.util.LevelAttached;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
|
||||
|
@ -111,6 +112,7 @@ public final class FlywheelForge {
|
|||
|
||||
modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelCache.onEndClientResourceReload());
|
||||
modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelHolder.onEndClientResourceReload());
|
||||
modEventBus.addListener((EndClientResourceReloadEvent e) -> MeshTree.onEndClientResourceReload());
|
||||
|
||||
modEventBus.addListener(PartialModelEventHandler::onRegisterAdditional);
|
||||
modEventBus.addListener(PartialModelEventHandler::onBakingCompleted);
|
||||
|
|
|
@ -19,7 +19,7 @@ forge_version_range = [47.0.0,)
|
|||
|
||||
# General build dependency versions
|
||||
java_version = 17
|
||||
arch_loom_version=1.7.412
|
||||
arch_loom_version = 1.7.412
|
||||
cursegradle_version = 1.4.0
|
||||
parchment_version = 2023.09.03
|
||||
|
||||
|
|
Loading…
Reference in a new issue