Less building, more converting

- Add ModelPartConverter and remove ModelPartBuilder
  - ModelPartConverter can convert ModelParts into Flywheel Meshes,
optionally using a transformation and TextureMapper. A
ModelLayerLocation and TextureAtlasSprite can be used instead to quickly
replicate entity models.
- Add ModelHolder and ModelCache to lazily initialize models and
automatically delete them on renderer reload
- Add SimpleModel and remove SimpleLazyModel
- Add flw.useSerialExecutor system property
- Rename ModelBufferingUtil to BakedModelBufferer
- Remove Instance.copy
- Remove Mesh.name
- Make FlwMemoryTracker thread-safe
- Fix potential thread-safety issues, texture issues, and memory leaks
related to previously memoized models
- Fix MinecartVisual only updating when not visible
- Fix ChestVisual locks not moving when opening the chest
This commit is contained in:
PepperCode1 2023-11-20 22:03:04 -08:00
parent 8126a63216
commit 78e959d1a9
30 changed files with 675 additions and 830 deletions

View file

@ -5,6 +5,7 @@ import java.util.ArrayList;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.slf4j.Logger;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.api.visualization.VisualizationManager;
import com.jozufozu.flywheel.backend.Backends;
import com.jozufozu.flywheel.backend.Loader;
@ -24,7 +25,8 @@ import com.jozufozu.flywheel.lib.light.LightUpdater;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker;
import com.jozufozu.flywheel.lib.model.Models;
import com.jozufozu.flywheel.lib.model.ModelCache;
import com.jozufozu.flywheel.lib.model.ModelHolder;
import com.jozufozu.flywheel.lib.model.baked.PartialModel;
import com.jozufozu.flywheel.lib.util.LevelAttached;
import com.jozufozu.flywheel.lib.util.ShadersModHandler;
@ -97,7 +99,8 @@ public class Flywheel {
forgeEventBus.addListener(UniformBuffer::onReloadRenderers);
forgeEventBus.addListener(LightUpdater::onClientTick);
forgeEventBus.addListener(Models::onReloadRenderers);
forgeEventBus.addListener((ReloadRenderersEvent e) -> ModelCache.onReloadRenderers(e));
forgeEventBus.addListener(ModelHolder::onReloadRenderers);
forgeEventBus.addListener((WorldEvent.Unload e) -> LevelAttached.onUnloadLevel(e));
modEventBus.addListener(PartialModel::onModelRegistry);

View file

@ -2,7 +2,4 @@ package com.jozufozu.flywheel.api.instance;
public interface Instance {
InstanceType<?> type();
@Deprecated
Instance copy(InstanceHandle handle);
}

View file

@ -63,9 +63,4 @@ public interface Mesh {
* Free this mesh's resources, memory, etc.
*/
void delete();
/**
* A name uniquely identifying this mesh.
*/
String name();
}

View file

@ -25,7 +25,7 @@ public class BatchedMeshPool {
private final List<BufferedMesh> allBuffered = new ArrayList<>();
private final List<BufferedMesh> pendingBuffer = new ArrayList<>();
private MemoryBlock memory;
private MemoryBlock data;
private long byteSize;
private boolean dirty;
@ -37,7 +37,7 @@ public class BatchedMeshPool {
public BatchedMeshPool(VertexFormat vertexFormat) {
this.vertexFormat = vertexFormat;
vertexList = VertexListProviderRegistry.getProvider(vertexFormat).createVertexList();
growthMargin = vertexFormat.getVertexSize() * 32;
growthMargin = vertexFormat.getVertexSize() * 128;
}
public VertexFormat getVertexFormat() {
@ -116,10 +116,10 @@ public class BatchedMeshPool {
return;
}
if (memory == null) {
memory = MemoryBlock.malloc(byteSize);
} else if (byteSize > memory.size()) {
memory = memory.realloc(byteSize + growthMargin);
if (data == null) {
data = MemoryBlock.malloc(byteSize);
} else if (byteSize > data.size()) {
data = data.realloc(byteSize + growthMargin);
}
}
@ -136,8 +136,8 @@ public class BatchedMeshPool {
}
public void delete() {
if (memory != null) {
memory.free();
if (data != null) {
data.free();
}
meshes.clear();
allBuffered.clear();
@ -191,7 +191,7 @@ public class BatchedMeshPool {
}
private long ptr() {
return BatchedMeshPool.this.memory.ptr() + byteIndex;
return BatchedMeshPool.this.data.ptr() + byteIndex;
}
private void buffer(ReusableVertexList vertexList) {

View file

@ -29,7 +29,7 @@ public class DrawBuffer {
private final int stride;
private final VertexListProvider provider;
private MemoryBlock memory;
private MemoryBlock data;
private ByteBuffer buffer;
private boolean prepared;
@ -64,12 +64,12 @@ public class DrawBuffer {
// is called and reallocates the buffer if there is not space for one more vertex.
int byteSize = stride * (vertexCount + 1);
if (memory == null) {
memory = MemoryBlock.malloc(byteSize);
buffer = memory.asBuffer();
} else if (byteSize > memory.size()) {
memory = memory.realloc(byteSize);
buffer = memory.asBuffer();
if (data == null) {
data = MemoryBlock.malloc(byteSize);
buffer = data.asBuffer();
} else if (byteSize > data.size()) {
data = data.realloc(byteSize);
buffer = data.asBuffer();
}
prepared = true;
@ -91,7 +91,7 @@ public class DrawBuffer {
}
public long ptrForVertex(long startVertex) {
return memory.ptr() + startVertex * stride;
return data.ptr() + startVertex * stride;
}
public void verticesToDraw(int verticesToDraw) {
@ -157,12 +157,12 @@ public class DrawBuffer {
public void free() {
reset();
if (memory == null) {
if (data == null) {
return;
}
memory.free();
memory = null;
data.free();
data = null;
buffer = null;
}

View file

@ -39,7 +39,7 @@ public class InstancedMeshPool {
this.vertexType = vertexType;
int stride = vertexType.getLayout().getStride();
vbo = new GlBuffer();
vbo.growthFunction(l -> Math.max(l + stride * 32L, (long) (l * 1.6)));
vbo.growthFunction(l -> Math.max(l + stride * 128L, (long) (l * 1.6)));
}
public VertexType getVertexType() {

View file

@ -3,8 +3,11 @@ package com.jozufozu.flywheel.impl.task;
import org.apache.commons.lang3.concurrent.AtomicSafeInitializer;
import org.apache.commons.lang3.concurrent.ConcurrentUtils;
import com.jozufozu.flywheel.api.task.TaskExecutor;
public final class FlwTaskExecutor {
// TODO: system property to use SerialTaskExecutor
public static final boolean USE_SERIAL_EXECUTOR = System.getProperty("flw.useSerialExecutor") != null;
private static final Initializer INITIALIZER = new Initializer();
private FlwTaskExecutor() {
@ -14,13 +17,17 @@ public final class FlwTaskExecutor {
* Get a thread pool for running Flywheel related work in parallel.
* @return A global Flywheel thread pool.
*/
public static ParallelTaskExecutor get() {
public static TaskExecutor get() {
return ConcurrentUtils.initializeUnchecked(INITIALIZER);
}
private static class Initializer extends AtomicSafeInitializer<ParallelTaskExecutor> {
private static class Initializer extends AtomicSafeInitializer<TaskExecutor> {
@Override
protected ParallelTaskExecutor initialize() {
protected TaskExecutor initialize() {
if (USE_SERIAL_EXECUTOR) {
return SerialTaskExecutor.INSTANCE;
}
ParallelTaskExecutor executor = new ParallelTaskExecutor("Flywheel");
executor.startWorkers();
return executor;

View file

@ -18,7 +18,6 @@ import com.jozufozu.flywheel.api.visualization.VisualizationLevel;
import com.jozufozu.flywheel.api.visualization.VisualizationManager;
import com.jozufozu.flywheel.extension.ClientLevelExtension;
import com.jozufozu.flywheel.impl.task.FlwTaskExecutor;
import com.jozufozu.flywheel.impl.task.ParallelTaskExecutor;
import com.jozufozu.flywheel.impl.visualization.manager.BlockEntityVisualManager;
import com.jozufozu.flywheel.impl.visualization.manager.EffectVisualManager;
import com.jozufozu.flywheel.impl.visualization.manager.EntityVisualManager;
@ -43,7 +42,7 @@ public class VisualizationManagerImpl implements VisualizationManager {
private static final LevelAttached<VisualizationManagerImpl> MANAGERS = new LevelAttached<>(VisualizationManagerImpl::new, VisualizationManagerImpl::delete);
private final Engine engine;
private final ParallelTaskExecutor taskExecutor;
private final TaskExecutor taskExecutor;
private final BlockEntityVisualManager blockEntities;
private final EntityVisualManager entities;
@ -58,7 +57,6 @@ public class VisualizationManagerImpl implements VisualizationManager {
private VisualizationManagerImpl(LevelAccessor level) {
engine = BackendManager.getBackend()
.createEngine(level);
// FIXME: All VisualizationManagerImpls use the same executor so calls like syncPoint and discardAndAwait could adversely impact other active VisualizationManagerImpls
taskExecutor = FlwTaskExecutor.get();
blockEntities = new BlockEntityVisualManager(engine);

View file

@ -84,26 +84,4 @@ public class OrientedInstance extends ColoredLitInstance {
setChanged();
return this;
}
@Override
public OrientedInstance copy(InstanceHandle handle) {
var out = InstanceTypes.ORIENTED.create(handle);
out.posX = this.posX;
out.posY = this.posY;
out.posZ = this.posZ;
out.pivotX = this.pivotX;
out.pivotY = this.pivotY;
out.pivotZ = this.pivotZ;
out.qX = this.qX;
out.qY = this.qY;
out.qZ = this.qZ;
out.qW = this.qW;
out.r = this.r;
out.g = this.g;
out.b = this.b;
out.a = this.a;
out.blockLight = this.blockLight;
out.skyLight = this.skyLight;
return out;
}
}

View file

@ -113,18 +113,4 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
normal.mul(normal);
return this;
}
@Override
public TransformedInstance copy(InstanceHandle handle) {
var out = InstanceTypes.TRANSFORMED.create(handle);
out.model.load(this.model);
out.normal.load(this.normal);
out.r = this.r;
out.g = this.g;
out.b = this.b;
out.a = this.a;
out.blockLight = this.blockLight;
out.skyLight = this.skyLight;
return out;
}
}

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.lib.memory;
import java.lang.ref.Cleaner;
import java.util.concurrent.atomic.AtomicLong;
import org.lwjgl.system.MemoryUtil;
@ -11,9 +12,8 @@ public final class FlwMemoryTracker {
static final Cleaner CLEANER = Cleaner.create();
// TODO: Should these be volatile?
private static long cpuMemory = 0;
private static long gpuMemory = 0;
private static final AtomicLong CPU_MEMORY = new AtomicLong(0);
private static final AtomicLong GPU_MEMORY = new AtomicLong(0);
private FlwMemoryTracker() {
}
@ -47,26 +47,26 @@ public final class FlwMemoryTracker {
}
public static void _allocCPUMemory(long size) {
cpuMemory += size;
CPU_MEMORY.getAndAdd(size);
}
public static void _freeCPUMemory(long size) {
cpuMemory -= size;
CPU_MEMORY.getAndAdd(-size);
}
public static void _allocGPUMemory(long size) {
gpuMemory += size;
GPU_MEMORY.getAndAdd(size);
}
public static void _freeGPUMemory(long size) {
gpuMemory -= size;
GPU_MEMORY.getAndAdd(-size);
}
public static long getCPUMemory() {
return cpuMemory;
return CPU_MEMORY.get();
}
public static long getGPUMemory() {
return gpuMemory;
return GPU_MEMORY.get();
}
}

View file

@ -0,0 +1,38 @@
package com.jozufozu.flywheel.lib.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.api.model.Model;
public class ModelCache<T> {
private static final List<ModelCache<?>> ALL = new ArrayList<>();
private final Function<T, Model> factory;
private final Map<T, Model> map = new ConcurrentHashMap<>();
public ModelCache(Function<T, Model> factory) {
this.factory = factory;
ALL.add(this);
}
public Model get(T key) {
return map.computeIfAbsent(key, factory);
}
public void clear() {
map.clear();
}
@ApiStatus.Internal
public static void onReloadRenderers(ReloadRenderersEvent event) {
for (ModelCache<?> cache : ALL) {
cache.clear();
}
}
}

View file

@ -0,0 +1,59 @@
package com.jozufozu.flywheel.lib.model;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.api.model.Model;
public class ModelHolder {
private static final List<ModelHolder> ALL = new ArrayList<>();
private final Supplier<Model> factory;
@Nullable
private volatile Model model;
public ModelHolder(Supplier<Model> factory) {
this.factory = factory;
ALL.add(this);
}
public Model get() {
Model model = this.model;
if (model == null) {
synchronized (this) {
model = this.model;
if (model == null) {
this.model = model = factory.get();
}
}
}
return model;
}
public void clear() {
Model model = this.model;
if (model != null) {
synchronized (this) {
model = this.model;
if (model != null) {
model.delete();
this.model = null;
}
}
}
}
@ApiStatus.Internal
public static void onReloadRenderers(ReloadRenderersEvent event) {
for (ModelHolder holder : ALL) {
holder.clear();
}
}
}

View file

@ -1,12 +1,5 @@
package com.jozufozu.flywheel.lib.model;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBuilder;
import com.jozufozu.flywheel.lib.model.baked.BlockModelBuilder;
@ -19,23 +12,23 @@ import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;
public final class Models {
private static final Map<BlockState, Model> BLOCK_STATE = new ConcurrentHashMap<>();
private static final Map<PartialModel, Model> PARTIAL = new ConcurrentHashMap<>();
private static final Map<Pair<PartialModel, Direction>, Model> PARTIAL_DIR = new ConcurrentHashMap<>();
private static final ModelCache<BlockState> BLOCK_STATE = new ModelCache<>(it -> new BlockModelBuilder(it).build());
private static final ModelCache<PartialModel> PARTIAL = new ModelCache<>(it -> new BakedModelBuilder(it.get()).build());
private static final ModelCache<Pair<PartialModel, Direction>> PARTIAL_DIR = new ModelCache<>(it -> new BakedModelBuilder(it.first().get()).poseStack(createRotation(it.second())).build());
private Models() {
}
public static Model block(BlockState state) {
return BLOCK_STATE.computeIfAbsent(state, it -> new BlockModelBuilder(it).build());
return BLOCK_STATE.get(state);
}
public static Model partial(PartialModel partial) {
return PARTIAL.computeIfAbsent(partial, it -> new BakedModelBuilder(it.get()).build());
return PARTIAL.get(partial);
}
public static Model partial(PartialModel partial, Direction dir) {
return PARTIAL_DIR.computeIfAbsent(Pair.of(partial, dir), it -> new BakedModelBuilder(it.first().get()).poseStack(createRotation(it.second())).build());
return PARTIAL_DIR.get(Pair.of(partial, dir));
}
private static PoseStack createRotation(Direction facing) {
@ -46,19 +39,4 @@ public final class Models {
.unCentre();
return stack;
}
@ApiStatus.Internal
public static void onReloadRenderers(ReloadRenderersEvent event) {
deleteAll(BLOCK_STATE.values());
deleteAll(PARTIAL.values());
deleteAll(PARTIAL_DIR.values());
BLOCK_STATE.clear();
PARTIAL.clear();
PARTIAL_DIR.clear();
}
private static void deleteAll(Collection<Model> values) {
values.forEach(Model::delete);
}
}

View file

@ -1,50 +0,0 @@
package com.jozufozu.flywheel.lib.model;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
public class SimpleLazyModel implements Model {
private final Supplier<@NotNull Mesh> meshSupplier;
private final Material material;
@Nullable
private Mesh mesh;
@Nullable
private Map<Material, Mesh> meshMap;
public SimpleLazyModel(Supplier<@NotNull Mesh> meshSupplier, Material material) {
this.meshSupplier = meshSupplier;
this.material = material;
}
@Override
public Map<Material, Mesh> getMeshes() {
if (mesh == null) {
mesh = meshSupplier.get();
meshMap = ImmutableMap.of(material, mesh);
}
return meshMap;
}
@Override
public void delete() {
if (mesh != null) {
mesh.delete();
}
}
@Override
public String toString() {
String name = mesh != null ? mesh.name() : "Uninitialized";
return "SimpleLazyModel{" + name + '}';
}
}

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.lib.model;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector4f;
import org.joml.Vector4fc;
@ -11,17 +12,18 @@ import com.jozufozu.flywheel.lib.memory.MemoryBlock;
public class SimpleMesh implements QuadMesh {
private final VertexType vertexType;
private final int vertexCount;
private final MemoryBlock contents;
private final MemoryBlock data;
private final ReusableVertexList vertexList;
private final Vector4f boundingSphere;
private final String name;
@Nullable
private final String descriptor;
public SimpleMesh(VertexType vertexType, MemoryBlock contents, String name) {
public SimpleMesh(VertexType vertexType, MemoryBlock data, @Nullable String descriptor) {
this.vertexType = vertexType;
this.contents = contents;
this.name = name;
this.data = data;
this.descriptor = descriptor;
int bytes = (int) contents.size();
int bytes = (int) data.size();
int stride = vertexType.getLayout().getStride();
if (bytes % stride != 0) {
throw new IllegalArgumentException("MemoryBlock contains non-whole amount of vertices!");
@ -29,12 +31,16 @@ public class SimpleMesh implements QuadMesh {
vertexCount = bytes / stride;
vertexList = vertexType().createVertexList();
vertexList.ptr(contents.ptr());
vertexList.ptr(data.ptr());
vertexList.vertexCount(vertexCount);
boundingSphere = ModelUtil.computeBoundingSphere(vertexList);
}
public SimpleMesh(VertexType vertexType, MemoryBlock data) {
this(vertexType, data, null);
}
@Override
public VertexType vertexType() {
return vertexType;
@ -47,7 +53,7 @@ public class SimpleMesh implements QuadMesh {
@Override
public void write(long ptr) {
contents.copyTo(ptr);
data.copyTo(ptr);
}
@Override
@ -62,16 +68,11 @@ public class SimpleMesh implements QuadMesh {
@Override
public void delete() {
contents.free();
}
@Override
public String name() {
return name;
data.free();
}
@Override
public String toString() {
return "SimpleMesh{" + "name='" + name + "',vertexType='" + vertexType + "}";
return "SimpleMesh{" + "vertexType=" + vertexType + ",vertexCount=" + vertexCount + ",descriptor={" + descriptor + "}" + "}";
}
}

View file

@ -0,0 +1,28 @@
package com.jozufozu.flywheel.lib.model;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
public class SimpleModel implements Model {
private final Mesh mesh;
private final Map<Material, Mesh> meshMap;
public SimpleModel(Mesh mesh, Material material) {
this.mesh = mesh;
meshMap = ImmutableMap.of(material, mesh);
}
@Override
public Map<Material, Mesh> getMeshes() {
return meshMap;
}
@Override
public void delete() {
mesh.delete();
}
}

View file

@ -29,14 +29,17 @@ import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.data.IModelData;
public final class ModelBufferingUtil {
public final class BakedModelBufferer {
private static final RenderType[] CHUNK_LAYERS = RenderType.chunkBufferLayers().toArray(RenderType[]::new);
private static final int CHUNK_LAYER_AMOUNT = CHUNK_LAYERS.length;
private static final ThreadLocal<ModelBufferingObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ModelBufferingObjects::new);
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
private BakedModelBufferer() {
}
public static void bufferSingle(ModelBlockRenderer blockRenderer, BlockAndTintGetter renderWorld, BakedModel model, BlockState state, @Nullable PoseStack poseStack, IModelData modelData, ResultConsumer resultConsumer) {
ModelBufferingObjects objects = THREAD_LOCAL_OBJECTS.get();
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
@ -68,7 +71,7 @@ public final class ModelBufferingUtil {
}
public static void bufferSingleShadeSeparated(ModelBlockRenderer blockRenderer, BlockAndTintGetter renderWorld, BakedModel model, BlockState state, @Nullable PoseStack poseStack, IModelData modelData, ShadeSeparatedResultConsumer resultConsumer) {
ModelBufferingObjects objects = THREAD_LOCAL_OBJECTS.get();
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
@ -126,7 +129,7 @@ public final class ModelBufferingUtil {
}
public static void bufferMultiBlock(Collection<StructureTemplate.StructureBlockInfo> blocks, BlockRenderDispatcher renderDispatcher, BlockAndTintGetter renderWorld, @Nullable PoseStack poseStack, Map<BlockPos, IModelData> modelDataMap, ResultConsumer resultConsumer) {
ModelBufferingObjects objects = THREAD_LOCAL_OBJECTS.get();
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
@ -183,7 +186,7 @@ public final class ModelBufferingUtil {
}
public static void bufferMultiBlockShadeSeparated(Collection<StructureTemplate.StructureBlockInfo> blocks, BlockRenderDispatcher renderDispatcher, BlockAndTintGetter renderWorld, @Nullable PoseStack poseStack, Map<BlockPos, IModelData> modelDataMap, ShadeSeparatedResultConsumer resultConsumer) {
ModelBufferingObjects objects = THREAD_LOCAL_OBJECTS.get();
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
@ -258,7 +261,7 @@ public final class ModelBufferingUtil {
void accept(RenderType renderType, boolean shaded, Pair<DrawState, ByteBuffer> data);
}
private static class ModelBufferingObjects {
private static class ThreadLocalObjects {
public final PoseStack identityPoseStack = new PoseStack();
public final Random random = new Random();

View file

@ -8,8 +8,8 @@ import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.mojang.blaze3d.vertex.PoseStack;
@ -85,22 +85,22 @@ public class BakedModelBuilder {
Material material = materialFunc.apply(renderType, shaded);
if (material != null) {
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "bakedModel=" + bakedModel.toString() + ",renderType=" + renderType.toString() + ",shaded=" + shaded));
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType + ",shaded=" + shaded));
}
}
};
ModelBufferingUtil.bufferSingleShadeSeparated(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
BakedModelBufferer.bufferSingleShadeSeparated(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
} else {
ResultConsumer resultConsumer = (renderType, data) -> {
if (!ModelUtil.isVanillaBufferEmpty(data)) {
Material material = materialFunc.apply(renderType, true);
if (material != null) {
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "bakedModel=" + bakedModel.toString() + ",renderType=" + renderType.toString()));
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "source=BakedModelBuilder," + "bakedModel=" + bakedModel + ",renderType=" + renderType));
}
}
};
ModelBufferingUtil.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
BakedModelBufferer.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
}
return new TessellatedModel(meshMapBuilder.build(), shadeSeparated);

View file

@ -8,8 +8,8 @@ import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.mojang.blaze3d.vertex.PoseStack;
@ -74,22 +74,22 @@ public class BlockModelBuilder {
Material material = materialFunc.apply(renderType, shaded);
if (material != null) {
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "state=" + state.toString() + ",renderType=" + renderType.toString() + ",shaded=" + shaded));
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType + ",shaded=" + shaded));
}
}
};
ModelBufferingUtil.bufferBlockShadeSeparated(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
BakedModelBufferer.bufferBlockShadeSeparated(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
} else {
ResultConsumer resultConsumer = (renderType, data) -> {
if (!ModelUtil.isVanillaBufferEmpty(data)) {
Material material = materialFunc.apply(renderType, true);
if (material != null) {
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "state=" + state.toString() + ",renderType=" + renderType.toString()));
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "source=BlockModelBuilder," + "blockState=" + state + ",renderType=" + renderType));
}
}
};
ModelBufferingUtil.bufferBlock(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
BakedModelBufferer.bufferBlock(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
}
return new TessellatedModel(meshMapBuilder.build(), shadeSeparated);

View file

@ -11,8 +11,8 @@ import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.ModelUtil;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ResultConsumer;
import com.jozufozu.flywheel.lib.model.baked.BakedModelBufferer.ShadeSeparatedResultConsumer;
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.mojang.blaze3d.vertex.PoseStack;
@ -78,22 +78,22 @@ public class MultiBlockModelBuilder {
Material material = materialFunc.apply(renderType, shaded);
if (material != null) {
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "renderType=" + renderType.toString() + ",shaded=" + shaded));
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType + ",shaded=" + shaded));
}
}
};
ModelBufferingUtil.bufferMultiBlockShadeSeparated(blocks, ModelUtil.VANILLA_RENDERER, renderWorld, poseStack, modelDataMap, resultConsumer);
BakedModelBufferer.bufferMultiBlockShadeSeparated(blocks, ModelUtil.VANILLA_RENDERER, renderWorld, poseStack, modelDataMap, resultConsumer);
} else {
ResultConsumer resultConsumer = (renderType, data) -> {
if (!ModelUtil.isVanillaBufferEmpty(data)) {
Material material = materialFunc.apply(renderType, true);
if (material != null) {
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "renderType=" + renderType.toString()));
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "source=MultiBlockModelBuilder," + "renderType=" + renderType));
}
}
};
ModelBufferingUtil.bufferMultiBlock(blocks, ModelUtil.VANILLA_RENDERER, renderWorld, poseStack, modelDataMap, resultConsumer);
BakedModelBufferer.bufferMultiBlock(blocks, ModelUtil.VANILLA_RENDERER, renderWorld, poseStack, modelDataMap, resultConsumer);
}
return new TessellatedModel(meshMapBuilder.build(), shadeSeparated);

View file

@ -1,287 +0,0 @@
package com.jozufozu.flywheel.lib.model.part;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.lib.math.RenderMath;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.mojang.math.Matrix3f;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.Direction;
public class ModelPartBuilder {
private final String name;
private final float textureWidth;
private final float textureHeight;
private final List<CuboidBuilder> cuboids = new ArrayList<>();
@Nullable
private TextureAtlasSprite sprite;
public ModelPartBuilder(String name, int textureWidth, int textureHeight) {
this.name = name;
this.textureWidth = (float) textureWidth;
this.textureHeight = (float) textureHeight;
}
public ModelPartBuilder sprite(TextureAtlasSprite sprite) {
this.sprite = sprite;
return this;
}
public CuboidBuilder cuboid() {
return new CuboidBuilder();
}
private ModelPartBuilder addCuboid(CuboidBuilder builder) {
cuboids.add(builder);
return this;
}
public Mesh build() {
VertexType vertexType = VertexTypes.POS_TEX_NORMAL;
int vertices = cuboids.size() * 24;
MemoryBlock contents = MemoryBlock.malloc(vertexType.getLayout().getStride() * vertices);
long ptr = contents.ptr();
VertexWriter writer = new VertexWriter(ptr);
for (CuboidBuilder cuboid : cuboids) {
cuboid.write(writer);
}
return new SimpleMesh(vertexType, contents, name);
}
public class CuboidBuilder {
private TextureAtlasSprite sprite;
private int textureOffsetU;
private int textureOffsetV;
private float posX1;
private float posY1;
private float posZ1;
private float posX2;
private float posY2;
private float posZ2;
private boolean useRotation;
private float rotationX;
private float rotationY;
private float rotationZ;
private boolean invertYZ;
private CuboidBuilder() {
sprite = ModelPartBuilder.this.sprite;
}
public CuboidBuilder sprite(TextureAtlasSprite sprite) {
this.sprite = sprite;
return this;
}
public CuboidBuilder textureOffset(int u, int v) {
textureOffsetU = u;
textureOffsetV = v;
return this;
}
public CuboidBuilder start(float x, float y, float z) {
posX1 = x;
posY1 = y;
posZ1 = z;
return this;
}
public CuboidBuilder end(float x, float y, float z) {
posX2 = x;
posY2 = y;
posZ2 = z;
return this;
}
public CuboidBuilder size(float x, float y, float z) {
posX2 = posX1 + x;
posY2 = posY1 + y;
posZ2 = posZ1 + z;
return this;
}
public CuboidBuilder shift(float x, float y, float z) {
posX1 = posX1 - x;
posY1 = posY1 - y;
posZ1 = posZ1 - z;
posX2 = posX2 - x;
posY2 = posY2 - y;
posZ2 = posZ2 - z;
return this;
}
public CuboidBuilder rotate(float x, float y, float z) {
useRotation = true;
rotationX = x;
rotationY = y;
rotationZ = z;
return this;
}
public CuboidBuilder rotateX(float x) {
useRotation = true;
rotationX = x;
return this;
}
public CuboidBuilder rotateY(float y) {
useRotation = true;
rotationY = y;
return this;
}
public CuboidBuilder rotateZ(float z) {
useRotation = true;
rotationZ = z;
return this;
}
/**
* Pulls the cuboid "inside out" through the Y and Z axes.
*/
public CuboidBuilder invertYZ() {
invertYZ = true;
return this;
}
public ModelPartBuilder endCuboid() {
return ModelPartBuilder.this.addCuboid(this);
}
private void write(VertexWriter writer) {
float sizeX = posX2 - posX1;
float sizeY = posY2 - posY1;
float sizeZ = posZ2 - posZ1;
float posX1 = this.posX1 / 16f;
float posY1 = this.posY1 / 16f;
float posZ1 = this.posZ1 / 16f;
float posX2 = this.posX2 / 16f;
float posY2 = this.posY2 / 16f;
float posZ2 = this.posZ2 / 16f;
Vector3f lll = new Vector3f(posX1, posY1, posZ1);
Vector3f hll = new Vector3f(posX2, posY1, posZ1);
Vector3f hhl = new Vector3f(posX2, posY2, posZ1);
Vector3f lhl = new Vector3f(posX1, posY2, posZ1);
Vector3f llh = new Vector3f(posX1, posY1, posZ2);
Vector3f hlh = new Vector3f(posX2, posY1, posZ2);
Vector3f hhh = new Vector3f(posX2, posY2, posZ2);
Vector3f lhh = new Vector3f(posX1, posY2, posZ2);
Vector3f down = Direction.DOWN.step();
Vector3f up = Direction.UP.step();
Vector3f west = Direction.WEST.step();
Vector3f north = Direction.NORTH.step();
Vector3f east = Direction.EAST.step();
Vector3f south = Direction.SOUTH.step();
if (useRotation) {
Matrix3f matrix3f = new Matrix3f(new Quaternion(rotationX, rotationY, rotationZ, false));
lll.transform(matrix3f);
hll.transform(matrix3f);
hhl.transform(matrix3f);
lhl.transform(matrix3f);
llh.transform(matrix3f);
hlh.transform(matrix3f);
hhh.transform(matrix3f);
lhh.transform(matrix3f);
down.transform(matrix3f);
up.transform(matrix3f);
west.transform(matrix3f);
north.transform(matrix3f);
east.transform(matrix3f);
south.transform(matrix3f);
}
float f4 = getU((float)textureOffsetU);
float f5 = getU((float)textureOffsetU + sizeZ);
float f6 = getU((float)textureOffsetU + sizeZ + sizeX);
float f7 = getU((float)textureOffsetU + sizeZ + sizeX + sizeX);
float f8 = getU((float)textureOffsetU + sizeZ + sizeX + sizeZ);
float f9 = getU((float)textureOffsetU + sizeZ + sizeX + sizeZ + sizeX);
float f10 = getV((float)textureOffsetV);
float f11 = getV((float)textureOffsetV + sizeZ);
float f12 = getV((float)textureOffsetV + sizeZ + sizeY);
if (invertYZ) {
writeQuad(writer, new Vector3f[]{hlh, llh, lll, hll}, f6, f11, f7, f10, down);
writeQuad(writer, new Vector3f[]{hhl, lhl, lhh, hhh}, f5, f10, f6, f11, up);
writeQuad(writer, new Vector3f[]{lll, llh, lhh, lhl}, f5, f12, f4, f11, west);
writeQuad(writer, new Vector3f[]{hll, lll, lhl, hhl}, f9, f12, f8, f11, north);
writeQuad(writer, new Vector3f[]{hlh, hll, hhl, hhh}, f8, f12, f6, f11, east);
writeQuad(writer, new Vector3f[]{llh, hlh, hhh, lhh}, f6, f12, f5, f11, south);
} else {
writeQuad(writer, new Vector3f[]{hlh, llh, lll, hll}, f5, f10, f6, f11, down);
writeQuad(writer, new Vector3f[]{hhl, lhl, lhh, hhh}, f6, f11, f7, f10, up);
writeQuad(writer, new Vector3f[]{lll, llh, lhh, lhl}, f4, f11, f5, f12, west);
writeQuad(writer, new Vector3f[]{hll, lll, lhl, hhl}, f5, f11, f6, f12, north);
writeQuad(writer, new Vector3f[]{hlh, hll, hhl, hhh}, f6, f11, f8, f12, east);
writeQuad(writer, new Vector3f[]{llh, hlh, hhh, lhh}, f8, f11, f9, f12, south);
}
}
private void writeQuad(VertexWriter writer, Vector3f[] vertices, float minU, float minV, float maxU, float maxV, Vector3f normal) {
writer.putVertex(vertices[0].x(), vertices[0].y(), vertices[0].z(), maxU, minV, normal.x(), normal.y(), normal.z());
writer.putVertex(vertices[1].x(), vertices[1].y(), vertices[1].z(), minU, minV, normal.x(), normal.y(), normal.z());
writer.putVertex(vertices[2].x(), vertices[2].y(), vertices[2].z(), minU, maxV, normal.x(), normal.y(), normal.z());
writer.putVertex(vertices[3].x(), vertices[3].y(), vertices[3].z(), maxU, maxV, normal.x(), normal.y(), normal.z());
}
private float getU(float u) {
if (sprite != null) {
return sprite.getU(u * 16 / textureWidth);
} else {
return u / textureWidth;
}
}
private float getV(float v) {
if (sprite != null) {
return sprite.getV(v * 16 / textureHeight);
} else {
return v / textureHeight;
}
}
}
private static class VertexWriter {
private long ptr;
public VertexWriter(long ptr) {
this.ptr = ptr;
}
public void putVertex(float x, float y, float z, float u, float v, float nX, float nY, float nZ) {
MemoryUtil.memPutFloat(ptr, x);
MemoryUtil.memPutFloat(ptr + 4, y);
MemoryUtil.memPutFloat(ptr + 8, z);
MemoryUtil.memPutFloat(ptr + 12, u);
MemoryUtil.memPutFloat(ptr + 16, v);
MemoryUtil.memPutByte(ptr + 20, RenderMath.nb(nX));
MemoryUtil.memPutByte(ptr + 21, RenderMath.nb(nY));
MemoryUtil.memPutByte(ptr + 22, RenderMath.nb(nZ));
ptr += 23;
}
}
}

View file

@ -0,0 +1,64 @@
package com.jozufozu.flywheel.lib.model.part;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.SimpleMesh;
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.mojang.blaze3d.vertex.PoseStack;
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.renderer.LightTexture;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
public final class ModelPartConverter {
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
private ModelPartConverter() {
}
public static Mesh convert(ModelPart modelPart, @Nullable PoseStack poseStack, @Nullable TextureMapper textureMapper) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
VertexWriter vertexWriter = objects.vertexWriter;
vertexWriter.setTextureMapper(textureMapper);
modelPart.render(poseStack, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY);
MemoryBlock data = vertexWriter.copyDataAndReset();
return new SimpleMesh(VertexTypes.POS_TEX_NORMAL, data, "source=ModelPartConverter");
}
public static Mesh convert(ModelLayerLocation layer, @Nullable TextureAtlasSprite sprite, String... childPath) {
EntityModelSet entityModels = Minecraft.getInstance().getEntityModels();
ModelPart modelPart = entityModels.bakeLayer(layer);
for (String pathPart : childPath) {
modelPart = modelPart.getChild(pathPart);
}
TextureMapper textureMapper = sprite == null ? null : TextureMapper.toSprite(sprite);
return convert(modelPart, null, textureMapper);
}
public static Mesh convert(ModelLayerLocation layer, String... childPath) {
return convert(layer, null, childPath);
}
public interface TextureMapper {
void map(Vector2f uv);
static TextureMapper toSprite(TextureAtlasSprite sprite) {
return uv -> uv.set(sprite.getU(uv.x * 16), sprite.getV(uv.y * 16));
}
}
private static class ThreadLocalObjects {
public final PoseStack identityPoseStack = new PoseStack();
public final VertexWriter vertexWriter = new VertexWriter();
}
}

View file

@ -0,0 +1,139 @@
package com.jozufozu.flywheel.lib.model.part;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.lib.math.RenderMath;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.model.part.ModelPartConverter.TextureMapper;
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
import com.mojang.blaze3d.vertex.VertexConsumer;
class VertexWriter implements VertexConsumer {
private static final VertexType VERTEX_TYPE = VertexTypes.POS_TEX_NORMAL;
private static final int STRIDE = VERTEX_TYPE.getStride();
private static final int GROWTH_MARGIN = 128 * STRIDE;
private MemoryBlock data;
@Nullable
private TextureMapper textureMapper;
private final Vector2f uvVec = new Vector2f();
private int vertexCount;
private boolean filledPosition;
private boolean filledTexture;
private boolean filledNormal;
public VertexWriter() {
data = MemoryBlock.malloc(GROWTH_MARGIN);
}
public void setTextureMapper(@Nullable TextureMapper mapper) {
textureMapper = mapper;
}
@Override
public VertexConsumer vertex(double x, double y, double z) {
if (!filledPosition) {
long ptr = vertexPtr();
MemoryUtil.memPutFloat(ptr, (float) x);
MemoryUtil.memPutFloat(ptr + 4, (float) y);
MemoryUtil.memPutFloat(ptr + 8, (float) z);
filledPosition = true;
}
return this;
}
@Override
public VertexConsumer color(int red, int green, int blue, int alpha) {
// ignore color
return this;
}
@Override
public VertexConsumer uv(float u, float v) {
if (!filledTexture) {
if (textureMapper != null) {
uvVec.set(u, v);
textureMapper.map(uvVec);
u = uvVec.x;
v = uvVec.y;
}
long ptr = vertexPtr();
MemoryUtil.memPutFloat(ptr + 12, u);
MemoryUtil.memPutFloat(ptr + 16, v);
filledTexture = true;
}
return this;
}
@Override
public VertexConsumer overlayCoords(int u, int v) {
// ignore overlay
return this;
}
@Override
public VertexConsumer uv2(int u, int v) {
// ignore light
return this;
}
@Override
public VertexConsumer normal(float x, float y, float z) {
if (!filledNormal) {
long ptr = vertexPtr();
MemoryUtil.memPutByte(ptr + 20, RenderMath.nb(x));
MemoryUtil.memPutByte(ptr + 21, RenderMath.nb(y));
MemoryUtil.memPutByte(ptr + 22, RenderMath.nb(z));
filledNormal = true;
}
return this;
}
@Override
public void endVertex() {
if (!filledPosition || !filledTexture || !filledNormal) {
throw new IllegalStateException("Not filled all elements of the vertex");
}
filledPosition = false;
filledTexture = false;
filledNormal = false;
vertexCount++;
long byteSize = (vertexCount + 1) * STRIDE;
if (byteSize > data.size()) {
data = data.realloc(byteSize + GROWTH_MARGIN);
}
}
@Override
public void defaultColor(int red, int green, int blue, int alpha) {
}
@Override
public void unsetDefaultColor() {
}
private long vertexPtr() {
return data.ptr() + vertexCount * STRIDE;
}
public MemoryBlock copyDataAndReset() {
MemoryBlock dataCopy = MemoryBlock.malloc(vertexCount * STRIDE);
data.copyTo(dataCopy.ptr(), dataCopy.size());
vertexCount = 0;
filledPosition = false;
filledTexture = false;
filledNormal = false;
textureMapper = null;
return dataCopy;
}
}

View file

@ -16,7 +16,6 @@ import com.google.common.cache.LoadingCache;
import net.minecraft.world.level.LevelAccessor;
import net.minecraftforge.event.world.WorldEvent;
// FIXME
public final class LevelAttached<T> {
private static final ConcurrentLinkedDeque<WeakReference<LevelAttached<?>>> ALL = new ConcurrentLinkedDeque<>();
private static final Cleaner CLEANER = Cleaner.create();

View file

@ -97,11 +97,11 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
*
* @return The position this visual should be rendered at to appear in the correct location.
*/
public Vector3f getVisualPosition(float partialTicks) {
public Vector3f getVisualPosition(float partialTick) {
Vec3 pos = entity.position();
return new Vector3f((float) (Mth.lerp(partialTicks, entity.xOld, pos.x) - renderOrigin.getX()),
(float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - renderOrigin.getY()),
(float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - renderOrigin.getZ()));
return new Vector3f((float) (Mth.lerp(partialTick, entity.xOld, pos.x) - renderOrigin.getX()),
(float) (Mth.lerp(partialTick, entity.yOld, pos.y) - renderOrigin.getY()),
(float) (Mth.lerp(partialTick, entity.zOld, pos.z) - renderOrigin.getZ()));
}
public boolean isVisible(FrustumIntersection frustum) {

View file

@ -2,29 +2,30 @@ package com.jozufozu.flywheel.vanilla;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.OrientedInstance;
import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.model.SimpleLazyModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartBuilder;
import com.jozufozu.flywheel.lib.model.ModelHolder;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartConverter;
import com.jozufozu.flywheel.lib.visual.AbstractBlockEntityVisual;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.blockentity.BellRenderer;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.entity.BellBlockEntity;
public class BellVisual extends AbstractBlockEntityVisual<BellBlockEntity> implements DynamicVisual {
private static final SimpleLazyModel BELL_MODEL = new SimpleLazyModel(BellVisual::createBellMesh, Materials.BELL);
private static final ModelHolder BELL_MODEL = new ModelHolder(() -> {
return new SimpleModel(ModelPartConverter.convert(ModelLayers.BELL, BellRenderer.BELL_RESOURCE_LOCATION.sprite(), "bell_body"), Materials.BELL);
});
private OrientedInstance bell;
@ -44,6 +45,11 @@ public class BellVisual extends AbstractBlockEntityVisual<BellBlockEntity> imple
super.init(partialTick);
}
private OrientedInstance createBellInstance() {
return instancerProvider.instancer(InstanceTypes.ORIENTED, BELL_MODEL.get(), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
@Override
public void beginFrame(VisualFrameContext context) {
if (doDistanceLimitThisFrame(context) || !isVisible(context.frustum())) {
@ -87,25 +93,4 @@ public class BellVisual extends AbstractBlockEntityVisual<BellBlockEntity> imple
protected void _delete() {
bell.delete();
}
private OrientedInstance createBellInstance() {
return instancerProvider.instancer(InstanceTypes.ORIENTED, BELL_MODEL, RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
@NotNull
private static Mesh createBellMesh() {
return new ModelPartBuilder("bell", 32, 32)
.sprite(BellRenderer.BELL_RESOURCE_LOCATION.sprite())
.cuboid()
.start(5.0F, 6.0F, 5.0F)
.size(6.0F, 7.0F, 6.0F)
.endCuboid()
.cuboid()
.textureOffset(0, 13)
.start(4.0F, 4.0F, 4.0F)
.size(8.0F, 2.0F, 8.0F)
.endCuboid()
.build();
}
}

View file

@ -1,12 +1,12 @@
package com.jozufozu.flywheel.vanilla;
import java.util.Calendar;
import java.util.EnumMap;
import java.util.List;
import java.util.function.BiFunction;
import java.util.Map;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
@ -14,16 +14,19 @@ import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.OrientedInstance;
import com.jozufozu.flywheel.lib.instance.TransformedInstance;
import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.model.SimpleLazyModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartBuilder;
import com.jozufozu.flywheel.lib.model.ModelCache;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartConverter;
import com.jozufozu.flywheel.lib.util.Pair;
import com.jozufozu.flywheel.lib.visual.AbstractBlockEntityVisual;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;
import it.unimi.dsi.fastutil.floats.Float2FloatFunction;
import net.minecraft.Util;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.Material;
import net.minecraft.world.level.block.AbstractChestBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
@ -34,16 +37,31 @@ 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 DynamicVisual {
private static final BiFunction<ChestType, TextureAtlasSprite, SimpleLazyModel> BODY_MODEL_FUNC = Util.memoize((type, mat) -> new SimpleLazyModel(() -> createBodyMesh(type, mat), Materials.CHEST));
private static final BiFunction<ChestType, TextureAtlasSprite, SimpleLazyModel> LID_MODEL_FUNC = Util.memoize((type, mat) -> new SimpleLazyModel(() -> createLidMesh(type, mat), Materials.CHEST));
private static final Map<ChestType, ModelLayerLocation> LAYER_LOCATIONS = new EnumMap<>(ChestType.class);
static {
LAYER_LOCATIONS.put(ChestType.SINGLE, ModelLayers.CHEST);
LAYER_LOCATIONS.put(ChestType.LEFT, ModelLayers.DOUBLE_CHEST_LEFT);
LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT);
}
private OrientedInstance body;
private static final ModelCache<Pair<ChestType, Material>> BOTTOM_MODELS = new ModelCache<>(key -> {
return new SimpleModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "bottom"), Materials.CHEST);
});
private static final ModelCache<Pair<ChestType, Material>> LID_MODELS = new ModelCache<>(key -> {
return new SimpleModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lid"), Materials.CHEST);
});
private static final ModelCache<Pair<ChestType, Material>> LOCK_MODELS = new ModelCache<>(key -> {
return new SimpleModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lock"), Materials.CHEST);
});
private OrientedInstance bottom;
private TransformedInstance lid;
private TransformedInstance lock;
private Float2FloatFunction lidProgress;
private TextureAtlasSprite sprite;
private ChestType chestType;
private Material texture;
private Quaternion baseRotation;
private Float2FloatFunction lidProgress;
private float lastProgress = Float.NaN;
@ -53,25 +71,21 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
@Override
public void init(float partialTick) {
Block block = blockState.getBlock();
chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE;
sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas())
.sprite();
texture = Sheets.chooseMaterial(blockEntity, chestType, isChristmas());
body = createBodyInstance().setPosition(getVisualPosition());
bottom = createBottomInstance().setPosition(getVisualPosition());
lid = createLidInstance();
lock = createLockInstance();
Block block = blockState.getBlock();
if (block instanceof AbstractChestBlock<?> chestBlock) {
float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot();
baseRotation = Vector3f.YP.rotationDegrees(-horizontalAngle);
body.setRotation(baseRotation);
bottom.setRotation(baseRotation);
DoubleBlockCombiner.NeighborCombineResult<? extends ChestBlockEntity> wrapper = chestBlock.combine(blockState, level, pos, true);
this.lidProgress = wrapper.apply(ChestBlock.opennessCombiner(blockEntity));
lidProgress = wrapper.apply(ChestBlock.opennessCombiner(blockEntity));
} else {
baseRotation = Quaternion.ONE;
lidProgress = $ -> 0f;
@ -80,6 +94,26 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
super.init(partialTick);
}
private OrientedInstance createBottomInstance() {
return instancerProvider.instancer(InstanceTypes.ORIENTED, BOTTOM_MODELS.get(Pair.of(chestType, texture)), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedInstance createLidInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(Pair.of(chestType, texture)), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedInstance createLockInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LOCK_MODELS.get(Pair.of(chestType, texture)), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private static boolean isChristmas() {
Calendar calendar = Calendar.getInstance();
return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26;
}
@Override
public void beginFrame(VisualFrameContext context) {
if (doDistanceLimitThisFrame(context) || !isVisible(context.frustum())) {
@ -87,11 +121,9 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
}
float progress = lidProgress.get(context.partialTick());
if (lastProgress == progress) {
return;
}
lastProgress = progress;
progress = 1.0F - progress;
@ -101,113 +133,37 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
lid.loadIdentity()
.translate(getVisualPosition())
.translate(0, 9f / 16f, 0)
.centre()
.multiply(baseRotation)
.unCentre()
.translate(0, 0, 1f / 16f)
.multiply(Vector3f.XP.rotation(angleX))
.translate(0, 0, -1f / 16f);
.translate(0, 9f / 16f, 1f / 16f)
.rotateXRadians(angleX)
.translate(0, -9f / 16f, -1f / 16f);
lock.loadIdentity()
.translate(getVisualPosition())
.centre()
.multiply(baseRotation)
.unCentre()
.translate(0, 8f / 16f, 0)
.rotateXRadians(angleX)
.translate(0, -8f / 16f, 0);
}
@Override
public void updateLight() {
relight(pos, body, lid);
relight(pos, bottom, lid, lock);
}
@Override
public List<Instance> getCrumblingInstances() {
return List.of(body, lid);
return List.of(bottom, lid, lock);
}
@Override
protected void _delete() {
body.delete();
bottom.delete();
lid.delete();
}
private OrientedInstance createBodyInstance() {
return instancerProvider.instancer(InstanceTypes.ORIENTED, BODY_MODEL_FUNC.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedInstance createLidInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODEL_FUNC.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private static Mesh createBodyMesh(ChestType type, TextureAtlasSprite sprite) {
return switch (type) {
case LEFT -> new ModelPartBuilder("chest_base_left", 64, 64)
.sprite(sprite)
.cuboid()
.textureOffset(0, 19)
.start(0, 0, 1)
.size(15, 10, 14)
.endCuboid()
.build();
case RIGHT -> new ModelPartBuilder("chest_base_right", 64, 64)
.sprite(sprite)
.cuboid()
.textureOffset(0, 19)
.start(1, 0, 1)
.size(15, 10, 14)
.endCuboid()
.build();
default -> new ModelPartBuilder("chest_base", 64, 64)
.sprite(sprite)
.cuboid()
.textureOffset(0, 19)
.start(1, 0, 1)
.end(15, 10, 15)
.endCuboid()
.build();
};
}
private static Mesh createLidMesh(ChestType type, TextureAtlasSprite sprite) {
return switch (type) {
case LEFT -> new ModelPartBuilder("chest_lid_left", 64, 64)
.sprite(sprite)
.cuboid()
.textureOffset(0, 0)
.start(0, 0, 1)
.size(15, 5, 14)
.endCuboid()
.cuboid()
.start(0, -2, 15)
.size(1, 4, 1)
.endCuboid()
.build();
case RIGHT -> new ModelPartBuilder("chest_lid_right", 64, 64)
.sprite(sprite)
.cuboid()
.textureOffset(0, 0)
.start(1, 0, 1)
.size(15, 5, 14)
.endCuboid()
.cuboid()
.start(15, -2, 15)
.size(1, 4, 1)
.endCuboid()
.build();
default -> new ModelPartBuilder("chest_lid", 64, 64)
.sprite(sprite)
.cuboid()
.textureOffset(0, 0)
.start(1, 0, 1)
.size(14, 5, 14)
.endCuboid()
.cuboid()
.start(7, -2, 15)
.size(2, 4, 1)
.endCuboid()
.build();
};
}
public static boolean isChristmas() {
Calendar calendar = Calendar.getInstance();
return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26;
lock.delete();
}
}

View file

@ -1,9 +1,6 @@
package com.jozufozu.flywheel.vanilla;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
@ -12,14 +9,15 @@ import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.TransformedInstance;
import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.model.ModelHolder;
import com.jozufozu.flywheel.lib.model.Models;
import com.jozufozu.flywheel.lib.model.SimpleLazyModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartBuilder;
import com.jozufozu.flywheel.lib.transform.TransformStack;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartConverter;
import com.jozufozu.flywheel.lib.visual.AbstractEntityVisual;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Vector3f;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.vehicle.AbstractMinecart;
import net.minecraft.world.level.block.RenderShape;
@ -27,15 +25,17 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVisual<T> implements TickableVisual, DynamicVisual {
private static final SimpleLazyModel BODY_MODEL = new SimpleLazyModel(MinecartVisual::createBodyMesh, Materials.MINECART);
private final PoseStack stack = new PoseStack();
private static final ModelHolder BODY_MODEL = new ModelHolder(() -> {
return new SimpleModel(ModelPartConverter.convert(ModelLayers.MINECART), Materials.MINECART);
});
private TransformedInstance body;
private TransformedInstance contents;
private BlockState blockState;
private boolean active;
private final PoseStack stack = new PoseStack();
public MinecartVisual(VisualizationContext ctx, T entity) {
super(ctx, entity);
}
@ -51,119 +51,8 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
super.init(partialTick);
}
@Override
public void tick(VisualTickContext c) {
BlockState displayBlockState = entity.getDisplayBlockState();
if (displayBlockState != blockState) {
blockState = displayBlockState;
contents.delete();
contents = createContentsInstance();
if (contents != null) {
relight(entity.blockPosition(), contents);
}
}
}
@Override
public void beginFrame(VisualFrameContext context) {
if (isVisible(context.frustum())) {
return;
}
// TODO: add proper way to temporarily disable rendering a specific instance
if (!active) {
return;
}
updatePosition(context.partialTick());
}
private void updatePosition(float partialTick) {
TransformStack tstack = TransformStack.cast(stack);
stack.setIdentity();
tstack.translate(Mth.lerp(partialTick, entity.xOld, entity.getX()) - renderOrigin.getX(), Mth.lerp(partialTick, entity.yOld, entity.getY()) - renderOrigin.getY(), Mth.lerp(partialTick, entity.zOld, entity.getZ()) - renderOrigin.getZ());
float yaw = Mth.lerp(partialTick, entity.yRotO, entity.getYRot());
long i = (long) entity.getId() * 493286711L;
i = i * i * 4392167121L + i * 98761L;
float f = (((float)(i >> 16 & 7L) + 0.5F) / 8 - 0.5F) * 0.004F;
float f1 = (((float)(i >> 20 & 7L) + 0.5F) / 8 - 0.5F) * 0.004F;
float f2 = (((float)(i >> 24 & 7L) + 0.5F) / 8 - 0.5F) * 0.004F;
tstack.translate(f, f1, f2);
tstack.nudge(entity.getId());
double d0 = Mth.lerp(partialTick, entity.xOld, entity.getX());
double d1 = Mth.lerp(partialTick, entity.yOld, entity.getY());
double d2 = Mth.lerp(partialTick, entity.zOld, entity.getZ());
Vec3 vector3d = entity.getPos(d0, d1, d2);
float f3 = Mth.lerp(partialTick, entity.xRotO, entity.getXRot());
if (vector3d != null) {
Vec3 vector3d1 = entity.getPosOffs(d0, d1, d2, 0.3F);
Vec3 vector3d2 = entity.getPosOffs(d0, d1, d2, -0.3F);
if (vector3d1 == null) {
vector3d1 = vector3d;
}
if (vector3d2 == null) {
vector3d2 = vector3d;
}
tstack.translate(vector3d.x - d0, (vector3d1.y + vector3d2.y) / 2.0D - d1, vector3d.z - d2);
Vec3 vector3d3 = vector3d2.add(-vector3d1.x, -vector3d1.y, -vector3d1.z);
if (vector3d3.length() != 0.0D) {
vector3d3 = vector3d3.normalize();
yaw = (float)(Math.atan2(vector3d3.z, vector3d3.x) * 180.0D / Math.PI);
f3 = (float)(Math.atan(vector3d3.y) * 73.0D);
}
}
tstack.translate(0.0D, 0.375D, 0.0D);
tstack.multiply(Vector3f.YP.rotationDegrees(180 - yaw));
tstack.multiply(Vector3f.ZP.rotationDegrees(-f3));
float f5 = (float)entity.getHurtTime() - partialTick;
float f6 = entity.getDamage() - partialTick;
if (f6 < 0) {
f6 = 0;
}
if (f5 > 0) {
tstack.multiply(Vector3f.XP.rotationDegrees(Mth.sin(f5) * f5 * f6 / 10 * (float)entity.getHurtDir()));
}
int j = entity.getDisplayOffset();
if (contents != null) {
tstack.pushPose();
tstack.scale(0.75F);
tstack.translate(-0.5D, (float)(j - 8) / 16, 0.5D);
tstack.multiply(Vector3f.YP.rotationDegrees(90));
contents.setTransform(stack);
tstack.popPose();
}
body.setTransform(stack);
}
@Override
public void updateLight() {
if (contents == null) {
relight(entity.blockPosition(), body);
} else {
relight(entity.blockPosition(), body, contents);
}
}
@Override
protected void _delete() {
body.delete();
if (contents != null) {
contents.delete();
}
}
private TransformedInstance createBodyInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, BODY_MODEL, RenderStage.AFTER_ENTITIES)
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, BODY_MODEL.get(), RenderStage.AFTER_ENTITIES)
.createInstance();
}
@ -185,15 +74,118 @@ public class MinecartVisual<T extends AbstractMinecart> extends AbstractEntityVi
.createInstance();
}
@NotNull
private static Mesh createBodyMesh() {
return new ModelPartBuilder("minecart", 64, 32)
.cuboid().invertYZ().start(-10, -8, 3).size(20, 16, 2).textureOffset(0, 10).rotateZ((float) Math.PI).rotateX(((float)Math.PI / 2F)).endCuboid()
.cuboid().invertYZ().start(-8, -3, -10).size(16, 8, 2).rotateY(((float)Math.PI * 1.5F)).endCuboid()
.cuboid().invertYZ().start(-8, -3, -10).size(16, 8, 2).rotateY(((float)Math.PI / 2F)).endCuboid()
.cuboid().invertYZ().start(-8, -3, -8).size(16, 8, 2).rotateY((float)Math.PI).endCuboid()
.cuboid().invertYZ().start(-8, -3, -8).size(16, 8, 2).endCuboid()
.build();
@Override
public void tick(VisualTickContext c) {
BlockState displayBlockState = entity.getDisplayBlockState();
if (displayBlockState != blockState) {
blockState = displayBlockState;
contents.delete();
contents = createContentsInstance();
if (contents != null) {
relight(entity.blockPosition(), contents);
}
}
}
@Override
public void beginFrame(VisualFrameContext context) {
if (!isVisible(context.frustum())) {
return;
}
// TODO: add proper way to temporarily disable rendering a specific instance
if (!active) {
return;
}
updatePosition(context.partialTick());
}
private void updatePosition(float partialTick) {
stack.setIdentity();
double posX = Mth.lerp(partialTick, entity.xOld, entity.getX());
double posY = Mth.lerp(partialTick, entity.yOld, entity.getY());
double posZ = Mth.lerp(partialTick, entity.zOld, entity.getZ());
stack.translate(posX - renderOrigin.getX(), posY - renderOrigin.getY(), posZ - renderOrigin.getZ());
float yaw = Mth.lerp(partialTick, entity.yRotO, entity.getYRot());
long randomBits = entity.getId() * 493286711L;
randomBits = randomBits * randomBits * 4392167121L + randomBits * 98761L;
float nudgeX = (((float) (randomBits >> 16 & 7L) + 0.5f) / 8.0f - 0.5F) * 0.004f;
float nudgeY = (((float) (randomBits >> 20 & 7L) + 0.5f) / 8.0f - 0.5F) * 0.004f;
float nudgeZ = (((float) (randomBits >> 24 & 7L) + 0.5f) / 8.0f - 0.5F) * 0.004f;
stack.translate(nudgeX, nudgeY, nudgeZ);
Vec3 pos = entity.getPos(posX, posY, posZ);
float pitch = Mth.lerp(partialTick, entity.xRotO, entity.getXRot());
if (pos != null) {
Vec3 offset1 = entity.getPosOffs(posX, posY, posZ, 0.3F);
Vec3 offset2 = entity.getPosOffs(posX, posY, posZ, -0.3F);
if (offset1 == null) {
offset1 = pos;
}
if (offset2 == null) {
offset2 = pos;
}
stack.translate(pos.x - posX, (offset1.y + offset2.y) / 2.0D - posY, pos.z - posZ);
Vec3 vec = offset2.add(-offset1.x, -offset1.y, -offset1.z);
if (vec.length() != 0.0D) {
vec = vec.normalize();
yaw = (float) (Math.atan2(vec.z, vec.x) * 180.0D / Math.PI);
pitch = (float) (Math.atan(vec.y) * 73.0D);
}
}
stack.translate(0.0D, 0.375D, 0.0D);
stack.mulPose(Vector3f.YP.rotationDegrees(180 - yaw));
stack.mulPose(Vector3f.ZP.rotationDegrees(-pitch));
float hurtTime = entity.getHurtTime() - partialTick;
float damage = entity.getDamage() - partialTick;
if (damage < 0) {
damage = 0;
}
if (hurtTime > 0) {
stack.mulPose(Vector3f.XP.rotationDegrees(Mth.sin(hurtTime) * hurtTime * damage / 10.0F * (float) entity.getHurtDir()));
}
int displayOffset = entity.getDisplayOffset();
if (contents != null) {
stack.pushPose();
stack.scale(0.75F, 0.75F, 0.75F);
stack.translate(-0.5D, (float) (displayOffset - 8) / 16, 0.5D);
stack.mulPose(Vector3f.YP.rotationDegrees(90));
contents.setTransform(stack);
stack.popPose();
}
stack.scale(-1.0F, -1.0F, 1.0F);
body.setTransform(stack);
}
@Override
public void updateLight() {
if (contents == null) {
relight(entity.blockPosition(), body);
} else {
relight(entity.blockPosition(), body, contents);
}
}
@Override
protected void _delete() {
body.delete();
if (contents != null) {
contents.delete();
}
}
public static boolean shouldSkipRender(AbstractMinecart minecart) {

View file

@ -1,41 +1,44 @@
package com.jozufozu.flywheel.vanilla;
import java.util.List;
import java.util.function.Function;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.instance.TransformedInstance;
import com.jozufozu.flywheel.lib.material.Materials;
import com.jozufozu.flywheel.lib.model.SimpleLazyModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartBuilder;
import com.jozufozu.flywheel.lib.model.ModelCache;
import com.jozufozu.flywheel.lib.model.SimpleModel;
import com.jozufozu.flywheel.lib.model.part.ModelPartConverter;
import com.jozufozu.flywheel.lib.transform.TransformStack;
import com.jozufozu.flywheel.lib.visual.AbstractBlockEntityVisual;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;
import net.minecraft.Util;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.Material;
import net.minecraft.core.Direction;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockEntity> implements DynamicVisual {
private static final Function<TextureAtlasSprite, SimpleLazyModel> BODY_MODEL_FUNC = Util.memoize(it -> new SimpleLazyModel(() -> createBodyMesh(it), Materials.SHULKER));
private static final Function<TextureAtlasSprite, SimpleLazyModel> LID_MODEL_FUNC = Util.memoize(it -> new SimpleLazyModel(() -> createLidMesh(it), Materials.SHULKER));
private static final ModelCache<Material> BASE_MODELS = new ModelCache<>(texture -> {
return new SimpleModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "base"), Materials.SHULKER);
});
private static final ModelCache<Material> LID_MODELS = new ModelCache<>(texture -> {
return new SimpleModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "lid"), Materials.SHULKER);
});
private TextureAtlasSprite texture;
private TransformedInstance body;
private TransformedInstance base;
private TransformedInstance lid;
private Material texture;
private final PoseStack stack = new PoseStack();
private float lastProgress = Float.NaN;
@ -48,31 +51,37 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockE
public void init(float partialTick) {
DyeColor color = blockEntity.getColor();
if (color == null) {
texture = Sheets.DEFAULT_SHULKER_TEXTURE_LOCATION.sprite();
texture = Sheets.DEFAULT_SHULKER_TEXTURE_LOCATION;
} else {
texture = Sheets.SHULKER_TEXTURE_LOCATION.get(color.getId())
.sprite();
texture = Sheets.SHULKER_TEXTURE_LOCATION.get(color.getId());
}
Quaternion rotation = getDirection().getRotation();
TransformStack tstack = TransformStack.cast(stack);
tstack.translate(getVisualPosition())
.translateAll(0.5)
.scale(0.9995f)
.translateAll(0.00025)
.centre()
.multiply(rotation)
.unCentre();
body = createBodyInstance().setTransform(stack);
tstack.translateY(0.25);
.scale(1, -1, -1)
.translateY(-1);
base = createBaseInstance().setTransform(stack);
lid = createLidInstance().setTransform(stack);
super.init(partialTick);
}
private TransformedInstance createBaseInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, BASE_MODELS.get(texture), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedInstance createLidInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(texture), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private Direction getDirection() {
if (blockState.getBlock() instanceof ShulkerBoxBlock) {
return blockState.getValue(ShulkerBoxBlock.FACING);
@ -93,64 +102,31 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockE
}
lastProgress = progress;
Quaternion spin = Vector3f.YP.rotationDegrees(270.0F * progress);
Quaternion spin = Vector3f.YP.rotationDegrees(270.0f * progress);
TransformStack.cast(stack)
.pushPose()
.centre()
.multiply(spin)
.unCentre()
.translateY(progress * 0.5f);
.translateY(-progress * 0.5f)
.multiply(spin);
lid.setTransform(stack);
stack.popPose();
}
@Override
public void updateLight() {
relight(pos, base, lid);
}
@Override
public List<Instance> getCrumblingInstances() {
return List.of(body, lid);
return List.of(base, lid);
}
@Override
protected void _delete() {
body.delete();
base.delete();
lid.delete();
}
@Override
public void updateLight() {
relight(pos, body, lid);
}
private TransformedInstance createBodyInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, BODY_MODEL_FUNC.apply(texture), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedInstance createLidInstance() {
return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODEL_FUNC.apply(texture), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private static Mesh createBodyMesh(TextureAtlasSprite texture) {
return new ModelPartBuilder("shulker_base", 64, 64)
.sprite(texture)
.cuboid()
.textureOffset(0, 28)
.size(16, 8, 16)
.invertYZ()
.endCuboid()
.build();
}
private static Mesh createLidMesh(TextureAtlasSprite texture) {
return new ModelPartBuilder("shulker_lid", 64, 64)
.sprite(texture)
.cuboid()
.size(16, 12, 16)
.invertYZ()
.endCuboid()
.build();
}
}