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:
PepperCode1 2023-11-18 11:46:04 -08:00
parent fcd70cccd0
commit 66f11018fe
158 changed files with 1960 additions and 3505 deletions

View file

@ -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'

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -17,7 +17,7 @@ public interface Material {
void clear();
RenderType getBatchingRenderType();
RenderType getFallbackRenderType();
MaterialVertexTransformer getVertexTransformer();
}

View file

@ -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);
}

View file

@ -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() {

View file

@ -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.

View file

@ -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) {
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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)));
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
}

View file

@ -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()

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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() {

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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));

View file

@ -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;

View file

@ -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() {
}
}

View file

@ -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;

View file

@ -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"),

View file

@ -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) {

View file

@ -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();

View file

@ -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 {

View file

@ -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 {

View file

@ -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;

View file

@ -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());
}
}
}

View file

@ -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();
}
}
}

View file

@ -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);
if (backendId == null) {
return "Unregistered";
}
return backendId.toString();
});
public static String getBackendString() {
ResourceLocation backendId = Backend.REGISTRY.getId(backend);
if (backendId == null) {
return "[unregistered]";
}
return backendId.toString();
}
private BackendManagerImpl() {
public static void init() {
CrashReportCallables.registerCrashCallable("Flywheel Backend", BackendManagerImpl::getBackendString);
}
}

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.task;
package com.jozufozu.flywheel.impl.task;
import com.jozufozu.flywheel.api.task.TaskExecutor;

View file

@ -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.

View file

@ -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() {

View file

@ -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);
}

View file

@ -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) {
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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");
}
}
}

View file

@ -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

View file

@ -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) {

View file

@ -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,15 +63,9 @@ 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;
BlockPos pos = blockEntity.getBlockPos();
BlockGetter existingChunk = level.getChunkForCollisions(pos.getX() >> 4, pos.getZ() >> 4);
return existingChunk != null;
}
@Override

View file

@ -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) {

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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() {
}

View file

@ -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() {
}

View file

@ -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;
}

View file

@ -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() {
}
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -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();

View file

@ -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;

View file

@ -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() {
}

View file

@ -49,7 +49,7 @@ public class SimpleMaterial implements Material {
}
@Override
public RenderType getBatchingRenderType() {
public RenderType getFallbackRenderType() {
return batchingRenderType;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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);
};
}
});

View file

@ -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());

View file

@ -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);
}
public int getVertexCount() {
return supplier.map(Mesh::vertexCount)
.orElse(0);
if (mesh != null) {
mesh.delete();
}
}
@Override
public String toString() {
return "SimpleLazyModel{" + supplier.map(Mesh::name)
.orElse("Uninitialized") + '}';
String name = mesh != null ? mesh.name() : "Uninitialized";
return "SimpleLazyModel{" + name + '}';
}
}

View file

@ -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();
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));
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, "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);
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()));
ResultConsumer resultConsumer = (renderType, data) -> {
if (!ModelUtil.isVanillaBufferEmpty(data)) {
Material material = materialFunc.apply(renderType, true);
if (material != null) {
MemoryBlock meshData = ModelUtil.convertVanillaBuffer(data, VertexTypes.BLOCK);
meshMapBuilder.put(material, new SimpleMesh(VertexTypes.BLOCK, meshData, "bakedModel=" + bakedModel.toString() + ",renderType=" + renderType.toString()));
}
}
};
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);

View file

@ -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);
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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!");
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.lib.model;
package com.jozufozu.flywheel.lib.model.baked;
import java.util.Map;

View file

@ -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

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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