mirror of
https://github.com/Creators-of-Create/Create.git
synced 2024-11-12 21:12:33 +01:00
WorldAttached contraption rendering
- ContraptionRenderDispatcher's meat was moved to WorldContraptions - Rename ContraptionWorldHolder to ContraptionRenderInfo - One ContraptionMatrices per contraption per frame - Cull contraptions - Bump flywheel version
This commit is contained in:
parent
ce2e2be2c2
commit
cd9f18a8c9
11 changed files with 365 additions and 245 deletions
|
@ -16,7 +16,7 @@ cursegradle_version = 1.4.0
|
||||||
|
|
||||||
# dependency versions
|
# dependency versions
|
||||||
registrate_version = 1.0.4
|
registrate_version = 1.0.4
|
||||||
flywheel_version = 1.16-0.2.0.31
|
flywheel_version = 1.16-0.2.0.32
|
||||||
jei_version = 7.7.1.110
|
jei_version = 7.7.1.110
|
||||||
|
|
||||||
# curseforge information
|
# curseforge information
|
||||||
|
|
|
@ -37,10 +37,9 @@ public class ContraptionEntityRenderer<C extends AbstractContraptionEntity> exte
|
||||||
int overlay) {
|
int overlay) {
|
||||||
super.render(entity, yaw, partialTicks, ms, buffers, overlay);
|
super.render(entity, yaw, partialTicks, ms, buffers, overlay);
|
||||||
|
|
||||||
ContraptionMatrices matrices = new ContraptionMatrices(ms, entity);
|
|
||||||
Contraption contraption = entity.getContraption();
|
Contraption contraption = entity.getContraption();
|
||||||
if (contraption != null) {
|
if (contraption != null) {
|
||||||
ContraptionRenderDispatcher.render(entity, contraption, matrices, buffers);
|
ContraptionRenderDispatcher.render(entity, contraption, buffers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,41 +10,52 @@ import net.minecraft.util.math.vector.Matrix3f;
|
||||||
import net.minecraft.util.math.vector.Matrix4f;
|
import net.minecraft.util.math.vector.Matrix4f;
|
||||||
|
|
||||||
public class ContraptionMatrices {
|
public class ContraptionMatrices {
|
||||||
|
|
||||||
|
public static final ContraptionMatrices IDENTITY = new ContraptionMatrices();
|
||||||
|
|
||||||
public final MatrixStack entityStack;
|
public final MatrixStack entityStack;
|
||||||
public final MatrixStack contraptionStack;
|
public final MatrixStack contraptionStack;
|
||||||
|
public final MatrixStack finalStack;
|
||||||
public final Matrix4f entityMatrix;
|
public final Matrix4f entityMatrix;
|
||||||
|
public final Matrix4f lightMatrix;
|
||||||
|
|
||||||
|
private ContraptionMatrices() {
|
||||||
|
this.entityStack = new MatrixStack();
|
||||||
|
this.contraptionStack = new MatrixStack();
|
||||||
|
this.finalStack = new MatrixStack();
|
||||||
|
this.entityMatrix = new Matrix4f();
|
||||||
|
this.lightMatrix = new Matrix4f();
|
||||||
|
}
|
||||||
|
|
||||||
public ContraptionMatrices(MatrixStack entityStack, AbstractContraptionEntity entity) {
|
public ContraptionMatrices(MatrixStack entityStack, AbstractContraptionEntity entity) {
|
||||||
this.entityStack = entityStack;
|
this.entityStack = copyStack(entityStack);
|
||||||
this.contraptionStack = new MatrixStack();
|
this.contraptionStack = new MatrixStack();
|
||||||
float partialTicks = AnimationTickHolder.getPartialTicks();
|
float partialTicks = AnimationTickHolder.getPartialTicks();
|
||||||
entity.doLocalTransforms(partialTicks, new MatrixStack[] { this.contraptionStack });
|
entity.doLocalTransforms(partialTicks, new MatrixStack[] { this.contraptionStack });
|
||||||
|
|
||||||
entityMatrix = translateTo(entity, partialTicks);
|
entityMatrix = translateTo(entity, partialTicks);
|
||||||
|
|
||||||
|
lightMatrix = entityMatrix.copy();
|
||||||
|
lightMatrix.multiply(contraptionStack.last().pose());
|
||||||
|
|
||||||
|
finalStack = copyStack(entityStack);
|
||||||
|
transform(finalStack, contraptionStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MatrixStack getFinalStack() {
|
public MatrixStack getFinalStack() {
|
||||||
MatrixStack finalStack = new MatrixStack();
|
|
||||||
transform(finalStack, entityStack);
|
|
||||||
transform(finalStack, contraptionStack);
|
|
||||||
return finalStack;
|
return finalStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Matrix4f getFinalModel() {
|
public Matrix4f getFinalModel() {
|
||||||
Matrix4f finalModel = entityStack.last().pose().copy();
|
return finalStack.last().pose();
|
||||||
finalModel.multiply(contraptionStack.last().pose());
|
|
||||||
return finalModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Matrix3f getFinalNormal() {
|
public Matrix3f getFinalNormal() {
|
||||||
Matrix3f finalNormal = entityStack.last().normal().copy();
|
return finalStack.last().normal();
|
||||||
finalNormal.mul(contraptionStack.last().normal());
|
|
||||||
return finalNormal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Matrix4f getFinalLight() {
|
public Matrix4f getFinalLight() {
|
||||||
Matrix4f lightTransform = entityMatrix.copy();
|
return lightMatrix;
|
||||||
lightTransform.multiply(contraptionStack.last().pose());
|
|
||||||
return lightTransform;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Matrix4f translateTo(Entity entity, float partialTicks) {
|
public static Matrix4f translateTo(Entity entity, float partialTicks) {
|
||||||
|
@ -62,4 +73,12 @@ public class ContraptionMatrices {
|
||||||
.mul(transform.last()
|
.mul(transform.last()
|
||||||
.normal());
|
.normal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MatrixStack copyStack(MatrixStack ms) {
|
||||||
|
MatrixStack cms = new MatrixStack();
|
||||||
|
|
||||||
|
transform(cms, ms);
|
||||||
|
|
||||||
|
return cms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,31 @@
|
||||||
package com.simibubi.create.content.contraptions.components.structureMovement.render;
|
package com.simibubi.create.content.contraptions.components.structureMovement.render;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL11.GL_QUADS;
|
import static org.lwjgl.opengl.GL11.GL_QUADS;
|
||||||
import static org.lwjgl.opengl.GL11.glBindTexture;
|
|
||||||
import static org.lwjgl.opengl.GL12.GL_TEXTURE_3D;
|
|
||||||
|
|
||||||
import java.lang.ref.Reference;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.Backend;
|
|
||||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
|
||||||
import com.jozufozu.flywheel.backend.state.RenderLayer;
|
|
||||||
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
||||||
import com.jozufozu.flywheel.event.GatherContextEvent;
|
import com.jozufozu.flywheel.event.GatherContextEvent;
|
||||||
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
|
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
|
||||||
import com.jozufozu.flywheel.event.RenderLayerEvent;
|
import com.jozufozu.flywheel.event.RenderLayerEvent;
|
||||||
|
import com.jozufozu.flywheel.util.WorldAttached;
|
||||||
import com.jozufozu.flywheel.util.transform.MatrixTransformStack;
|
import com.jozufozu.flywheel.util.transform.MatrixTransformStack;
|
||||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
import com.simibubi.create.AllMovementBehaviours;
|
import com.simibubi.create.AllMovementBehaviours;
|
||||||
import com.simibubi.create.CreateClient;
|
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
|
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
|
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandler;
|
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionLighter;
|
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
|
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
|
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
|
||||||
import com.simibubi.create.foundation.config.AllConfigs;
|
|
||||||
import com.simibubi.create.foundation.render.AllProgramSpecs;
|
|
||||||
import com.simibubi.create.foundation.render.Compartment;
|
import com.simibubi.create.foundation.render.Compartment;
|
||||||
import com.simibubi.create.foundation.render.CreateContexts;
|
|
||||||
import com.simibubi.create.foundation.render.SuperByteBuffer;
|
import com.simibubi.create.foundation.render.SuperByteBuffer;
|
||||||
import com.simibubi.create.foundation.render.TileEntityRenderHelper;
|
import com.simibubi.create.foundation.render.TileEntityRenderHelper;
|
||||||
import com.simibubi.create.foundation.utility.AnimationTickHolder;
|
|
||||||
import com.simibubi.create.foundation.utility.worldWrappers.PlacementSimulationWorld;
|
import com.simibubi.create.foundation.utility.worldWrappers.PlacementSimulationWorld;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
import net.minecraft.block.BlockRenderType;
|
import net.minecraft.block.BlockRenderType;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.ActiveRenderInfo;
|
|
||||||
import net.minecraft.client.renderer.BlockModelRenderer;
|
import net.minecraft.client.renderer.BlockModelRenderer;
|
||||||
import net.minecraft.client.renderer.BlockModelShapes;
|
import net.minecraft.client.renderer.BlockModelShapes;
|
||||||
import net.minecraft.client.renderer.BufferBuilder;
|
import net.minecraft.client.renderer.BufferBuilder;
|
||||||
|
@ -53,7 +37,6 @@ import net.minecraft.client.renderer.WorldRenderer;
|
||||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.MathHelper;
|
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.LightType;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import net.minecraft.world.gen.feature.template.Template;
|
import net.minecraft.world.gen.feature.template.Template;
|
||||||
|
@ -70,123 +53,25 @@ import net.minecraftforge.fml.common.Mod;
|
||||||
public class ContraptionRenderDispatcher {
|
public class ContraptionRenderDispatcher {
|
||||||
private static final Lazy<BlockModelRenderer> MODEL_RENDERER = Lazy.of(() -> new BlockModelRenderer(Minecraft.getInstance().getBlockColors()));
|
private static final Lazy<BlockModelRenderer> MODEL_RENDERER = Lazy.of(() -> new BlockModelRenderer(Minecraft.getInstance().getBlockColors()));
|
||||||
private static final Lazy<BlockModelShapes> BLOCK_MODELS = Lazy.of(() -> Minecraft.getInstance().getModelManager().getBlockModelShaper());
|
private static final Lazy<BlockModelShapes> BLOCK_MODELS = Lazy.of(() -> Minecraft.getInstance().getModelManager().getBlockModelShaper());
|
||||||
private static int worldHolderRefreshCounter;
|
|
||||||
|
|
||||||
public static final Int2ObjectMap<RenderedContraption> RENDERERS = new Int2ObjectOpenHashMap<>();
|
private static final WorldAttached<WorldContraptions> WORLDS = new WorldAttached<>(WorldContraptions::new);
|
||||||
public static final Int2ObjectMap<ContraptionWorldHolder> WORLD_HOLDERS = new Int2ObjectOpenHashMap<>();
|
|
||||||
public static final Compartment<Pair<Contraption, RenderType>> CONTRAPTION = new Compartment<>();
|
public static final Compartment<Pair<Contraption, RenderType>> CONTRAPTION = new Compartment<>();
|
||||||
|
|
||||||
public static void tick() {
|
public static void tick(World world) {
|
||||||
if (Minecraft.getInstance().isPaused()) return;
|
if (Minecraft.getInstance().isPaused()) return;
|
||||||
|
|
||||||
for (RenderedContraption contraption : RENDERERS.values()) {
|
WORLDS.get(world).tick();
|
||||||
ContraptionLighter<?> lighter = contraption.getLighter();
|
|
||||||
if (lighter.getBounds().volume() < AllConfigs.CLIENT.maxContraptionLightVolume.get())
|
|
||||||
lighter.tick(contraption);
|
|
||||||
|
|
||||||
contraption.kinetics.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
worldHolderRefreshCounter++;
|
|
||||||
if (worldHolderRefreshCounter >= 20) {
|
|
||||||
removeDeadHolders();
|
|
||||||
worldHolderRefreshCounter = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void beginFrame(BeginFrameEvent event) {
|
public static void beginFrame(BeginFrameEvent event) {
|
||||||
ActiveRenderInfo info = event.getInfo();
|
WORLDS.get(event.getWorld()).beginFrame(event);
|
||||||
double camX = info.getPosition().x;
|
|
||||||
double camY = info.getPosition().y;
|
|
||||||
double camZ = info.getPosition().z;
|
|
||||||
for (RenderedContraption renderer : RENDERERS.values()) {
|
|
||||||
renderer.beginFrame(info, camX, camY, camZ);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void renderLayer(RenderLayerEvent event) {
|
public static void renderLayer(RenderLayerEvent event) {
|
||||||
if (Backend.getInstance().available()) {
|
WORLDS.get(event.getWorld()).renderLayer(event);
|
||||||
renderLayerFlywheel(event);
|
|
||||||
} else {
|
|
||||||
renderLayerSBB(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void renderLayerFlywheel(RenderLayerEvent event) {
|
|
||||||
removeDeadContraptions();
|
|
||||||
|
|
||||||
if (RENDERERS.isEmpty()) return;
|
|
||||||
|
|
||||||
RenderType layer = event.getType();
|
|
||||||
|
|
||||||
layer.setupRenderState();
|
|
||||||
GlTextureUnit.T4.makeActive(); // the shaders expect light volumes to be in texture 4
|
|
||||||
|
|
||||||
ContraptionProgram structureShader = CreateContexts.STRUCTURE.getProgram(AllProgramSpecs.STRUCTURE);
|
|
||||||
|
|
||||||
structureShader.bind();
|
|
||||||
structureShader.uploadViewProjection(event.viewProjection);
|
|
||||||
structureShader.uploadCameraPos(event.camX, event.camY, event.camZ);
|
|
||||||
|
|
||||||
for (RenderedContraption renderer : RENDERERS.values()) {
|
|
||||||
renderer.doRenderLayer(layer, structureShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Backend.getInstance().canUseInstancing()) {
|
|
||||||
RenderLayer renderLayer = event.getLayer();
|
|
||||||
if (renderLayer != null) {
|
|
||||||
for (RenderedContraption renderer : RENDERERS.values()) {
|
|
||||||
renderer.materialManager.render(renderLayer, event.viewProjection, event.camX, event.camY, event.camZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear the light volume state
|
|
||||||
GlTextureUnit.T4.makeActive();
|
|
||||||
glBindTexture(GL_TEXTURE_3D, 0);
|
|
||||||
|
|
||||||
layer.clearRenderState();
|
|
||||||
GlTextureUnit.T0.makeActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void renderLayerSBB(RenderLayerEvent event) {
|
|
||||||
ContraptionHandler.loadedContraptions.get(event.getWorld())
|
|
||||||
.values()
|
|
||||||
.stream()
|
|
||||||
.map(Reference::get)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.forEach(entity -> renderContraptionLayerSBB(event, entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void renderContraptionLayerSBB(RenderLayerEvent event, AbstractContraptionEntity entity) {
|
|
||||||
RenderType layer = event.getType();
|
|
||||||
|
|
||||||
Contraption contraption = entity.getContraption();
|
|
||||||
Pair<Contraption, RenderType> key = Pair.of(contraption, layer);
|
|
||||||
|
|
||||||
SuperByteBuffer contraptionBuffer = CreateClient.BUFFER_CACHE.get(CONTRAPTION, key, () -> buildStructureBuffer(getWorldHolder(entity.level, contraption).renderWorld, contraption, layer));
|
|
||||||
|
|
||||||
if (!contraptionBuffer.isEmpty()) {
|
|
||||||
MatrixStack stack = event.stack;
|
|
||||||
|
|
||||||
stack.pushPose();
|
|
||||||
|
|
||||||
double x = MathHelper.lerp(AnimationTickHolder.getPartialTicks(), entity.xOld, entity.getX()) - event.camX;
|
|
||||||
double y = MathHelper.lerp(AnimationTickHolder.getPartialTicks(), entity.yOld, entity.getY()) - event.camY;
|
|
||||||
double z = MathHelper.lerp(AnimationTickHolder.getPartialTicks(), entity.zOld, entity.getZ()) - event.camZ;
|
|
||||||
|
|
||||||
stack.translate(x, y, z);
|
|
||||||
ContraptionMatrices matrices = new ContraptionMatrices(stack, entity);
|
|
||||||
contraptionBuffer.transform(matrices.contraptionStack)
|
|
||||||
.light(matrices.entityMatrix)
|
|
||||||
.hybridLight()
|
|
||||||
.renderInto(matrices.entityStack, event.buffers.bufferSource().getBuffer(layer));
|
|
||||||
|
|
||||||
stack.popPose();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
|
@ -198,44 +83,13 @@ public class ContraptionRenderDispatcher {
|
||||||
invalidateAll();
|
invalidateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void render(AbstractContraptionEntity entity, Contraption contraption,
|
public static void render(AbstractContraptionEntity entity, Contraption contraption, IRenderTypeBuffer buffers) {
|
||||||
ContraptionMatrices matrices, IRenderTypeBuffer buffers) {
|
|
||||||
World world = entity.level;
|
World world = entity.level;
|
||||||
|
|
||||||
if (Backend.getInstance().available()) {
|
ContraptionRenderInfo renderInfo = WORLDS.get(world)
|
||||||
getRenderer(world, contraption); // hack to create the RenderedContraption when using Flywheel
|
.getRenderInfo(contraption);
|
||||||
}
|
|
||||||
|
|
||||||
PlacementSimulationWorld renderWorld = getWorldHolder(world, contraption).renderWorld;
|
renderDynamic(world, renderInfo.renderWorld, contraption, renderInfo.getMatrices(), buffers);
|
||||||
|
|
||||||
ContraptionRenderDispatcher.renderDynamic(world, renderWorld, contraption, matrices, buffers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RenderedContraption getRenderer(World world, Contraption c) {
|
|
||||||
int entityId = c.entity.getId();
|
|
||||||
RenderedContraption contraption = RENDERERS.get(entityId);
|
|
||||||
|
|
||||||
if (contraption == null) {
|
|
||||||
PlacementSimulationWorld renderWorld = setupRenderWorld(world, c);
|
|
||||||
contraption = new RenderedContraption(c, renderWorld);
|
|
||||||
RENDERERS.put(entityId, contraption);
|
|
||||||
WORLD_HOLDERS.put(entityId, contraption);
|
|
||||||
}
|
|
||||||
|
|
||||||
return contraption;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ContraptionWorldHolder getWorldHolder(World world, Contraption c) {
|
|
||||||
int entityId = c.entity.getId();
|
|
||||||
ContraptionWorldHolder holder = WORLD_HOLDERS.get(entityId);
|
|
||||||
|
|
||||||
if (holder == null) {
|
|
||||||
PlacementSimulationWorld renderWorld = setupRenderWorld(world, c);
|
|
||||||
holder = new ContraptionWorldHolder(c, renderWorld);
|
|
||||||
WORLD_HOLDERS.put(entityId, holder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return holder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PlacementSimulationWorld setupRenderWorld(World world, Contraption c) {
|
public static PlacementSimulationWorld setupRenderWorld(World world, Contraption c) {
|
||||||
|
@ -291,7 +145,7 @@ public class ContraptionRenderDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SuperByteBuffer buildStructureBuffer(PlacementSimulationWorld renderWorld, Contraption c, RenderType layer) {
|
public static SuperByteBuffer buildStructureBuffer(PlacementSimulationWorld renderWorld, Contraption c, RenderType layer) {
|
||||||
BufferBuilder builder = buildStructure(renderWorld, c, layer);
|
BufferBuilder builder = buildStructure(renderWorld, c, layer);
|
||||||
return new SuperByteBuffer(builder);
|
return new SuperByteBuffer(builder);
|
||||||
}
|
}
|
||||||
|
@ -349,26 +203,6 @@ public class ContraptionRenderDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void invalidateAll() {
|
public static void invalidateAll() {
|
||||||
for (RenderedContraption renderer : RENDERERS.values()) {
|
WORLDS.empty(WorldContraptions::invalidate);
|
||||||
renderer.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
RENDERERS.clear();
|
|
||||||
WORLD_HOLDERS.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void removeDeadContraptions() {
|
|
||||||
RENDERERS.values().removeIf(renderer -> {
|
|
||||||
if (renderer.isDead()) {
|
|
||||||
renderer.invalidate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeDeadHolders() {
|
|
||||||
WORLD_HOLDERS.values().removeIf(ContraptionWorldHolder::isDead);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.simibubi.create.content.contraptions.components.structureMovement.render;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
|
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
|
||||||
|
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
|
||||||
|
import com.simibubi.create.foundation.utility.AnimationTickHolder;
|
||||||
|
import com.simibubi.create.foundation.utility.worldWrappers.PlacementSimulationWorld;
|
||||||
|
|
||||||
|
import net.minecraft.client.renderer.culling.ClippingHelper;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
|
||||||
|
public class ContraptionRenderInfo {
|
||||||
|
public final Contraption contraption;
|
||||||
|
public final PlacementSimulationWorld renderWorld;
|
||||||
|
|
||||||
|
private ContraptionMatrices matrices = ContraptionMatrices.IDENTITY;
|
||||||
|
private boolean visible;
|
||||||
|
|
||||||
|
public ContraptionRenderInfo(Contraption contraption, PlacementSimulationWorld renderWorld) {
|
||||||
|
this.contraption = contraption;
|
||||||
|
this.renderWorld = renderWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEntityId() {
|
||||||
|
return contraption.entity.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDead() {
|
||||||
|
return !contraption.entity.isAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginFrame(ClippingHelper clippingHelper, MatrixStack mainStack, double camX, double camY, double camZ) {
|
||||||
|
AbstractContraptionEntity entity = contraption.entity;
|
||||||
|
|
||||||
|
visible = clippingHelper.isVisible(entity.getBoundingBoxForCulling().inflate(2));
|
||||||
|
|
||||||
|
mainStack.pushPose();
|
||||||
|
|
||||||
|
double x = MathHelper.lerp(AnimationTickHolder.getPartialTicks(), entity.xOld, entity.getX()) - camX;
|
||||||
|
double y = MathHelper.lerp(AnimationTickHolder.getPartialTicks(), entity.yOld, entity.getY()) - camY;
|
||||||
|
double z = MathHelper.lerp(AnimationTickHolder.getPartialTicks(), entity.zOld, entity.getZ()) - camZ;
|
||||||
|
|
||||||
|
mainStack.translate(x, y, z);
|
||||||
|
|
||||||
|
matrices = new ContraptionMatrices(mainStack, entity);
|
||||||
|
|
||||||
|
mainStack.popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisible() {
|
||||||
|
return visible && contraption.entity.isAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContraptionMatrices getMatrices() {
|
||||||
|
return matrices;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
package com.simibubi.create.content.contraptions.components.structureMovement.render;
|
|
||||||
|
|
||||||
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
|
|
||||||
import com.simibubi.create.foundation.utility.worldWrappers.PlacementSimulationWorld;
|
|
||||||
|
|
||||||
public class ContraptionWorldHolder {
|
|
||||||
public final Contraption contraption;
|
|
||||||
public final PlacementSimulationWorld renderWorld;
|
|
||||||
|
|
||||||
public ContraptionWorldHolder(Contraption contraption, PlacementSimulationWorld renderWorld) {
|
|
||||||
this.contraption = contraption;
|
|
||||||
this.renderWorld = renderWorld;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEntityId() {
|
|
||||||
return contraption.entity.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDead() {
|
|
||||||
return !contraption.entity.isAlive();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,25 +11,25 @@ import com.simibubi.create.foundation.utility.outliner.AABBOutline;
|
||||||
|
|
||||||
public class LightVolumeDebugger {
|
public class LightVolumeDebugger {
|
||||||
public static void render(MatrixStack ms, SuperRenderTypeBuffer buffer) {
|
public static void render(MatrixStack ms, SuperRenderTypeBuffer buffer) {
|
||||||
ContraptionRenderDispatcher.RENDERERS.values()
|
// ContraptionRenderDispatcher.RENDERERS.values()
|
||||||
.stream()
|
// .stream()
|
||||||
.flatMap(r -> {
|
// .flatMap(r -> {
|
||||||
GridAlignedBB texture = r.getLighter().lightVolume.getTextureVolume();
|
// GridAlignedBB texture = r.getLighter().lightVolume.getTextureVolume();
|
||||||
GridAlignedBB sample = r.getLighter().lightVolume.getSampleVolume();
|
// GridAlignedBB sample = r.getLighter().lightVolume.getSampleVolume();
|
||||||
|
//
|
||||||
ArrayList<Pair<GridAlignedBB, Integer>> pairs = new ArrayList<>(2);
|
// ArrayList<Pair<GridAlignedBB, Integer>> pairs = new ArrayList<>(2);
|
||||||
|
//
|
||||||
pairs.add(Pair.of(texture, 0xFFFFFF));
|
// pairs.add(Pair.of(texture, 0xFFFFFF));
|
||||||
pairs.add(Pair.of(sample, 0xFFFF00));
|
// pairs.add(Pair.of(sample, 0xFFFF00));
|
||||||
|
//
|
||||||
return pairs.stream();
|
// return pairs.stream();
|
||||||
})
|
// })
|
||||||
.map(pair -> {
|
// .map(pair -> {
|
||||||
AABBOutline outline = new AABBOutline(GridAlignedBB.toAABB(pair.getFirst()));
|
// AABBOutline outline = new AABBOutline(GridAlignedBB.toAABB(pair.getFirst()));
|
||||||
|
//
|
||||||
outline.getParams().colored(pair.getSecond());
|
// outline.getParams().colored(pair.getSecond());
|
||||||
return outline;
|
// return outline;
|
||||||
})
|
// })
|
||||||
.forEach(outline -> outline.render(ms, buffer, AnimationTickHolder.getPartialTicks()));
|
// .forEach(outline -> outline.render(ms, buffer, AnimationTickHolder.getPartialTicks()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,7 @@ import javax.annotation.Nullable;
|
||||||
import com.jozufozu.flywheel.backend.Backend;
|
import com.jozufozu.flywheel.backend.Backend;
|
||||||
import com.jozufozu.flywheel.backend.gl.attrib.CommonAttributes;
|
import com.jozufozu.flywheel.backend.gl.attrib.CommonAttributes;
|
||||||
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
|
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
|
||||||
import com.jozufozu.flywheel.backend.instancing.IInstanceRendered;
|
|
||||||
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
|
|
||||||
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
|
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
|
||||||
import com.jozufozu.flywheel.backend.material.MaterialGroup;
|
|
||||||
import com.jozufozu.flywheel.backend.material.MaterialManager;
|
import com.jozufozu.flywheel.backend.material.MaterialManager;
|
||||||
import com.jozufozu.flywheel.backend.model.ArrayModelRenderer;
|
import com.jozufozu.flywheel.backend.model.ArrayModelRenderer;
|
||||||
import com.jozufozu.flywheel.backend.model.BufferedModel;
|
import com.jozufozu.flywheel.backend.model.BufferedModel;
|
||||||
|
@ -41,7 +38,7 @@ import net.minecraft.util.math.MathHelper;
|
||||||
import net.minecraft.util.math.vector.Matrix4f;
|
import net.minecraft.util.math.vector.Matrix4f;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
public class RenderedContraption extends ContraptionWorldHolder {
|
public class RenderedContraption extends ContraptionRenderInfo {
|
||||||
public static final VertexFormat FORMAT = VertexFormat.builder()
|
public static final VertexFormat FORMAT = VertexFormat.builder()
|
||||||
.addAttributes(CommonAttributes.VEC3,
|
.addAttributes(CommonAttributes.VEC3,
|
||||||
CommonAttributes.NORMAL,
|
CommonAttributes.NORMAL,
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
package com.simibubi.create.content.contraptions.components.structureMovement.render;
|
||||||
|
|
||||||
|
import static com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher.CONTRAPTION;
|
||||||
|
import static com.simibubi.create.content.contraptions.components.structureMovement.render.ContraptionRenderDispatcher.buildStructureBuffer;
|
||||||
|
import static org.lwjgl.opengl.GL11.glBindTexture;
|
||||||
|
import static org.lwjgl.opengl.GL12.GL_TEXTURE_3D;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.backend.Backend;
|
||||||
|
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||||
|
import com.jozufozu.flywheel.backend.state.RenderLayer;
|
||||||
|
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
||||||
|
import com.jozufozu.flywheel.event.RenderLayerEvent;
|
||||||
|
import com.simibubi.create.CreateClient;
|
||||||
|
import com.simibubi.create.content.contraptions.components.structureMovement.AbstractContraptionEntity;
|
||||||
|
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
|
||||||
|
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionHandler;
|
||||||
|
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionLighter;
|
||||||
|
import com.simibubi.create.foundation.config.AllConfigs;
|
||||||
|
import com.simibubi.create.foundation.render.AllProgramSpecs;
|
||||||
|
import com.simibubi.create.foundation.render.CreateContexts;
|
||||||
|
import com.simibubi.create.foundation.render.SuperByteBuffer;
|
||||||
|
import com.simibubi.create.foundation.utility.worldWrappers.PlacementSimulationWorld;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
import net.minecraft.client.renderer.ActiveRenderInfo;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.world.IWorld;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
public class WorldContraptions {
|
||||||
|
private final World world;
|
||||||
|
|
||||||
|
private int worldHolderRefreshCounter;
|
||||||
|
|
||||||
|
public final Int2ObjectMap<RenderedContraption> flwRenderers = new Int2ObjectOpenHashMap<>();
|
||||||
|
public final Int2ObjectMap<ContraptionRenderInfo> renderInfos = new Int2ObjectOpenHashMap<>();
|
||||||
|
private final List<ContraptionRenderInfo> visible = new ObjectArrayList<>();
|
||||||
|
private final List<RenderedContraption> flwVisible = new ObjectArrayList<>();
|
||||||
|
|
||||||
|
public WorldContraptions(IWorld world) {
|
||||||
|
this.world = (World) world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
for (RenderedContraption contraption : flwRenderers.values()) {
|
||||||
|
ContraptionLighter<?> lighter = contraption.getLighter();
|
||||||
|
if (lighter.getBounds().volume() < AllConfigs.CLIENT.maxContraptionLightVolume.get())
|
||||||
|
lighter.tick(contraption);
|
||||||
|
|
||||||
|
contraption.kinetics.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
worldHolderRefreshCounter++;
|
||||||
|
if (worldHolderRefreshCounter >= 20) {
|
||||||
|
removeDeadHolders();
|
||||||
|
removeDeadContraptions();
|
||||||
|
worldHolderRefreshCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Consumer<Contraption> setup;
|
||||||
|
if (Backend.getInstance().available()) {
|
||||||
|
setup = this::createRenderer;
|
||||||
|
} else {
|
||||||
|
setup = this::getRenderInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContraptionHandler.loadedContraptions.get(world)
|
||||||
|
.values()
|
||||||
|
.stream()
|
||||||
|
.map(Reference::get)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(AbstractContraptionEntity::getContraption)
|
||||||
|
.forEach(setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginFrame(BeginFrameEvent event) {
|
||||||
|
ActiveRenderInfo info = event.getInfo();
|
||||||
|
double camX = info.getPosition().x;
|
||||||
|
double camY = info.getPosition().y;
|
||||||
|
double camZ = info.getPosition().z;
|
||||||
|
|
||||||
|
visible.clear();
|
||||||
|
flwVisible.clear();
|
||||||
|
|
||||||
|
renderInfos.int2ObjectEntrySet()
|
||||||
|
.stream()
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.forEach(renderInfo -> {
|
||||||
|
renderInfo.beginFrame(event.getClippingHelper(), event.getStack(), camX, camY, camZ);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Backend.getInstance()
|
||||||
|
.available()) {
|
||||||
|
flwRenderers.int2ObjectEntrySet()
|
||||||
|
.stream()
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.forEach(flwVisible::add);
|
||||||
|
|
||||||
|
for (RenderedContraption renderer : flwVisible) {
|
||||||
|
renderer.beginFrame(info, camX, camY, camZ);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderInfos.int2ObjectEntrySet()
|
||||||
|
.stream()
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.forEach(visible::add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderLayer(RenderLayerEvent event) {
|
||||||
|
if (Backend.getInstance().available()) {
|
||||||
|
renderLayerFlywheel(event);
|
||||||
|
} else {
|
||||||
|
renderLayerSBB(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderLayerFlywheel(RenderLayerEvent event) {
|
||||||
|
if (flwVisible.isEmpty()) return;
|
||||||
|
|
||||||
|
RenderType layer = event.getType();
|
||||||
|
|
||||||
|
layer.setupRenderState();
|
||||||
|
GlTextureUnit.T4.makeActive(); // the shaders expect light volumes to be in texture 4
|
||||||
|
|
||||||
|
ContraptionProgram structureShader = CreateContexts.STRUCTURE.getProgram(AllProgramSpecs.STRUCTURE);
|
||||||
|
|
||||||
|
structureShader.bind();
|
||||||
|
structureShader.uploadViewProjection(event.viewProjection);
|
||||||
|
structureShader.uploadCameraPos(event.camX, event.camY, event.camZ);
|
||||||
|
|
||||||
|
for (RenderedContraption renderedContraption : flwVisible) {
|
||||||
|
renderedContraption.doRenderLayer(layer, structureShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Backend.getInstance().canUseInstancing()) {
|
||||||
|
RenderLayer renderLayer = event.getLayer();
|
||||||
|
if (renderLayer != null) {
|
||||||
|
for (RenderedContraption renderer : flwVisible) {
|
||||||
|
renderer.materialManager.render(renderLayer, event.viewProjection, event.camX, event.camY, event.camZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the light volume state
|
||||||
|
GlTextureUnit.T4.makeActive();
|
||||||
|
glBindTexture(GL_TEXTURE_3D, 0);
|
||||||
|
|
||||||
|
layer.clearRenderState();
|
||||||
|
GlTextureUnit.T0.makeActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderLayerSBB(RenderLayerEvent event) {
|
||||||
|
visible.forEach(info -> renderContraptionLayerSBB(event, info));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderContraptionLayerSBB(RenderLayerEvent event, ContraptionRenderInfo renderInfo) {
|
||||||
|
RenderType layer = event.getType();
|
||||||
|
|
||||||
|
if (!renderInfo.isVisible()) return;
|
||||||
|
|
||||||
|
SuperByteBuffer contraptionBuffer = CreateClient.BUFFER_CACHE.get(CONTRAPTION, Pair.of(renderInfo.contraption, layer),
|
||||||
|
() -> buildStructureBuffer(renderInfo.renderWorld, renderInfo.contraption, layer));
|
||||||
|
|
||||||
|
if (!contraptionBuffer.isEmpty()) {
|
||||||
|
|
||||||
|
ContraptionMatrices matrices = renderInfo.getMatrices();
|
||||||
|
contraptionBuffer.transform(matrices.contraptionStack)
|
||||||
|
.light(matrices.entityMatrix)
|
||||||
|
.hybridLight()
|
||||||
|
.renderInto(matrices.entityStack, event.buffers.bufferSource().getBuffer(layer));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRenderer(Contraption c) {
|
||||||
|
int entityId = c.entity.getId();
|
||||||
|
RenderedContraption contraption = flwRenderers.get(entityId);
|
||||||
|
|
||||||
|
if (contraption == null) {
|
||||||
|
PlacementSimulationWorld renderWorld = ContraptionRenderDispatcher.setupRenderWorld(world, c);
|
||||||
|
contraption = new RenderedContraption(c, renderWorld);
|
||||||
|
flwRenderers.put(entityId, contraption);
|
||||||
|
renderInfos.put(entityId, contraption);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContraptionRenderInfo getRenderInfo(Contraption c) {
|
||||||
|
int entityId = c.entity.getId();
|
||||||
|
ContraptionRenderInfo renderInfo = renderInfos.get(entityId);
|
||||||
|
|
||||||
|
if (renderInfo == null) {
|
||||||
|
PlacementSimulationWorld renderWorld = ContraptionRenderDispatcher.setupRenderWorld(world, c);
|
||||||
|
renderInfo = new ContraptionRenderInfo(c, renderWorld);
|
||||||
|
renderInfos.put(entityId, renderInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void invalidate() {
|
||||||
|
for (RenderedContraption renderer : flwRenderers.values()) {
|
||||||
|
renderer.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
flwRenderers.clear();
|
||||||
|
renderInfos.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDeadContraptions() {
|
||||||
|
flwRenderers.values().removeIf(renderer -> {
|
||||||
|
if (renderer.isDead()) {
|
||||||
|
renderer.invalidate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDeadHolders() {
|
||||||
|
renderInfos.values().removeIf(ContraptionRenderInfo::isDead);
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,7 +141,7 @@ public class ClientEvents {
|
||||||
PlacementHelpers.tick();
|
PlacementHelpers.tick();
|
||||||
CreateClient.OUTLINER.tickOutlines();
|
CreateClient.OUTLINER.tickOutlines();
|
||||||
CreateClient.GHOST_BLOCKS.tickGhosts();
|
CreateClient.GHOST_BLOCKS.tickGhosts();
|
||||||
ContraptionRenderDispatcher.tick();
|
ContraptionRenderDispatcher.tick(world);
|
||||||
BlueprintOverlayRenderer.tick();
|
BlueprintOverlayRenderer.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,19 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import net.minecraft.world.IWorld;
|
import net.minecraft.world.IWorld;
|
||||||
|
import net.minecraftforge.common.util.NonNullFunction;
|
||||||
|
|
||||||
public class WorldAttached<T> {
|
public class WorldAttached<T> {
|
||||||
|
|
||||||
static List<Map<IWorld, ?>> allMaps = new ArrayList<>();
|
static List<Map<IWorld, ?>> allMaps = new ArrayList<>();
|
||||||
Map<IWorld, T> attached;
|
Map<IWorld, T> attached;
|
||||||
private Function<IWorld, T> factory;
|
private final NonNullFunction<IWorld, T> factory;
|
||||||
|
|
||||||
public WorldAttached(Function<IWorld, T> factory) {
|
public WorldAttached(NonNullFunction<IWorld, T> factory) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
attached = new HashMap<>();
|
attached = new HashMap<>();
|
||||||
allMaps.add(attached);
|
allMaps.add(attached);
|
||||||
|
@ -26,7 +28,7 @@ public class WorldAttached<T> {
|
||||||
allMaps.forEach(m -> m.remove(world));
|
allMaps.forEach(m -> m.remove(world));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nonnull
|
||||||
public T get(IWorld world) {
|
public T get(IWorld world) {
|
||||||
T t = attached.get(world);
|
T t = attached.get(world);
|
||||||
if (t != null)
|
if (t != null)
|
||||||
|
|
Loading…
Reference in a new issue