mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-06 10:14:59 +01:00
Sweeping changes
- Add VisualizationManager and VisualManager API - Simplify and fix memory leaks in model utilities - Pass partialTick directly to visuals and remove AnimationTickHolder - Fix LevelAttached and FlwTaskExecutor not being thread-safe - Reorganize and rename many things - Remove unnecessary things
This commit is contained in:
parent
fcd70cccd0
commit
66f11018fe
158 changed files with 1960 additions and 3505 deletions
|
@ -135,12 +135,12 @@ dependencies {
|
|||
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
|
||||
|
||||
jarJar('org.joml:joml:1.10.5') {
|
||||
jarJar.ranged(it, '[1.10.0,1.11.0)')
|
||||
jarJar.ranged(it, '[1.10.5,1.11.0)')
|
||||
}
|
||||
library 'org.joml:joml:1.10.5'
|
||||
|
||||
jarJar('com.dreizak:miniball:1.0.3') {
|
||||
jarJar.ranged(it, '[1.0,2.0)')
|
||||
jarJar.ranged(it, '[1.0.3,2.0.0)')
|
||||
}
|
||||
library 'com.dreizak:miniball:1.0.3'
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package com.jozufozu.flywheel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.jozufozu.flywheel.api.visualization.VisualizationManager;
|
||||
import com.jozufozu.flywheel.backend.Backends;
|
||||
import com.jozufozu.flywheel.backend.Loader;
|
||||
import com.jozufozu.flywheel.backend.compile.Pipelines;
|
||||
|
@ -11,29 +14,34 @@ import com.jozufozu.flywheel.backend.engine.batching.DrawBuffer;
|
|||
import com.jozufozu.flywheel.config.BackendArgument;
|
||||
import com.jozufozu.flywheel.config.FlwCommands;
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
import com.jozufozu.flywheel.handler.EntityWorldHandler;
|
||||
import com.jozufozu.flywheel.handler.ForgeEvents;
|
||||
import com.jozufozu.flywheel.impl.BackendManagerImpl;
|
||||
import com.jozufozu.flywheel.impl.IdRegistryImpl;
|
||||
import com.jozufozu.flywheel.impl.RegistryImpl;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizationEventHandler;
|
||||
import com.jozufozu.flywheel.lib.context.Contexts;
|
||||
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
|
||||
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.PartialModel;
|
||||
import com.jozufozu.flywheel.lib.model.baked.PartialModel;
|
||||
import com.jozufozu.flywheel.lib.util.LevelAttached;
|
||||
import com.jozufozu.flywheel.lib.util.ShadersModHandler;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
|
||||
import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor;
|
||||
import com.jozufozu.flywheel.vanilla.VanillaVisuals;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.commands.synchronization.ArgumentTypes;
|
||||
import net.minecraft.commands.synchronization.EmptyArgumentSerializer;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.IExtensionPoint;
|
||||
|
@ -73,24 +81,24 @@ public class Flywheel {
|
|||
}
|
||||
|
||||
private static void clientInit(IEventBus forgeEventBus, IEventBus modEventBus) {
|
||||
forgeEventBus.addListener(FlwCommands::registerClientCommands);
|
||||
forgeEventBus.addListener(Flywheel::addDebugInfo);
|
||||
|
||||
forgeEventBus.addListener(BackendManagerImpl::onReloadRenderers);
|
||||
|
||||
forgeEventBus.addListener(Models::onReloadRenderers);
|
||||
forgeEventBus.addListener(VisualizationEventHandler::onClientTick);
|
||||
forgeEventBus.addListener(VisualizationEventHandler::onBeginFrame);
|
||||
forgeEventBus.addListener(VisualizationEventHandler::onRenderStage);
|
||||
forgeEventBus.addListener(VisualizationEventHandler::onEntityJoinWorld);
|
||||
forgeEventBus.addListener(VisualizationEventHandler::onEntityLeaveWorld);
|
||||
|
||||
forgeEventBus.addListener(FlwCommands::registerClientCommands);
|
||||
|
||||
forgeEventBus.addListener(DrawBuffer::onReloadRenderers);
|
||||
forgeEventBus.addListener(UniformBuffer::onReloadRenderers);
|
||||
|
||||
forgeEventBus.addListener(VisualizedRenderDispatcher::onRenderStage);
|
||||
forgeEventBus.addListener(VisualizedRenderDispatcher::onBeginFrame);
|
||||
forgeEventBus.addListener(VisualizedRenderDispatcher::tick);
|
||||
|
||||
forgeEventBus.addListener(EntityWorldHandler::onEntityJoinWorld);
|
||||
forgeEventBus.addListener(EntityWorldHandler::onEntityLeaveWorld);
|
||||
|
||||
forgeEventBus.addListener(ForgeEvents::addToDebugScreen);
|
||||
forgeEventBus.addListener(ForgeEvents::unloadWorld);
|
||||
forgeEventBus.addListener(ForgeEvents::tickLight);
|
||||
forgeEventBus.addListener(LightUpdater::onClientTick);
|
||||
forgeEventBus.addListener(Models::onReloadRenderers);
|
||||
forgeEventBus.addListener((WorldEvent.Unload e) -> LevelAttached.onUnloadLevel(e));
|
||||
|
||||
modEventBus.addListener(PartialModel::onModelRegistry);
|
||||
modEventBus.addListener(PartialModel::onModelBake);
|
||||
|
@ -114,13 +122,6 @@ public class Flywheel {
|
|||
MaterialIndices.init();
|
||||
|
||||
VanillaVisuals.init();
|
||||
|
||||
// https://github.com/Jozufozu/Flywheel/issues/69
|
||||
// Weird issue with accessor loading.
|
||||
// Only thing I've seen that's close to a fix is to force the class to load before trying to use it.
|
||||
// From the SpongePowered discord:
|
||||
// https://discord.com/channels/142425412096491520/626802111455297538/675007581168599041
|
||||
LOGGER.debug("Successfully loaded {}", PausedPartialTickAccessor.class.getName());
|
||||
}
|
||||
|
||||
private static void setup(final FMLCommonSetupEvent event) {
|
||||
|
@ -130,6 +131,30 @@ public class Flywheel {
|
|||
ArgumentTypes.register(rl("backend").toString(), BackendArgument.class, new EmptyArgumentSerializer<>(() -> BackendArgument.INSTANCE));
|
||||
}
|
||||
|
||||
private static void addDebugInfo(RenderGameOverlayEvent.Text event) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (!mc.options.renderDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<String> info = event.getRight();
|
||||
info.add("");
|
||||
info.add("Flywheel: " + getVersion());
|
||||
info.add("Backend: " + BackendManagerImpl.getBackendString());
|
||||
info.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString());
|
||||
|
||||
VisualizationManager manager = VisualizationManager.get(mc.level);
|
||||
if (manager != null) {
|
||||
info.add("B: " + manager.getBlockEntities().getVisualCount()
|
||||
+ ", E: " + manager.getEntities().getVisualCount()
|
||||
+ ", F: " + manager.getEffects().getVisualCount());
|
||||
Vec3i renderOrigin = manager.getRenderOrigin();
|
||||
info.add("Origin: " + renderOrigin.getX() + ", " + renderOrigin.getY() + ", " + renderOrigin.getZ());
|
||||
}
|
||||
|
||||
info.add("Memory Usage: CPU: " + StringUtil.formatBytes(FlwMemoryTracker.getCPUMemory()) + ", GPU: " + StringUtil.formatBytes(FlwMemoryTracker.getGPUMemory()));
|
||||
}
|
||||
|
||||
public static ArtifactVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package com.jozufozu.flywheel.api.backend;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.api.event.RenderContext;
|
||||
import com.jozufozu.flywheel.api.event.RenderStage;
|
||||
import com.jozufozu.flywheel.api.instance.InstancerProvider;
|
||||
|
@ -26,7 +24,7 @@ public interface Engine extends InstancerProvider {
|
|||
|
||||
Vec3i renderOrigin();
|
||||
|
||||
void addDebugInfo(List<String> info);
|
||||
|
||||
// TODO: "delete" implies that the object cannot be used afterwards, but all current implementations
|
||||
// support the "invalidate" contract as well, meaning they can be reused after this call. Rename?
|
||||
void delete();
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@ import net.minecraft.client.renderer.LevelRenderer;
|
|||
import net.minecraft.client.renderer.RenderBuffers;
|
||||
|
||||
public record RenderContext(LevelRenderer renderer, ClientLevel level, RenderBuffers buffers, PoseStack stack,
|
||||
Matrix4f projection, Matrix4f viewProjection, Camera camera) {
|
||||
public static RenderContext create(LevelRenderer renderer, ClientLevel level, RenderBuffers buffers, PoseStack stack, Matrix4f projection, Camera camera) {
|
||||
Matrix4f projection, Matrix4f viewProjection, Camera camera, float partialTick) {
|
||||
public static RenderContext create(LevelRenderer renderer, ClientLevel level, RenderBuffers buffers, PoseStack stack, Matrix4f projection, Camera camera, float partialTick) {
|
||||
Matrix4f viewProjection = projection.copy();
|
||||
viewProjection.multiply(stack.last().pose());
|
||||
|
||||
return new RenderContext(renderer, level, buffers, stack, projection, viewProjection, camera);
|
||||
return new RenderContext(renderer, level, buffers, stack, projection, viewProjection, camera, partialTick);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public interface Material {
|
|||
|
||||
void clear();
|
||||
|
||||
RenderType getBatchingRenderType();
|
||||
RenderType getFallbackRenderType();
|
||||
|
||||
MaterialVertexTransformer getVertexTransformer();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.jozufozu.flywheel.api.visual;
|
|||
|
||||
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
|
||||
|
||||
// TODO: Consider adding LevelAccessor getter
|
||||
public interface Effect {
|
||||
EffectVisual<?> visualize(VisualizationContext ctx);
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import com.jozufozu.flywheel.lib.task.UnitPlan;
|
|||
|
||||
/**
|
||||
* An interface giving {@link Visual}s a way to define complex, parallelized update plans.
|
||||
* <p>
|
||||
* Plans allow for
|
||||
*/
|
||||
public interface PlannedVisual extends Visual {
|
||||
default Plan<VisualFrameContext> planFrame() {
|
||||
|
|
|
@ -10,14 +10,14 @@ public interface Visual {
|
|||
/**
|
||||
* Initialize instances here.
|
||||
*/
|
||||
void init();
|
||||
void init(float partialTick);
|
||||
|
||||
/**
|
||||
* Update instances here. Good for when instances don't change very often and when animations are GPU based.
|
||||
*
|
||||
* <br><br> If your animations are complex or more CPU driven, see {@link DynamicVisual} or {@link TickableVisual}.
|
||||
*/
|
||||
void update();
|
||||
void update(float partialTick);
|
||||
|
||||
/**
|
||||
* When a visual is reset, the visual is deleted and re-created.
|
||||
|
|
|
@ -3,5 +3,5 @@ package com.jozufozu.flywheel.api.visual;
|
|||
import org.joml.FrustumIntersection;
|
||||
|
||||
public record VisualFrameContext(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum,
|
||||
DistanceUpdateLimiter limiter) {
|
||||
float partialTick, DistanceUpdateLimiter limiter) {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.jozufozu.flywheel.api.visualization;
|
||||
|
||||
public interface VisualManager<T> {
|
||||
/**
|
||||
* Get the number of game objects that are currently being visualized.
|
||||
*
|
||||
* @return The visual count.
|
||||
*/
|
||||
int getVisualCount();
|
||||
|
||||
void queueAdd(T obj);
|
||||
|
||||
void queueRemove(T obj);
|
||||
|
||||
void queueUpdate(T obj);
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
package com.jozufozu.flywheel.api;
|
||||
package com.jozufozu.flywheel.api.visualization;
|
||||
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
|
||||
/**
|
||||
* A marker interface custom levels can override to indicate
|
||||
|
@ -7,8 +9,8 @@ package com.jozufozu.flywheel.api;
|
|||
*
|
||||
* {@link net.minecraft.client.Minecraft#level Minecraft#level} is special cased and will support Flywheel by default.
|
||||
*/
|
||||
public interface FlywheelLevel {
|
||||
default boolean supportsFlywheel() {
|
||||
public interface VisualizationLevel extends LevelAccessor {
|
||||
default boolean supportsVisualization() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package com.jozufozu.flywheel.api.visualization;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.api.visual.Effect;
|
||||
import com.jozufozu.flywheel.api.visual.Visual;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizationManagerImpl;
|
||||
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
public interface VisualizationManager {
|
||||
static boolean supportsVisualization(@Nullable LevelAccessor level) {
|
||||
return VisualizationManagerImpl.supportsVisualization(level);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static VisualizationManager get(@Nullable LevelAccessor level) {
|
||||
return VisualizationManagerImpl.get(level);
|
||||
}
|
||||
|
||||
static VisualizationManager getOrThrow(@Nullable LevelAccessor level) {
|
||||
return VisualizationManagerImpl.getOrThrow(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to run {@link Visual#update()}.
|
||||
* @param blockEntity The block entity whose visual you want to update.
|
||||
*/
|
||||
static void queueUpdate(BlockEntity blockEntity) {
|
||||
Level level = blockEntity.getLevel();
|
||||
VisualizationManager manager = get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.getBlockEntities().queueUpdate(blockEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to run {@link Visual#update()}.
|
||||
* @param entity The entity whose visual you want to update.
|
||||
*/
|
||||
static void queueUpdate(Entity entity) {
|
||||
Level level = entity.level;
|
||||
VisualizationManager manager = get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.getEntities().queueUpdate(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to run {@link Visual#update()}.
|
||||
* @param effect The effect whose visual you want to update.
|
||||
*/
|
||||
static void queueUpdate(LevelAccessor level, Effect effect) {
|
||||
VisualizationManager manager = get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.getEffects().queueUpdate(effect);
|
||||
}
|
||||
|
||||
Vec3i getRenderOrigin();
|
||||
|
||||
VisualManager<BlockEntity> getBlockEntities();
|
||||
|
||||
VisualManager<Entity> getEntities();
|
||||
|
||||
VisualManager<Effect> getEffects();
|
||||
}
|
|
@ -2,7 +2,7 @@ package com.jozufozu.flywheel.api.visualization;
|
|||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.impl.VisualizerRegistryImpl;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizerRegistryImpl;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
|
|
|
@ -26,6 +26,9 @@ public class Loader implements ResourceManagerReloadListener {
|
|||
FlwPrograms.reload(manager);
|
||||
|
||||
// TODO: Move this to the impl package
|
||||
// TODO: To ensure this runs after all backends are ready, inject into Minecraft after the reload and before levelRenderer.allChanged()
|
||||
// Alternatively, consider adding API
|
||||
// TODO: This should reset all VisualizationManagerImpls, not just the one for the static client level
|
||||
BackendManagerImpl.refresh(Minecraft.getInstance().level);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.backend.compile.core.ProgramLinker;
|
||||
|
@ -16,7 +18,6 @@ import com.jozufozu.flywheel.gl.shader.GlShader;
|
|||
import com.jozufozu.flywheel.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.glsl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.glsl.SourceComponent;
|
||||
import com.jozufozu.flywheel.util.NotNullFunction;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
@ -98,11 +99,11 @@ public class Compile {
|
|||
return withComponent($ -> component);
|
||||
}
|
||||
|
||||
public ShaderCompilerBuilder<K> withComponent(NotNullFunction<K, SourceComponent> sourceFetcher) {
|
||||
public ShaderCompilerBuilder<K> withComponent(Function<K, @NotNull SourceComponent> sourceFetcher) {
|
||||
return with((key, $) -> sourceFetcher.apply(key));
|
||||
}
|
||||
|
||||
public ShaderCompilerBuilder<K> withResource(NotNullFunction<K, ResourceLocation> sourceFetcher) {
|
||||
public ShaderCompilerBuilder<K> withResource(Function<K, @NotNull ResourceLocation> sourceFetcher) {
|
||||
return with((key, loader) -> loader.find(sourceFetcher.apply(key)));
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.compile;
|
|||
|
||||
import com.jozufozu.flywheel.api.instance.InstanceType;
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.backend.compile.Pipeline.InstanceAssembler;
|
||||
import com.jozufozu.flywheel.glsl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.glsl.SourceComponent;
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import java.util.Collection;
|
|||
import java.util.Map;
|
||||
|
||||
import com.jozufozu.flywheel.glsl.SourceComponent;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.lib.util.ResourceUtil;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import com.jozufozu.flywheel.gl.shader.ShaderType;
|
|||
import com.jozufozu.flywheel.glsl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.glsl.SourceComponent;
|
||||
import com.jozufozu.flywheel.glsl.SourceFile;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import com.jozufozu.flywheel.Flywheel;
|
|||
import com.jozufozu.flywheel.glsl.LoadError;
|
||||
import com.jozufozu.flywheel.glsl.LoadResult;
|
||||
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class CompilerStats {
|
||||
private long compileStart;
|
||||
|
|
|
@ -11,10 +11,10 @@ import org.jetbrains.annotations.NotNull;
|
|||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.glsl.SourceFile;
|
||||
import com.jozufozu.flywheel.glsl.SourceLines;
|
||||
import com.jozufozu.flywheel.glsl.error.ConsoleColors;
|
||||
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.glsl.span.Span;
|
||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
|
|
@ -11,15 +11,16 @@ import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
|
|||
import com.jozufozu.flywheel.gl.buffer.GlBuffer;
|
||||
import com.jozufozu.flywheel.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.lib.math.MoreMath;
|
||||
import com.jozufozu.flywheel.lib.math.RenderMath;
|
||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class UniformBuffer {
|
||||
|
||||
private static final int OFFSET_ALIGNMENT = GL32.glGetInteger(GL32.GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
|
||||
private static final int MAX_SIZE = GL32.glGetInteger(GL32.GL_MAX_UNIFORM_BLOCK_SIZE);
|
||||
private static final int MAX_BINDINGS = GL32.glGetInteger(GL32.GL_MAX_UNIFORM_BUFFER_BINDINGS);
|
||||
private static final boolean PO2_ALIGNMENT = RenderMath.isPowerOf2(OFFSET_ALIGNMENT);
|
||||
private static final boolean PO2_ALIGNMENT = Mth.isPowerOfTwo(OFFSET_ALIGNMENT);
|
||||
|
||||
private static UniformBuffer instance;
|
||||
private final ProviderSet providerSet;
|
||||
|
|
|
@ -5,7 +5,7 @@ import org.joml.FrustumIntersection;
|
|||
|
||||
import com.jozufozu.flywheel.api.event.RenderContext;
|
||||
import com.jozufozu.flywheel.lib.math.MatrixUtil;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.lib.util.FlwUtil;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
|
|
|
@ -89,12 +89,6 @@ public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan
|
|||
initializedInstancers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebugInfo(List<String> info) {
|
||||
info.add("Batching");
|
||||
info.add("Origin: " + renderOrigin.getX() + ", " + renderOrigin.getY() + ", " + renderOrigin.getZ());
|
||||
}
|
||||
|
||||
private void flush() {
|
||||
for (var instancer : uninitializedInstancers) {
|
||||
add(instancer.instancer(), instancer.model(), instancer.stage());
|
||||
|
@ -111,7 +105,7 @@ public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan
|
|||
var meshes = model.getMeshes();
|
||||
for (var entry : meshes.entrySet()) {
|
||||
var material = entry.getKey();
|
||||
RenderType renderType = material.getBatchingRenderType();
|
||||
RenderType renderType = material.getFallbackRenderType();
|
||||
var transformCall = new TransformCall<>(instancer, material, alloc(entry.getValue(), renderType.format()));
|
||||
stagePlan.put(renderType, transformCall);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import com.jozufozu.flywheel.api.instance.Instancer;
|
|||
import com.jozufozu.flywheel.api.model.Model;
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.backend.engine.InstancerKey;
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
|
||||
public class IndirectDrawManager {
|
||||
private final Map<InstancerKey<?>, IndirectInstancer<?>> instancers = new HashMap<>();
|
||||
|
@ -43,7 +43,7 @@ public class IndirectDrawManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
public void invalidate() {
|
||||
instancers.clear();
|
||||
|
||||
renderLists.values()
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package com.jozufozu.flywheel.backend.engine.indirect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.jozufozu.flywheel.api.event.RenderContext;
|
||||
|
@ -78,12 +76,6 @@ public class IndirectEngine extends AbstractEngine {
|
|||
|
||||
@Override
|
||||
public void delete() {
|
||||
drawManager.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebugInfo(List<String> info) {
|
||||
info.add("GL46 Indirect");
|
||||
info.add("Origin: " + renderOrigin.getX() + ", " + renderOrigin.getY() + ", " + renderOrigin.getZ());
|
||||
drawManager.invalidate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public class EBOCache {
|
|||
private final List<Entry> quads = new ArrayList<>();
|
||||
private final Object2ReferenceMap<Key, Entry> others = new Object2ReferenceOpenHashMap<>();
|
||||
|
||||
public void delete() {
|
||||
public void invalidate() {
|
||||
quads.forEach(Entry::delete);
|
||||
others.values()
|
||||
.forEach(Entry::delete);
|
||||
|
|
|
@ -57,7 +57,7 @@ public class InstancedDrawManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
public void invalidate() {
|
||||
instancers.clear();
|
||||
|
||||
meshPools.values()
|
||||
|
@ -71,7 +71,7 @@ public class InstancedDrawManager {
|
|||
initializedInstancers.forEach(InstancedInstancer::delete);
|
||||
initializedInstancers.clear();
|
||||
|
||||
eboCache.delete();
|
||||
eboCache.invalidate();
|
||||
}
|
||||
|
||||
public void clearInstancers() {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package com.jozufozu.flywheel.backend.engine.instancing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.jozufozu.flywheel.api.context.Context;
|
||||
|
@ -122,12 +120,6 @@ public class InstancingEngine extends AbstractEngine {
|
|||
|
||||
@Override
|
||||
public void delete() {
|
||||
drawManager.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebugInfo(List<String> info) {
|
||||
info.add("GL33 Instanced Arrays");
|
||||
info.add("Origin: " + renderOrigin.getX() + ", " + renderOrigin.getY() + ", " + renderOrigin.getZ());
|
||||
drawManager.invalidate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.task;
|
||||
|
||||
public class FlwTaskExecutor {
|
||||
private static ParallelTaskExecutor executor;
|
||||
|
||||
/**
|
||||
* Get a thread pool for running Flywheel related work in parallel.
|
||||
* @return A global Flywheel thread pool.
|
||||
*/
|
||||
public static ParallelTaskExecutor get() {
|
||||
if (executor == null) {
|
||||
executor = new ParallelTaskExecutor("Flywheel");
|
||||
executor.startWorkers();
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL45C;
|
|||
import org.lwjgl.system.Checks;
|
||||
|
||||
import com.jozufozu.flywheel.gl.GlCompat;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.lib.util.FlwUtil;
|
||||
|
||||
public class GlVertexArrayDSA extends GlVertexArray {
|
||||
public static final boolean SUPPORTED = isSupported();
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.lwjgl.system.Checks;
|
|||
|
||||
import com.jozufozu.flywheel.gl.GlCompat;
|
||||
import com.jozufozu.flywheel.gl.buffer.GlBufferType;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.lib.util.FlwUtil;
|
||||
|
||||
public abstract class GlVertexArrayGL3 extends GlVertexArray {
|
||||
private final BitSet attributeDirty = new BitSet(MAX_ATTRIBS);
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.lwjgl.system.Checks;
|
|||
import com.jozufozu.flywheel.gl.GlCompat;
|
||||
import com.jozufozu.flywheel.gl.GlStateTracker;
|
||||
import com.jozufozu.flywheel.gl.buffer.GlBufferType;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.lib.util.FlwUtil;
|
||||
|
||||
public class GlVertexArraySeparateAttributes extends GlVertexArray {
|
||||
public static final boolean SUPPORTED = isSupported();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package com.jozufozu.flywheel.gl.shader;
|
||||
|
||||
import static org.lwjgl.opengl.GL20.glDeleteProgram;
|
||||
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
|
||||
import static org.lwjgl.opengl.GL20.glUniform1i;
|
||||
import static org.lwjgl.opengl.GL31.GL_INVALID_INDEX;
|
||||
import static org.lwjgl.opengl.GL31.glGetUniformBlockIndex;
|
||||
import static org.lwjgl.opengl.GL31.glUniformBlockBinding;
|
||||
import static org.lwjgl.opengl.GL32.glDeleteProgram;
|
||||
import static org.lwjgl.opengl.GL32.glGetUniformLocation;
|
||||
import static org.lwjgl.opengl.GL32.glUniform1i;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import com.jozufozu.flywheel.glsl.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.glsl.span.Span;
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.jozufozu.flywheel.glsl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
|
@ -10,10 +12,10 @@ import java.util.Map;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.ResourceUtil;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
|
||||
/**
|
||||
|
@ -66,11 +68,9 @@ public class ShaderSources {
|
|||
|
||||
@NotNull
|
||||
protected LoadResult load(ResourceLocation loc) {
|
||||
try {
|
||||
var resource = manager.getResource(ResourceUtil.prefixed(SHADER_DIR, loc));
|
||||
|
||||
var sourceString = StringUtil.readToString(resource.getInputStream());
|
||||
|
||||
try (Resource resource = manager.getResource(ResourceUtil.prefixed(SHADER_DIR, loc))) {
|
||||
InputStream stream = resource.getInputStream();
|
||||
String sourceString = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
return SourceFile.parse(this, loc, sourceString);
|
||||
} catch (IOException e) {
|
||||
return new LoadResult.Failure(new LoadError.IOError(loc, e));
|
||||
|
|
|
@ -17,8 +17,8 @@ import com.jozufozu.flywheel.glsl.parse.ShaderFunction;
|
|||
import com.jozufozu.flywheel.glsl.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.glsl.span.Span;
|
||||
import com.jozufozu.flywheel.glsl.span.StringSpan;
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.ResourceUtil;
|
||||
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.jozufozu.flywheel.util;
|
||||
package com.jozufozu.flywheel.glsl.error;
|
||||
|
||||
// https://stackoverflow.com/a/45444716
|
||||
public class ConsoleColors {
|
||||
public final class ConsoleColors {
|
||||
// Reset
|
||||
public static final String RESET = "\033[0m"; // Text Reset
|
||||
|
||||
|
@ -74,4 +74,7 @@ public class ConsoleColors {
|
|||
public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLE
|
||||
public static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYAN
|
||||
public static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITE
|
||||
|
||||
private ConsoleColors() {
|
||||
}
|
||||
}
|
|
@ -19,8 +19,7 @@ import com.jozufozu.flywheel.glsl.error.lines.SourceLine;
|
|||
import com.jozufozu.flywheel.glsl.error.lines.SpanHighlightLine;
|
||||
import com.jozufozu.flywheel.glsl.error.lines.TextLine;
|
||||
import com.jozufozu.flywheel.glsl.span.Span;
|
||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package com.jozufozu.flywheel.glsl.error;
|
||||
|
||||
import com.jozufozu.flywheel.util.ConsoleColors;
|
||||
|
||||
public enum ErrorLevel {
|
||||
WARN(ConsoleColors.YELLOW, "warn"),
|
||||
ERROR(ConsoleColors.RED, "error"),
|
||||
|
|
|
@ -4,7 +4,7 @@ import java.util.Collection;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
|
||||
public record FnSignature(String returnType, String name, ImmutableList<Pair<String, String>> args) {
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.jozufozu.flywheel.glsl.generate;
|
|||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslFn implements GlslBuilder.Declaration {
|
||||
private final GlslBlock body = new GlslBlock();
|
||||
|
|
|
@ -4,8 +4,8 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslStruct implements GlslBuilder.Declaration {
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslSwitch implements GlslStmt {
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslUniformBlock implements GlslBuilder.Declaration {
|
||||
private String qualifier;
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package com.jozufozu.flywheel.handler;
|
||||
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.event.entity.EntityLeaveWorldEvent;
|
||||
|
||||
public class EntityWorldHandler {
|
||||
public static void onEntityJoinWorld(EntityJoinWorldEvent event) {
|
||||
Level level = event.getWorld();
|
||||
if (!level.isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FlwUtil.canUseVisualization(level)) {
|
||||
VisualizedRenderDispatcher.getEntities(level)
|
||||
.queueAdd(event.getEntity());
|
||||
}
|
||||
}
|
||||
|
||||
public static void onEntityLeaveWorld(EntityLeaveWorldEvent event) {
|
||||
Level level = event.getWorld();
|
||||
if (!level.isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FlwUtil.canUseVisualization(level)) {
|
||||
VisualizedRenderDispatcher.getEntities(level)
|
||||
.queueRemove(event.getEntity());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.jozufozu.flywheel.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
|
||||
import com.jozufozu.flywheel.lib.light.LightUpdater;
|
||||
import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.util.WorldAttached;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
|
||||
public class ForgeEvents {
|
||||
public static void addToDebugScreen(RenderGameOverlayEvent.Text event) {
|
||||
if (Minecraft.getInstance().options.renderDebug) {
|
||||
ArrayList<String> debug = event.getRight();
|
||||
debug.add("");
|
||||
debug.add("Flywheel: " + Flywheel.getVersion());
|
||||
|
||||
VisualizedRenderDispatcher.addDebugInfo(debug);
|
||||
|
||||
debug.add("Memory Usage: CPU: " + StringUtil.formatBytes(FlwMemoryTracker.getCPUMemory()) + ", GPU: " + StringUtil.formatBytes(FlwMemoryTracker.getGPUMemory()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void unloadWorld(WorldEvent.Unload event) {
|
||||
WorldAttached.invalidateWorld(event.getWorld());
|
||||
}
|
||||
|
||||
public static void tickLight(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.END && FlwUtil.isGameActive()) {
|
||||
LightUpdater.get(Minecraft.getInstance().level)
|
||||
.tick();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,13 +8,14 @@ import com.jozufozu.flywheel.api.backend.Backend;
|
|||
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
|
||||
import com.jozufozu.flywheel.backend.Backends;
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizedRenderDispatcher;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizationManagerImpl;
|
||||
import com.jozufozu.flywheel.lib.backend.SimpleBackend;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.network.chat.TextComponent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.fml.CrashReportCallables;
|
||||
|
||||
public final class BackendManagerImpl {
|
||||
|
@ -23,7 +24,7 @@ public final class BackendManagerImpl {
|
|||
private static final Backend OFF_BACKEND = SimpleBackend.builder()
|
||||
.engineMessage(new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED))
|
||||
.engineFactory(level -> {
|
||||
throw new IllegalStateException("Cannot create engine when backend is off.");
|
||||
throw new UnsupportedOperationException("Cannot create engine when backend is off.");
|
||||
})
|
||||
.supported(() -> true)
|
||||
.register(Flywheel.rl("off"));
|
||||
|
@ -32,6 +33,9 @@ public final class BackendManagerImpl {
|
|||
|
||||
private static Backend backend = OFF_BACKEND;
|
||||
|
||||
private BackendManagerImpl() {
|
||||
}
|
||||
|
||||
public static Backend getBackend() {
|
||||
return backend;
|
||||
}
|
||||
|
@ -62,7 +66,7 @@ public final class BackendManagerImpl {
|
|||
backend = chooseBackend();
|
||||
|
||||
if (level != null) {
|
||||
VisualizedRenderDispatcher.resetVisualWorld(level);
|
||||
VisualizationManagerImpl.reset(level);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,16 +81,15 @@ public final class BackendManagerImpl {
|
|||
return actual;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
CrashReportCallables.registerCrashCallable("Flywheel Backend", () -> {
|
||||
var backendId = Backend.REGISTRY.getId(backend);
|
||||
public static String getBackendString() {
|
||||
ResourceLocation backendId = Backend.REGISTRY.getId(backend);
|
||||
if (backendId == null) {
|
||||
return "Unregistered";
|
||||
return "[unregistered]";
|
||||
}
|
||||
return backendId.toString();
|
||||
});
|
||||
}
|
||||
|
||||
private BackendManagerImpl() {
|
||||
public static void init() {
|
||||
CrashReportCallables.registerCrashCallable("Flywheel Backend", BackendManagerImpl::getBackendString);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package com.jozufozu.flywheel.impl.task;
|
||||
|
||||
import org.apache.commons.lang3.concurrent.AtomicSafeInitializer;
|
||||
import org.apache.commons.lang3.concurrent.ConcurrentUtils;
|
||||
|
||||
public final class FlwTaskExecutor {
|
||||
// TODO: system property to use SerialTaskExecutor
|
||||
private static final Initializer INITIALIZER = new Initializer();
|
||||
|
||||
private FlwTaskExecutor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a thread pool for running Flywheel related work in parallel.
|
||||
* @return A global Flywheel thread pool.
|
||||
*/
|
||||
public static ParallelTaskExecutor get() {
|
||||
return ConcurrentUtils.initializeUnchecked(INITIALIZER);
|
||||
}
|
||||
|
||||
private static class Initializer extends AtomicSafeInitializer<ParallelTaskExecutor> {
|
||||
@Override
|
||||
protected ParallelTaskExecutor initialize() {
|
||||
ParallelTaskExecutor executor = new ParallelTaskExecutor("Flywheel");
|
||||
executor.startWorkers();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.task;
|
||||
package com.jozufozu.flywheel.impl.task;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
|
@ -13,8 +13,6 @@ import org.slf4j.Logger;
|
|||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||
import com.jozufozu.flywheel.lib.task.ThreadGroupNotifier;
|
||||
import com.jozufozu.flywheel.lib.task.WaitGroup;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.task;
|
||||
package com.jozufozu.flywheel.impl.task;
|
||||
|
||||
import com.jozufozu.flywheel.api.task.TaskExecutor;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.lib.task;
|
||||
package com.jozufozu.flywheel.impl.task;
|
||||
|
||||
/**
|
||||
* Thin wrapper around Java's built-in object synchronization primitives.
|
|
@ -1,15 +1,10 @@
|
|||
package com.jozufozu.flywheel.lib.task;
|
||||
package com.jozufozu.flywheel.impl.task;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
public class WaitGroup {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
public void add() {
|
|
@ -5,11 +5,9 @@ import com.jozufozu.flywheel.api.vertex.VertexListProvider;
|
|||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
|
||||
public class InferredVertexListProviderImpl implements VertexListProvider {
|
||||
private final VertexFormat format;
|
||||
private final InferredVertexFormatInfo formatInfo;
|
||||
|
||||
public InferredVertexListProviderImpl(VertexFormat format) {
|
||||
this.format = format;
|
||||
formatInfo = new InferredVertexFormatInfo(format);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,5 +2,5 @@ package com.jozufozu.flywheel.impl.visualization;
|
|||
|
||||
import org.joml.FrustumIntersection;
|
||||
|
||||
public record FrameContext(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
|
||||
public record FrameContext(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum, float partialTick) {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package com.jozufozu.flywheel.impl.visualization;
|
||||
|
||||
import com.jozufozu.flywheel.api.event.BeginFrameEvent;
|
||||
import com.jozufozu.flywheel.api.event.RenderStageEvent;
|
||||
import com.jozufozu.flywheel.api.visualization.VisualizationManager;
|
||||
import com.jozufozu.flywheel.lib.util.FlwUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.event.entity.EntityLeaveWorldEvent;
|
||||
|
||||
public final class VisualizationEventHandler {
|
||||
private VisualizationEventHandler() {
|
||||
}
|
||||
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END || !FlwUtil.isGameActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.isPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entity cameraEntity = mc.getCameraEntity() == null ? mc.player : mc.getCameraEntity();
|
||||
if (cameraEntity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Level level = cameraEntity.level;
|
||||
VisualizationManagerImpl manager = VisualizationManagerImpl.get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
double cameraX = cameraEntity.getX();
|
||||
double cameraY = cameraEntity.getEyeY();
|
||||
double cameraZ = cameraEntity.getZ();
|
||||
|
||||
manager.tick(cameraX, cameraY, cameraZ);
|
||||
}
|
||||
|
||||
public static void onBeginFrame(BeginFrameEvent event) {
|
||||
ClientLevel level = event.getContext().level();
|
||||
VisualizationManagerImpl manager = VisualizationManagerImpl.get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.beginFrame(event.getContext());
|
||||
}
|
||||
|
||||
public static void onRenderStage(RenderStageEvent event) {
|
||||
ClientLevel level = event.getContext().level();
|
||||
VisualizationManagerImpl manager = VisualizationManagerImpl.get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.renderStage(event.getContext(), event.getStage());
|
||||
}
|
||||
|
||||
public static void onEntityJoinWorld(EntityJoinWorldEvent event) {
|
||||
Level level = event.getWorld();
|
||||
VisualizationManager manager = VisualizationManager.get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.getEntities().queueAdd(event.getEntity());
|
||||
}
|
||||
|
||||
public static void onEntityLeaveWorld(EntityLeaveWorldEvent event) {
|
||||
Level level = event.getWorld();
|
||||
VisualizationManager manager = VisualizationManager.get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.getEntities().queueRemove(event.getEntity());
|
||||
}
|
||||
}
|
|
@ -4,14 +4,19 @@ import org.jetbrains.annotations.Nullable;
|
|||
|
||||
import com.jozufozu.flywheel.api.visualization.BlockEntityVisualizer;
|
||||
import com.jozufozu.flywheel.api.visualization.EntityVisualizer;
|
||||
import com.jozufozu.flywheel.api.visualization.VisualizationManager;
|
||||
import com.jozufozu.flywheel.api.visualization.VisualizerRegistry;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
|
||||
public final class VisualizationHelper {
|
||||
private VisualizationHelper() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static <T extends BlockEntity> BlockEntityVisualizer<? super T> getVisualizer(T blockEntity) {
|
||||
|
@ -72,6 +77,20 @@ public final class VisualizationHelper {
|
|||
return visualizer.shouldSkipRender(entity);
|
||||
}
|
||||
|
||||
private VisualizationHelper() {
|
||||
public static <T extends BlockEntity> boolean tryAddBlockEntity(T blockEntity) {
|
||||
Level level = blockEntity.getLevel();
|
||||
VisualizationManager manager = VisualizationManager.get(level);
|
||||
if (manager == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockEntityVisualizer<? super T> visualizer = getVisualizer(blockEntity);
|
||||
if (visualizer == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
manager.getBlockEntities().queueAdd(blockEntity);
|
||||
|
||||
return visualizer.shouldSkipRender(blockEntity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.jozufozu.flywheel.impl.visualization;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.FrustumIntersection;
|
||||
|
||||
import com.jozufozu.flywheel.api.backend.BackendManager;
|
||||
|
@ -13,42 +12,47 @@ import com.jozufozu.flywheel.api.task.TaskExecutor;
|
|||
import com.jozufozu.flywheel.api.visual.DynamicVisual;
|
||||
import com.jozufozu.flywheel.api.visual.Effect;
|
||||
import com.jozufozu.flywheel.api.visual.TickableVisual;
|
||||
import com.jozufozu.flywheel.backend.task.FlwTaskExecutor;
|
||||
import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor;
|
||||
import com.jozufozu.flywheel.config.FlwCommands;
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
import com.jozufozu.flywheel.api.visualization.VisualManager;
|
||||
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;
|
||||
import com.jozufozu.flywheel.impl.visualization.manager.VisualManager;
|
||||
import com.jozufozu.flywheel.lib.math.MatrixUtil;
|
||||
import com.jozufozu.flywheel.lib.task.NestedPlan;
|
||||
import com.jozufozu.flywheel.lib.task.SimplyComposedPlan;
|
||||
import com.jozufozu.flywheel.util.Unit;
|
||||
import com.jozufozu.flywheel.lib.util.LevelAttached;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
/**
|
||||
* A manager class for a single world where instancing is supported.
|
||||
* A manager class for a single world where visualization is supported.
|
||||
*/
|
||||
// AutoCloseable is implemented to prevent leaking this object from WorldAttached
|
||||
public class VisualWorld implements AutoCloseable {
|
||||
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 VisualManager<BlockEntity> blockEntities;
|
||||
private final VisualManager<Entity> entities;
|
||||
private final VisualManager<Effect> effects;
|
||||
private final BlockEntityVisualManager blockEntities;
|
||||
private final EntityVisualManager entities;
|
||||
private final EffectVisualManager effects;
|
||||
|
||||
private final Plan<TickContext> tickPlan;
|
||||
private final Plan<RenderContext> framePlan;
|
||||
|
||||
public VisualWorld(LevelAccessor level) {
|
||||
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);
|
||||
|
@ -62,18 +66,74 @@ public class VisualWorld implements AutoCloseable {
|
|||
framePlan = new FramePlan();
|
||||
}
|
||||
|
||||
public Engine getEngine() {
|
||||
return engine;
|
||||
public static boolean supportsVisualization(@Nullable LevelAccessor level) {
|
||||
if (!BackendManager.isOn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (level == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!level.isClientSide()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (level instanceof VisualizationLevel flywheelLevel && flywheelLevel.supportsVisualization()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return level == Minecraft.getInstance().level;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VisualizationManagerImpl get(@Nullable LevelAccessor level) {
|
||||
if (!supportsVisualization(level)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return MANAGERS.get(level);
|
||||
}
|
||||
|
||||
public static VisualizationManagerImpl getOrThrow(@Nullable LevelAccessor level) {
|
||||
if (!supportsVisualization(level)) {
|
||||
throw new IllegalStateException("Cannot retrieve visualization manager when visualization is not supported by level '" + level + "'!");
|
||||
}
|
||||
|
||||
return MANAGERS.get(level);
|
||||
}
|
||||
|
||||
// TODO: Consider making this reset action reuse the existing added game objects instead of readding them, potentially by keeping the same VisualizationManagerImpl and not fully deleting it
|
||||
// TODO: Consider changing parameter type to Level since it is also possible to get all entities from it
|
||||
public static void reset(ClientLevel level) {
|
||||
MANAGERS.remove(level);
|
||||
VisualizationManagerImpl manager = get(level);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Block entities are loaded while chunks are baked.
|
||||
// Entities are loaded with the level, so when chunks are reloaded they need to be re-added.
|
||||
ClientLevelExtension.getAllLoadedEntities(level)
|
||||
.forEach(manager.getEntities()::queueAdd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3i getRenderOrigin() {
|
||||
return engine.renderOrigin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualManager<BlockEntity> getBlockEntities() {
|
||||
return blockEntities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualManager<Entity> getEntities() {
|
||||
return entities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualManager<Effect> getEffects() {
|
||||
return effects;
|
||||
}
|
||||
|
@ -111,16 +171,8 @@ public class VisualWorld implements AutoCloseable {
|
|||
engine.renderStage(taskExecutor, context, stage);
|
||||
}
|
||||
|
||||
public void addDebugInfo(List<String> info) {
|
||||
info.add("B: " + blockEntities.getVisualCount()
|
||||
+ ", E: " + entities.getVisualCount()
|
||||
+ ", F: " + effects.getVisualCount());
|
||||
info.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString());
|
||||
engine.addDebugInfo(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free all acquired resources and invalidate this visual world.
|
||||
* Free all acquired resources and delete this manager.
|
||||
*/
|
||||
public void delete() {
|
||||
taskExecutor.discardAndAwait();
|
||||
|
@ -130,13 +182,8 @@ public class VisualWorld implements AutoCloseable {
|
|||
engine.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
delete();
|
||||
}
|
||||
|
||||
private class FramePlan implements SimplyComposedPlan<RenderContext> {
|
||||
private final Plan<Unit> recreationPlan = NestedPlan.of(blockEntities.createRecreationPlan(), entities.createRecreationPlan(), effects.createRecreationPlan());
|
||||
private final Plan<Float> recreationPlan = NestedPlan.of(blockEntities.createRecreationPlan(), entities.createRecreationPlan(), effects.createRecreationPlan());
|
||||
private final Plan<FrameContext> normalPlan = blockEntities.createFramePlan()
|
||||
.and(entities.createFramePlan())
|
||||
.and(effects.createFramePlan());
|
||||
|
@ -146,9 +193,10 @@ public class VisualWorld implements AutoCloseable {
|
|||
@Override
|
||||
public void execute(TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) {
|
||||
Runnable then = () -> enginePlan.execute(taskExecutor, context, onCompletion);
|
||||
float partialTick = context.partialTick();
|
||||
|
||||
if (engine.updateRenderOrigin(context.camera())) {
|
||||
recreationPlan.execute(taskExecutor, Unit.INSTANCE, then);
|
||||
recreationPlan.execute(taskExecutor, partialTick, then);
|
||||
} else {
|
||||
Vec3i renderOrigin = engine.renderOrigin();
|
||||
var cameraPos = context.camera()
|
||||
|
@ -161,7 +209,7 @@ public class VisualWorld implements AutoCloseable {
|
|||
proj.translate((float) (renderOrigin.getX() - cameraX), (float) (renderOrigin.getY() - cameraY), (float) (renderOrigin.getZ() - cameraZ));
|
||||
FrustumIntersection frustum = new FrustumIntersection(proj);
|
||||
|
||||
var frameContext = new FrameContext(cameraX, cameraY, cameraZ, frustum);
|
||||
var frameContext = new FrameContext(cameraX, cameraY, cameraZ, frustum, partialTick);
|
||||
|
||||
normalPlan.execute(taskExecutor, frameContext, then);
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
package com.jozufozu.flywheel.impl.visualization;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.api.event.BeginFrameEvent;
|
||||
import com.jozufozu.flywheel.api.event.RenderStageEvent;
|
||||
import com.jozufozu.flywheel.api.visual.Effect;
|
||||
import com.jozufozu.flywheel.api.visual.Visual;
|
||||
import com.jozufozu.flywheel.api.visualization.BlockEntityVisualizer;
|
||||
import com.jozufozu.flywheel.extension.ClientLevelExtension;
|
||||
import com.jozufozu.flywheel.impl.visualization.manager.VisualManager;
|
||||
import com.jozufozu.flywheel.lib.util.AnimationTickHolder;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.util.WorldAttached;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
|
||||
public class VisualizedRenderDispatcher {
|
||||
private static final WorldAttached<VisualWorld> VISUAL_WORLDS = new WorldAttached<>(VisualWorld::new);
|
||||
|
||||
/**
|
||||
* Call this when you want to run {@link Visual#update()}.
|
||||
* @param blockEntity The block entity whose visual you want to update.
|
||||
*/
|
||||
public static void queueUpdate(BlockEntity blockEntity) {
|
||||
if (!(blockEntity.getLevel() instanceof ClientLevel level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VISUAL_WORLDS.get(level)
|
||||
.getBlockEntities()
|
||||
.queueUpdate(blockEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to run {@link Visual#update()}.
|
||||
* @param entity The entity whose visual you want to update.
|
||||
*/
|
||||
public static void queueUpdate(Entity entity) {
|
||||
Level level = entity.level;
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VISUAL_WORLDS.get(level)
|
||||
.getEntities()
|
||||
.queueUpdate(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to run {@link Visual#update()}.
|
||||
* @param effect The effect whose visual you want to update.
|
||||
*/
|
||||
public static void queueUpdate(LevelAccessor level, Effect effect) {
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VISUAL_WORLDS.get(level)
|
||||
.getEffects()
|
||||
.queueUpdate(effect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the {@link VisualWorld} for the given world.
|
||||
* @throws IllegalStateException if the backend is off
|
||||
*/
|
||||
private static VisualWorld getVisualWorld(LevelAccessor level) {
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
throw new IllegalStateException("Cannot retrieve visual world when backend is off!");
|
||||
}
|
||||
return VISUAL_WORLDS.get(level);
|
||||
}
|
||||
|
||||
public static VisualManager<BlockEntity> getBlockEntities(LevelAccessor level) {
|
||||
return getVisualWorld(level).getBlockEntities();
|
||||
}
|
||||
|
||||
public static VisualManager<Entity> getEntities(LevelAccessor level) {
|
||||
return getVisualWorld(level).getEntities();
|
||||
}
|
||||
|
||||
public static VisualManager<Effect> getEffects(LevelAccessor level) {
|
||||
return getVisualWorld(level).getEffects();
|
||||
}
|
||||
|
||||
public static Vec3i getRenderOrigin(LevelAccessor level) {
|
||||
return getVisualWorld(level).getEngine().renderOrigin();
|
||||
}
|
||||
|
||||
public static void tick(TickEvent.ClientTickEvent event) {
|
||||
if (!FlwUtil.isGameActive() || event.phase == TickEvent.Phase.START) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnimationTickHolder.tick();
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.isPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entity cameraEntity = mc.getCameraEntity() == null ? mc.player : mc.getCameraEntity();
|
||||
if (cameraEntity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Level level = cameraEntity.level;
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
double cameraX = cameraEntity.getX();
|
||||
double cameraY = cameraEntity.getEyeY();
|
||||
double cameraZ = cameraEntity.getZ();
|
||||
|
||||
VISUAL_WORLDS.get(level).tick(cameraX, cameraY, cameraZ);
|
||||
}
|
||||
|
||||
public static void onBeginFrame(BeginFrameEvent event) {
|
||||
if (!FlwUtil.isGameActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientLevel level = event.getContext().level();
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VISUAL_WORLDS.get(level).beginFrame(event.getContext());
|
||||
}
|
||||
|
||||
public static void onRenderStage(RenderStageEvent event) {
|
||||
ClientLevel level = event.getContext().level();
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VISUAL_WORLDS.get(level).renderStage(event.getContext(), event.getStage());
|
||||
}
|
||||
|
||||
public static void resetVisualWorld(ClientLevel level) {
|
||||
VISUAL_WORLDS.remove(level, VisualWorld::delete);
|
||||
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VisualWorld world = VISUAL_WORLDS.get(level);
|
||||
// Block entities are loaded while chunks are baked.
|
||||
// Entities are loaded with the level, so when chunks are reloaded they need to be re-added.
|
||||
ClientLevelExtension.getAllLoadedEntities(level)
|
||||
.forEach(world.getEntities()::queueAdd);
|
||||
}
|
||||
|
||||
public static <T extends BlockEntity> boolean tryAddBlockEntity(T blockEntity) {
|
||||
Level level = blockEntity.getLevel();
|
||||
if (!FlwUtil.canUseVisualization(level)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockEntityVisualizer<? super T> visualizer = VisualizationHelper.getVisualizer(blockEntity);
|
||||
if (visualizer == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getBlockEntities(level).queueAdd(blockEntity);
|
||||
|
||||
return visualizer.shouldSkipRender(blockEntity);
|
||||
}
|
||||
|
||||
public static void addDebugInfo(List<String> info) {
|
||||
ClientLevel level = Minecraft.getInstance().level;
|
||||
if (FlwUtil.canUseVisualization(level)) {
|
||||
VISUAL_WORLDS.get(level).addDebugInfo(info);
|
||||
} else {
|
||||
info.add("Disabled");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.impl;
|
||||
package com.jozufozu.flywheel.impl.visualization;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@ -12,7 +12,7 @@ import net.minecraft.world.entity.EntityType;
|
|||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
|
||||
//TODO: Add freezing
|
||||
// TODO: Add freezing
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class VisualizerRegistryImpl {
|
||||
@Nullable
|
|
@ -6,6 +6,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||
import com.jozufozu.flywheel.api.task.Plan;
|
||||
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
|
||||
import com.jozufozu.flywheel.api.visual.VisualTickContext;
|
||||
import com.jozufozu.flywheel.api.visualization.VisualManager;
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
import com.jozufozu.flywheel.impl.visualization.FrameContext;
|
||||
import com.jozufozu.flywheel.impl.visualization.TickContext;
|
||||
|
@ -15,15 +16,14 @@ import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter;
|
|||
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
|
||||
import com.jozufozu.flywheel.impl.visualization.storage.Transaction;
|
||||
import com.jozufozu.flywheel.lib.task.SimplePlan;
|
||||
import com.jozufozu.flywheel.util.Unit;
|
||||
|
||||
public abstract class VisualManager<T> {
|
||||
public abstract class AbstractVisualManager<T> implements VisualManager<T> {
|
||||
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
protected DistanceUpdateLimiterImpl tickLimiter;
|
||||
protected DistanceUpdateLimiterImpl frameLimiter;
|
||||
|
||||
public VisualManager() {
|
||||
public AbstractVisualManager() {
|
||||
tickLimiter = createUpdateLimiter();
|
||||
frameLimiter = createUpdateLimiter();
|
||||
}
|
||||
|
@ -39,15 +39,12 @@ public abstract class VisualManager<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of game objects that are currently being visualized.
|
||||
*
|
||||
* @return The object count.
|
||||
*/
|
||||
@Override
|
||||
public int getVisualCount() {
|
||||
return getStorage().getAllVisuals().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueAdd(T obj) {
|
||||
if (!getStorage().willAccept(obj)) {
|
||||
return;
|
||||
|
@ -56,10 +53,12 @@ public abstract class VisualManager<T> {
|
|||
queue.add(Transaction.add(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueRemove(T obj) {
|
||||
queue.add(Transaction.remove(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueUpdate(T obj) {
|
||||
if (!getStorage().willAccept(obj)) {
|
||||
return;
|
||||
|
@ -68,7 +67,7 @@ public abstract class VisualManager<T> {
|
|||
queue.add(Transaction.update(obj));
|
||||
}
|
||||
|
||||
public Plan<Unit> createRecreationPlan() {
|
||||
public Plan<Float> createRecreationPlan() {
|
||||
return SimplePlan.of(getStorage()::recreateAll);
|
||||
}
|
||||
|
||||
|
@ -76,32 +75,32 @@ public abstract class VisualManager<T> {
|
|||
getStorage().invalidate();
|
||||
}
|
||||
|
||||
protected void processQueue() {
|
||||
protected void processQueue(float partialTick) {
|
||||
var storage = getStorage();
|
||||
Transaction<T> transaction;
|
||||
while ((transaction = queue.poll()) != null) {
|
||||
transaction.apply(storage);
|
||||
transaction.apply(storage, partialTick);
|
||||
}
|
||||
}
|
||||
|
||||
public Plan<TickContext> createTickPlan() {
|
||||
return SimplePlan.<TickContext>of(() -> {
|
||||
tickLimiter.tick();
|
||||
processQueue();
|
||||
processQueue(0);
|
||||
})
|
||||
.thenMap(this::createVisualTickContext, getStorage().getTickPlan());
|
||||
}
|
||||
|
||||
public Plan<FrameContext> createFramePlan() {
|
||||
return SimplePlan.<FrameContext>of(() -> {
|
||||
return SimplePlan.<FrameContext>of(context -> {
|
||||
frameLimiter.tick();
|
||||
processQueue();
|
||||
processQueue(context.partialTick());
|
||||
})
|
||||
.thenMap(this::createVisualContext, getStorage().getFramePlan());
|
||||
}
|
||||
|
||||
private VisualFrameContext createVisualContext(FrameContext ctx) {
|
||||
return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), frameLimiter);
|
||||
return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), ctx.partialTick(), frameLimiter);
|
||||
}
|
||||
|
||||
private VisualTickContext createVisualTickContext(TickContext ctx) {
|
|
@ -10,7 +10,6 @@ import com.jozufozu.flywheel.api.visual.Visual;
|
|||
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizationHelper;
|
||||
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
@ -19,7 +18,7 @@ import net.minecraft.world.level.BlockGetter;
|
|||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
public class BlockEntityVisualManager extends VisualManager<BlockEntity> {
|
||||
public class BlockEntityVisualManager extends AbstractVisualManager<BlockEntity> {
|
||||
private final BlockEntityStorage storage;
|
||||
|
||||
public BlockEntityVisualManager(Engine engine) {
|
||||
|
@ -56,7 +55,6 @@ public class BlockEntityVisualManager extends VisualManager<BlockEntity> {
|
|||
}
|
||||
|
||||
Level level = blockEntity.getLevel();
|
||||
|
||||
if (level == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -65,17 +63,11 @@ public class BlockEntityVisualManager extends VisualManager<BlockEntity> {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (FlwUtil.isFlywheelLevel(level)) {
|
||||
BlockPos pos = blockEntity.getBlockPos();
|
||||
|
||||
BlockGetter existingChunk = level.getChunkForCollisions(pos.getX() >> 4, pos.getZ() >> 4);
|
||||
|
||||
return existingChunk != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Visual createRaw(BlockEntity obj) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import com.jozufozu.flywheel.api.visual.EffectVisual;
|
|||
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
|
||||
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
|
||||
|
||||
public class EffectVisualManager extends VisualManager<Effect> {
|
||||
public class EffectVisualManager extends AbstractVisualManager<Effect> {
|
||||
private final EffectStorage storage;
|
||||
|
||||
public EffectVisualManager(Engine engine) {
|
||||
|
|
|
@ -7,12 +7,11 @@ import com.jozufozu.flywheel.api.visual.Visual;
|
|||
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
|
||||
import com.jozufozu.flywheel.impl.visualization.VisualizationHelper;
|
||||
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public class EntityVisualManager extends VisualManager<Entity> {
|
||||
public class EntityVisualManager extends AbstractVisualManager<Entity> {
|
||||
private final EntityStorage storage;
|
||||
|
||||
public EntityVisualManager(Engine engine) {
|
||||
|
@ -51,8 +50,7 @@ public class EntityVisualManager extends VisualManager<Entity> {
|
|||
}
|
||||
|
||||
Level level = entity.level;
|
||||
|
||||
return FlwUtil.isFlywheelLevel(level);
|
||||
return level != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,11 +41,11 @@ public abstract class Storage<T> {
|
|||
return visuals.values();
|
||||
}
|
||||
|
||||
public void add(T obj) {
|
||||
public void add(T obj, float partialTick) {
|
||||
Visual visual = visuals.get(obj);
|
||||
|
||||
if (visual == null) {
|
||||
create(obj);
|
||||
create(obj, partialTick);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ public abstract class Storage<T> {
|
|||
visual.delete();
|
||||
}
|
||||
|
||||
public void update(T obj) {
|
||||
public void update(T obj, float partialTick) {
|
||||
Visual visual = visuals.get(obj);
|
||||
|
||||
if (visual == null) {
|
||||
|
@ -76,13 +76,13 @@ public abstract class Storage<T> {
|
|||
// delete and re-create the visual.
|
||||
// resetting a visual supersedes updating it.
|
||||
remove(obj);
|
||||
create(obj);
|
||||
create(obj, partialTick);
|
||||
} else {
|
||||
visual.update();
|
||||
visual.update(partialTick);
|
||||
}
|
||||
}
|
||||
|
||||
public void recreateAll() {
|
||||
public void recreateAll(float partialTick) {
|
||||
tickableVisuals.clear();
|
||||
dynamicVisuals.clear();
|
||||
plannedVisuals.clear();
|
||||
|
@ -92,7 +92,7 @@ public abstract class Storage<T> {
|
|||
Visual out = createRaw(obj);
|
||||
|
||||
if (out != null) {
|
||||
setup(out);
|
||||
setup(out, partialTick);
|
||||
}
|
||||
|
||||
return out;
|
||||
|
@ -110,11 +110,11 @@ public abstract class Storage<T> {
|
|||
visuals.clear();
|
||||
}
|
||||
|
||||
private void create(T obj) {
|
||||
private void create(T obj, float partialTick) {
|
||||
Visual visual = createRaw(obj);
|
||||
|
||||
if (visual != null) {
|
||||
setup(visual);
|
||||
setup(visual, partialTick);
|
||||
visuals.put(obj, visual);
|
||||
}
|
||||
}
|
||||
|
@ -130,8 +130,8 @@ public abstract class Storage<T> {
|
|||
return tickPlan.and(ForEachPlan.of(() -> tickableVisuals, TickableVisual::tick));
|
||||
}
|
||||
|
||||
private void setup(Visual visual) {
|
||||
visual.init();
|
||||
private void setup(Visual visual, float partialTick) {
|
||||
visual.init(partialTick);
|
||||
|
||||
if (visual instanceof TickableVisual tickable) {
|
||||
tickableVisuals.add(tickable);
|
||||
|
|
|
@ -13,11 +13,11 @@ public record Transaction<T>(T obj, Action action) {
|
|||
return new Transaction<>(obj, Action.UPDATE);
|
||||
}
|
||||
|
||||
public void apply(Storage<T> storage) {
|
||||
public void apply(Storage<T> storage, float partialTick) {
|
||||
switch (action) {
|
||||
case ADD -> storage.add(obj);
|
||||
case ADD -> storage.add(obj, partialTick);
|
||||
case REMOVE -> storage.remove(obj);
|
||||
case UPDATE -> storage.update(obj);
|
||||
case UPDATE -> storage.update(obj, partialTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ public class VisualUpdatePlan<C> implements SimplyComposedPlan<C> {
|
|||
private final Supplier<List<Plan<C>>> initializer;
|
||||
@Nullable
|
||||
private Plan<C> plan;
|
||||
private boolean initialized = false;
|
||||
private boolean needsSimplify = true;
|
||||
|
||||
public VisualUpdatePlan(Supplier<List<Plan<C>>> initializer) {
|
||||
|
@ -32,13 +33,21 @@ public class VisualUpdatePlan<C> implements SimplyComposedPlan<C> {
|
|||
} else {
|
||||
this.plan = this.plan.and(plan);
|
||||
}
|
||||
|
||||
needsSimplify = true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Plan<C> updatePlans() {
|
||||
if (plan == null) {
|
||||
plan = new NestedPlan<>(initializer.get()).simplify();
|
||||
if (!initialized) {
|
||||
Plan<C> mainPlan = new NestedPlan<>(initializer.get());
|
||||
if (plan != null) {
|
||||
plan = mainPlan.and(plan);
|
||||
} else {
|
||||
plan = mainPlan;
|
||||
}
|
||||
plan = plan.simplify();
|
||||
initialized = true;
|
||||
} else if (needsSimplify) {
|
||||
plan = plan.simplify();
|
||||
}
|
||||
|
@ -49,5 +58,6 @@ public class VisualUpdatePlan<C> implements SimplyComposedPlan<C> {
|
|||
|
||||
public void clear() {
|
||||
plan = null;
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package com.jozufozu.flywheel.lib.box;
|
||||
|
||||
import static com.jozufozu.flywheel.lib.math.RenderMath.isPowerOf2;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
public interface ImmutableBox {
|
||||
public interface Box {
|
||||
int getMinX();
|
||||
|
||||
int getMinY();
|
||||
|
@ -33,16 +32,16 @@ public interface ImmutableBox {
|
|||
return sizeX() * sizeY() * sizeZ();
|
||||
}
|
||||
|
||||
default boolean empty() {
|
||||
default boolean isEmpty() {
|
||||
// if any dimension has side length 0 this box contains no volume
|
||||
return getMinX() == getMaxX() || getMinY() == getMaxY() || getMinZ() == getMaxZ();
|
||||
}
|
||||
|
||||
default boolean sameAs(ImmutableBox other) {
|
||||
default boolean sameAs(Box other) {
|
||||
return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
|
||||
}
|
||||
|
||||
default boolean sameAs(ImmutableBox other, int margin) {
|
||||
default boolean sameAs(Box other, int margin) {
|
||||
return getMinX() == other.getMinX() - margin &&
|
||||
getMinY() == other.getMinY() - margin &&
|
||||
getMinZ() == other.getMinZ() - margin &&
|
||||
|
@ -60,33 +59,11 @@ public interface ImmutableBox {
|
|||
&& getMaxZ() == Math.ceil(other.maxZ);
|
||||
}
|
||||
|
||||
default boolean hasPowerOf2Sides() {
|
||||
// this is only true if all individual side lengths are powers of 2
|
||||
return isPowerOf2(volume());
|
||||
default boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
return this.getMinX() < maxX && this.getMaxX() > minX && this.getMinY() < maxY && this.getMaxY() > minY && this.getMinZ() < maxZ && this.getMaxZ() > minZ;
|
||||
}
|
||||
|
||||
default MutableBox intersect(ImmutableBox other) {
|
||||
int minX = Math.max(this.getMinX(), other.getMinX());
|
||||
int minY = Math.max(this.getMinY(), other.getMinY());
|
||||
int minZ = Math.max(this.getMinZ(), other.getMinZ());
|
||||
int maxX = Math.min(this.getMaxX(), other.getMaxX());
|
||||
int maxY = Math.min(this.getMaxY(), other.getMaxY());
|
||||
int maxZ = Math.min(this.getMaxZ(), other.getMaxZ());
|
||||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
default ImmutableBox union(ImmutableBox other) {
|
||||
int minX = Math.min(this.getMinX(), other.getMinX());
|
||||
int minY = Math.min(this.getMinY(), other.getMinY());
|
||||
int minZ = Math.min(this.getMinZ(), other.getMinZ());
|
||||
int maxX = Math.max(this.getMaxX(), other.getMaxX());
|
||||
int maxY = Math.max(this.getMaxY(), other.getMaxY());
|
||||
int maxZ = Math.max(this.getMaxZ(), other.getMaxZ());
|
||||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
|
||||
default boolean intersects(ImmutableBox other) {
|
||||
default boolean intersects(Box other) {
|
||||
return this.intersects(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ());
|
||||
}
|
||||
|
||||
|
@ -99,7 +76,7 @@ public interface ImmutableBox {
|
|||
&& z <= getMaxZ();
|
||||
}
|
||||
|
||||
default boolean contains(ImmutableBox other) {
|
||||
default boolean contains(Box other) {
|
||||
return other.getMinX() >= this.getMinX()
|
||||
&& other.getMaxX() <= this.getMaxX()
|
||||
&& other.getMinY() >= this.getMinY()
|
||||
|
@ -108,26 +85,48 @@ public interface ImmutableBox {
|
|||
&& other.getMaxZ() <= this.getMaxZ();
|
||||
}
|
||||
|
||||
default boolean isContainedBy(MutableBox other) {
|
||||
return other.contains(this);
|
||||
}
|
||||
|
||||
default boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
return this.getMinX() < maxX && this.getMaxX() > minX && this.getMinY() < maxY && this.getMaxY() > minY && this.getMinZ() < maxZ && this.getMaxZ() > minZ;
|
||||
}
|
||||
|
||||
default void forEachContained(CoordinateConsumer func) {
|
||||
if (empty()) return;
|
||||
int minX = getMinX();
|
||||
int minY = getMinY();
|
||||
int minZ = getMinZ();
|
||||
int maxX = getMaxX();
|
||||
int maxY = getMaxY();
|
||||
int maxZ = getMaxZ();
|
||||
|
||||
for (int x = getMinX(); x < getMaxX(); x++) {
|
||||
for (int y = getMinY(); y < getMaxY(); y++) {
|
||||
for (int z = getMinZ(); z < getMaxZ(); z++) {
|
||||
func.consume(x, y, z);
|
||||
for (int x = minX; x < maxX; x++) {
|
||||
for (int y = minY; y < maxY; y++) {
|
||||
for (int z = minZ; z < maxZ; z++) {
|
||||
func.accept(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default boolean hasPowerOf2Sides() {
|
||||
// this is only true if all individual side lengths are powers of 2
|
||||
return Mth.isPowerOfTwo(volume());
|
||||
}
|
||||
|
||||
default MutableBox union(Box other) {
|
||||
int minX = Math.min(this.getMinX(), other.getMinX());
|
||||
int minY = Math.min(this.getMinY(), other.getMinY());
|
||||
int minZ = Math.min(this.getMinZ(), other.getMinZ());
|
||||
int maxX = Math.max(this.getMaxX(), other.getMaxX());
|
||||
int maxY = Math.max(this.getMaxY(), other.getMaxY());
|
||||
int maxZ = Math.max(this.getMaxZ(), other.getMaxZ());
|
||||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
default MutableBox intersect(Box other) {
|
||||
int minX = Math.max(this.getMinX(), other.getMinX());
|
||||
int minY = Math.max(this.getMinY(), other.getMinY());
|
||||
int minZ = Math.max(this.getMinZ(), other.getMinZ());
|
||||
int maxX = Math.min(this.getMaxX(), other.getMaxX());
|
||||
int maxY = Math.min(this.getMaxY(), other.getMaxY());
|
||||
int maxZ = Math.min(this.getMaxZ(), other.getMaxZ());
|
||||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
default AABB toAABB() {
|
||||
return new AABB(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ());
|
||||
}
|
||||
|
@ -138,6 +137,6 @@ public interface ImmutableBox {
|
|||
|
||||
@FunctionalInterface
|
||||
interface CoordinateConsumer {
|
||||
void consume(int x, int y, int z);
|
||||
void accept(int x, int y, int z);
|
||||
}
|
||||
}
|
|
@ -10,13 +10,13 @@ import net.minecraft.core.SectionPos;
|
|||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
public class MutableBox implements ImmutableBox {
|
||||
private int minX;
|
||||
private int minY;
|
||||
private int minZ;
|
||||
private int maxX;
|
||||
private int maxY;
|
||||
private int maxZ;
|
||||
public class MutableBox implements Box {
|
||||
protected int minX;
|
||||
protected int minY;
|
||||
protected int minZ;
|
||||
protected int maxX;
|
||||
protected int maxY;
|
||||
protected int maxZ;
|
||||
|
||||
public MutableBox() {
|
||||
}
|
||||
|
@ -30,10 +30,6 @@ public class MutableBox implements ImmutableBox {
|
|||
this.maxZ = maxZ;
|
||||
}
|
||||
|
||||
public static MutableBox ofRadius(int radius) {
|
||||
return new MutableBox(-radius, -radius, -radius, radius + 1, radius + 1, radius + 1);
|
||||
}
|
||||
|
||||
public static MutableBox from(AABB aabb) {
|
||||
int minX = (int) Math.floor(aabb.minX);
|
||||
int minY = (int) Math.floor(aabb.minY);
|
||||
|
@ -44,25 +40,23 @@ public class MutableBox implements ImmutableBox {
|
|||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public static MutableBox from(Vec3i pos) {
|
||||
return new MutableBox(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
|
||||
}
|
||||
|
||||
public static MutableBox from(SectionPos pos) {
|
||||
return new MutableBox(pos.minBlockX(), pos.minBlockY(), pos.minBlockZ(), pos.maxBlockX() + 1, pos.maxBlockY() + 1, pos.maxBlockZ() + 1);
|
||||
}
|
||||
|
||||
public static MutableBox from(BlockPos start, BlockPos end) {
|
||||
public static MutableBox from(Vec3i start, Vec3i end) {
|
||||
return new MutableBox(start.getX(), start.getY(), start.getZ(), end.getX() + 1, end.getY() + 1, end.getZ() + 1);
|
||||
}
|
||||
|
||||
public static MutableBox from(BlockPos pos) {
|
||||
return new MutableBox(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
|
||||
public static MutableBox ofRadius(int radius) {
|
||||
return new MutableBox(-radius, -radius, -radius, radius + 1, radius + 1, radius + 1);
|
||||
}
|
||||
|
||||
public static MutableBox from(int sectionX, int sectionZ) {
|
||||
int startX = sectionX << 4;
|
||||
int startZ = sectionZ << 4;
|
||||
return new MutableBox(startX, 0, startZ, startX + 16, 256, startZ + 16);
|
||||
}
|
||||
|
||||
public static ImmutableBox containingAll(Collection<BlockPos> positions) {
|
||||
public static Box containingAll(Collection<BlockPos> positions) {
|
||||
if (positions.isEmpty()) {
|
||||
return new MutableBox();
|
||||
}
|
||||
|
@ -83,169 +77,6 @@ public class MutableBox implements ImmutableBox {
|
|||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public void fixMinMax() {
|
||||
int minX = Math.min(this.minX, this.maxX);
|
||||
int minY = Math.min(this.minY, this.maxY);
|
||||
int minZ = Math.min(this.minZ, this.maxZ);
|
||||
int maxX = Math.max(this.minX, this.maxX);
|
||||
int maxY = Math.max(this.minY, this.maxY);
|
||||
int maxZ = Math.max(this.minZ, this.maxZ);
|
||||
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.minZ = minZ;
|
||||
this.maxX = maxX;
|
||||
this.maxY = maxY;
|
||||
this.maxZ = maxZ;
|
||||
}
|
||||
|
||||
public void translate(Vec3i by) {
|
||||
translate(by.getX(), by.getY(), by.getZ());
|
||||
}
|
||||
|
||||
public void translate(int x, int y, int z) {
|
||||
minX = minX + x;
|
||||
maxX = maxX + x;
|
||||
minY = minY + y;
|
||||
maxY = maxY + y;
|
||||
minZ = minZ + z;
|
||||
maxZ = maxZ + z;
|
||||
}
|
||||
|
||||
public void mirrorAbout(Direction.Axis axis) {
|
||||
Vec3i axisVec = Direction.get(Direction.AxisDirection.POSITIVE, axis)
|
||||
.getNormal();
|
||||
int flipX = axisVec.getX() - 1;
|
||||
int flipY = axisVec.getY() - 1;
|
||||
int flipZ = axisVec.getZ() - 1;
|
||||
|
||||
int maxX = this.maxX * flipX;
|
||||
int maxY = this.maxY * flipY;
|
||||
int maxZ = this.maxZ * flipZ;
|
||||
this.maxX = this.minX * flipX;
|
||||
this.maxY = this.minY * flipY;
|
||||
this.maxZ = this.minZ * flipZ;
|
||||
this.minX = maxX;
|
||||
this.minY = maxY;
|
||||
this.minZ = maxZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grow this bounding box to have power of 2 side length, scaling from the center.
|
||||
*/
|
||||
public void nextPowerOf2Centered() {
|
||||
int sizeX = sizeX();
|
||||
int sizeY = sizeY();
|
||||
int sizeZ = sizeZ();
|
||||
|
||||
int newSizeX = RenderMath.nextPowerOf2(sizeX);
|
||||
int newSizeY = RenderMath.nextPowerOf2(sizeY);
|
||||
int newSizeZ = RenderMath.nextPowerOf2(sizeZ);
|
||||
|
||||
int diffX = newSizeX - sizeX;
|
||||
int diffY = newSizeY - sizeY;
|
||||
int diffZ = newSizeZ - sizeZ;
|
||||
|
||||
minX = minX - diffX / 2; // floor division for the minimums
|
||||
minY = minY - diffY / 2;
|
||||
minZ = minZ - diffZ / 2;
|
||||
maxX = maxX + (diffX + 1) / 2; // ceiling divison for the maximums
|
||||
maxY = maxY + (diffY + 1) / 2;
|
||||
maxZ = maxZ + (diffZ + 1) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grow this bounding box to have power of 2 side lengths, scaling from the minimum coords.
|
||||
*/
|
||||
public void nextPowerOf2() {
|
||||
int sizeX = RenderMath.nextPowerOf2(sizeX());
|
||||
int sizeY = RenderMath.nextPowerOf2(sizeY());
|
||||
int sizeZ = RenderMath.nextPowerOf2(sizeZ());
|
||||
|
||||
maxX = minX + sizeX;
|
||||
maxY = minY + sizeY;
|
||||
maxZ = minZ + sizeZ;
|
||||
}
|
||||
|
||||
public void grow(int s) {
|
||||
this.grow(s, s, s);
|
||||
}
|
||||
|
||||
public void grow(int x, int y, int z) {
|
||||
minX = minX - x;
|
||||
minY = minY - y;
|
||||
minZ = minZ - z;
|
||||
maxX = maxX + x;
|
||||
maxY = maxY + y;
|
||||
maxZ = maxZ + z;
|
||||
}
|
||||
|
||||
public void intersectAssign(ImmutableBox other) {
|
||||
minX = Math.max(this.minX, other.getMinX());
|
||||
minY = Math.max(this.minY, other.getMinY());
|
||||
minZ = Math.max(this.minZ, other.getMinZ());
|
||||
maxX = Math.min(this.maxX, other.getMaxX());
|
||||
maxY = Math.min(this.maxY, other.getMaxY());
|
||||
maxZ = Math.min(this.maxZ, other.getMaxZ());
|
||||
}
|
||||
|
||||
public void unionAssign(ImmutableBox other) {
|
||||
minX = Math.min(this.minX, other.getMinX());
|
||||
minY = Math.min(this.minY, other.getMinY());
|
||||
minZ = Math.min(this.minZ, other.getMinZ());
|
||||
maxX = Math.max(this.maxX, other.getMaxX());
|
||||
maxY = Math.max(this.maxY, other.getMaxY());
|
||||
maxZ = Math.max(this.maxZ, other.getMaxZ());
|
||||
}
|
||||
|
||||
public void unionAssign(AABB other) {
|
||||
minX = Math.min(this.minX, (int) Math.floor(other.minX));
|
||||
minY = Math.min(this.minY, (int) Math.floor(other.minY));
|
||||
minZ = Math.min(this.minZ, (int) Math.floor(other.minZ));
|
||||
maxX = Math.max(this.maxX, (int) Math.ceil(other.maxX));
|
||||
maxY = Math.max(this.maxY, (int) Math.ceil(other.maxY));
|
||||
maxZ = Math.max(this.maxZ, (int) Math.ceil(other.maxZ));
|
||||
}
|
||||
|
||||
public void assign(AABB other) {
|
||||
minX = (int) Math.floor(other.minX);
|
||||
minY = (int) Math.floor(other.minY);
|
||||
minZ = (int) Math.floor(other.minZ);
|
||||
maxX = (int) Math.ceil(other.maxX);
|
||||
maxY = (int) Math.ceil(other.maxY);
|
||||
maxZ = (int) Math.ceil(other.maxZ);
|
||||
}
|
||||
|
||||
public void assign(ImmutableBox other) {
|
||||
minX = other.getMinX();
|
||||
minY = other.getMinY();
|
||||
minZ = other.getMinZ();
|
||||
maxX = other.getMaxX();
|
||||
maxY = other.getMaxY();
|
||||
maxZ = other.getMaxZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ImmutableBox that = (ImmutableBox) o;
|
||||
|
||||
return this.sameAs(that);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = getMinX();
|
||||
result = 31 * result + getMinY();
|
||||
result = 31 * result + getMinZ();
|
||||
result = 31 * result + getMaxX();
|
||||
result = 31 * result + getMaxY();
|
||||
result = 31 * result + getMaxZ();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinX() {
|
||||
return minX;
|
||||
|
@ -276,14 +107,12 @@ public class MutableBox implements ImmutableBox {
|
|||
return maxZ;
|
||||
}
|
||||
|
||||
public MutableBox setMinX(int minX) {
|
||||
public void setMinX(int minX) {
|
||||
this.minX = minX;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MutableBox setMinY(int minY) {
|
||||
public void setMinY(int minY) {
|
||||
this.minY = minY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MutableBox setMinZ(int minZ) {
|
||||
|
@ -291,142 +120,207 @@ public class MutableBox implements ImmutableBox {
|
|||
return this;
|
||||
}
|
||||
|
||||
public MutableBox setMaxX(int maxX) {
|
||||
public void setMaxX(int maxX) {
|
||||
this.maxX = maxX;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MutableBox setMaxY(int maxY) {
|
||||
public void setMaxY(int maxY) {
|
||||
this.maxY = maxY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MutableBox setMaxZ(int maxZ) {
|
||||
public void setMaxZ(int maxZ) {
|
||||
this.maxZ = maxZ;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MutableBox assign(BlockPos start, BlockPos end) {
|
||||
public void setMin(int x, int y, int z) {
|
||||
minX = x;
|
||||
minY = y;
|
||||
minZ = z;
|
||||
}
|
||||
|
||||
public void setMax(int x, int y, int z) {
|
||||
maxX = x;
|
||||
maxY = y;
|
||||
maxZ = z;
|
||||
}
|
||||
|
||||
public void setMin(Vec3i v) {
|
||||
setMin(v.getX(), v.getY(), v.getZ());
|
||||
}
|
||||
|
||||
public void setMax(Vec3i v) {
|
||||
setMax(v.getX(), v.getY(), v.getZ());
|
||||
}
|
||||
|
||||
public void assign(Box other) {
|
||||
minX = other.getMinX();
|
||||
minY = other.getMinY();
|
||||
minZ = other.getMinZ();
|
||||
maxX = other.getMaxX();
|
||||
maxY = other.getMaxY();
|
||||
maxZ = other.getMaxZ();
|
||||
}
|
||||
|
||||
public void assign(AABB other) {
|
||||
minX = (int) Math.floor(other.minX);
|
||||
minY = (int) Math.floor(other.minY);
|
||||
minZ = (int) Math.floor(other.minZ);
|
||||
maxX = (int) Math.ceil(other.maxX);
|
||||
maxY = (int) Math.ceil(other.maxY);
|
||||
maxZ = (int) Math.ceil(other.maxZ);
|
||||
}
|
||||
|
||||
public void assign(Vec3i start, Vec3i end) {
|
||||
minX = start.getX();
|
||||
minY = start.getY();
|
||||
minZ = start.getZ();
|
||||
maxX = end.getX() + 1;
|
||||
maxY = end.getY() + 1;
|
||||
maxZ = end.getZ() + 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MutableBox setMax(Vec3i v) {
|
||||
return setMax(v.getX(), v.getY(), v.getZ());
|
||||
public void unionAssign(Box other) {
|
||||
minX = Math.min(this.minX, other.getMinX());
|
||||
minY = Math.min(this.minY, other.getMinY());
|
||||
minZ = Math.min(this.minZ, other.getMinZ());
|
||||
maxX = Math.max(this.maxX, other.getMaxX());
|
||||
maxY = Math.max(this.maxY, other.getMaxY());
|
||||
maxZ = Math.max(this.maxZ, other.getMaxZ());
|
||||
}
|
||||
|
||||
public MutableBox setMin(Vec3i v) {
|
||||
return setMin(v.getX(), v.getY(), v.getZ());
|
||||
public void unionAssign(AABB other) {
|
||||
minX = Math.min(this.minX, (int) Math.floor(other.minX));
|
||||
minY = Math.min(this.minY, (int) Math.floor(other.minY));
|
||||
minZ = Math.min(this.minZ, (int) Math.floor(other.minZ));
|
||||
maxX = Math.max(this.maxX, (int) Math.ceil(other.maxX));
|
||||
maxY = Math.max(this.maxY, (int) Math.ceil(other.maxY));
|
||||
maxZ = Math.max(this.maxZ, (int) Math.ceil(other.maxZ));
|
||||
}
|
||||
|
||||
public MutableBox setMax(int x, int y, int z) {
|
||||
maxX = x;
|
||||
maxY = y;
|
||||
maxZ = z;
|
||||
return this;
|
||||
public void intersectAssign(Box other) {
|
||||
minX = Math.max(this.minX, other.getMinX());
|
||||
minY = Math.max(this.minY, other.getMinY());
|
||||
minZ = Math.max(this.minZ, other.getMinZ());
|
||||
maxX = Math.min(this.maxX, other.getMaxX());
|
||||
maxY = Math.min(this.maxY, other.getMaxY());
|
||||
maxZ = Math.min(this.maxZ, other.getMaxZ());
|
||||
}
|
||||
|
||||
public MutableBox setMin(int x, int y, int z) {
|
||||
minX = x;
|
||||
minY = y;
|
||||
minZ = z;
|
||||
return this;
|
||||
public void fixMinMax() {
|
||||
int minX = Math.min(this.minX, this.maxX);
|
||||
int minY = Math.min(this.minY, this.maxY);
|
||||
int minZ = Math.min(this.minZ, this.maxZ);
|
||||
int maxX = Math.max(this.minX, this.maxX);
|
||||
int maxY = Math.max(this.minY, this.maxY);
|
||||
int maxZ = Math.max(this.minZ, this.maxZ);
|
||||
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.minZ = minZ;
|
||||
this.maxX = maxX;
|
||||
this.maxY = maxY;
|
||||
this.maxZ = maxZ;
|
||||
}
|
||||
|
||||
public void translate(int x, int y, int z) {
|
||||
minX = minX + x;
|
||||
maxX = maxX + x;
|
||||
minY = minY + y;
|
||||
maxY = maxY + y;
|
||||
minZ = minZ + z;
|
||||
maxZ = maxZ + z;
|
||||
}
|
||||
|
||||
public void translate(Vec3i by) {
|
||||
translate(by.getX(), by.getY(), by.getZ());
|
||||
}
|
||||
|
||||
public void grow(int x, int y, int z) {
|
||||
minX = minX - x;
|
||||
minY = minY - y;
|
||||
minZ = minZ - z;
|
||||
maxX = maxX + x;
|
||||
maxY = maxY + y;
|
||||
maxZ = maxZ + z;
|
||||
}
|
||||
|
||||
public void grow(int s) {
|
||||
this.grow(s, s, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grow this box to have power of 2 side lengths, scaling from the minimum coords.
|
||||
*/
|
||||
public void nextPowerOf2() {
|
||||
int sizeX = RenderMath.nextPowerOf2(sizeX());
|
||||
int sizeY = RenderMath.nextPowerOf2(sizeY());
|
||||
int sizeZ = RenderMath.nextPowerOf2(sizeZ());
|
||||
|
||||
maxX = minX + sizeX;
|
||||
maxY = minY + sizeY;
|
||||
maxZ = minZ + sizeZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grow this box to have power of 2 side length, scaling from the center.
|
||||
*/
|
||||
public void nextPowerOf2Centered() {
|
||||
int sizeX = sizeX();
|
||||
int sizeY = sizeY();
|
||||
int sizeZ = sizeZ();
|
||||
|
||||
int newSizeX = RenderMath.nextPowerOf2(sizeX);
|
||||
int newSizeY = RenderMath.nextPowerOf2(sizeY);
|
||||
int newSizeZ = RenderMath.nextPowerOf2(sizeZ);
|
||||
|
||||
int diffX = newSizeX - sizeX;
|
||||
int diffY = newSizeY - sizeY;
|
||||
int diffZ = newSizeZ - sizeZ;
|
||||
|
||||
minX = minX - diffX / 2; // floor division for the minimums
|
||||
minY = minY - diffY / 2;
|
||||
minZ = minZ - diffZ / 2;
|
||||
maxX = maxX + (diffX + 1) / 2; // ceiling divison for the maximums
|
||||
maxY = maxY + (diffY + 1) / 2;
|
||||
maxZ = maxZ + (diffZ + 1) / 2;
|
||||
}
|
||||
|
||||
public void mirrorAbout(Direction.Axis axis) {
|
||||
Vec3i axisVec = Direction.get(Direction.AxisDirection.POSITIVE, axis)
|
||||
.getNormal();
|
||||
int flipX = axisVec.getX() - 1;
|
||||
int flipY = axisVec.getY() - 1;
|
||||
int flipZ = axisVec.getZ() - 1;
|
||||
|
||||
int maxX = this.maxX * flipX;
|
||||
int maxY = this.maxY * flipY;
|
||||
int maxZ = this.maxZ * flipZ;
|
||||
this.maxX = this.minX * flipX;
|
||||
this.maxY = this.minY * flipY;
|
||||
this.maxZ = this.minZ * flipZ;
|
||||
this.minX = maxX;
|
||||
this.minY = maxY;
|
||||
this.minZ = maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeX() {
|
||||
return maxX - minX;
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof Box that)) return false;
|
||||
|
||||
return this.sameAs(that);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeY() {
|
||||
return maxY - minY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeZ() {
|
||||
return maxZ - minZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean empty() {
|
||||
// if any dimension has side length 0 this box contains no volume
|
||||
return minX == maxX || minY == maxY || minZ == maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(ImmutableBox other) {
|
||||
return minX == other.getMinX() && minY == other.getMinY() && minZ == other.getMinZ() && maxX == other.getMaxX() && maxY == other.getMaxY() && maxZ == other.getMaxZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(AABB other) {
|
||||
return minX == Math.floor(other.minX)
|
||||
&& minY == Math.floor(other.minY)
|
||||
&& minZ == Math.floor(other.minZ)
|
||||
&& maxX == Math.ceil(other.maxX)
|
||||
&& maxY == Math.ceil(other.maxY)
|
||||
&& maxZ == Math.ceil(other.maxZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableBox intersect(ImmutableBox other) {
|
||||
int minX = Math.max(this.minX, other.getMinX());
|
||||
int minY = Math.max(this.minY, other.getMinY());
|
||||
int minZ = Math.max(this.minZ, other.getMinZ());
|
||||
int maxX = Math.min(this.maxX, other.getMaxX());
|
||||
int maxY = Math.min(this.maxY, other.getMaxY());
|
||||
int maxZ = Math.min(this.maxZ, other.getMaxZ());
|
||||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableBox union(ImmutableBox other) {
|
||||
int minX = Math.min(this.minX, other.getMinX());
|
||||
int minY = Math.min(this.minY, other.getMinY());
|
||||
int minZ = Math.min(this.minZ, other.getMinZ());
|
||||
int maxX = Math.max(this.maxX, other.getMaxX());
|
||||
int maxY = Math.max(this.maxY, other.getMaxY());
|
||||
int maxZ = Math.max(this.maxZ, other.getMaxZ());
|
||||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(ImmutableBox other) {
|
||||
return other.getMinX() >= this.minX && other.getMaxX() <= this.maxX && other.getMinY() >= this.minY && other.getMaxY() <= this.maxY && other.getMinZ() >= this.minZ && other.getMaxZ() <= this.maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachContained(CoordinateConsumer func) {
|
||||
if (empty()) return;
|
||||
|
||||
for (int x = minX; x < maxX; x++) {
|
||||
for (int y = minY; y < maxY; y++) {
|
||||
for (int z = minZ; z < maxZ; z++) {
|
||||
func.consume(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB toAABB() {
|
||||
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableBox copy() {
|
||||
return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
public int hashCode() {
|
||||
int result = minX;
|
||||
result = 31 * result + minY;
|
||||
result = 31 * result + minZ;
|
||||
result = 31 * result + maxX;
|
||||
result = 31 * result + maxY;
|
||||
result = 31 * result + maxZ;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.jetbrains.annotations.ApiStatus;
|
|||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.api.context.Context;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.lib.util.ResourceUtil;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
@ -12,6 +12,9 @@ public final class Contexts {
|
|||
public static final SimpleContext WORLD = Context.REGISTRY.registerAndGet(new SimpleContext(Files.WORLD_VERTEX, Files.WORLD_FRAGMENT));
|
||||
public static final SimpleContext CRUMBLING = Context.REGISTRY.registerAndGet(new SimpleContext(Files.WORLD_VERTEX, Files.CRUMBLING_FRAGMENT));
|
||||
|
||||
private Contexts() {
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void init() {
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.jetbrains.annotations.ApiStatus;
|
|||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.api.instance.InstanceType;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.lib.util.ResourceUtil;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
@ -12,6 +12,9 @@ public final class InstanceTypes {
|
|||
public static final InstanceType<TransformedInstance> TRANSFORMED = InstanceType.REGISTRY.registerAndGet(new TransformedType());
|
||||
public static final InstanceType<OrientedInstance> ORIENTED = InstanceType.REGISTRY.registerAndGet(new OrientedType());
|
||||
|
||||
private InstanceTypes() {
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void init() {
|
||||
}
|
||||
|
|
|
@ -11,12 +11,17 @@ import com.mojang.math.Quaternion;
|
|||
import net.minecraft.util.Mth;
|
||||
|
||||
public class TransformedInstance extends ColoredLitInstance implements Transform<TransformedInstance> {
|
||||
private static final Matrix4f EMPTY_MATRIX_4f = new Matrix4f();
|
||||
private static final Matrix3f EMPTY_MATRIX_3f = new Matrix3f();
|
||||
private static final Matrix4f ZERO_MATRIX_4f = new Matrix4f();
|
||||
private static final Matrix3f ZERO_MATRIX_3f = new Matrix3f();
|
||||
|
||||
public final Matrix4f model = new Matrix4f();
|
||||
public final Matrix3f normal = new Matrix3f();
|
||||
|
||||
{
|
||||
model.setIdentity();
|
||||
normal.setIdentity();
|
||||
}
|
||||
|
||||
public TransformedInstance(InstanceType<? extends TransformedInstance> type, InstanceHandle handle) {
|
||||
super(type, handle);
|
||||
}
|
||||
|
@ -41,16 +46,16 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
|
|||
public TransformedInstance setEmptyTransform() {
|
||||
setChanged();
|
||||
|
||||
this.model.load(EMPTY_MATRIX_4f);
|
||||
this.normal.load(EMPTY_MATRIX_3f);
|
||||
model.load(ZERO_MATRIX_4f);
|
||||
normal.load(ZERO_MATRIX_3f);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransformedInstance loadIdentity() {
|
||||
setChanged();
|
||||
|
||||
this.model.setIdentity();
|
||||
this.normal.setIdentity();
|
||||
model.setIdentity();
|
||||
normal.setIdentity();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -95,13 +100,17 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
|
|||
|
||||
@Override
|
||||
public TransformedInstance mulPose(Matrix4f pose) {
|
||||
this.model.multiply(pose);
|
||||
setChanged();
|
||||
|
||||
model.multiply(pose);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedInstance mulNormal(Matrix3f normal) {
|
||||
this.normal.mul(normal);
|
||||
setChanged();
|
||||
|
||||
normal.mul(normal);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import com.jozufozu.flywheel.gl.array.VertexAttribute;
|
|||
import com.jozufozu.flywheel.glsl.generate.FnSignature;
|
||||
import com.jozufozu.flywheel.glsl.generate.GlslExpr;
|
||||
|
||||
public class CommonItems {
|
||||
public final class CommonItems {
|
||||
private static final String VEC2_TYPE = "vec2";
|
||||
private static final String VEC3_TYPE = "vec3";
|
||||
private static final String VEC4_TYPE = "vec4";
|
||||
|
@ -128,4 +128,7 @@ public class CommonItems {
|
|||
|
||||
public static final MatInput MAT3 = new MatInput(3, 3, "mat3", "Mat3F", "unpackMat3F");
|
||||
public static final MatInput MAT4 = new MatInput(4, 4, "mat4", "Mat4F", "unpackMat4F");
|
||||
|
||||
private CommonItems() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,15 @@ package com.jozufozu.flywheel.lib.light;
|
|||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.jozufozu.flywheel.lib.box.ImmutableBox;
|
||||
import com.jozufozu.flywheel.lib.box.Box;
|
||||
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
|
||||
public class DummyLightUpdater extends LightUpdater {
|
||||
public final class DummyLightUpdater extends LightUpdater {
|
||||
public static final DummyLightUpdater INSTANCE = new DummyLightUpdater();
|
||||
|
||||
private DummyLightUpdater() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,7 +34,7 @@ public class DummyLightUpdater extends LightUpdater {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<ImmutableBox> getAllBoxes() {
|
||||
public Stream<Box> getAllBoxes() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.lwjgl.opengl.GL30;
|
|||
|
||||
import com.jozufozu.flywheel.gl.GlTexture;
|
||||
import com.jozufozu.flywheel.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.lib.box.ImmutableBox;
|
||||
import com.jozufozu.flywheel.lib.box.Box;
|
||||
import com.jozufozu.flywheel.lib.box.MutableBox;
|
||||
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
|
@ -36,7 +36,7 @@ public class GPULightVolume extends LightVolume {
|
|||
private final GlTextureUnit textureUnit = GlTextureUnit.T4;
|
||||
protected boolean bufferDirty;
|
||||
|
||||
public GPULightVolume(BlockAndTintGetter level, ImmutableBox sampleVolume) {
|
||||
public GPULightVolume(BlockAndTintGetter level, Box sampleVolume) {
|
||||
super(level, sampleVolume);
|
||||
this.sampleVolume.assign(sampleVolume);
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class GPULightVolume extends LightVolume {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void setBox(ImmutableBox box) {
|
||||
protected void setBox(Box box) {
|
||||
this.box.assign(box);
|
||||
this.box.nextPowerOf2Centered();
|
||||
// called during super ctor
|
||||
|
@ -110,7 +110,7 @@ public class GPULightVolume extends LightVolume {
|
|||
glTexture.delete();
|
||||
}
|
||||
|
||||
public void move(ImmutableBox newSampleVolume) {
|
||||
public void move(Box newSampleVolume) {
|
||||
if (lightData == null) return;
|
||||
|
||||
if (box.contains(newSampleVolume)) {
|
||||
|
@ -122,7 +122,7 @@ public class GPULightVolume extends LightVolume {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ImmutableBox getVolume() {
|
||||
public Box getVolume() {
|
||||
return sampleVolume;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.jozufozu.flywheel.lib.light;
|
||||
|
||||
import com.jozufozu.flywheel.lib.box.ImmutableBox;
|
||||
import com.jozufozu.flywheel.lib.box.Box;
|
||||
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
|
@ -12,8 +12,7 @@ import net.minecraft.world.level.LightLayer;
|
|||
* It is the responsibility of the implementor to keep a reference to the level an object is contained in.
|
||||
*/
|
||||
public interface LightListener {
|
||||
|
||||
ImmutableBox getVolume();
|
||||
Box getVolume();
|
||||
|
||||
/**
|
||||
* Check the status of the light listener.
|
||||
|
@ -23,7 +22,7 @@ public interface LightListener {
|
|||
boolean isInvalid();
|
||||
|
||||
/**
|
||||
* Called when a light updates in a chunk the implementor cares about.
|
||||
* Called when light updates in a section the implementor cares about.
|
||||
*/
|
||||
void onLightUpdate(LightLayer type, SectionPos pos);
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package com.jozufozu.flywheel.lib.light;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
|
||||
/**
|
||||
* Marker interface for custom/fake levels to indicate that LightUpdater should bother interacting with it.<p>
|
||||
*
|
||||
* Implement this if your custom level has light updates at all. If so, be sure to call
|
||||
* {@link com.jozufozu.flywheel.util.WorldAttached#invalidateWorld} when your level in unloaded.
|
||||
*/
|
||||
public interface LightUpdated extends LevelAccessor {
|
||||
|
||||
/**
|
||||
* @return {@code true} if this level is passing light updates into LightUpdater.
|
||||
*/
|
||||
default boolean receivesLightUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean receivesLightUpdates(LevelAccessor level) {
|
||||
// The client level is guaranteed to receive updates.
|
||||
if (Minecraft.getInstance().level == level) {
|
||||
return true;
|
||||
}
|
||||
// Custom/fake levels need to indicate that LightUpdater has meaning.
|
||||
if (level instanceof LightUpdated c) {
|
||||
return c.receivesLightUpdates();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.jozufozu.flywheel.lib.light;
|
||||
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
|
||||
/**
|
||||
* Marker interface for custom/fake levels to indicate that LightUpdater should interact with it.<p>
|
||||
*
|
||||
* Implement this if your custom level has light updates at all. If so, be sure to call
|
||||
* {@link com.jozufozu.flywheel.lib.util.LevelAttached#invalidateLevel} when your level is unloaded.
|
||||
*/
|
||||
public interface LightUpdatedLevel extends LevelAccessor {
|
||||
/**
|
||||
* @return {@code true} if this level is passing light updates into LightUpdater.
|
||||
*/
|
||||
default boolean receivesLightUpdates() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -5,107 +5,71 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.jozufozu.flywheel.lib.box.ImmutableBox;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.util.WorldAttached;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import com.jozufozu.flywheel.lib.box.Box;
|
||||
import com.jozufozu.flywheel.lib.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.lib.util.LevelAttached;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
|
||||
/**
|
||||
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
|
||||
*
|
||||
* @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement
|
||||
* {@link LightUpdated} for LightUpdater to work with them.
|
||||
* {@link LightUpdatedLevel} for LightUpdater to work with them.
|
||||
*/
|
||||
public class LightUpdater {
|
||||
private static final WorldAttached<LightUpdater> LEVELS = new WorldAttached<>(LightUpdater::new);
|
||||
|
||||
private final LevelAccessor level;
|
||||
private static final LevelAttached<LightUpdater> UPDATERS = new LevelAttached<>(level -> new LightUpdater());
|
||||
|
||||
private final WeakContainmentMultiMap<LightListener> listenersBySection = new WeakContainmentMultiMap<>();
|
||||
private final Set<TickingLightListener> tickingListeners = FlwUtil.createWeakHashSet();
|
||||
|
||||
private final Queue<LightListener> queue = new ConcurrentLinkedQueue<>();
|
||||
private final Queue<LightListener> additionQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public static boolean supports(LevelAccessor level) {
|
||||
// The client level is guaranteed to receive updates.
|
||||
if (Minecraft.getInstance().level == level) {
|
||||
return true;
|
||||
}
|
||||
// Custom/fake levels need to indicate that LightUpdater has meaning.
|
||||
if (level instanceof LightUpdatedLevel c) {
|
||||
return c.receivesLightUpdates();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static LightUpdater get(LevelAccessor level) {
|
||||
if (LightUpdated.receivesLightUpdates(level)) {
|
||||
// The level is valid, add it to the map.
|
||||
return LEVELS.get(level);
|
||||
if (supports(level)) {
|
||||
// The level is valid, so add it to the map.
|
||||
return UPDATERS.get(level);
|
||||
} else {
|
||||
// Fake light updater for a fake level.
|
||||
return DummyLightUpdater.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
public LightUpdater(LevelAccessor level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
processQueue();
|
||||
tickSerial();
|
||||
}
|
||||
|
||||
private void tickSerial() {
|
||||
for (TickingLightListener tickingLightListener : tickingListeners) {
|
||||
if (tickingLightListener.tickLightListener()) {
|
||||
addListener(tickingLightListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener.
|
||||
*
|
||||
* @param listener The object that wants to receive light update notifications.
|
||||
*/
|
||||
public void addListener(LightListener listener) {
|
||||
queue.add(listener);
|
||||
}
|
||||
|
||||
private synchronized void processQueue() {
|
||||
LightListener listener;
|
||||
while ((listener = queue.poll()) != null) {
|
||||
doAdd(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void doAdd(LightListener listener) {
|
||||
if (listener instanceof TickingLightListener) {
|
||||
tickingListeners.add(((TickingLightListener) listener));
|
||||
}
|
||||
|
||||
ImmutableBox box = listener.getVolume();
|
||||
|
||||
LongSet sections = this.listenersBySection.getAndResetContainment(listener);
|
||||
|
||||
int minX = SectionPos.blockToSectionCoord(box.getMinX());
|
||||
int minY = SectionPos.blockToSectionCoord(box.getMinY());
|
||||
int minZ = SectionPos.blockToSectionCoord(box.getMinZ());
|
||||
int maxX = SectionPos.blockToSectionCoord(box.getMaxX());
|
||||
int maxY = SectionPos.blockToSectionCoord(box.getMaxY());
|
||||
int maxZ = SectionPos.blockToSectionCoord(box.getMaxZ());
|
||||
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
long sectionPos = SectionPos.asLong(x, y, z);
|
||||
this.listenersBySection.put(sectionPos, listener);
|
||||
sections.add(sectionPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
additionQueue.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(LightListener listener) {
|
||||
this.listenersBySection.remove(listener);
|
||||
listenersBySection.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch light updates to all registered {@link LightListener}s.
|
||||
*
|
||||
* @param type The type of light that changed.
|
||||
* @param pos The section position where light changed.
|
||||
*/
|
||||
|
@ -125,11 +89,63 @@ public class LightUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
public Stream<ImmutableBox> getAllBoxes() {
|
||||
public Stream<Box> getAllBoxes() {
|
||||
return listenersBySection.stream().map(LightListener::getVolume);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return listenersBySection.isEmpty();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.END && FlwUtil.isGameActive()) {
|
||||
get(Minecraft.getInstance().level)
|
||||
.tick();
|
||||
}
|
||||
}
|
||||
|
||||
void tick() {
|
||||
processQueue();
|
||||
|
||||
for (TickingLightListener tickingListener : tickingListeners) {
|
||||
if (tickingListener.tickLightListener()) {
|
||||
addListener(tickingListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void processQueue() {
|
||||
LightListener listener;
|
||||
while ((listener = additionQueue.poll()) != null) {
|
||||
doAdd(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void doAdd(LightListener listener) {
|
||||
if (listener instanceof TickingLightListener ticking) {
|
||||
tickingListeners.add(ticking);
|
||||
}
|
||||
|
||||
Box box = listener.getVolume();
|
||||
|
||||
LongSet sections = listenersBySection.getAndResetContainment(listener);
|
||||
|
||||
int minX = SectionPos.blockToSectionCoord(box.getMinX());
|
||||
int minY = SectionPos.blockToSectionCoord(box.getMinY());
|
||||
int minZ = SectionPos.blockToSectionCoord(box.getMinZ());
|
||||
int maxX = SectionPos.blockToSectionCoord(box.getMaxX());
|
||||
int maxY = SectionPos.blockToSectionCoord(box.getMaxY());
|
||||
int maxZ = SectionPos.blockToSectionCoord(box.getMaxZ());
|
||||
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
long sectionPos = SectionPos.asLong(x, y, z);
|
||||
listenersBySection.put(sectionPos, listener);
|
||||
sections.add(sectionPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.jozufozu.flywheel.lib.light;
|
|||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.lib.box.ImmutableBox;
|
||||
import com.jozufozu.flywheel.lib.box.Box;
|
||||
import com.jozufozu.flywheel.lib.box.MutableBox;
|
||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
||||
|
||||
|
@ -11,13 +11,13 @@ import net.minecraft.core.SectionPos;
|
|||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
|
||||
public class LightVolume implements ImmutableBox, LightListener {
|
||||
public class LightVolume implements Box, LightListener {
|
||||
|
||||
protected final BlockAndTintGetter level;
|
||||
protected final MutableBox box = new MutableBox();
|
||||
protected MemoryBlock lightData;
|
||||
|
||||
public LightVolume(BlockAndTintGetter level, ImmutableBox sampleVolume) {
|
||||
public LightVolume(BlockAndTintGetter level, Box sampleVolume) {
|
||||
this.level = level;
|
||||
this.setBox(sampleVolume);
|
||||
|
||||
|
@ -25,7 +25,7 @@ public class LightVolume implements ImmutableBox, LightListener {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ImmutableBox getVolume() {
|
||||
public Box getVolume() {
|
||||
return box;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class LightVolume implements ImmutableBox, LightListener {
|
|||
return lightData == null;
|
||||
}
|
||||
|
||||
protected void setBox(ImmutableBox box) {
|
||||
protected void setBox(Box box) {
|
||||
this.box.assign(box);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class LightVolume implements ImmutableBox, LightListener {
|
|||
}
|
||||
}
|
||||
|
||||
public void move(ImmutableBox newSampleVolume) {
|
||||
public void move(Box newSampleVolume) {
|
||||
if (lightData == null) return;
|
||||
|
||||
setBox(newSampleVolume);
|
||||
|
@ -112,7 +112,7 @@ public class LightVolume implements ImmutableBox, LightListener {
|
|||
*
|
||||
* @param worldVolume the region in the world to copy data from.
|
||||
*/
|
||||
public void copyLight(ImmutableBox worldVolume) {
|
||||
public void copyLight(Box worldVolume) {
|
||||
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
|
||||
|
||||
int xShift = box.getMinX();
|
||||
|
@ -143,7 +143,7 @@ public class LightVolume implements ImmutableBox, LightListener {
|
|||
*
|
||||
* @param worldVolume the region in the world to copy data from.
|
||||
*/
|
||||
public void copyBlock(ImmutableBox worldVolume) {
|
||||
public void copyBlock(Box worldVolume) {
|
||||
var pos = new BlockPos.MutableBlockPos();
|
||||
|
||||
int xShift = box.getMinX();
|
||||
|
@ -168,7 +168,7 @@ public class LightVolume implements ImmutableBox, LightListener {
|
|||
*
|
||||
* @param worldVolume the region in the world to copy data from.
|
||||
*/
|
||||
public void copySky(ImmutableBox worldVolume) {
|
||||
public void copySky(Box worldVolume) {
|
||||
var pos = new BlockPos.MutableBlockPos();
|
||||
|
||||
int xShift = box.getMinX();
|
||||
|
|
|
@ -6,15 +6,14 @@ import java.util.Set;
|
|||
import java.util.WeakHashMap;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
import com.jozufozu.flywheel.lib.util.FlwUtil;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongRBTreeSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
|
||||
public class WeakContainmentMultiMap<T> extends AbstractCollection<T> {
|
||||
|
||||
class WeakContainmentMultiMap<T> extends AbstractCollection<T> {
|
||||
private final Long2ObjectMap<Set<T>> forward;
|
||||
private final WeakHashMap<T, LongSet> reverse;
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import com.jozufozu.flywheel.api.material.MaterialVertexTransformer;
|
|||
import com.jozufozu.flywheel.gl.GlTextureUnit;
|
||||
import com.jozufozu.flywheel.lib.material.SimpleMaterial.GlStateShard;
|
||||
import com.jozufozu.flywheel.lib.math.DiffuseLightCalculator;
|
||||
import com.jozufozu.flywheel.lib.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.lib.util.ShadersModHandler;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
|
@ -134,6 +134,9 @@ public final class Materials {
|
|||
.batchingRenderType(RenderType.entitySolid(MINECART_LOCATION))
|
||||
.register();
|
||||
|
||||
private Materials() {
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void init() {
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public class SimpleMaterial implements Material {
|
|||
}
|
||||
|
||||
@Override
|
||||
public RenderType getBatchingRenderType() {
|
||||
public RenderType getFallbackRenderType() {
|
||||
return batchingRenderType;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,19 +36,6 @@ public final class RenderMath {
|
|||
return (h == a) ? h : (h << 1);
|
||||
}
|
||||
|
||||
public static boolean isPowerOf2(int n) {
|
||||
int b = n & (n - 1);
|
||||
return b == 0 && n != 0;
|
||||
}
|
||||
|
||||
public static double lengthSqr(double x, double y, double z) {
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
public static double length(double x, double y, double z) {
|
||||
return Math.sqrt(lengthSqr(x, y, z));
|
||||
}
|
||||
|
||||
public static float diffuseLight(float x, float y, float z, boolean shaded) {
|
||||
if (!shaded) {
|
||||
return 1f;
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.jozufozu.flywheel.lib.memory;
|
|||
import java.lang.ref.Cleaner;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
class DebugMemoryBlockImpl extends MemoryBlockImpl {
|
||||
final CleaningAction cleaningAction;
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
package com.jozufozu.flywheel.lib.memory;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
import com.jozufozu.flywheel.lib.util.StringUtil;
|
||||
|
||||
public final class FlwMemoryTracker {
|
||||
public static final boolean DEBUG_MEMORY_SAFETY = System.getProperty("flw.debugMemorySafety") != null;
|
||||
|
||||
static final Cleaner CLEANER = Cleaner.create();
|
||||
|
||||
// TODO: Should these be volatile?
|
||||
private static long cpuMemory = 0;
|
||||
private static long gpuMemory = 0;
|
||||
|
||||
private FlwMemoryTracker() {
|
||||
}
|
||||
|
||||
public static long malloc(long size) {
|
||||
long ptr = MemoryUtil.nmemAlloc(size);
|
||||
if (ptr == MemoryUtil.NULL) {
|
||||
|
@ -23,18 +26,6 @@ public final class FlwMemoryTracker {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link MemoryBlock#malloc(long)} or {@link MemoryBlock#mallocTracked(long)} and
|
||||
* {@link MemoryBlock#asBuffer()} instead. This method should only be used if specifically a {@linkplain ByteBuffer} is needed and it is
|
||||
* short-lived.
|
||||
*/
|
||||
@Deprecated
|
||||
public static ByteBuffer mallocBuffer(int size) {
|
||||
ByteBuffer buffer = MemoryUtil.memByteBuffer(malloc(size), size);
|
||||
_allocCPUMemory(buffer.capacity());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static long calloc(long num, long size) {
|
||||
long ptr = MemoryUtil.nmemCalloc(num, size);
|
||||
if (ptr == MemoryUtil.NULL) {
|
||||
|
@ -43,18 +34,6 @@ public final class FlwMemoryTracker {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link MemoryBlock#calloc(long, long)} or {@link MemoryBlock#callocTracked(long, long)} and
|
||||
* {@link MemoryBlock#asBuffer()} instead. This method should only be used if specifically a {@linkplain ByteBuffer} is needed and it is
|
||||
* short-lived.
|
||||
*/
|
||||
@Deprecated
|
||||
public static ByteBuffer callocBuffer(int num, int size) {
|
||||
ByteBuffer buffer = MemoryUtil.memByteBuffer(calloc(num, size), num * size);
|
||||
_allocCPUMemory(buffer.capacity());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static long realloc(long ptr, long size) {
|
||||
ptr = MemoryUtil.nmemRealloc(ptr, size);
|
||||
if (ptr == MemoryUtil.NULL) {
|
||||
|
@ -63,32 +42,10 @@ public final class FlwMemoryTracker {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link MemoryBlock#realloc(long)} or {@link MemoryBlock#reallocTracked(long)} instead. This method
|
||||
* should only be used if specifically a {@linkplain ByteBuffer} is needed and it is short-lived.
|
||||
*/
|
||||
@Deprecated
|
||||
public static ByteBuffer reallocBuffer(ByteBuffer buffer, int size) {
|
||||
ByteBuffer newBuffer = MemoryUtil.memByteBuffer(realloc(MemoryUtil.memAddress(buffer), size), size);
|
||||
_freeCPUMemory(buffer.capacity());
|
||||
_allocCPUMemory(newBuffer.capacity());
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
public static void free(long ptr) {
|
||||
MemoryUtil.nmemFree(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link MemoryBlock#free} instead. This method should only be used if specifically a {@linkplain ByteBuffer} is needed and
|
||||
* it is short-lived.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void freeBuffer(ByteBuffer buffer) {
|
||||
free(MemoryUtil.memAddress(buffer));
|
||||
_freeCPUMemory(buffer.capacity());
|
||||
}
|
||||
|
||||
public static void _allocCPUMemory(long size) {
|
||||
cpuMemory += size;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,9 @@ public final class ModelUtil {
|
|||
*/
|
||||
public static final BlockRenderDispatcher VANILLA_RENDERER = createVanillaRenderer();
|
||||
|
||||
private ModelUtil() {
|
||||
}
|
||||
|
||||
private static BlockRenderDispatcher createVanillaRenderer() {
|
||||
BlockRenderDispatcher defaultDispatcher = Minecraft.getInstance().getBlockRenderer();
|
||||
BlockRenderDispatcher dispatcher = new BlockRenderDispatcher(null, null, null);
|
||||
|
@ -53,6 +56,10 @@ public final class ModelUtil {
|
|||
return dispatcher;
|
||||
}
|
||||
|
||||
public static boolean isVanillaBufferEmpty(Pair<DrawState, ByteBuffer> pair) {
|
||||
return pair.getFirst().vertexCount() == 0;
|
||||
}
|
||||
|
||||
public static MemoryBlock convertVanillaBuffer(Pair<DrawState, ByteBuffer> pair, VertexType vertexType) {
|
||||
DrawState drawState = pair.getFirst();
|
||||
int vertexCount = drawState.vertexCount();
|
||||
|
@ -108,12 +115,12 @@ public final class ModelUtil {
|
|||
}
|
||||
|
||||
@Override
|
||||
public double coord(int i, int j) {
|
||||
return switch (j) {
|
||||
public double coord(int i, int dim) {
|
||||
return switch (dim) {
|
||||
case 0 -> vertexList.x(i);
|
||||
case 1 -> vertexList.y(i);
|
||||
case 2 -> vertexList.z(i);
|
||||
default -> throw new IllegalArgumentException("Invalid dimension: " + j);
|
||||
default -> throw new IllegalArgumentException("Invalid dimension: " + dim);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,12 +4,15 @@ 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.buffering.BakedModelBuilder;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.BlockModelBuilder;
|
||||
import com.jozufozu.flywheel.lib.model.baked.BakedModelBuilder;
|
||||
import com.jozufozu.flywheel.lib.model.baked.BlockModelBuilder;
|
||||
import com.jozufozu.flywheel.lib.model.baked.PartialModel;
|
||||
import com.jozufozu.flywheel.lib.transform.TransformStack;
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
import com.jozufozu.flywheel.lib.util.Pair;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
|
@ -20,6 +23,9 @@ public final class Models {
|
|||
private static final Map<PartialModel, Model> PARTIAL = new ConcurrentHashMap<>();
|
||||
private static final Map<Pair<PartialModel, Direction>, Model> PARTIAL_DIR = new ConcurrentHashMap<>();
|
||||
|
||||
private Models() {
|
||||
}
|
||||
|
||||
public static Model block(BlockState state) {
|
||||
return BLOCK_STATE.computeIfAbsent(state, it -> new BlockModelBuilder(it).build());
|
||||
}
|
||||
|
@ -32,7 +38,7 @@ public final class Models {
|
|||
return PARTIAL_DIR.computeIfAbsent(Pair.of(partial, dir), it -> new BakedModelBuilder(it.first().get()).poseStack(createRotation(it.second())).build());
|
||||
}
|
||||
|
||||
public static PoseStack createRotation(Direction facing) {
|
||||
private static PoseStack createRotation(Direction facing) {
|
||||
PoseStack stack = new PoseStack();
|
||||
TransformStack.cast(stack)
|
||||
.centre()
|
||||
|
@ -41,6 +47,7 @@ public final class Models {
|
|||
return stack;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void onReloadRenderers(ReloadRenderersEvent event) {
|
||||
deleteAll(BLOCK_STATE.values());
|
||||
deleteAll(PARTIAL.values());
|
||||
|
|
|
@ -1,41 +1,50 @@
|
|||
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;
|
||||
import com.jozufozu.flywheel.util.Lazy;
|
||||
import com.jozufozu.flywheel.util.NonNullSupplier;
|
||||
|
||||
public class SimpleLazyModel implements Model {
|
||||
private final Lazy<Mesh> supplier;
|
||||
private final Supplier<@NotNull Mesh> meshSupplier;
|
||||
private final Material material;
|
||||
|
||||
public SimpleLazyModel(NonNullSupplier<Mesh> supplier, Material material) {
|
||||
this.supplier = Lazy.of(supplier);
|
||||
@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() {
|
||||
return ImmutableMap.of(material, supplier.get());
|
||||
if (mesh == null) {
|
||||
mesh = meshSupplier.get();
|
||||
meshMap = ImmutableMap.of(material, mesh);
|
||||
}
|
||||
|
||||
return meshMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
supplier.ifPresent(Mesh::delete);
|
||||
if (mesh != null) {
|
||||
mesh.delete();
|
||||
}
|
||||
|
||||
public int getVertexCount() {
|
||||
return supplier.map(Mesh::vertexCount)
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleLazyModel{" + supplier.map(Mesh::name)
|
||||
.orElse("Uninitialized") + '}';
|
||||
String name = mesh != null ? mesh.name() : "Uninitialized";
|
||||
return "SimpleLazyModel{" + name + '}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.lib.model.buffering;
|
||||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
|
@ -8,18 +8,10 @@ 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.TessellatedModel;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.BufferFactory;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.ResultConsumer;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.ShadeSeparatedBufferFactory;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.ShadeSeparatedResultConsumer;
|
||||
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ResultConsumer;
|
||||
import com.jozufozu.flywheel.lib.model.baked.ModelBufferingUtil.ShadeSeparatedResultConsumer;
|
||||
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
|
||||
import com.jozufozu.flywheel.lib.virtualworld.VirtualEmptyBlockGetter;
|
||||
import com.jozufozu.flywheel.lib.virtualworld.VirtualEmptyModelData;
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
|
@ -29,8 +21,6 @@ import net.minecraft.world.level.block.state.BlockState;
|
|||
import net.minecraftforge.client.model.data.IModelData;
|
||||
|
||||
public class BakedModelBuilder {
|
||||
private static final int STARTING_CAPACITY = 64;
|
||||
|
||||
private final BakedModel bakedModel;
|
||||
private boolean shadeSeparated = true;
|
||||
private BlockAndTintGetter renderWorld;
|
||||
|
@ -73,19 +63,13 @@ public class BakedModelBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TessellatedModel build() {
|
||||
ModelBufferingObjects objects = ModelBufferingObjects.THREAD_LOCAL.get();
|
||||
|
||||
if (renderWorld == null) {
|
||||
renderWorld = VirtualEmptyBlockGetter.INSTANCE;
|
||||
}
|
||||
if (blockState == null) {
|
||||
blockState = Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
if (poseStack == null) {
|
||||
poseStack = objects.identityPoseStack;
|
||||
}
|
||||
if (modelData == null) {
|
||||
modelData = VirtualEmptyModelData.INSTANCE;
|
||||
}
|
||||
|
@ -96,35 +80,27 @@ public class BakedModelBuilder {
|
|||
ImmutableMap.Builder<Material, Mesh> meshMapBuilder = ImmutableMap.builder();
|
||||
|
||||
if (shadeSeparated) {
|
||||
ShadeSeparatedBufferFactory<BufferBuilder> bufferFactory = (renderType, shaded) -> {
|
||||
BufferBuilder buffer = new BufferBuilder(STARTING_CAPACITY);
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
return buffer;
|
||||
};
|
||||
ShadeSeparatedResultConsumer<BufferBuilder> resultConsumer = (renderType, shaded, buffer) -> {
|
||||
buffer.end();
|
||||
ShadeSeparatedResultConsumer resultConsumer = (renderType, shaded, data) -> {
|
||||
if (!ModelUtil.isVanillaBufferEmpty(data)) {
|
||||
Material material = materialFunc.apply(renderType, shaded);
|
||||
if (material != null) {
|
||||
MemoryBlock data = ModelUtil.convertVanillaBuffer(buffer.popNextBuffer(), VertexTypes.BLOCK);
|
||||
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, data, "bakedModel=" + bakedModel.toString() + ",renderType=" + renderType.toString() + ",shaded=" + shaded));
|
||||
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
|
||||
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "bakedModel=" + bakedModel.toString() + ",renderType=" + renderType.toString() + ",shaded=" + shaded));
|
||||
}
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.bufferSingleShadeSeparated(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, bufferFactory, objects.shadeSeparatingBufferWrapper, objects.random, modelData, resultConsumer);
|
||||
ModelBufferingUtil.bufferSingleShadeSeparated(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
|
||||
} else {
|
||||
BufferFactory<BufferBuilder> bufferFactory = (renderType) -> {
|
||||
BufferBuilder buffer = new BufferBuilder(STARTING_CAPACITY);
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
return buffer;
|
||||
};
|
||||
ResultConsumer<BufferBuilder> resultConsumer = (renderType, buffer) -> {
|
||||
buffer.end();
|
||||
Material material = materialFunc.apply(renderType, false);
|
||||
ResultConsumer resultConsumer = (renderType, data) -> {
|
||||
if (!ModelUtil.isVanillaBufferEmpty(data)) {
|
||||
Material material = materialFunc.apply(renderType, true);
|
||||
if (material != null) {
|
||||
MemoryBlock data = ModelUtil.convertVanillaBuffer(buffer.popNextBuffer(), VertexTypes.BLOCK);
|
||||
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, data, "bakedModel=" + bakedModel.toString() + ",renderType=" + renderType.toString()));
|
||||
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
|
||||
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "bakedModel=" + bakedModel.toString() + ",renderType=" + renderType.toString()));
|
||||
}
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, bufferFactory, objects.bufferWrapper, objects.random, modelData, resultConsumer);
|
||||
ModelBufferingUtil.bufferSingle(ModelUtil.VANILLA_RENDERER.getModelRenderer(), renderWorld, bakedModel, blockState, poseStack, modelData, resultConsumer);
|
||||
}
|
||||
|
||||
return new TessellatedModel(meshMapBuilder.build(), shadeSeparated);
|
|
@ -0,0 +1,97 @@
|
|||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
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.vertex.VertexTypes;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
|
||||
public class BlockModelBuilder {
|
||||
private final BlockState state;
|
||||
private boolean shadeSeparated = true;
|
||||
private BlockAndTintGetter renderWorld;
|
||||
private PoseStack poseStack;
|
||||
private IModelData modelData;
|
||||
private BiFunction<RenderType, Boolean, Material> materialFunc;
|
||||
|
||||
public BlockModelBuilder(BlockState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public BlockModelBuilder disableShadeSeparation() {
|
||||
shadeSeparated = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder renderWorld(BlockAndTintGetter renderWorld) {
|
||||
this.renderWorld = renderWorld;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder poseStack(PoseStack poseStack) {
|
||||
this.poseStack = poseStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder modelData(IModelData modelData) {
|
||||
this.modelData = modelData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
|
||||
this.materialFunc = materialFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TessellatedModel build() {
|
||||
if (renderWorld == null) {
|
||||
renderWorld = VirtualEmptyBlockGetter.INSTANCE;
|
||||
}
|
||||
if (modelData == null) {
|
||||
modelData = VirtualEmptyModelData.INSTANCE;
|
||||
}
|
||||
if (materialFunc == null) {
|
||||
materialFunc = ModelUtil::getMaterial;
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<Material, Mesh> meshMapBuilder = ImmutableMap.builder();
|
||||
|
||||
if (shadeSeparated) {
|
||||
ShadeSeparatedResultConsumer resultConsumer = (renderType, shaded, data) -> {
|
||||
if (!ModelUtil.isVanillaBufferEmpty(data)) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.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()));
|
||||
}
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.bufferBlock(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, modelData, resultConsumer);
|
||||
}
|
||||
|
||||
return new TessellatedModel(meshMapBuilder.build(), shadeSeparated);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder.DrawState;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
|
||||
import net.minecraft.client.renderer.block.ModelBlockRenderer;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
import net.minecraftforge.client.model.data.EmptyModelData;
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
|
||||
public final class ModelBufferingUtil {
|
||||
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);
|
||||
|
||||
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();
|
||||
if (poseStack == null) {
|
||||
poseStack = objects.identityPoseStack;
|
||||
}
|
||||
Random random = objects.random;
|
||||
BufferBuilder[] buffers = objects.shadedBuffers;
|
||||
|
||||
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
|
||||
RenderType renderType = CHUNK_LAYERS[layerIndex];
|
||||
|
||||
if (!ItemBlockRenderTypes.canRenderInLayer(state, renderType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BufferBuilder buffer = buffers[layerIndex];
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
|
||||
ForgeHooksClient.setRenderType(renderType);
|
||||
|
||||
poseStack.pushPose();
|
||||
blockRenderer.tesselateBlock(renderWorld, model, state, BlockPos.ZERO, poseStack, buffer, false, random, 42L, OverlayTexture.NO_OVERLAY, modelData);
|
||||
poseStack.popPose();
|
||||
|
||||
buffer.end();
|
||||
Pair<DrawState, ByteBuffer> data = buffer.popNextBuffer();
|
||||
resultConsumer.accept(renderType, data);
|
||||
}
|
||||
|
||||
ForgeHooksClient.setRenderType(null);
|
||||
}
|
||||
|
||||
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();
|
||||
if (poseStack == null) {
|
||||
poseStack = objects.identityPoseStack;
|
||||
}
|
||||
Random random = objects.random;
|
||||
ShadeSeparatingVertexConsumer shadeSeparatingWrapper = objects.shadeSeparatingWrapper;
|
||||
BufferBuilder[] shadedBuffers = objects.shadedBuffers;
|
||||
BufferBuilder[] unshadedBuffers = objects.unshadedBuffers;
|
||||
|
||||
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
|
||||
RenderType renderType = CHUNK_LAYERS[layerIndex];
|
||||
|
||||
if (!ItemBlockRenderTypes.canRenderInLayer(state, renderType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BufferBuilder shadedBuffer = shadedBuffers[layerIndex];
|
||||
BufferBuilder unshadedBuffer = unshadedBuffers[layerIndex];
|
||||
shadeSeparatingWrapper.prepare(shadedBuffer, unshadedBuffer);
|
||||
shadedBuffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
unshadedBuffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
|
||||
ForgeHooksClient.setRenderType(renderType);
|
||||
|
||||
poseStack.pushPose();
|
||||
blockRenderer.tesselateBlock(renderWorld, model, state, BlockPos.ZERO, poseStack, shadeSeparatingWrapper, false, random, 42L, OverlayTexture.NO_OVERLAY, modelData);
|
||||
poseStack.popPose();
|
||||
|
||||
shadedBuffer.end();
|
||||
unshadedBuffer.end();
|
||||
Pair<DrawState, ByteBuffer> shadedData = shadedBuffer.popNextBuffer();
|
||||
Pair<DrawState, ByteBuffer> unshadedData = unshadedBuffer.popNextBuffer();
|
||||
resultConsumer.accept(renderType, true, shadedData);
|
||||
resultConsumer.accept(renderType, false, unshadedData);
|
||||
}
|
||||
|
||||
ForgeHooksClient.setRenderType(null);
|
||||
|
||||
shadeSeparatingWrapper.clear();
|
||||
}
|
||||
|
||||
public static void bufferBlock(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter renderWorld, BlockState state, @Nullable PoseStack poseStack, IModelData modelData, ResultConsumer resultConsumer) {
|
||||
if (state.getRenderShape() != RenderShape.MODEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
bufferSingle(renderDispatcher.getModelRenderer(), renderWorld, renderDispatcher.getBlockModel(state), state, poseStack, modelData, resultConsumer);
|
||||
}
|
||||
|
||||
public static void bufferBlockShadeSeparated(BlockRenderDispatcher renderDispatcher, BlockAndTintGetter renderWorld, BlockState state, @Nullable PoseStack poseStack, IModelData modelData, ShadeSeparatedResultConsumer resultConsumer) {
|
||||
if (state.getRenderShape() != RenderShape.MODEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
bufferSingleShadeSeparated(renderDispatcher.getModelRenderer(), renderWorld, renderDispatcher.getBlockModel(state), state, poseStack, modelData, resultConsumer);
|
||||
}
|
||||
|
||||
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();
|
||||
if (poseStack == null) {
|
||||
poseStack = objects.identityPoseStack;
|
||||
}
|
||||
Random random = objects.random;
|
||||
|
||||
BufferBuilder[] buffers = objects.shadedBuffers;
|
||||
for (BufferBuilder buffer : buffers) {
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
}
|
||||
|
||||
ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer();
|
||||
ModelBlockRenderer.enableCaching();
|
||||
|
||||
for (StructureTemplate.StructureBlockInfo blockInfo : blocks) {
|
||||
BlockState state = blockInfo.state;
|
||||
|
||||
if (state.getRenderShape() != RenderShape.MODEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BakedModel model = renderDispatcher.getBlockModel(state);
|
||||
BlockPos pos = blockInfo.pos;
|
||||
long seed = state.getSeed(pos);
|
||||
IModelData modelData = modelDataMap.getOrDefault(pos, EmptyModelData.INSTANCE);
|
||||
|
||||
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
|
||||
RenderType renderType = CHUNK_LAYERS[layerIndex];
|
||||
|
||||
if (!ItemBlockRenderTypes.canRenderInLayer(state, renderType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BufferBuilder buffer = buffers[layerIndex];
|
||||
|
||||
ForgeHooksClient.setRenderType(renderType);
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(pos.getX(), pos.getY(), pos.getZ());
|
||||
blockRenderer.tesselateBlock(renderWorld, model, state, pos, poseStack, buffer, true, random, seed, OverlayTexture.NO_OVERLAY, modelData);
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
ForgeHooksClient.setRenderType(null);
|
||||
ModelBlockRenderer.clearCache();
|
||||
|
||||
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
|
||||
RenderType renderType = CHUNK_LAYERS[layerIndex];
|
||||
BufferBuilder buffer = buffers[layerIndex];
|
||||
buffer.end();
|
||||
Pair<DrawState, ByteBuffer> data = buffer.popNextBuffer();
|
||||
resultConsumer.accept(renderType, data);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (poseStack == null) {
|
||||
poseStack = objects.identityPoseStack;
|
||||
}
|
||||
Random random = objects.random;
|
||||
ShadeSeparatingVertexConsumer shadeSeparatingWrapper = objects.shadeSeparatingWrapper;
|
||||
|
||||
BufferBuilder[] shadedBuffers = objects.shadedBuffers;
|
||||
BufferBuilder[] unshadedBuffers = objects.unshadedBuffers;
|
||||
for (BufferBuilder buffer : shadedBuffers) {
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
}
|
||||
for (BufferBuilder buffer : unshadedBuffers) {
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
}
|
||||
|
||||
ModelBlockRenderer blockRenderer = renderDispatcher.getModelRenderer();
|
||||
ModelBlockRenderer.enableCaching();
|
||||
|
||||
for (StructureTemplate.StructureBlockInfo blockInfo : blocks) {
|
||||
BlockState state = blockInfo.state;
|
||||
|
||||
if (state.getRenderShape() != RenderShape.MODEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BakedModel model = renderDispatcher.getBlockModel(state);
|
||||
BlockPos pos = blockInfo.pos;
|
||||
long seed = state.getSeed(pos);
|
||||
IModelData modelData = modelDataMap.getOrDefault(pos, EmptyModelData.INSTANCE);
|
||||
|
||||
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
|
||||
RenderType renderType = CHUNK_LAYERS[layerIndex];
|
||||
|
||||
if (!ItemBlockRenderTypes.canRenderInLayer(state, renderType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
shadeSeparatingWrapper.prepare(shadedBuffers[layerIndex], unshadedBuffers[layerIndex]);
|
||||
|
||||
ForgeHooksClient.setRenderType(renderType);
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(pos.getX(), pos.getY(), pos.getZ());
|
||||
blockRenderer.tesselateBlock(renderWorld, model, state, pos, poseStack, shadeSeparatingWrapper, true, random, seed, OverlayTexture.NO_OVERLAY, modelData);
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
ForgeHooksClient.setRenderType(null);
|
||||
ModelBlockRenderer.clearCache();
|
||||
|
||||
shadeSeparatingWrapper.clear();
|
||||
|
||||
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
|
||||
RenderType renderType = CHUNK_LAYERS[layerIndex];
|
||||
BufferBuilder shadedBuffer = shadedBuffers[layerIndex];
|
||||
BufferBuilder unshadedBuffer = unshadedBuffers[layerIndex];
|
||||
shadedBuffer.end();
|
||||
unshadedBuffer.end();
|
||||
Pair<DrawState, ByteBuffer> shadedData = shadedBuffer.popNextBuffer();
|
||||
Pair<DrawState, ByteBuffer> unshadedData = unshadedBuffer.popNextBuffer();
|
||||
resultConsumer.accept(renderType, true, shadedData);
|
||||
resultConsumer.accept(renderType, false, unshadedData);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ResultConsumer {
|
||||
void accept(RenderType renderType, Pair<DrawState, ByteBuffer> data);
|
||||
}
|
||||
|
||||
public interface ShadeSeparatedResultConsumer {
|
||||
void accept(RenderType renderType, boolean shaded, Pair<DrawState, ByteBuffer> data);
|
||||
}
|
||||
|
||||
private static class ModelBufferingObjects {
|
||||
public final PoseStack identityPoseStack = new PoseStack();
|
||||
public final Random random = new Random();
|
||||
|
||||
public final ShadeSeparatingVertexConsumer shadeSeparatingWrapper = new ShadeSeparatingVertexConsumer();
|
||||
|
||||
public final BufferBuilder[] shadedBuffers = new BufferBuilder[CHUNK_LAYER_AMOUNT];
|
||||
public final BufferBuilder[] unshadedBuffers = new BufferBuilder[CHUNK_LAYER_AMOUNT];
|
||||
|
||||
{
|
||||
for (int layerIndex = 0; layerIndex < CHUNK_LAYER_AMOUNT; layerIndex++) {
|
||||
int initialSize = CHUNK_LAYERS[layerIndex].bufferSize();
|
||||
shadedBuffers[layerIndex] = new BufferBuilder(initialSize);
|
||||
unshadedBuffers[layerIndex] = new BufferBuilder(initialSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
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.vertex.VertexTypes;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
|
||||
public class MultiBlockModelBuilder {
|
||||
private final Collection<StructureTemplate.StructureBlockInfo> blocks;
|
||||
private boolean shadeSeparated = true;
|
||||
private BlockAndTintGetter renderWorld;
|
||||
private PoseStack poseStack;
|
||||
private Map<BlockPos, IModelData> modelDataMap;
|
||||
private BiFunction<RenderType, Boolean, Material> materialFunc;
|
||||
|
||||
public MultiBlockModelBuilder(Collection<StructureTemplate.StructureBlockInfo> blocks) {
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
public MultiBlockModelBuilder disableShadeSeparation() {
|
||||
shadeSeparated = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MultiBlockModelBuilder renderWorld(BlockAndTintGetter renderWorld) {
|
||||
this.renderWorld = renderWorld;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MultiBlockModelBuilder poseStack(PoseStack poseStack) {
|
||||
this.poseStack = poseStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MultiBlockModelBuilder modelDataMap(Map<BlockPos, IModelData> modelDataMap) {
|
||||
this.modelDataMap = modelDataMap;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MultiBlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
|
||||
this.materialFunc = materialFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TessellatedModel build() {
|
||||
if (renderWorld == null) {
|
||||
renderWorld = VirtualEmptyBlockGetter.INSTANCE;
|
||||
}
|
||||
if (modelDataMap == null) {
|
||||
modelDataMap = Collections.emptyMap();
|
||||
}
|
||||
if (materialFunc == null) {
|
||||
materialFunc = ModelUtil::getMaterial;
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<Material, Mesh> meshMapBuilder = ImmutableMap.builder();
|
||||
|
||||
if (shadeSeparated) {
|
||||
ShadeSeparatedResultConsumer resultConsumer = (renderType, shaded, data) -> {
|
||||
if (!ModelUtil.isVanillaBufferEmpty(data)) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.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()));
|
||||
}
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.bufferMultiBlock(blocks, ModelUtil.VANILLA_RENDERER, renderWorld, poseStack, modelDataMap, resultConsumer);
|
||||
}
|
||||
|
||||
return new TessellatedModel(meshMapBuilder.build(), shadeSeparated);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.lib.model;
|
||||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -24,7 +24,6 @@ import net.minecraftforge.client.model.ForgeModelBakery;
|
|||
* Attempting to create a PartialModel after {@link ModelRegistryEvent} will cause an error.
|
||||
*/
|
||||
public class PartialModel {
|
||||
|
||||
private static final List<PartialModel> ALL = new ArrayList<>();
|
||||
private static boolean tooLate = false;
|
||||
|
||||
|
@ -68,5 +67,4 @@ public class PartialModel {
|
|||
public BakedModel get() {
|
||||
return bakedModel;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
|
||||
class ShadeSeparatingVertexConsumer implements VertexConsumer {
|
||||
private VertexConsumer shadedConsumer;
|
||||
private VertexConsumer unshadedConsumer;
|
||||
|
||||
public void prepare(VertexConsumer shadedConsumer, VertexConsumer unshadedConsumer) {
|
||||
this.shadedConsumer = shadedConsumer;
|
||||
this.unshadedConsumer = unshadedConsumer;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
shadedConsumer = null;
|
||||
unshadedConsumer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBulkData(PoseStack.Pose poseEntry, BakedQuad quad, float[] colorMuls, float red, float green, float blue, int[] combinedLights, int combinedOverlay, boolean mulColor) {
|
||||
if (quad.isShade()) {
|
||||
shadedConsumer.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
|
||||
} else {
|
||||
unshadedConsumer.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBulkData(PoseStack.Pose matrixEntry, BakedQuad bakedQuad, float[] baseBrightness, float red, float green, float blue, float alpha, int[] lightmapCoords, int overlayCoords, boolean readExistingColor) {
|
||||
if (bakedQuad.isShade()) {
|
||||
shadedConsumer.putBulkData(matrixEntry, bakedQuad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
|
||||
} else {
|
||||
unshadedConsumer.putBulkData(matrixEntry, bakedQuad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer vertex(double x, double y, double z) {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer color(int red, int green, int blue, int alpha) {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer uv(float u, float v) {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer overlayCoords(int u, int v) {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer uv2(int u, int v) {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer normal(float x, float y, float z) {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVertex() {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defaultColor(int red, int green, int blue, int alpha) {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetDefaultColor() {
|
||||
throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.lib.model;
|
||||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.lib.virtualworld;
|
||||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
@ -99,7 +99,7 @@ public interface VirtualEmptyBlockGetter extends BlockAndTintGetter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onBlockEmissionIncrease(BlockPos pos, int p_164456_) {
|
||||
public void onBlockEmissionIncrease(BlockPos pos, int emissionLevel) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -108,16 +108,16 @@ public interface VirtualEmptyBlockGetter extends BlockAndTintGetter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int runUpdates(int p_164449_, boolean p_164450_, boolean p_164451_) {
|
||||
return p_164449_;
|
||||
public int runUpdates(int pos, boolean isQueueEmpty, boolean updateBlockLight) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSectionStatus(SectionPos pos, boolean p_75838_) {
|
||||
public void updateSectionStatus(SectionPos pos, boolean isQueueEmpty) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableLightSources(ChunkPos pos, boolean p_164453_) {
|
||||
public void enableLightSources(ChunkPos pos, boolean isQueueEmpty) {
|
||||
}
|
||||
|
||||
@Override
|
|
@ -1,15 +1,16 @@
|
|||
package com.jozufozu.flywheel.lib.virtualworld;
|
||||
package com.jozufozu.flywheel.lib.model.baked;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
import net.minecraftforge.client.model.data.ModelProperty;
|
||||
|
||||
/**
|
||||
* This model data instance is passed whenever a model is rendered without
|
||||
* available in-world context. IBakedModel#getModelData can react accordingly
|
||||
* and avoid looking for model data itself
|
||||
* available in-world context. BakedModel#getModelData can react accordingly
|
||||
* and avoid looking for model data itself.
|
||||
**/
|
||||
public enum VirtualEmptyModelData implements IModelData {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public static boolean is(IModelData data) {
|
||||
|
@ -22,13 +23,14 @@ public enum VirtualEmptyModelData implements IModelData {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T getData(ModelProperty<T> prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T setData(ModelProperty<T> prop, T data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
package com.jozufozu.flywheel.lib.model.buffering;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jozufozu.flywheel.api.material.Material;
|
||||
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.TessellatedModel;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.BufferFactory;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.ResultConsumer;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.ShadeSeparatedBufferFactory;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.ShadeSeparatedResultConsumer;
|
||||
import com.jozufozu.flywheel.lib.vertex.VertexTypes;
|
||||
import com.jozufozu.flywheel.lib.virtualworld.VirtualEmptyBlockGetter;
|
||||
import com.jozufozu.flywheel.lib.virtualworld.VirtualEmptyModelData;
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
|
||||
public class BlockModelBuilder {
|
||||
private static final int STARTING_CAPACITY = 64;
|
||||
|
||||
private final BlockState state;
|
||||
private boolean shadeSeparated = true;
|
||||
private BlockAndTintGetter renderWorld;
|
||||
private PoseStack poseStack;
|
||||
private IModelData modelData;
|
||||
private BiFunction<RenderType, Boolean, Material> materialFunc;
|
||||
|
||||
public BlockModelBuilder(BlockState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public BlockModelBuilder disableShadeSeparation() {
|
||||
shadeSeparated = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder renderWorld(BlockAndTintGetter renderWorld) {
|
||||
this.renderWorld = renderWorld;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder poseStack(PoseStack poseStack) {
|
||||
this.poseStack = poseStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder modelData(IModelData modelData) {
|
||||
this.modelData = modelData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockModelBuilder materialFunc(BiFunction<RenderType, Boolean, Material> materialFunc) {
|
||||
this.materialFunc = materialFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TessellatedModel build() {
|
||||
ModelBufferingObjects objects = ModelBufferingObjects.THREAD_LOCAL.get();
|
||||
|
||||
if (renderWorld == null) {
|
||||
renderWorld = VirtualEmptyBlockGetter.INSTANCE;
|
||||
}
|
||||
if (poseStack == null) {
|
||||
poseStack = objects.identityPoseStack;
|
||||
}
|
||||
if (modelData == null) {
|
||||
modelData = VirtualEmptyModelData.INSTANCE;
|
||||
}
|
||||
if (materialFunc == null) {
|
||||
materialFunc = ModelUtil::getMaterial;
|
||||
}
|
||||
|
||||
ImmutableMap.Builder<Material, Mesh> meshMapBuilder = ImmutableMap.builder();
|
||||
|
||||
if (shadeSeparated) {
|
||||
ShadeSeparatedBufferFactory<BufferBuilder> bufferFactory = (renderType, shaded) -> {
|
||||
BufferBuilder buffer = new BufferBuilder(STARTING_CAPACITY);
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
return buffer;
|
||||
};
|
||||
ShadeSeparatedResultConsumer<BufferBuilder> resultConsumer = (renderType, shaded, buffer) -> {
|
||||
buffer.end();
|
||||
Material material = materialFunc.apply(renderType, shaded);
|
||||
if (material != null) {
|
||||
MemoryBlock data = ModelUtil.convertVanillaBuffer(buffer.popNextBuffer(), VertexTypes.BLOCK);
|
||||
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, data, "state=" + state.toString() + ",renderType=" + renderType.toString() + ",shaded=" + shaded));
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.bufferBlockShadeSeparated(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, bufferFactory, objects.shadeSeparatingBufferWrapper, objects.random, modelData, resultConsumer);
|
||||
} else {
|
||||
BufferFactory<BufferBuilder> bufferFactory = (renderType) -> {
|
||||
BufferBuilder buffer = new BufferBuilder(STARTING_CAPACITY);
|
||||
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
|
||||
return buffer;
|
||||
};
|
||||
ResultConsumer<BufferBuilder> resultConsumer = (renderType, buffer) -> {
|
||||
buffer.end();
|
||||
Material material = materialFunc.apply(renderType, false);
|
||||
if (material != null) {
|
||||
MemoryBlock data = ModelUtil.convertVanillaBuffer(buffer.popNextBuffer(), VertexTypes.BLOCK);
|
||||
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, data, "state=" + state.toString() + ",renderType=" + renderType.toString()));
|
||||
}
|
||||
};
|
||||
ModelBufferingUtil.bufferBlock(ModelUtil.VANILLA_RENDERER, renderWorld, state, poseStack, bufferFactory, objects.bufferWrapper, objects.random, modelData, resultConsumer);
|
||||
}
|
||||
|
||||
return new TessellatedModel(meshMapBuilder.build(), shadeSeparated);
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package com.jozufozu.flywheel.lib.model.buffering;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
|
||||
public interface LazyDelegatingVertexConsumer<T extends VertexConsumer> extends VertexConsumer {
|
||||
T getDelegate();
|
||||
|
||||
@Override
|
||||
default VertexConsumer vertex(double x, double y, double z) {
|
||||
return getDelegate().vertex(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
default VertexConsumer color(int red, int green, int blue, int alpha) {
|
||||
return getDelegate().color(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
default VertexConsumer uv(float u, float v) {
|
||||
return getDelegate().uv(u, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
default VertexConsumer overlayCoords(int u, int v) {
|
||||
return getDelegate().overlayCoords(u, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
default VertexConsumer uv2(int u, int v) {
|
||||
return getDelegate().uv2(u, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
default VertexConsumer normal(float x, float y, float z) {
|
||||
return getDelegate().normal(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void endVertex() {
|
||||
getDelegate().endVertex();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void defaultColor(int red, int green, int blue, int alpha) {
|
||||
getDelegate().defaultColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void unsetDefaultColor() {
|
||||
getDelegate().unsetDefaultColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void putBulkData(PoseStack.Pose poseEntry, BakedQuad quad, float[] colorMuls, float red, float green, float blue, int[] combinedLights, int combinedOverlay, boolean mulColor) {
|
||||
getDelegate().putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void putBulkData(PoseStack.Pose matrixEntry, BakedQuad bakedQuad, float[] baseBrightness, float red, float green, float blue, float alpha, int[] lightmapCoords, int overlayCoords, boolean readExistingColor) {
|
||||
getDelegate().putBulkData(matrixEntry, bakedQuad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package com.jozufozu.flywheel.lib.model.buffering;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.BufferWrapper;
|
||||
import com.jozufozu.flywheel.lib.model.buffering.ModelBufferingUtil.ShadeSeparatingBufferWrapper;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
public class ModelBufferingObjects {
|
||||
public static final ThreadLocal<ModelBufferingObjects> THREAD_LOCAL = ThreadLocal.withInitial(ModelBufferingObjects::new);
|
||||
|
||||
public final PoseStack identityPoseStack = new PoseStack();
|
||||
@SuppressWarnings("rawtypes")
|
||||
public final BufferWrapper bufferWrapper = new BufferWrapper<>();
|
||||
@SuppressWarnings("rawtypes")
|
||||
public final ShadeSeparatingBufferWrapper shadeSeparatingBufferWrapper = new ShadeSeparatingBufferWrapper<>();
|
||||
public final Random random = new Random();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue