diff --git a/build.gradle b/build.gradle index 00bb9ac..babd4b9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.13-SNAPSHOT' + id 'fabric-loom' version "${loom_version}" id 'maven-publish' id 'me.fallenbreath.yamlang' version '1.4.1' } diff --git a/gradle.properties b/gradle.properties index 2b88620..abf4337 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,18 +4,19 @@ org.gradle.parallel=true # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=1.21.10 -loader_version=0.17.3 +minecraft_version=1.21.11 +loader_version=0.18.2 +loom_version=1.14-SNAPSHOT # Fabric API -fabric_version=0.138.3+1.21.10 +fabric_version=0.139.5+1.21.11 # Mod Properties -mod_version=0.4.4+1.21.10 +mod_version=0.5.0+1.21.11 maven_group=com.goby56.wakes archives_base_name=wakes # Dependencies -midnightlib_version=1.8.3+1.21.9-fabric -iris_version=1.9.6+1.21.10-fabric -modmenu_version=16.0.0-rc.1 +midnightlib_version=1.9.2+1.21.11-fabric +iris_version=1.10.2+1.21.11-fabric +modmenu_version=17.0.0-alpha.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3c44eb1..20413ca 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/goby56/wakes/WakesClient.java b/src/main/java/com/goby56/wakes/WakesClient.java index 297eff1..fef3e80 100644 --- a/src/main/java/com/goby56/wakes/WakesClient.java +++ b/src/main/java/com/goby56/wakes/WakesClient.java @@ -8,14 +8,11 @@ import com.goby56.wakes.particle.ModParticles; import com.goby56.wakes.render.SplashPlaneRenderer; import com.goby56.wakes.render.WakeRenderer; -import com.mojang.blaze3d.pipeline.BlendFunction; import com.mojang.blaze3d.pipeline.RenderPipeline; -import com.mojang.blaze3d.platform.DepthTestFunction; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.VertexFormat; import eu.midnightdust.lib.config.MidnightConfig; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents; import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; import net.fabricmc.loader.api.FabricLoader; @@ -23,7 +20,7 @@ import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.client.gui.components.debug.DebugScreenEntries; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,19 +32,12 @@ public class WakesClient implements ClientModInitializer { public static boolean areShadersEnabled = false; public static final RenderPipeline GUI_HSV_PIPELINE = RenderPipelines.register( RenderPipeline.builder(RenderPipelines.GUI_TEXTURED_SNIPPET) - .withLocation(ResourceLocation.fromNamespaceAndPath("wakes", "pipeline/gui_hsv")) - .withFragmentShader(ResourceLocation.fromNamespaceAndPath("wakes", "gui_hsv")) + .withLocation(Identifier.fromNamespaceAndPath("wakes", "pipeline/gui_hsv")) + .withFragmentShader(Identifier.fromNamespaceAndPath("wakes", "gui_hsv")) .build() ); - public static final RenderPipeline SPLASH_PLANE_PIPELINE = RenderPipelines.register( - RenderPipeline.builder(RenderPipelines.BEACON_BEAM_SNIPPET) - .withLocation("pipeline/beacon_beam_translucent") - .withVertexFormat(DefaultVertexFormat.BLOCK, VertexFormat.Mode.TRIANGLES) - .withDepthWrite(false) - .withCull(false) - .withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST) - .withBlend(BlendFunction.TRANSLUCENT) - .build()); + + public static WakeRenderer wakeRenderer; @Override public void onInitializeClient() { @@ -65,13 +55,13 @@ public void onInitializeClient() { ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(new WakeWorldTicker()); // Rendering events - WorldRenderEvents.END_MAIN.register(new WakeRenderer()); + wakeRenderer = new WakeRenderer(); + WorldRenderEvents.END_MAIN.register(wakeRenderer); WorldRenderEvents.END_MAIN.register(new SplashPlaneRenderer()); - WorldRenderEvents.BEFORE_DEBUG_RENDER.register(new WakeDebugRenderer()); SplashPlaneRenderer.initSplashPlane(); DebugScreenEntries.register( - ResourceLocation.fromNamespaceAndPath("wakes", "debug_entry"), + Identifier.fromNamespaceAndPath("wakes", "debug_entry"), new WakesDebugInfo()); } @@ -81,4 +71,4 @@ public static boolean areShadersEnabled() { } return false; } -} \ No newline at end of file +} diff --git a/src/main/java/com/goby56/wakes/config/enums/Resolution.java b/src/main/java/com/goby56/wakes/config/enums/Resolution.java index 0066aba..8bf0324 100644 --- a/src/main/java/com/goby56/wakes/config/enums/Resolution.java +++ b/src/main/java/com/goby56/wakes/config/enums/Resolution.java @@ -15,6 +15,10 @@ public enum Resolution { this.power = Mth.log2(res); } + public static Resolution getHighest() { + return THIRTYTWO; + } + @Override public String toString() { return String.valueOf(this.res); diff --git a/src/main/java/com/goby56/wakes/config/gui/ColorPicker.java b/src/main/java/com/goby56/wakes/config/gui/ColorPicker.java index 5c7bd89..ec878c2 100644 --- a/src/main/java/com/goby56/wakes/config/gui/ColorPicker.java +++ b/src/main/java/com/goby56/wakes/config/gui/ColorPicker.java @@ -14,7 +14,7 @@ import net.minecraft.client.gui.components.AbstractSliderButton; import net.minecraft.client.gui.components.EditBox; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import org.joml.Matrix3x2f; import org.joml.Vector2f; @@ -26,9 +26,9 @@ import java.util.regex.Pattern; public class ColorPicker extends AbstractWidget { - private static final ResourceLocation FRAME_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/sprites/widget/slot_frame.png"); - private static final ResourceLocation PICKER_BG_TEXTURE = ResourceLocation.fromNamespaceAndPath(WakesClient.MOD_ID, "textures/picker_background.png"); - private static final ResourceLocation PICKER_KNOB_TEXTURE = ResourceLocation.fromNamespaceAndPath(WakesClient.MOD_ID, "textures/picker_knob.png"); + private static final Identifier FRAME_TEXTURE = Identifier.withDefaultNamespace("textures/gui/sprites/widget/slot_frame.png"); + private static final Identifier PICKER_BG_TEXTURE = Identifier.fromNamespaceAndPath(WakesClient.MOD_ID, "textures/picker_background.png"); + private static final Identifier PICKER_KNOB_TEXTURE = Identifier.fromNamespaceAndPath(WakesClient.MOD_ID, "textures/picker_knob.png"); private static final int pickerKnobDim = 7; private final Map widgets = new HashMap<>(); @@ -355,7 +355,7 @@ public void renderWidget(GuiGraphics context, int mouseX, int mouseY, float delt } context.blitSprite(RenderPipelines.GUI_TEXTURED, this.getHandleSprite(), this.getX() + (int)(this.value * (double)(this.width - 8)), this.getY(), 8, this.getHeight()); int i = this.active ? 0xFFFFFF : 0xA0A0A0; - this.renderScrollingString(context, Minecraft.getInstance().font, 2, i | Mth.ceil(this.alpha * 255.0f) << 24); + this.renderScrollingStringOverContents(context.textRenderer(), this.message, i | Mth.ceil(this.alpha * 255.0f) << 24); } @Override diff --git a/src/main/java/com/goby56/wakes/config/gui/ColorPickerScreen.java b/src/main/java/com/goby56/wakes/config/gui/ColorPickerScreen.java index 48d3acd..2eaf957 100644 --- a/src/main/java/com/goby56/wakes/config/gui/ColorPickerScreen.java +++ b/src/main/java/com/goby56/wakes/config/gui/ColorPickerScreen.java @@ -12,7 +12,7 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; public class ColorPickerScreen extends Screen { private final Screen parent; @@ -20,8 +20,8 @@ public class ColorPickerScreen extends Screen { private ColorIntervalSlider colorIntervalSlider; private WakeAffectingSlider wakeOpacitySlider; private WakeAffectingSlider blendStrengthSlider; - private static final ResourceLocation INFO_ICON_TEXTURE = ResourceLocation.fromNamespaceAndPath("minecraft", "textures/gui/sprites/icon/info.png"); - private static final ResourceLocation RESET_ICON_TEXTURE = ResourceLocation.fromNamespaceAndPath(WakesClient.MOD_ID, "textures/reset_icon.png"); + private static final Identifier INFO_ICON_TEXTURE = Identifier.fromNamespaceAndPath("minecraft", "textures/gui/sprites/icon/info.png"); + private static final Identifier RESET_ICON_TEXTURE = Identifier.fromNamespaceAndPath(WakesClient.MOD_ID, "textures/reset_icon.png"); public ColorPickerScreen(Screen parent) { super(Component.nullToEmpty("Configure wake colors")); this.parent = parent; diff --git a/src/main/java/com/goby56/wakes/config/gui/SliderHandle.java b/src/main/java/com/goby56/wakes/config/gui/SliderHandle.java index c6dc1b1..a8bb6b1 100644 --- a/src/main/java/com/goby56/wakes/config/gui/SliderHandle.java +++ b/src/main/java/com/goby56/wakes/config/gui/SliderHandle.java @@ -1,14 +1,14 @@ package com.goby56.wakes.config.gui; import com.goby56.wakes.render.WakeColor; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import org.jetbrains.annotations.NotNull; public class SliderHandle implements Comparable { - private static final ResourceLocation HANDLE_TEXTURE = ResourceLocation.withDefaultNamespace("widget/slider_handle"); - private static final ResourceLocation HANDLE_HIGHLIGHTED_TEXTURE = ResourceLocation.withDefaultNamespace("widget/slider_handle_highlighted"); + private static final Identifier HANDLE_TEXTURE = Identifier.withDefaultNamespace("widget/slider_handle"); + private static final Identifier HANDLE_HIGHLIGHTED_TEXTURE = Identifier.withDefaultNamespace("widget/slider_handle_highlighted"); protected WakeColor color; protected float value; @@ -31,7 +31,7 @@ public boolean inProximity(float value, int sliderWidth, int handleWidth) { return (Math.abs(value - this.value) < (float) handleWidth / (2 * sliderWidth)); } - public ResourceLocation getHandleTexture(boolean isHovered) { + public Identifier getHandleTexture(boolean isHovered) { if (focused || isHovered) { return HANDLE_HIGHLIGHTED_TEXTURE; } diff --git a/src/main/java/com/goby56/wakes/config/gui/TexturedButton.java b/src/main/java/com/goby56/wakes/config/gui/TexturedButton.java index b1c19ad..34bb908 100644 --- a/src/main/java/com/goby56/wakes/config/gui/TexturedButton.java +++ b/src/main/java/com/goby56/wakes/config/gui/TexturedButton.java @@ -4,13 +4,13 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; public class TexturedButton extends Button { - private final ResourceLocation texture; + private final Identifier texture; private final int textureWidth; private final int textureHeight; - protected TexturedButton(int width, int height, OnPress onPress, ResourceLocation texture, int texWidth, int texHeight) { + protected TexturedButton(int width, int height, OnPress onPress, Identifier texture, int texWidth, int texHeight) { super(0, 0, width, height, Component.empty(), onPress, DEFAULT_NARRATION); this.texture = texture; this.textureWidth = texWidth; @@ -22,20 +22,20 @@ public static com.goby56.wakes.config.gui.TexturedButton.Builder builder(Button. } @Override - protected void renderWidget(GuiGraphics context, int mouseX, int mouseY, float delta) { - super.renderWidget(context, mouseX, mouseY, delta); + protected void renderContents(GuiGraphics context, int mouseX, int mouseY, float delta) { int tw = this.textureWidth; int th = this.textureHeight; int x = this.getX() + this.getWidth() / 2 - this.textureWidth / 2; int y = this.getY() + this.getHeight() / 2 - this.textureHeight / 2; context.blit(RenderPipelines.GUI_TEXTURED, this.texture, x, y, 0, 0, tw, th, tw, th); + } public static class Builder { private final Button.OnPress onPress; private int width = 30; private int height = 30; - private ResourceLocation texture; + private Identifier texture; private int textureWidth = 20; private int textureHeight = 20; @@ -49,7 +49,7 @@ public com.goby56.wakes.config.gui.TexturedButton.Builder dimension(int width, i return this; } - public com.goby56.wakes.config.gui.TexturedButton.Builder texture(ResourceLocation texture, int width, int height) { + public com.goby56.wakes.config.gui.TexturedButton.Builder texture(Identifier texture, int width, int height) { this.texture = texture; this.textureWidth = width; this.textureHeight = height; diff --git a/src/main/java/com/goby56/wakes/debug/WakeDebugRenderer.java b/src/main/java/com/goby56/wakes/debug/WakeDebugRenderer.java index 269c2ba..b010859 100644 --- a/src/main/java/com/goby56/wakes/debug/WakeDebugRenderer.java +++ b/src/main/java/com/goby56/wakes/debug/WakeDebugRenderer.java @@ -1,41 +1,35 @@ package com.goby56.wakes.debug; import com.goby56.wakes.config.WakesConfig; -import com.goby56.wakes.simulation.Brick; +import com.goby56.wakes.render.WakeColor; +import com.goby56.wakes.simulation.WakeChunk; import com.goby56.wakes.simulation.WakeHandler; import com.goby56.wakes.simulation.WakeNode; -import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext; -import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents; -import net.minecraft.client.Camera; -import net.minecraft.client.renderer.debug.DebugRenderer; +import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElement; +import net.minecraft.gizmos.GizmoStyle; +import net.minecraft.gizmos.Gizmos; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import java.awt.*; import java.util.Random; -public class WakeDebugRenderer implements WorldRenderEvents.DebugRender { - - @Override - public void beforeDebugRender(WorldRenderContext context) { +public class WakeDebugRenderer { + public static void addDebugGizmos() { WakeHandler wakeHandler = WakeHandler.getInstance().orElse(null); if (wakeHandler == null) return; + int color = new WakeColor(255, 0, 255, 128).argb; + if (WakesConfig.drawDebugBoxes) { - Camera camera = context.gameRenderer().getMainCamera(); - for (var node : wakeHandler.getVisible(WakeNode.class)) { - DebugRenderer.renderFilledBox(context.matrices(), context.consumers(), - node.toBox().move(camera.getPosition().reverse()), - 1, 0, 1, 0.5f); + for (var node : wakeHandler.getVisibleNodes()) { + Gizmos.cuboid(node.toBox(), GizmoStyle.fill(color)); } - for (var brick : wakeHandler.getVisible(Brick.class)) { - Vec3 pos = brick.pos; - AABB box = new AABB(pos.x, pos.y - (1 - WakeNode.WATER_OFFSET), pos.z, pos.x + brick.dim, pos.y, pos.z + brick.dim); - var col = Color.getHSBColor(new Random(pos.hashCode()).nextFloat(), 1f, 1f).getRGBColorComponents(null); - DebugRenderer.renderFilledBox(context.matrices(), context.consumers(), - box.move(camera.getPosition().reverse()), - col[0], col[1], col[2], 0.5f); + for (var chunk : wakeHandler.getVisibleChunks()) { + Vec3 pos = chunk.pos; + AABB box = new AABB(pos.x, pos.y - (1 - WakeNode.WATER_OFFSET), pos.z, pos.x + WakeChunk.WIDTH, pos.y, pos.z + WakeChunk.WIDTH); + var col = Color.getHSBColor(new Random(pos.hashCode()).nextFloat(), 1f, 1f).getRGB(); + Gizmos.cuboid(box, GizmoStyle.fill(col)); } } - } } diff --git a/src/main/java/com/goby56/wakes/debug/WakesDebugInfo.java b/src/main/java/com/goby56/wakes/debug/WakesDebugInfo.java index c64d706..db35636 100644 --- a/src/main/java/com/goby56/wakes/debug/WakesDebugInfo.java +++ b/src/main/java/com/goby56/wakes/debug/WakesDebugInfo.java @@ -3,7 +3,7 @@ import com.goby56.wakes.config.WakesConfig; import net.minecraft.client.gui.components.debug.DebugScreenDisplayer; import net.minecraft.client.gui.components.debug.DebugScreenEntry; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.LevelChunk; import org.jetbrains.annotations.Nullable; @@ -31,7 +31,7 @@ public void display(DebugScreenDisplayer debugScreenDisplayer, @Nullable Level l if (WakesConfig.disableMod) { debugScreenDisplayer.addLine("[Wakes] Mod disabled!"); } else { - debugScreenDisplayer.addToGroup(ResourceLocation.fromNamespaceAndPath("wakes", "debug_category"), + debugScreenDisplayer.addToGroup(Identifier.fromNamespaceAndPath("wakes", "debug_category"), List.of( String.format("[Wakes] Rendering %d quads for %d wake nodes", WakesDebugInfo.quadsRendered, WakesDebugInfo.nodeCount), String.format("[Wakes] Node logic: %.2fms/t", 10e-6 * WakesDebugInfo.nodeLogicTime), diff --git a/src/main/java/com/goby56/wakes/event/WakeWorldTicker.java b/src/main/java/com/goby56/wakes/event/WakeWorldTicker.java index f9c4d73..e02e54f 100644 --- a/src/main/java/com/goby56/wakes/event/WakeWorldTicker.java +++ b/src/main/java/com/goby56/wakes/event/WakeWorldTicker.java @@ -1,6 +1,7 @@ package com.goby56.wakes.event; import com.goby56.wakes.WakesClient; +import com.goby56.wakes.debug.WakeDebugRenderer; import com.goby56.wakes.simulation.WakeHandler; import com.goby56.wakes.debug.WakesDebugInfo; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; @@ -16,6 +17,7 @@ public void onEndTick(ClientLevel world) { WakesClient.areShadersEnabled = WakesClient.areShadersEnabled(); WakesDebugInfo.reset(); WakeHandler.getInstance().ifPresent(WakeHandler::tick); + WakeDebugRenderer.addDebugGizmos(); } @Override diff --git a/src/main/java/com/goby56/wakes/mixin/LightmapTextureManagerMixin.java b/src/main/java/com/goby56/wakes/mixin/LightmapTextureManagerMixin.java index cc46325..1b16126 100644 --- a/src/main/java/com/goby56/wakes/mixin/LightmapTextureManagerMixin.java +++ b/src/main/java/com/goby56/wakes/mixin/LightmapTextureManagerMixin.java @@ -2,13 +2,16 @@ import com.goby56.wakes.duck.LightmapAccess; import com.goby56.wakes.render.LightmapInfo; -import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.EndFlashState; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.world.entity.LivingEntity; +import net.minecraft.util.ARGB; +import net.minecraft.world.attribute.EnvironmentAttributeProbe; +import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; import org.joml.Vector3f; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -22,46 +25,71 @@ public abstract class LightmapTextureManagerMixin implements LightmapAccess { @Shadow private float blockLightRedFlicker; @Shadow @Final private Minecraft minecraft; + @Shadow @Final private GameRenderer renderer; + @Shadow protected abstract float calculateDarknessScale(LivingEntity entity, float factor, float tickProgress); @Unique private int currentTick; - @Shadow protected abstract float calculateDarknessScale(LivingEntity entity, float factor, float tickProgress); - - @Shadow @Final private GameRenderer renderer; @Unique private LightmapInfo info; @Inject(method = "updateLightTexture", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/CommandEncoder;createRenderPass(Ljava/util/function/Supplier;Lcom/mojang/blaze3d/textures/GpuTextureView;Ljava/util/OptionalInt;)Lcom/mojang/blaze3d/systems/RenderPass;")) - private void wakes$onUpdate(float tickProgress, CallbackInfo ci, @Local ClientLevel world, @Local(ordinal = 1) Vector3f skyColor) { - float f = world.getSkyDarken(1.0F); - float g; - if (world.getSkyFlashTime() > 0) { - g = 1.0F; - } else { - g = f * 0.95F + 0.05F; + private void wakes$onUpdate(float tickProgress, CallbackInfo ci) { + ClientLevel world = this.minecraft.level; + if (world == null || this.minecraft.player == null) { + return; } - float k = this.minecraft.player.getWaterVision(); + EnvironmentAttributeProbe probe = this.minecraft.gameRenderer.getMainCamera().attributeProbe(); + int skyLightColorRgb = ((Integer) probe.getValue(EnvironmentAttributes.SKY_LIGHT_COLOR, tickProgress)).intValue(); + float skyFactor = ((Float) probe.getValue(EnvironmentAttributes.SKY_LIGHT_FACTOR, tickProgress)).floatValue(); - float l; - if (this.minecraft.player.hasEffect(MobEffects.NIGHT_VISION)) { - l = GameRenderer.getNightVisionScale(this.minecraft.player, tickProgress); - } else if (k > 0.0F && this.minecraft.player.hasEffect(MobEffects.CONDUIT_POWER)) { - l = k; + EndFlashState endFlashState = world.endFlashState(); + Vector3f ambientColor; + if (endFlashState != null) { + ambientColor = new Vector3f(0.99f, 1.12f, 1.0f); + if (!this.minecraft.options.hideLightningFlash().get()) { + float intensity = endFlashState.getIntensity(tickProgress); + if (this.minecraft.gui.getBossOverlay().shouldCreateWorldFog()) { + skyFactor += intensity / 3.0f; + } else { + skyFactor += intensity; + } + } } else { - l = 0.0F; + ambientColor = new Vector3f(1.0f, 1.0f, 1.0f); } - float h = this.minecraft.options.darknessEffectScale().get().floatValue(); - float i = this.minecraft.player.getEffectBlendFactor(MobEffects.DARKNESS, tickProgress) * h; - float j = this.calculateDarknessScale(this.minecraft.player, i, tickProgress) * h; + float darknessScaleSetting = this.minecraft.options.darknessEffectScale().get().floatValue(); + float darknessBlend = this.minecraft.player.getEffectBlendFactor(MobEffects.DARKNESS, tickProgress) * darknessScaleSetting; + float darknessScale = this.calculateDarknessScale(this.minecraft.player, darknessBlend, tickProgress) * darknessScaleSetting; - float o = this.minecraft.options.gamma().get().floatValue(); + float waterVision = this.minecraft.player.getWaterVision(); + float nightVisionFactor; + if (this.minecraft.player.hasEffect(MobEffects.NIGHT_VISION)) { + nightVisionFactor = GameRenderer.getNightVisionScale(this.minecraft.player, tickProgress); + } else if (waterVision > 0.0f && this.minecraft.player.hasEffect(MobEffects.CONDUIT_POWER)) { + nightVisionFactor = waterVision; + } else { + nightVisionFactor = 0.0f; + } + float blockFactor = this.blockLightRedFlicker + 1.5f; + float brightnessFactor = Math.max(0.0f, this.minecraft.options.gamma().get().floatValue() - darknessBlend); - info = new LightmapInfo(world.dimensionType().ambientLight(), g, this.blockLightRedFlicker + 1.5f, - world.effects().constantAmbientLight(), l, j, this.renderer.getDarkenWorldAmount(tickProgress), Math.max(0.0F, o - i), skyColor, currentTick++); + this.info = new LightmapInfo( + world.dimensionType().ambientLight(), + skyFactor, + blockFactor, + nightVisionFactor, + darknessScale, + this.renderer.getDarkenWorldAmount(tickProgress), + brightnessFactor, + ARGB.vector3fFromRGB24(skyLightColorRgb), + ambientColor, + currentTick++ + ); } @Override diff --git a/src/main/java/com/goby56/wakes/mixin/WakeRendererCleanup.java b/src/main/java/com/goby56/wakes/mixin/WakeRendererCleanup.java new file mode 100644 index 0000000..a6f88f7 --- /dev/null +++ b/src/main/java/com/goby56/wakes/mixin/WakeRendererCleanup.java @@ -0,0 +1,17 @@ +package com.goby56.wakes.mixin; + +import com.goby56.wakes.WakesClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.renderer.GameRenderer; + +@Mixin(GameRenderer.class) +public class WakeRendererCleanup { + @Inject(method = "close", at = @At("RETURN")) + private void onGameRendererClose(CallbackInfo ci) { + WakesClient.wakeRenderer.close(); + } +} diff --git a/src/main/java/com/goby56/wakes/particle/ModParticles.java b/src/main/java/com/goby56/wakes/particle/ModParticles.java index 301d709..ff0a30b 100644 --- a/src/main/java/com/goby56/wakes/particle/ModParticles.java +++ b/src/main/java/com/goby56/wakes/particle/ModParticles.java @@ -6,17 +6,17 @@ import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; public class ModParticles { public static WithOwnerParticleType SPLASH_PLANE; public static WithOwnerParticleType SPLASH_CLOUD; public static void registerParticles() { - SPLASH_PLANE = Registry.register(BuiltInRegistries.PARTICLE_TYPE, ResourceLocation.fromNamespaceAndPath(WakesClient.MOD_ID, "splash_plane"), new WithOwnerParticleType(true)); + SPLASH_PLANE = Registry.register(BuiltInRegistries.PARTICLE_TYPE, Identifier.fromNamespaceAndPath(WakesClient.MOD_ID, "splash_plane"), new WithOwnerParticleType(true)); ParticleFactoryRegistry.getInstance().register(SPLASH_PLANE, SplashPlaneParticle.Factory::new); - SPLASH_CLOUD = Registry.register(BuiltInRegistries.PARTICLE_TYPE, ResourceLocation.fromNamespaceAndPath(WakesClient.MOD_ID, "splash_cloud"), new WithOwnerParticleType(true)); + SPLASH_CLOUD = Registry.register(BuiltInRegistries.PARTICLE_TYPE, Identifier.fromNamespaceAndPath(WakesClient.MOD_ID, "splash_cloud"), new WithOwnerParticleType(true)); ParticleFactoryRegistry.getInstance().register(SPLASH_CLOUD, SplashCloudParticle.Factory::new); } } diff --git a/src/main/java/com/goby56/wakes/particle/custom/SplashPlaneParticle.java b/src/main/java/com/goby56/wakes/particle/custom/SplashPlaneParticle.java index f3ae7ed..5bcd2ac 100644 --- a/src/main/java/com/goby56/wakes/particle/custom/SplashPlaneParticle.java +++ b/src/main/java/com/goby56/wakes/particle/custom/SplashPlaneParticle.java @@ -9,19 +9,22 @@ import com.goby56.wakes.utils.WakesUtils; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.BiomeColors; -import net.minecraft.client.particle.*; import net.minecraft.client.Camera; -import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.ParticleProvider; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.particle.SpriteSet; +import net.minecraft.client.renderer.BiomeColors; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.core.particles.SimpleParticleType; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.vehicle.AbstractBoat; -import net.minecraft.core.particles.SimpleParticleType; +import net.minecraft.world.entity.vehicle.boat.AbstractBoat; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; -import org.lwjgl.system.MemoryUtil; import java.util.Random; @@ -34,7 +37,7 @@ public class SplashPlaneParticle extends Particle { private final SimulationNode simulationNode = new SimulationNode.SplashPlaneSimulation(); - public long imgPtr = -1; + public NativeImage image; public int texRes; public boolean hasPopulatedPixels = false; @@ -78,7 +81,6 @@ public void tick() { } private void aliveTick(ProducesWake wakeProducer) { - // Vec3d vel = wakeProducer.wakes$getNumericalVelocity(); // UNCOMMENT IF WEIRD SPLASH BEHAVIOR Vec3 vel = this.owner.getDeltaMovement(); if (this.owner instanceof AbstractBoat) { this.yaw = -this.owner.getYRot(); @@ -104,30 +106,31 @@ private void aliveTick(ProducesWake wakeProducer) { } public void initTexture(int res) { - long size = 4L * res * res; - if (imgPtr == -1) { - imgPtr = MemoryUtil.nmemAlloc(size); - } else { - imgPtr = MemoryUtil.nmemRealloc(imgPtr, size); + if (this.image != null) { + this.image.close(); } + this.image = new NativeImage(res, res, false); this.texRes = res; this.hasPopulatedPixels = false; } public void deallocTexture() { - MemoryUtil.nmemFree(imgPtr); + if (this.image != null) { + this.image.close(); + this.image = null; + } } public void populatePixels() { int fluidColor = BiomeColors.getAverageWaterColor(level, this.owner.blockPosition()); int lightCol = WakesUtils.getLightColor(level, this.owner.blockPosition()); - float opacity = WakesConfig.wakeOpacity * 0.9f; int res = WakeHandler.resolution.res; + float opacity = WakesConfig.wakeOpacity * 0.9f; for (int r = 0; r < res; r++) { for (int c = 0; c < res; c++) { - long pixelOffset = 4L * (((long) r * res) + c); - MemoryUtil.memPutInt(imgPtr + pixelOffset, simulationNode.getPixelColor(c, r, fluidColor, lightCol, opacity)); + int color = simulationNode.getPixelColor(c, r, fluidColor, lightCol, opacity); + this.image.setPixel(c, r, color); } } this.hasPopulatedPixels = true; @@ -146,7 +149,7 @@ public void updateYaw(float tickDelta) { } public void translateMatrix(Camera camera, PoseStack matrices) { - Vec3 cameraPos = camera.getPosition(); + Vec3 cameraPos = camera.position(); float tickDelta = camera.getPartialTickTime(); float x = (float) (Mth.lerp(tickDelta, this.xo, this.x) - cameraPos.x()); float y = (float) (Mth.lerp(tickDelta, this.yo, this.y) - cameraPos.y()); diff --git a/src/main/java/com/goby56/wakes/render/BetterDynamicTexture.java b/src/main/java/com/goby56/wakes/render/BetterDynamicTexture.java new file mode 100644 index 0000000..f3d0f1c --- /dev/null +++ b/src/main/java/com/goby56/wakes/render/BetterDynamicTexture.java @@ -0,0 +1,43 @@ +package com.goby56.wakes.render; + +import com.goby56.wakes.WakesClient; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.GpuDevice; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.FilterMode; +import com.mojang.blaze3d.textures.TextureFormat; +import net.minecraft.client.renderer.texture.AbstractTexture; + +import java.util.function.Supplier; + +public class BetterDynamicTexture extends AbstractTexture { + private NativeImage pixels; + public boolean dirty = true; + + public BetterDynamicTexture(Supplier supplier, NativeImage nativeImage) { + this.pixels = nativeImage; + this.createTexture(supplier); + } + + private void createTexture(Supplier supplier) { + GpuDevice gpuDevice = RenderSystem.getDevice(); + this.texture = gpuDevice.createTexture(supplier, 5, TextureFormat.RGBA8, this.pixels.getWidth(), this.pixels.getHeight(), 1, 1); + this.sampler = RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST); + this.textureView = gpuDevice.createTextureView(this.texture); + } + + public void uploadIfDirty() { + if (!dirty) return; + if (this.texture != null) { + RenderSystem.getDevice().createCommandEncoder().writeToTexture(this.texture, this.pixels); + dirty = false; + } else { + WakesClient.LOGGER.warn("Trying to upload disposed texture {}", this.getTexture().getLabel()); + } + } + + public void close() { + this.pixels.close(); + super.close(); + } +} diff --git a/src/main/java/com/goby56/wakes/render/LightmapInfo.java b/src/main/java/com/goby56/wakes/render/LightmapInfo.java index 414383b..debf35f 100644 --- a/src/main/java/com/goby56/wakes/render/LightmapInfo.java +++ b/src/main/java/com/goby56/wakes/render/LightmapInfo.java @@ -2,7 +2,16 @@ import org.joml.Vector3f; -public record LightmapInfo(float AmbientLightFactor, float SkyFactor, float BlockFactor, boolean UseBrightLightmap, float NightVisionFactor, - float DarknessScale, float DarkenWorldFactor, float BrightnessFactor, Vector3f skyLightColor, - int currentTick) { +public record LightmapInfo( + float ambientLightFactor, + float skyFactor, + float blockFactor, + float nightVisionFactor, + float darknessScale, + float darkenWorldFactor, + float brightnessFactor, + Vector3f skyLightColor, + Vector3f ambientColor, + int currentTick +) { } diff --git a/src/main/java/com/goby56/wakes/render/LightmapWrapper.java b/src/main/java/com/goby56/wakes/render/LightmapWrapper.java index a606013..7096279 100644 --- a/src/main/java/com/goby56/wakes/render/LightmapWrapper.java +++ b/src/main/java/com/goby56/wakes/render/LightmapWrapper.java @@ -8,86 +8,89 @@ import org.joml.Vector3f; public class LightmapWrapper { - private static int[][] color = new int[16][16]; - private static int[][] tick = new int[16][16]; + private static final int[][] color = new int[16][16]; + private static final int[][] tick = new int[16][16]; private static float mix(float x, float y, float a) { return x * (1.0f - a) + y * a; } - private static float get_brightness(float level, float AmbientLightFactor) { - float curved_level = level / (4.0f - 3.0f * level); - return mix(curved_level, 1.0f, AmbientLightFactor); + private static float getBrightness(float level) { + return level / (4.0f - 3.0f * level); } - public static Vector3f notGamma(Vector3f v) { - float nx0 = 1.0f - v.x; - float nx1 = 1.0f - v.y; - float nx2 = 1.0f - v.z; - return new Vector3f( - 1.0f - nx0 * nx0 * nx0 * nx0, - 1.0f - nx1 * nx1 * nx1 * nx1, - 1.0f - nx2 * nx2 * nx2 * nx2); + // Mirrors assets/minecraft/shaders/core/lightmap.fsh + private static Vector3f notGamma(Vector3f color) { + float maxComponent = Math.max(color.x, Math.max(color.y, color.z)); + if (maxComponent <= 0.0f) { + return new Vector3f(0.0f, 0.0f, 0.0f); + } + + float maxInverted = 1.0f - maxComponent; + float maxScaled = 1.0f - maxInverted * maxInverted * maxInverted * maxInverted; + float scale = maxScaled / maxComponent; + return new Vector3f(color).mul(scale); } private static Vector3f mix3(Vector3f color, Vector3f c2, float v) { - return new Vector3f(mix(color.x, c2.x, v), + return new Vector3f( + mix(color.x, c2.x, v), mix(color.y, c2.y, v), - mix(color.z, c2.z, v)); + mix(color.z, c2.z, v) + ); + } + + private static Vector3f clamp3(Vector3f color, float min, float max) { + return color.set( + Mth.clamp(color.x, min, max), + Mth.clamp(color.y, min, max), + Mth.clamp(color.z, min, max) + ); } private static int calculatePixel(LightmapInfo info, int block, int sky) { - float block_brightness = get_brightness(block / 15.0f, info.AmbientLightFactor()) * info.BlockFactor(); - float sky_brightness = get_brightness(sky / 15.0f, info.AmbientLightFactor()) * info.SkyFactor(); + float blockBrightness = getBrightness(block / 15.0f) * info.blockFactor(); + float skyBrightness = getBrightness(sky / 15.0f) * info.skyFactor(); // cubic nonsense, dips to yellowish in the middle, white when fully saturated - Vector3f color = new Vector3f( - block_brightness, - block_brightness * ((block_brightness * 0.6f + 0.4f) * 0.6f + 0.4f), - block_brightness * (block_brightness * block_brightness * 0.6f + 0.4f)); - - if (info.UseBrightLightmap()) { - color = mix3(color, new Vector3f(0.99f, 1.12f, 1.0f), 0.25f); - color = clamp3(color, 0.0, 1.0); - } else { - color.add(info.skyLightColor().mul(sky_brightness, new Vector3f())); - color = mix3(color, new Vector3f(0.75f), 0.04f); - - Vector3f darkened_color = color.mul(new Vector3f(0.7f, 0.6f, 0.6f), new Vector3f()); - color = mix3(color, darkened_color, info.DarkenWorldFactor()); + Vector3f c = new Vector3f( + blockBrightness, + blockBrightness * ((blockBrightness * 0.6f + 0.4f) * 0.6f + 0.4f), + blockBrightness * (blockBrightness * blockBrightness * 0.6f + 0.4f) + ); + + c = mix3(c, info.ambientColor(), info.ambientLightFactor()); + c.add(new Vector3f(info.skyLightColor()).mul(skyBrightness)); + c = mix3(c, new Vector3f(0.75f), 0.04f); + + if (info.ambientLightFactor() == 0.0f) { + Vector3f darkened = new Vector3f(c).mul(0.7f, 0.6f, 0.6f); + c = mix3(c, darkened, info.darkenWorldFactor()); } - if (info.NightVisionFactor() > 0.0) { - // scale up uniformly until 1.0 is hit by one of the colors - float max_component = Math.max(color.x, Math.max(color.y, color.z)); - if (max_component < 1.0) { - Vector3f bright_color = color.div(max_component, new Vector3f()); - color = mix3(color, bright_color, info.NightVisionFactor()); + if (info.nightVisionFactor() > 0.0f) { + float maxComponent = Math.max(c.x, Math.max(c.y, c.z)); + if (maxComponent > 0.0f && maxComponent < 1.0f) { + Vector3f bright = new Vector3f(c).div(maxComponent); + c = mix3(c, bright, info.nightVisionFactor()); } } - if (!info.UseBrightLightmap()) { - color = clamp3(color.sub(new Vector3f(info.DarknessScale()), new Vector3f()), 0.0, 1.0); + if (info.ambientLightFactor() == 0.0f) { + c.sub(info.darknessScale(), info.darknessScale(), info.darknessScale()); } - Vector3f notGamma = notGamma(color); - color = mix3(color, notGamma, info.BrightnessFactor()); - color = mix3(color, new Vector3f(0.75f), 0.04f); - color = clamp3(color, 0.0, 1.0); - - return ARGB.colorFromFloat(1.0f, color.x, color.y, color.z); - } + c = clamp3(c, 0.0f, 1.0f); + c = mix3(c, notGamma(c), info.brightnessFactor()); + c = mix3(c, new Vector3f(0.75f), 0.04f); - private static Vector3f clamp3(Vector3f color, double v, double v1) { - return color.set(Mth.clamp(color.x, v, v1), Mth.clamp(color.y, v, v1), - Mth.clamp(color.z, v, v1)); + return ARGB.colorFromFloat(1.0f, c.x, c.y, c.z); } public static int readPixel(int block, int sky) { LightmapInfo info = ((LightmapAccess) Minecraft.getInstance().gameRenderer.lightTexture()) .wakes$getLightmapInfo(); - // Added null check to avoid NullPointerException if info is null if (info == null) { return color[block][sky]; } diff --git a/src/main/java/com/goby56/wakes/render/SplashPlaneRenderer.java b/src/main/java/com/goby56/wakes/render/SplashPlaneRenderer.java index facdd86..b02f9c8 100644 --- a/src/main/java/com/goby56/wakes/render/SplashPlaneRenderer.java +++ b/src/main/java/com/goby56/wakes/render/SplashPlaneRenderer.java @@ -8,9 +8,11 @@ import com.goby56.wakes.simulation.WakeHandler; import com.goby56.wakes.utils.WakesUtils; import com.mojang.blaze3d.buffers.GpuBuffer; -import com.mojang.blaze3d.opengl.GlConst; +import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.FilterMode; +import com.mojang.blaze3d.textures.GpuSampler; import com.mojang.blaze3d.vertex.*; import io.github.jdiemke.triangulation.DelaunayTriangulator; import io.github.jdiemke.triangulation.NotEnoughPointsException; @@ -21,39 +23,54 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.world.entity.Entity; import com.mojang.math.Axis; import net.minecraft.world.phys.Vec3; import org.joml.Matrix4f; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.OptionalDouble; +import java.util.OptionalInt; public class SplashPlaneRenderer implements WorldRenderEvents.EndMain { + private static final ByteBufferBuilder allocator = new ByteBufferBuilder(RenderType.BIG_BUFFER_SIZE); private static ArrayList points; private static List triangles; private static ArrayList vertices; private static ArrayList normals; - public static Map wakeTextures = null; + public static Map wakeTextures = null; private static void initTextures() { wakeTextures = Map.of( - Resolution.EIGHT, new WakeTexture(Resolution.EIGHT.res, false), - Resolution.SIXTEEN, new WakeTexture(Resolution.SIXTEEN.res, false), - Resolution.THIRTYTWO, new WakeTexture(Resolution.THIRTYTWO.res, false) + Resolution.EIGHT, new SplashPlaneTexture(Resolution.EIGHT.res), + Resolution.SIXTEEN, new SplashPlaneTexture(Resolution.SIXTEEN.res), + Resolution.THIRTYTWO, new SplashPlaneTexture(Resolution.THIRTYTWO.res) ); } private static final double SQRT_8 = Math.sqrt(8); + private static RenderPipeline getPipeline() { + if (WakesClient.areShadersEnabled) { + return RenderPipelines.TRANSLUCENT_MOVING_BLOCK; + } else { + return RenderPipelines.BEACON_BEAM_TRANSLUCENT; + } + } + @Override public void endMain(WorldRenderContext context) { if (WakeHandler.getInstance().isEmpty()) { return; } WakeHandler wakeHandler = WakeHandler.getInstance().get(); - for (SplashPlaneParticle particle : wakeHandler.getVisible(SplashPlaneParticle.class)) { + for (SplashPlaneParticle particle : wakeHandler.getVisibleSplashPlanes()) { SplashPlaneRenderer.render(particle.owner, particle, context, context.matrices()); } } @@ -80,50 +97,72 @@ public static void render(T entity, SplashPlaneParticle splas matrices.scale(scalar, scalar, scalar); Matrix4f matrix = matrices.last().pose(); - wakeTextures.get(WakeHandler.resolution).loadTexture(splashPlane.imgPtr, GlConst.GL_RGBA); - renderSurface(matrix); + SplashPlaneTexture texture = wakeTextures.get(WakeHandler.resolution); + texture.loadTexture(splashPlane.image); + renderSurface(matrix, texture); matrices.popPose(); } - private static void renderSurface(Matrix4f matrix) { - BufferBuilder bb = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.BLOCK); - // TODO IMPROVE ANIMATION (WATER TRAVELS IN AN OUTWARDS DIRECTION) - // AND ADD A BOUNCY FEEL TO IT (BOBBING UP AND DOWN) WAIT IT IS JUST THE BOAT THAT IS DOING THAT - // MAYBE ADD TO BLAZINGLY FAST BOATS? - // https://streamable.com/tz0gp + private static void renderSurface(Matrix4f matrix, SplashPlaneTexture splashTexture) { + RenderPipeline pipeline = getPipeline(); + BufferBuilder bb = Tesselator.getInstance().begin(pipeline.getVertexFormatMode(), pipeline.getVertexFormat()); for (int s = -1; s < 2; s++) { if (s == 0) continue; - for (int i = 0; i < vertices.size(); i++) { - Vec3 vertex = vertices.get(i); - Vec3 normal = normals.get(i); - bb.addVertex(matrix, - (float) (s * (vertex.x * WakesConfig.splashPlaneWidth + WakesConfig.splashPlaneGap)), - (float) (vertex.z * WakesConfig.splashPlaneHeight), - (float) (vertex.y * WakesConfig.splashPlaneDepth)) - .setUv((float) (vertex.x), (float) (vertex.y)) - .setLight(LightTexture.FULL_BRIGHT) - .setColor(1f, 1f, 1f, 1f) - .setNormal((float) normal.x, (float) normal.y, (float) normal.z); + for (int i = 0; i < vertices.size(); i += 3) { + Vec3 v0 = vertices.get(i); + Vec3 n0 = normals.get(i); + Vec3 v1 = vertices.get(i + 1); + Vec3 n1 = normals.get(i + 1); + Vec3 v2 = vertices.get(i + 2); + Vec3 n2 = normals.get(i + 2); + addDegenerateQuad(bb, matrix, s, v0, n0, v1, n1, v2, n2); + addDegenerateQuad(bb, matrix, s, v0, n0, v2, n2, v1, n1); } } MeshData built = bb.buildOrThrow(); - - GpuBuffer buffer = DefaultVertexFormat.BLOCK.uploadImmediateVertexBuffer(built.vertexBuffer()); - GpuBuffer indices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.TRIANGLES).getBuffer(built.drawState().indexCount()); - try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(() -> "Splash Plane", Minecraft.getInstance().getMainRenderTarget().getColorTextureView(), OptionalInt.empty(), Minecraft.getInstance().getMainRenderTarget().getDepthTextureView(), OptionalDouble.empty())) { - pass.setPipeline(WakesClient.SPLASH_PLANE_PIPELINE); - pass.bindSampler("Sampler0", RenderSystem.getShaderTexture(0)); - pass.bindSampler("Sampler2", RenderSystem.getShaderTexture(2)); + MeshData.DrawState drawState = built.drawState(); + VertexFormat format = drawState.format(); + GpuBuffer vertexBuffer = format.uploadImmediateVertexBuffer(built.vertexBuffer()); + built.sortQuads(allocator, RenderSystem.getProjectionType().vertexSorting()); + GpuBuffer indexBuffer = pipeline.getVertexFormat().uploadImmediateIndexBuffer(built.indexBuffer()); + + GpuSampler sampler = RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST); + try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( + () -> "Splash Plane", + Minecraft.getInstance().getMainRenderTarget().getColorTextureView(), + OptionalInt.empty(), + Minecraft.getInstance().getMainRenderTarget().getDepthTextureView(), + OptionalDouble.empty())) { + pass.setPipeline(pipeline); RenderSystem.bindDefaultUniforms(pass); - pass.setVertexBuffer(0, buffer); - pass.setIndexBuffer(indices, RenderSystem.getSequentialBuffer(VertexFormat.Mode.TRIANGLES).type()); - pass.drawIndexed(0, 0, built.drawState().indexCount(), 1); + pass.bindTexture("Sampler0", splashTexture.getTextureView(), sampler); + pass.setVertexBuffer(0, vertexBuffer); + pass.setIndexBuffer(indexBuffer, drawState.indexType()); + pass.drawIndexed(0 / format.getVertexSize(), 0, drawState.indexCount(), 1); } built.close(); } + private static void addVertex(BufferBuilder bb, Matrix4f matrix, int side, Vec3 vertex, Vec3 normal) { + bb.addVertex(matrix, + (float) (side * (vertex.x * WakesConfig.splashPlaneWidth + WakesConfig.splashPlaneGap)), + (float) (vertex.z * WakesConfig.splashPlaneHeight), + (float) (vertex.y * WakesConfig.splashPlaneDepth)) + .setUv((float) vertex.x, (float) vertex.y) + .setLight(LightTexture.FULL_BRIGHT) + .setColor(1f, 1f, 1f, 1f) + .setNormal((float) normal.x, (float) normal.y, (float) normal.z); + } + + private static void addDegenerateQuad(BufferBuilder bb, Matrix4f matrix, int side, Vec3 a, Vec3 an, Vec3 b, Vec3 bn, Vec3 c, Vec3 cn) { + addVertex(bb, matrix, side, a, an); + addVertex(bb, matrix, side, b, bn); + addVertex(bb, matrix, side, c, cn); + addVertex(bb, matrix, side, c, cn); + } + private static double upperBound(double x) { return - 2 * x * x + SQRT_8 * x; } @@ -133,7 +172,7 @@ private static double lowerBound(double x) { } private static double height(double x, double y) { - return 4 * (x * (SQRT_8 - x) -y - x * x) / SQRT_8; + return 4 * (x * (SQRT_8 - x) - y - x * x) / SQRT_8; } private static Vec3 normal(double x, double y) { @@ -149,9 +188,9 @@ private static void distributePoints() { for (float i = 0; i < res; i++) { double x = i / (res - 1); double h = upperBound(x) - lowerBound(x); - int n_points = (int) Math.max(1, Math.floor(h * res)); - for (float j = 0; j < n_points + 1; j++) { - float y = (float) ((j / n_points) * h + lowerBound(x)); + int nPoints = (int) Math.max(1, Math.floor(h * res)); + for (float j = 0; j < nPoints + 1; j++) { + float y = (float) ((j / nPoints) * h + lowerBound(x)); points.add(new Vector2D(x, y)); } } @@ -169,7 +208,8 @@ private static void generateMesh() { } for (Triangle2D tri : triangles) { for (Vector2D vec : new Vector2D[] {tri.a, tri.b, tri.c}) { - double x = vec.x, y = vec.y; + double x = vec.x; + double y = vec.y; vertices.add(new Vec3(x, y, height(x, y))); normals.add(normal(x, y)); } diff --git a/src/main/java/com/goby56/wakes/render/SplashPlaneTexture.java b/src/main/java/com/goby56/wakes/render/SplashPlaneTexture.java new file mode 100644 index 0000000..1211f85 --- /dev/null +++ b/src/main/java/com/goby56/wakes/render/SplashPlaneTexture.java @@ -0,0 +1,34 @@ +package com.goby56.wakes.render; + +import com.goby56.wakes.WakesClient; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.GpuTexture; +import com.mojang.blaze3d.textures.GpuTextureView; +import com.mojang.blaze3d.textures.TextureFormat; + +public class SplashPlaneTexture { + private final GpuTexture texture; + private final GpuTextureView textureView; + public final int resolution; + + public SplashPlaneTexture(int resolution) { + this.resolution = resolution; + + this.texture = RenderSystem.getDevice().createTexture(() -> WakesClient.MOD_ID + " splash plane texture", + GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_TEXTURE_BINDING, + TextureFormat.RGBA8, resolution, resolution, 1, 1); + this.textureView = RenderSystem.getDevice().createTextureView(texture); + } + + public void loadTexture(NativeImage image) { + if (image == null) { + return; + } + RenderSystem.getDevice().createCommandEncoder().writeToTexture(this.texture, image); + } + + public GpuTextureView getTextureView() { + return this.textureView; + } +} diff --git a/src/main/java/com/goby56/wakes/render/WakeColor.java b/src/main/java/com/goby56/wakes/render/WakeColor.java index 7da9719..a53d3db 100644 --- a/src/main/java/com/goby56/wakes/render/WakeColor.java +++ b/src/main/java/com/goby56/wakes/render/WakeColor.java @@ -52,7 +52,7 @@ private static double invertedLogisticCurve(float x) { return WakesClient.areShadersEnabled ? k * (4 * Math.pow(x - 0.5f, 3) + 0.5f) : x; } - public static int sampleColor(float waveEqAvg, int fluidCol, int lightColor, float opacity) { + public static WakeColor sampleColor(float waveEqAvg, int fluidCol, int lightColor, float opacity) { WakeColor tint = new WakeColor(fluidCol); double clampedRange = 1 / (1 + Math.exp(-0.1 * waveEqAvg)); var ranges = WakesConfig.wakeColorIntervals; @@ -64,7 +64,7 @@ public static int sampleColor(float waveEqAvg, int fluidCol, int lightColor, flo } } WakeColor color = WakesConfig.getWakeColor(returnIndex); - return color.blend(tint, lightColor, opacity).abgr; + return color.blend(tint, lightColor, opacity); } public WakeColor modifyOpacity(float opacityMultiplier) { @@ -79,9 +79,10 @@ public WakeColor blend(WakeColor tint, int lightColor, float opacity) { int g = (int) ((this.g) * (srcA) + (tint.g) * (1 - srcA)); int b = (int) ((this.b) * (srcA) + (tint.b) * (1 - srcA)); - r = (int) ((r * invertedLogisticCurve((lightColor >> 16 & 0xFF) / 255f))); - g = (int) ((g * invertedLogisticCurve((lightColor >> 8 & 0xFF) / 255f))); - b = (int) ((b * invertedLogisticCurve((lightColor & 0xFF) / 255f))); + // Lighting is baked directly into wake/splash pixels. + r = (int) (r * invertedLogisticCurve((lightColor >> 16 & 0xFF) / 255f)); + g = (int) (g * invertedLogisticCurve((lightColor >> 8 & 0xFF) / 255f)); + b = (int) (b * invertedLogisticCurve((lightColor & 0xFF) / 255f)); return new WakeColor(r, g, b, (int) (this.a * opacity)); } diff --git a/src/main/java/com/goby56/wakes/render/WakeRenderer.java b/src/main/java/com/goby56/wakes/render/WakeRenderer.java index 2bcd3ba..d592bb2 100644 --- a/src/main/java/com/goby56/wakes/render/WakeRenderer.java +++ b/src/main/java/com/goby56/wakes/render/WakeRenderer.java @@ -1,37 +1,49 @@ package com.goby56.wakes.render; +import com.goby56.wakes.WakesClient; import com.goby56.wakes.config.WakesConfig; -import com.goby56.wakes.config.enums.Resolution; -import com.goby56.wakes.simulation.Brick; +import com.goby56.wakes.simulation.WakeChunk; import com.goby56.wakes.simulation.WakeHandler; import com.goby56.wakes.simulation.WakeNode; import com.goby56.wakes.debug.WakesDebugInfo; import com.mojang.blaze3d.buffers.GpuBuffer; -import com.mojang.blaze3d.opengl.GlConst; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.systems.CommandEncoder; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.FilterMode; +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTextureView; import com.mojang.blaze3d.vertex.*; import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents; import net.minecraft.client.Minecraft; +import net.minecraft.client.model.geom.builders.UVPair; import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MappableRingBuffer; import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.world.phys.Vec3; import org.joml.Matrix4f; +import org.joml.Matrix4fc; import org.joml.Vector3f; +import org.joml.Vector4f; +import org.lwjgl.system.MemoryUtil; import java.util.*; public class WakeRenderer implements WorldRenderEvents.EndMain { - public static Map wakeTextures = null; - - private void initTextures() { - wakeTextures = Map.of( - Resolution.EIGHT, new WakeTexture(Resolution.EIGHT.res, true), - Resolution.SIXTEEN, new WakeTexture(Resolution.SIXTEEN.res, true), - Resolution.THIRTYTWO, new WakeTexture(Resolution.THIRTYTWO.res, true) - ); - } + + private static final ByteBufferBuilder allocator = new ByteBufferBuilder(RenderType.BIG_BUFFER_SIZE); + private BufferBuilder buffer; + + private static final Vector4f COLOR_MODULATOR = new Vector4f(1f, 1f, 1f, 1f); + private static final Vector3f MODEL_OFFSET = new Vector3f(); + private static final Matrix4f TEXTURE_MATRIX = new Matrix4f(); + private MappableRingBuffer vertexBuffer; + + private final RenderPipeline PIPELINE = RenderPipelines.TRANSLUCENT_MOVING_BLOCK; @Override public void endMain(WorldRenderContext context) { @@ -40,74 +52,162 @@ public void endMain(WorldRenderContext context) { return; } - context.gameRenderer().lightTexture().turnOnLightLayer(); - if (wakeTextures == null) initTextures(); - WakeHandler wakeHandler = WakeHandler.getInstance().orElse(null); - if (wakeHandler == null || WakeHandler.resolutionResetScheduled) return; - ArrayList bricks = wakeHandler.getVisible(Brick.class); + if (wakeHandler == null) return; - Vec3 cameraPos = context.gameRenderer().getMainCamera().getPosition(); - PoseStack matrices = context.matrices(); - matrices.pushPose(); - matrices.translate(cameraPos.reverse()); + List wakeChunks = wakeHandler.getVisibleChunks(); - Matrix4f matrix = matrices.last().pose(); + if (wakeChunks.isEmpty()) return; - Resolution resolution = WakeHandler.resolution; - int n = 0; long tRendering = System.nanoTime(); - for (var brick : bricks) { - render(matrix, brick, wakeTextures.get(resolution)); - n++; + + if (buffer == null) { + buffer = new BufferBuilder(allocator, PIPELINE.getVertexFormatMode(), PIPELINE.getVertexFormat()); } - WakesDebugInfo.renderingTime.add(System.nanoTime() - tRendering); - WakesDebugInfo.quadsRendered = n; + + + PoseStack matrices = context.matrices(); + Vec3 camera = context.worldState().cameraRenderState.pos; + + matrices.pushPose(); + matrices.translate(-camera.x, -camera.y, -camera.z); + + addVertices(matrices.last().pose(), buffer, wakeChunks); matrices.popPose(); + + MeshData builtBuffer = buffer.buildOrThrow(); + MeshData.DrawState drawParameters = builtBuffer.drawState(); + VertexFormat format = drawParameters.format(); + + GpuBuffer vertices = uploadMesh(drawParameters, format, builtBuffer); + + BetterDynamicTexture texture = wakeHandler.getTextureAtlas().dynamicTexture; + texture.uploadIfDirty(); + draw(builtBuffer, drawParameters, vertices, format, texture.getTextureView()); + + // Rotate the vertex buffer so we are less likely to use buffers that the GPU is using + vertexBuffer.rotate(); + buffer = null; + + WakesDebugInfo.renderingTime.add(System.nanoTime() - tRendering); + WakesDebugInfo.quadsRendered = wakeChunks.size(); } - private void render(Matrix4f matrix, Brick brick, WakeTexture texture) { - if (!brick.hasPopulatedPixels) return; - texture.loadTexture(brick.imgPtr, GlConst.GL_RGBA); - - BufferBuilder bb = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK); - Vector3f pos = brick.pos.toVector3f().add(0, WakeNode.WATER_OFFSET, 0); - bb.addVertex(matrix, pos.x, pos.y, pos.z) - .setUv(0, 0) - .setColor(1f, 1f, 1f, 1f) - .setLight(LightTexture.FULL_BRIGHT) - .setNormal(0f, 1f, 0f); - bb.addVertex(matrix, pos.x, pos.y, pos.z + brick.dim) - .setUv(0, 1) - .setColor(1f, 1f, 1f, 1f) - .setLight(LightTexture.FULL_BRIGHT) - .setNormal(0f, 1f, 0f); - bb.addVertex(matrix, pos.x + brick.dim, pos.y, pos.z + brick.dim) - .setUv(1, 1) - .setColor(1f, 1f, 1f, 1f) - .setLight(LightTexture.FULL_BRIGHT) - .setNormal(0f, 1f, 0f); - bb.addVertex(matrix, pos.x + brick.dim, pos.y, pos.z) - .setUv(1, 0) - .setColor(1f, 1f, 1f, 1f) - .setLight(LightTexture.FULL_BRIGHT) - .setNormal(0f, 1f, 0f); - - MeshData built = bb.buildOrThrow(); - - GpuBuffer buffer = DefaultVertexFormat.BLOCK.uploadImmediateVertexBuffer(built.vertexBuffer()); - GpuBuffer indices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).getBuffer(built.drawState().indexCount()); - try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(() -> "Wake", Minecraft.getInstance().getMainRenderTarget().getColorTextureView(), OptionalInt.empty(), Minecraft.getInstance().getMainRenderTarget().getDepthTextureView(), OptionalDouble.empty())) { - pass.setPipeline(RenderPipelines.TRANSLUCENT_MOVING_BLOCK); - pass.bindSampler("Sampler0", RenderSystem.getShaderTexture(0)); - pass.bindSampler("Sampler2", RenderSystem.getShaderTexture(2)); + private void addVertices(Matrix4fc matrix, BufferBuilder bb, List chunks) { + for (WakeChunk wakeChunk : chunks) { + UVPair uv = wakeChunk.drawContext.getUV(); + float uvOffset = wakeChunk.drawContext.getUVOffset(); + + float x0 = (float) wakeChunk.pos.x; + float y = (float) (wakeChunk.pos.y + WakeNode.WATER_OFFSET); + float z0 = (float) wakeChunk.pos.z; + + float x1 = x0 + WakeChunk.WIDTH; + float z1 = z0 + WakeChunk.WIDTH; + + bb.addVertex(matrix, x0, y, z0) + .setUv(uv.u(), uv.v()) + .setColor(1f, 1f, 1f, 1f) + .setLight(LightTexture.FULL_BRIGHT) + .setNormal(0f, 1f, 0f); + bb.addVertex(matrix, x0, y, z1) + .setUv(uv.u(), uv.v() + uvOffset) + .setColor(1f, 1f, 1f, 1f) + .setLight(LightTexture.FULL_BRIGHT) + .setNormal(0f, 1f, 0f); + bb.addVertex(matrix, x1, y, z1) + .setUv(uv.u() + uvOffset, uv.v() + uvOffset) + .setColor(1f, 1f, 1f, 1f) + .setLight(LightTexture.FULL_BRIGHT) + .setNormal(0f, 1f, 0f); + bb.addVertex(matrix, x1, y, z0) + .setUv(uv.u() + uvOffset, uv.v()) + .setColor(1f, 1f, 1f, 1f) + .setLight(LightTexture.FULL_BRIGHT) + .setNormal(0f, 1f, 0f); + } + } + + private GpuBuffer uploadMesh(MeshData.DrawState drawParameters, VertexFormat format, MeshData builtBuffer) { + // Calculate the size needed for the vertex buffer + int vertexBufferSize = drawParameters.vertexCount() * format.getVertexSize(); + + // Initialize or resize the vertex buffer as needed + if (vertexBuffer == null || vertexBuffer.size() < vertexBufferSize) { + if (vertexBuffer != null) { + vertexBuffer.close(); + } + + vertexBuffer = new MappableRingBuffer(() -> WakesClient.MOD_ID + " wake ring buffer", GpuBuffer.USAGE_VERTEX | GpuBuffer.USAGE_MAP_WRITE, vertexBufferSize); + } + + // Copy vertex data into the vertex buffer + CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); + + try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(vertexBuffer.currentBuffer().slice(0, builtBuffer.vertexBuffer().remaining()), false, true)) { + MemoryUtil.memCopy(builtBuffer.vertexBuffer(), mappedView.data()); + } + + return vertexBuffer.currentBuffer(); + } + + private void draw(MeshData builtBuffer, MeshData.DrawState drawParameters, GpuBuffer vertices, VertexFormat format, GpuTextureView texture) { + GpuBuffer indices; + VertexFormat.IndexType indexType; + + if (PIPELINE.getVertexFormatMode() == VertexFormat.Mode.QUADS) { + // Sort the quads if there is translucency + builtBuffer.sortQuads(allocator, RenderSystem.getProjectionType().vertexSorting()); + // Upload the index buffer + indices = PIPELINE.getVertexFormat().uploadImmediateIndexBuffer(builtBuffer.indexBuffer()); + indexType = builtBuffer.drawState().indexType(); + } else { + // Use the general shape index buffer for non-quad draw modes + RenderSystem.AutoStorageIndexBuffer shapeIndexBuffer = RenderSystem.getSequentialBuffer(PIPELINE.getVertexFormatMode()); + indices = shapeIndexBuffer.getBuffer(drawParameters.indexCount()); + indexType = shapeIndexBuffer.type(); + } + + // Actually execute the draw + GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms() + .writeTransform(RenderSystem.getModelViewMatrix(), COLOR_MODULATOR, MODEL_OFFSET, TEXTURE_MATRIX); + GpuSampler sampler = RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST); + Minecraft client = Minecraft.getInstance(); + try (RenderPass pass = RenderSystem.getDevice() + .createCommandEncoder() + .createRenderPass( + () -> WakesClient.MOD_ID + " wake render pipeline rendering", + client.getMainRenderTarget().getColorTextureView(), + OptionalInt.empty(), + client.getMainRenderTarget().getDepthTextureView(), + OptionalDouble.empty())) { + + pass.setPipeline(PIPELINE); RenderSystem.bindDefaultUniforms(pass); + pass.setUniform("DynamicTransforms", dynamicTransforms); + + pass.bindTexture("Sampler0", texture, sampler); + pass.bindTexture("Sampler2", Minecraft.getInstance().gameRenderer.lightTexture().getTextureView(), sampler); + + pass.setVertexBuffer(0, vertices); + pass.setIndexBuffer(indices, indexType); + + // The base vertex is the starting index when we copied the data into the vertex buffer divided by vertex size + //noinspection ConstantValue + pass.drawIndexed(0 / format.getVertexSize(), 0, drawParameters.indexCount(), 1); + + } + + builtBuffer.close(); + } + + public void close() { + allocator.close(); - pass.setVertexBuffer(0, buffer); - pass.setIndexBuffer(indices, RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS).type()); - pass.drawIndexed(0, 0, built.drawState().indexCount(), 1); + if (vertexBuffer != null) { + vertexBuffer.close(); + vertexBuffer = null; } - built.close(); } } diff --git a/src/main/java/com/goby56/wakes/render/WakeTexture.java b/src/main/java/com/goby56/wakes/render/WakeTexture.java deleted file mode 100644 index 6a40426..0000000 --- a/src/main/java/com/goby56/wakes/render/WakeTexture.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.goby56.wakes.render; - -import com.goby56.wakes.simulation.QuadTree; -import com.goby56.wakes.simulation.WakeHandler; -import com.mojang.blaze3d.opengl.GlConst; -import com.mojang.blaze3d.opengl.GlStateManager; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.textures.FilterMode; -import com.mojang.blaze3d.textures.GpuTexture; -import com.mojang.blaze3d.textures.GpuTextureView; -import com.mojang.blaze3d.textures.TextureFormat; -import com.mojang.blaze3d.opengl.GlTexture; - -public class WakeTexture { - private final GpuTextureView textureView; - public int res; - public GpuTexture texture; - public final boolean isUsingBricks; - private final int resolutionScaling; - - public WakeTexture(int res, boolean useBricks) { - this.res = res; - this.isUsingBricks = useBricks; - this.resolutionScaling = useBricks ? QuadTree.BRICK_WIDTH : 1; - - this.texture = RenderSystem.getDevice().createTexture(() -> "Wake Texture", GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_TEXTURE_BINDING, - TextureFormat.RGBA8, resolutionScaling * res, resolutionScaling * res, 1, 1); - texture.setTextureFilter(FilterMode.NEAREST, false); - this.textureView = RenderSystem.getDevice().createTextureView(texture); - } - - public void loadTexture(long imgPtr, int glFormat) { - GlStateManager._pixelStore(GlConst.GL_UNPACK_ROW_LENGTH, 0); - GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_PIXELS, 0); - GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_ROWS, 0); - GlStateManager._pixelStore(GlConst.GL_UNPACK_ALIGNMENT, 4); - - int dim = resolutionScaling * WakeHandler.resolution.res; - GlStateManager._bindTexture(((GlTexture) texture).glId()); - GlStateManager._texSubImage2D(GlConst.GL_TEXTURE_2D, 0,0,0,dim, dim, glFormat, GlConst.GL_UNSIGNED_BYTE, imgPtr); - - //RenderSystem.setShaderTexture(0, texture); - //RenderSystem.setShader(ShaderProgramKeys.POSITION_TEX_COLOR); - //RenderSystem.enableDepthTest(); // Is it THIS simple? https://github.com/Goby56/wakes/issues/46 - //RenderSystem.disableCull(); - RenderSystem.setShaderTexture(0, textureView); - } - - public GpuTexture getTexture() { - return texture; - } - - public GpuTextureView getTextureView() { - return textureView; - } -} diff --git a/src/main/java/com/goby56/wakes/render/WakeTextureAtlas.java b/src/main/java/com/goby56/wakes/render/WakeTextureAtlas.java new file mode 100644 index 0000000..46b7892 --- /dev/null +++ b/src/main/java/com/goby56/wakes/render/WakeTextureAtlas.java @@ -0,0 +1,109 @@ +package com.goby56.wakes.render; + +import com.goby56.wakes.WakesClient; +import com.goby56.wakes.config.enums.Resolution; +import com.goby56.wakes.simulation.WakeChunk; +import com.goby56.wakes.simulation.WakeHandler; +import com.mojang.blaze3d.opengl.GlConst; +import com.mojang.blaze3d.opengl.GlStateManager; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.opengl.GlTexture; +import net.minecraft.client.model.geom.builders.UVPair; +import net.minecraft.client.renderer.texture.DynamicTexture; + +import java.util.Arrays; +import java.util.function.Supplier; + +public class WakeTextureAtlas { + public static final int CHUNKS_PER_ROW = 16; + + public final int resolution; + + public int nodeResolution; + public int chunkResolution; + public int effectiveResolution; + public float chunkUVOffset; + + protected final NativeImage nativeImage; + public final BetterDynamicTexture dynamicTexture; + + private final boolean[] occupiedSubTextures = new boolean[CAPACITY]; + + private static final int CAPACITY = CHUNKS_PER_ROW * CHUNKS_PER_ROW - 1; // Include error texture + + public WakeTextureAtlas() { + resolution = Resolution.getHighest().res * WakeChunk.WIDTH * CHUNKS_PER_ROW; + + nativeImage = new NativeImage(resolution, resolution, false); + Supplier name = () -> String.format("%s %dx%x texture atlas", + WakesClient.MOD_ID, resolution, resolution); + dynamicTexture = new BetterDynamicTexture(name, nativeImage); + } + + public void setResolution(int wakeNodeRes) { + nodeResolution = wakeNodeRes; + chunkResolution = wakeNodeRes * WakeChunk.WIDTH; + effectiveResolution = chunkResolution * CHUNKS_PER_ROW; + chunkUVOffset = chunkResolution / (float) resolution; + + Arrays.fill(occupiedSubTextures, false); + } + + public DrawContext claimSubTexture() { + for (int i = 0; i < CAPACITY; i++) { + if (!occupiedSubTextures[i]) { + occupiedSubTextures[i] = true; + return new DrawContext(i, this); + } + } + return null; + } + + protected void vacateSubTexture(int subTextureIndex) { + occupiedSubTextures[subTextureIndex] = false; + } + + public static class DrawContext { + private boolean active; + private final int subTextureIndex; + private final WakeTextureAtlas atlas; + + public final int row; + public final int column; + + public DrawContext(int subTextureIndex, WakeTextureAtlas atlas) { + this.subTextureIndex = subTextureIndex; + this.active = true; + this.atlas = atlas; + + this.row = subTextureIndex / CHUNKS_PER_ROW; + this.column = subTextureIndex % CHUNKS_PER_ROW; + } + + public void invalidate() { + this.atlas.vacateSubTexture(subTextureIndex); + this.active = false; + } + + public float getUVOffset() { + return atlas.chunkUVOffset; + } + + public UVPair getUV() { + float uvOffset = atlas.chunkUVOffset; + return new UVPair(column * uvOffset, row * uvOffset); + } + + public void draw(int x, int y, int color) { + if (!active) { + return; + //throw new IllegalAccessException("Wake texture atlas draw context has been invalidated and cannot be drawn to"); + } + int globX = x + column * atlas.chunkResolution; + int globY = y + row * atlas.chunkResolution; + this.atlas.nativeImage.setPixel(globX, globY, color); + this.atlas.dynamicTexture.dirty = true; + } + } +} diff --git a/src/main/java/com/goby56/wakes/simulation/QuadTree.java b/src/main/java/com/goby56/wakes/simulation/QuadTree.java deleted file mode 100644 index e882093..0000000 --- a/src/main/java/com/goby56/wakes/simulation/QuadTree.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.goby56.wakes.simulation; - -import com.goby56.wakes.render.FrustumManager; -import net.minecraft.world.phys.AABB; - -import java.util.*; - -public class QuadTree { - public static final int BRICK_WIDTH = 4; - private static final int MAX_DEPTH = (int) (26 - Math.log(BRICK_WIDTH) / Math.log(2)); - private static final int ROOT_X = (int) - Math.pow(2, 25); - private static final int ROOT_Z = (int) - Math.pow(2, 25); - private static final int ROOT_WIDTH = (int) Math.pow(2, 26); - - private final QuadTree ROOT; - private List children; - - private final DecentralizedBounds bounds; - private final int depth; - private Brick brick; - public final float yLevel; - - public QuadTree(float y) { - this(ROOT_X, y, ROOT_Z, ROOT_WIDTH, 0, null); - } - - private QuadTree(int x, float y, int z, int width, int depth, QuadTree root) { - this.bounds = new DecentralizedBounds(x, y, z, width); - this.depth = depth; - this.ROOT = root == null ? this : root; - this.yLevel = y; - } - - private boolean hasLeaf() { - return depth == MAX_DEPTH && brick != null; - } - - private void initLeaf() { - if (depth >= MAX_DEPTH) { - this.brick = new Brick(bounds.x, this.yLevel, bounds.z, bounds.width); - this.ROOT.updateAdjacency(this); - } - } - - protected void updateAdjacency(QuadTree leaf) { - if (this == leaf) return; - if (!this.bounds.neighbors(leaf.bounds) && !this.bounds.intersects(leaf.bounds)) { - return; - } - if (brick != null) { - brick.updateAdjacency(leaf.brick); - return; - } - if (children != null) { - for (var tree : children) { - tree.updateAdjacency(leaf); - } - } - } - - public boolean tick(WakeHandler wakeHandler) { - if (hasLeaf()) { - return brick.tick(wakeHandler); - } - if (children == null) return false; - int aliveChildren = 0; - for (var tree : children) { - if (tree.tick(wakeHandler)) aliveChildren++; - } - if (aliveChildren == 0) this.prune(); - return aliveChildren > 0; - } - - public boolean insert(WakeNode node) { - if (!this.bounds.contains(node.x, node.z)) { - return false; - } - - if (depth == MAX_DEPTH) { - if (brick == null) { - initLeaf(); - } - brick.insert(node); - return true; - } - - if (children == null) this.subdivide(); - for (var tree : children) { - if (tree.insert(node)) return true; - } - return false; - } - - public void recolorWakes() { - if (hasLeaf()) { - brick.populatePixels(); - } - if (children == null) return; - for (var tree : children) { - tree.recolorWakes(); - } - } - - public void query(ArrayList output, Class type) { - if (!FrustumManager.isVisible(this.bounds.toBox((int) yLevel))) { - return; - } - if (hasLeaf() && brick.occupied > 0) { - if (type.equals(Brick.class)) { - output.add(type.cast(brick)); - } - if (type.equals(WakeNode.class)) { - ArrayList nodes = new ArrayList<>(); - brick.query(nodes); - for (var node : nodes) { - output.add(type.cast(node)); - } - } - return; - } - if (children == null) return; - for (var tree : children) { - tree.query(output, type); - } - } - - private void subdivide() { - if (depth == MAX_DEPTH) return; - int x = this.bounds.x; - int z = this.bounds.z; - int w = this.bounds.width >> 1; - children = new ArrayList<>(); - children.add(0, new QuadTree(x, yLevel, z, w, depth + 1, this.ROOT)); // NW - children.add(1, new QuadTree(x + w, yLevel, z, w, depth + 1, this.ROOT)); // NE - children.add(2, new QuadTree(x, yLevel, z + w, w, depth + 1, this.ROOT)); // SW - children.add(3, new QuadTree(x + w, yLevel, z + w, w, depth + 1, this.ROOT)); // SE - } - - public void prune() { - if (children != null) { - for (var tree : children) { - tree.prune(); - if (tree.hasLeaf()) tree.brick.deallocTexture(); - } - children.set(0, null); - children.set(1, null); - children.set(2, null); - children.set(3, null); - } - children = null; - } - - public record DecentralizedBounds(int x, float y, int z, int width) { - public boolean contains(int x, int z) { - return this.x <= x && x < this.x + this.width && - this.z <= z && z < this.z + this.width; - } - - public boolean intersects(DecentralizedBounds other) { - return !(this.x > other.x + other.width || - this.x + this.width < other.x || - this.z > other.z + other.width || - this.z + this.width < other.z); - } - - public boolean neighbors(DecentralizedBounds other) { - return !(this.x == other.x + other.width || - this.x + this.width == other.x || - this.z == other.z + other.width || - this.z + this.width == other.z); - } - - public AABB toBox(int y) { - return new AABB(this.x, y - 0.5, this.z, - this.x + this.width, y + 0.5, this.z + this.width); - } - } -} diff --git a/src/main/java/com/goby56/wakes/simulation/SimulationNode.java b/src/main/java/com/goby56/wakes/simulation/SimulationNode.java index ef9c439..d39890a 100644 --- a/src/main/java/com/goby56/wakes/simulation/SimulationNode.java +++ b/src/main/java/com/goby56/wakes/simulation/SimulationNode.java @@ -32,9 +32,9 @@ public int getPixelColor(int x, int z, int fluidCol, int lightCol, float opacity float waveEqAvg = (this.u[0][z + 1][x + 1] + this.u[1][z + 1][x + 1] + this.u[2][z + 1][x + 1]) / 3; if (WakesConfig.debugColors) { int clampedRange = (int) (255 * (2 / (1 + Math.exp(-0.1 * waveEqAvg)) - 1)); - return new WakeColor(Math.max(-clampedRange, 0), Math.max(clampedRange, 0), 0, 255).abgr; + return new WakeColor(Math.max(-clampedRange, 0), Math.max(clampedRange, 0), 0, 255).argb; } - return WakeColor.sampleColor(waveEqAvg, fluidCol, lightCol, opacity); + return WakeColor.sampleColor(waveEqAvg, fluidCol, lightCol, opacity).argb; } public abstract void tick(@Nullable Float velocity, @Nullable SimulationNode NORTH, @Nullable SimulationNode SOUTH, @Nullable SimulationNode EAST, @Nullable SimulationNode WEST); diff --git a/src/main/java/com/goby56/wakes/simulation/Brick.java b/src/main/java/com/goby56/wakes/simulation/WakeChunk.java similarity index 50% rename from src/main/java/com/goby56/wakes/simulation/Brick.java rename to src/main/java/com/goby56/wakes/simulation/WakeChunk.java index 1344670..63d67bf 100644 --- a/src/main/java/com/goby56/wakes/simulation/Brick.java +++ b/src/main/java/com/goby56/wakes/simulation/WakeChunk.java @@ -3,66 +3,52 @@ import com.goby56.wakes.config.WakesConfig; import com.goby56.wakes.debug.WakesDebugInfo; import com.goby56.wakes.render.FrustumManager; +import com.goby56.wakes.render.WakeTextureAtlas; import com.goby56.wakes.utils.WakesUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BiomeColors; import net.minecraft.client.renderer.LightTexture; +import net.minecraft.world.level.Level; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -import net.minecraft.world.level.Level; -import org.lwjgl.system.MemoryUtil; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Stream; -public class Brick { +public class WakeChunk { + private final WakeHandler wakeHandler; + + public static final int WIDTH = 8; private final WakeNode[][] nodes; public final int capacity; - public final int dim; public int occupied = 0; + private boolean destroyed = false; public final Vec3 pos; + public final WakeChunkPos chunkPos; + public final AABB boundingBox; - public Brick NORTH; - public Brick EAST; - public Brick SOUTH; - public Brick WEST; + public final Map neighbors; - public long imgPtr = -1; - public int texRes; - public boolean hasPopulatedPixels = false; + public WakeTextureAtlas.DrawContext drawContext; - public Brick(int x, float y, int z, int width) { - this.dim = width; - this.capacity = dim * dim; - this.nodes = new WakeNode[dim][dim]; - this.pos = new Vec3(x, y, z); + public WakeChunk(WakeChunkPos chunkPos, WakeHandler wakeHandler) { + this.capacity = WIDTH * WIDTH; + this.nodes = new WakeNode[WIDTH][WIDTH]; + this.chunkPos = chunkPos; + this.pos = new Vec3(chunkPos.cx() * WIDTH, chunkPos.y(), chunkPos.cz() * WIDTH); + this.boundingBox = new AABB(pos.x, pos.y, pos.z, pos.x + WIDTH, pos.y + 1, pos.z + WIDTH); + this.neighbors = new HashMap<>(); + this.wakeHandler = wakeHandler; - initTexture(WakeHandler.resolution.res); - } - - public void initTexture(int res) { - long size = 4L * dim * dim * res * res; - if (imgPtr == -1) { - this.imgPtr = MemoryUtil.nmemAlloc(size); - } else { - this.imgPtr = MemoryUtil.nmemRealloc(imgPtr, size); - } - this.texRes = res; - this.hasPopulatedPixels = false; + this.drawContext = wakeHandler.getTextureAtlas().claimSubTexture(); } - public void deallocTexture() { - MemoryUtil.nmemFree(imgPtr); - } - - public boolean tick(WakeHandler wakeHandler) { + public boolean tick() { long tNode = System.nanoTime(); - for (int z = 0; z < dim; z++) { - for (int x = 0; x < dim; x++) { + for (int z = 0; z < WIDTH; z++) { + for (int x = 0; x < WIDTH; x++) { if (this.get(x, z) == null) continue; if (!this.get(x, z).tick(wakeHandler)) { @@ -72,15 +58,15 @@ public boolean tick(WakeHandler wakeHandler) { } WakesDebugInfo.nodeLogicTime += (System.nanoTime() - tNode); long tTexturing = System.nanoTime(); - populatePixels(); + drawWakes(); WakesDebugInfo.texturingTime += (System.nanoTime() - tTexturing); WakesDebugInfo.nodeCount += occupied; return occupied != 0; } public void query(ArrayList output) { - for (int z = 0; z < dim; z++) { - for (int x = 0; x < dim; x++) { + for (int z = 0; z < WIDTH; z++) { + for (int x = 0; x < WIDTH; x++) { var node = this.get(x, z); if (node == null) continue; AABB b = node.toBox(); @@ -90,27 +76,39 @@ public void query(ArrayList output) { } public WakeNode get(int x, int z) { - if (x >= 0 && x < dim) { - if (z < 0 && NORTH != null) { - return NORTH.nodes[Math.floorMod(z, dim)][x]; - } else if (z >= dim && SOUTH != null) { - return SOUTH.nodes[Math.floorMod(z, dim)][x]; - } else if (z >= 0 && z < dim){ + if (x >= 0 && x < WIDTH) { + if (z < 0) { + return getNeighbor(WakeChunkPos.Direction.NORTH).map( + wakeChunk -> wakeChunk.nodes[Math.floorMod(z, WIDTH)][x]).orElse(null); + } else if (z >= WIDTH) { + return getNeighbor(WakeChunkPos.Direction.SOUTH).map( + wakeChunk -> wakeChunk.nodes[Math.floorMod(z, WIDTH)][x]).orElse(null); + } else { return nodes[z][x]; } } - if (z >= 0 && z < dim) { - if (x < 0 && WEST != null) { - return WEST.nodes[z][Math.floorMod(x, dim)]; - } else if (x >= dim && EAST != null) { - return EAST.nodes[z][Math.floorMod(x, dim)]; + if (z >= 0 && z < WIDTH) { + if (x < 0) { + return getNeighbor(WakeChunkPos.Direction.WEST).map( + wakeChunk -> wakeChunk.nodes[z][Math.floorMod(x, WIDTH)]).orElse(null); + } else { + return getNeighbor(WakeChunkPos.Direction.EAST).map( + wakeChunk -> wakeChunk.nodes[z][Math.floorMod(x, WIDTH)]).orElse(null); } } return null; } + private Optional getNeighbor(WakeChunkPos.Direction direction) { + WakeChunk chunk = neighbors.get(direction); + if (chunk == null || chunk.destroyed) { + neighbors.put(direction, wakeHandler.getChunk(chunkPos.offset(direction))); + } + return Optional.ofNullable(neighbors.get(direction)); + } + public void insert(WakeNode node) { - int x = Math.floorMod(node.x, dim), z = Math.floorMod(node.z, dim); + int x = Math.floorMod(node.x, WIDTH), z = Math.floorMod(node.z, WIDTH); if (nodes[z][x] != null) { nodes[z][x].revive(node); return; @@ -135,6 +133,17 @@ public void clear(int x, int z) { this.set(x, z, null); } + public void destroy() { + for (int z = 0; z < WIDTH; z++) { + for (int x = 0; x < WIDTH; x++) { + nodes[z][x] = null; + } + } + occupied = 0; + destroyed = true; + drawContext.invalidate(); + } + private List getAdjacentNodes(int x, int z) { return Stream.of( this.get(x, z + 1), @@ -143,33 +152,12 @@ private List getAdjacentNodes(int x, int z) { this.get(x - 1, z)).filter(Objects::nonNull).toList(); } - public void updateAdjacency(Brick brick) { - if (brick.pos.x == this.pos.x && brick.pos.z == this.pos.z - dim) { - this.NORTH = brick; - brick.SOUTH = this; - return; - } - if (brick.pos.x == this.pos.x + dim && brick.pos.z == this.pos.z) { - this.EAST = brick; - brick.WEST = this; - return; - } - if (brick.pos.x == this.pos.x && brick.pos.z == this.pos.z + dim) { - this.SOUTH = brick; - brick.NORTH = this; - return; - } - if (brick.pos.x == this.pos.x - dim && brick.pos.z == this.pos.z) { - this.WEST = brick; - brick.EAST = this; - } - } - - public void populatePixels() { + public void drawWakes() { Level world = Minecraft.getInstance().level; - for (int z = 0; z < dim; z++) { - for (int x = 0; x < dim; x++) { - WakeNode node = this.get(x, z); + int nodeRes = WakeHandler.resolution.res; + for (int nodeZ = 0; nodeZ < WIDTH; nodeZ++) { + for (int nodeX = 0; nodeX < WIDTH; nodeX++) { + WakeNode node = this.get(nodeX, nodeZ); int lightCol = LightTexture.FULL_BRIGHT; int fluidColor = 0; float opacity = 0; @@ -180,21 +168,18 @@ public void populatePixels() { opacity = (float) ((-Math.pow(node.t, 2) + 1) * WakesConfig.wakeOpacity); } - // TODO MASS SET PIXELS TO NO COLOR IF NODE DOESNT EXIST (NEED TO REORDER PIXELS STORED?) - long nodeOffset = texRes * 4L * (((long) z * dim * texRes) + (long) x); - for (int r = 0; r < texRes; r++) { - for (int c = 0; c < texRes; c++) { + int xOffset = nodeX * nodeRes; + int yOffset = nodeZ * nodeRes; + for (int x = 0; x < nodeRes; x++) { + for (int y = 0; y < nodeRes; y++) { int color = 0; if (node != null) { - // TODO USE SHADERS TO COLOR THE WAKES? - color = node.simulationNode.getPixelColor(c, r, fluidColor, lightCol, opacity); + color = node.simulationNode.getPixelColor(x, y, fluidColor, lightCol, opacity); } - long pixelOffset = 4L * (((long) r * dim * texRes) + c); - MemoryUtil.memPutInt(imgPtr + nodeOffset + pixelOffset, color); + drawContext.draw(x + xOffset, y + yOffset, color); } } } } - hasPopulatedPixels = true; } } diff --git a/src/main/java/com/goby56/wakes/simulation/WakeChunkPos.java b/src/main/java/com/goby56/wakes/simulation/WakeChunkPos.java new file mode 100644 index 0000000..5e09dfb --- /dev/null +++ b/src/main/java/com/goby56/wakes/simulation/WakeChunkPos.java @@ -0,0 +1,23 @@ +package com.goby56.wakes.simulation; + +public record WakeChunkPos(int cx, int y, int cz) { + public static WakeChunkPos fromWakeNode(WakeNode wakeNode) { + return new WakeChunkPos(Math.floorDiv(wakeNode.x, WakeChunk.WIDTH), wakeNode.y, Math.floorDiv(wakeNode.z, WakeChunk.WIDTH)); + } + + public enum Direction { + NORTH, + SOUTH, + EAST, + WEST + } + + public WakeChunkPos offset(Direction direction) { + return switch (direction) { + case NORTH -> new WakeChunkPos(cx, y, cz - 1); + case SOUTH -> new WakeChunkPos(cx, y, cz + 1); + case EAST -> new WakeChunkPos(cx + 1, y, cz); + case WEST -> new WakeChunkPos(cx - 1, y, cz); + }; + } +} diff --git a/src/main/java/com/goby56/wakes/simulation/WakeHandler.java b/src/main/java/com/goby56/wakes/simulation/WakeHandler.java index c0a2d8d..094c37a 100644 --- a/src/main/java/com/goby56/wakes/simulation/WakeHandler.java +++ b/src/main/java/com/goby56/wakes/simulation/WakeHandler.java @@ -4,37 +4,26 @@ import com.goby56.wakes.config.enums.Resolution; import com.goby56.wakes.particle.custom.SplashPlaneParticle; import com.goby56.wakes.render.FrustumManager; +import com.goby56.wakes.render.WakeTextureAtlas; import net.minecraft.client.Minecraft; import net.minecraft.world.level.Level; -import java.util.ArrayList; -import java.util.Optional; -import java.util.Queue; +import java.util.*; public class WakeHandler { private static WakeHandler INSTANCE; public Level world; - private QuadTree[] trees; - private QueueSet[] toBeInserted; - private final int minY; - private final int maxY; - private ArrayList splashPlanes; + private final HashMap wakeChunks = new HashMap<>(); + private final QueueSet toBeInserted; + private final ArrayList splashPlanes; public static Resolution resolution = WakesConfig.wakeResolution; - - public static boolean resolutionResetScheduled = false; + private WakeTextureAtlas textureAtlas; private WakeHandler(Level world) { this.world = world; - this.minY = world.getMinY(); - this.maxY = world.getMaxY(); - int worldHeight = this.maxY - this.minY; - this.trees = new QuadTree[worldHeight]; - this.toBeInserted = new QueueSet[worldHeight]; - for (int i = 0; i < worldHeight; i++) { - toBeInserted[i] = new QueueSet<>(); - } + this.toBeInserted = new QueueSet<>(); this.splashPlanes = new ArrayList<>(); } @@ -53,44 +42,59 @@ public static void init(Level world) { } public static void kill() { + getInstance().ifPresent(wakeHandler -> wakeHandler.wakeChunks.clear()); INSTANCE = null; } public void tick() { if (WakesConfig.wakeResolution.res != WakeHandler.resolution.res) { - scheduleResolutionChange(WakesConfig.wakeResolution); + WakeHandler.resolution = WakesConfig.wakeResolution; + textureAtlas.setResolution(resolution.res); + reset(); + } else { + wakeLogic(); } - for (int i = 0; i < this.maxY - this.minY; i++) { - Queue pendingNodes = this.toBeInserted[i]; - if (resolutionResetScheduled) { - if (pendingNodes != null) - pendingNodes.clear(); - continue; + } + + private void wakeLogic() { + ArrayList toBeRemovedChunks = new ArrayList<>(); + for (WakeChunk chunk : wakeChunks.values()) { + boolean wakesPresent = chunk.tick(); + if (!wakesPresent) { + chunk.destroy(); + toBeRemovedChunks.add(chunk.chunkPos); } - QuadTree tree = this.trees[i]; - if (tree != null) { - tree.tick(this); - while (pendingNodes.peek() != null) { - tree.insert(pendingNodes.poll()); - } + } + for (WakeChunkPos pos : toBeRemovedChunks) { + wakeChunks.remove(pos); + } + + while (toBeInserted.peek() != null) { + WakeNode node = toBeInserted.poll(); + WakeChunkPos pos = WakeChunkPos.fromWakeNode(node); + WakeChunk chunk = wakeChunks.get(pos); + if (chunk == null) { + chunk = new WakeChunk(pos, this); + wakeChunks.put(pos, chunk); } + chunk.insert(node); } + for (int i = this.splashPlanes.size() - 1; i >= 0; i--) { if (!this.splashPlanes.get(i).isAlive()) { this.splashPlanes.remove(i); } } - if (resolutionResetScheduled) { - this.changeResolution(); - } + + } + + public WakeChunk getChunk(WakeChunkPos pos) { + return wakeChunks.get(pos); } public void recolorWakes() { - for (int i = 0; i < this.maxY - this.minY; i++) { - QuadTree tree = this.trees[i]; - if (tree != null) { - tree.recolorWakes(); - } + for (WakeChunk chunk : wakeChunks.values()) { + chunk.drawWakes(); } for (var splashPlane : this.splashPlanes) { if (splashPlane != null) { @@ -104,63 +108,54 @@ public void registerSplashPlane(SplashPlaneParticle splashPlane) { } public void insert(WakeNode node) { - if (resolutionResetScheduled) - return; - int i = this.getArrayIndex(node.y); - if (i < 0) - return; - - if (this.trees[i] == null) { - this.trees[i] = new QuadTree(node.y); - } - if (node.validPos(world)) { - this.toBeInserted[i].add(node); + this.toBeInserted.add(node); } } - public ArrayList getVisible(Class type) { - ArrayList visibleObjects = new ArrayList<>(); - if (type.equals(SplashPlaneParticle.class)) { - for (SplashPlaneParticle particle : splashPlanes) { - if (FrustumManager.isVisible(particle.getBoundingBox())) { - visibleObjects.add(type.cast(particle)); - } - } - } else { - for (int i = 0; i < this.maxY - this.minY; i++) { - if (this.trees[i] != null) { - this.trees[i].query(visibleObjects, type); - } + public List getVisibleNodes() { + ArrayList nodes = new ArrayList<>(); + for (WakeChunk chunk : wakeChunks.values()) { + if (FrustumManager.isVisible(chunk.boundingBox)) { + chunk.query(nodes); } } - return visibleObjects; + return nodes; } - private int getArrayIndex(int y) { - if (y < this.minY || y >= this.maxY) { - return -1; + public List getVisibleChunks() { + ArrayList chunks = new ArrayList<>(); + for (WakeChunk chunk : wakeChunks.values()) { + if (FrustumManager.isVisible(chunk.boundingBox)) { + chunks.add(chunk); + } } - return y - this.minY; + return chunks; } - public static void scheduleResolutionChange(Resolution newRes) { - resolutionResetScheduled = true; + public List getVisibleSplashPlanes() { + ArrayList splashPlanes = new ArrayList<>(); + for (SplashPlaneParticle particle : this.splashPlanes) { + if (FrustumManager.isVisible(particle.getBoundingBox())) { + splashPlanes.add(particle); + } + } + return splashPlanes; } - private void changeResolution() { - this.reset(); - WakeHandler.resolution = WakesConfig.wakeResolution; - resolutionResetScheduled = false; + public WakeTextureAtlas getTextureAtlas() { + if (textureAtlas == null) { + textureAtlas = new WakeTextureAtlas(); + textureAtlas.setResolution(resolution.res); + } + return textureAtlas; } private void reset() { - for (int i = 0; i < this.maxY - this.minY; i++) { - QuadTree tree = this.trees[i]; - if (tree != null) { - tree.prune(); - } - toBeInserted[i].clear(); + for (WakeChunk chunk : wakeChunks.values()) { + chunk.destroy(); } + wakeChunks.clear(); + toBeInserted.clear(); } } diff --git a/src/main/java/com/goby56/wakes/simulation/WakeNode.java b/src/main/java/com/goby56/wakes/simulation/WakeNode.java index 0cb5d0b..dd3ed58 100644 --- a/src/main/java/com/goby56/wakes/simulation/WakeNode.java +++ b/src/main/java/com/goby56/wakes/simulation/WakeNode.java @@ -3,7 +3,7 @@ import com.goby56.wakes.config.WakesConfig; import com.goby56.wakes.utils.WakesUtils; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.vehicle.AbstractBoat; +import net.minecraft.world.entity.vehicle.boat.AbstractBoat; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; import net.minecraft.core.BlockPos; @@ -187,9 +187,9 @@ public String toString() { public static class Factory { public static Set splashNodes(Entity entity, int y) { int res = WakeHandler.resolution.res; - int w = (int) (0.8 * entity.getBbWidth() * res / 2); - int x = (int) (entity.getX() * res); - int z = (int) (entity.getZ() * res); + int w = (int) Math.floor(0.8 * entity.getBbWidth() * res / 2); + int x = (int) Math.floor(entity.getX() * res); + int z = (int) Math.floor(entity.getZ() * res); ArrayList pixelsAffected = new ArrayList<>(); for (int i = -w; i < w; i++) { @@ -225,10 +225,10 @@ public static Set rowingNodes(AbstractBoat boat, int y) { public static Set nodeTrail(double fromX, double fromZ, double toX, double toZ, int y, float waveStrength, double velocity) { int res = WakeHandler.resolution.res; - int x1 = (int) (fromX * res); - int z1 = (int) (fromZ * res); - int x2 = (int) (toX * res); - int z2 = (int) (toZ * res); + int x1 = (int) Math.floor(fromX * res); + int z1 = (int) Math.floor(fromZ * res); + int x2 = (int) Math.floor(toX * res); + int z2 = (int) Math.floor(toZ * res); ArrayList pixelsAffected = new ArrayList<>(); WakesUtils.bresenhamLine(x1, z1, x2, z2, pixelsAffected); @@ -237,10 +237,10 @@ public static Set nodeTrail(double fromX, double fromZ, double toX, do public static Set thickNodeTrail(double fromX, double fromZ, double toX, double toZ, int y, float waveStrength, double velocity, float width) { int res = WakeHandler.resolution.res; - int x1 = (int) (fromX * res); - int z1 = (int) (fromZ * res); - int x2 = (int) (toX * res); - int z2 = (int) (toZ * res); + int x1 = (int) Math.floor(fromX * res); + int z1 = (int) Math.floor(fromZ * res); + int x2 = (int) Math.floor(toX * res); + int z2 = (int) Math.floor(toZ * res); int w = (int) (0.8 * width * res / 2); // TODO MAKE MORE EFFICIENT THICK LINE DRAWER @@ -249,7 +249,7 @@ public static Set thickNodeTrail(double fromX, double fromZ, double to float nz = (x2 - x1) / len; ArrayList pixelsAffected = new ArrayList<>(); for (int i = -w; i < w; i++) { - WakesUtils.bresenhamLine((int) (x1 + nx * i), (int) (z1 + nz * i), (int) (x2 + nx * i), (int) (z2 + nz * i), pixelsAffected); + WakesUtils.bresenhamLine((int) Math.floor(x1 + nx * i), (int) Math.floor(z1 + nz * i), (int) Math.floor(x2 + nx * i), (int) Math.floor(z2 + nz * i), pixelsAffected); } return pixelsToNodes(pixelsAffected, y, waveStrength, velocity); } @@ -259,12 +259,12 @@ public static Set nodeLine(double x, int y, double z, float waveStreng Vec3 dir = velocity.normalize(); double nx = -dir.z; double nz = dir.x; - int w = (int) (0.8 * width * res / 2); + int w = (int) Math.floor(0.8 * width * res / 2); - int x1 = (int) (x * res - nx * w); - int z1 = (int) (z * res - nz * w); - int x2 = (int) (x * res + nx * w); - int z2 = (int) (z * res + nz * w); + int x1 = (int) Math.floor(x * res - nx * w); + int z1 = (int) Math.floor(z * res - nz * w); + int x2 = (int) Math.floor(x * res + nx * w); + int z2 = (int) Math.floor(z * res + nz * w); ArrayList pixelsAffected = new ArrayList<>(); WakesUtils.bresenhamLine(x1, z1, x2, z2, pixelsAffected); @@ -273,13 +273,13 @@ public static Set nodeLine(double x, int y, double z, float waveStreng private static Set pixelsToNodes(ArrayList pixelsAffected, int y, float waveStrength, double velocity) { int res = WakeHandler.resolution.res; - int power = (int) (Math.log(res) / Math.log(2)); + int power = WakeHandler.resolution.power; HashMap> pixelsInNodes = new HashMap<>(); for (Long pixel : pixelsAffected) { int[] pos = WakesUtils.longAsPos(pixel); long k = WakesUtils.posAsLong(pos[0] >> power, pos[1] >> power); - pos[0] %= res; - pos[1] %= res; + pos[0] = Math.floorMod(pos[0], res); + pos[1] = Math.floorMod(pos[1], res); long v = WakesUtils.posAsLong(pos[0], pos[1]); if (pixelsInNodes.containsKey(k)) { pixelsInNodes.get(k).add(v); diff --git a/src/main/java/com/goby56/wakes/utils/WakesUtils.java b/src/main/java/com/goby56/wakes/utils/WakesUtils.java index d975d3b..2f7dca8 100644 --- a/src/main/java/com/goby56/wakes/utils/WakesUtils.java +++ b/src/main/java/com/goby56/wakes/utils/WakesUtils.java @@ -18,7 +18,7 @@ import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.vehicle.AbstractBoat; +import net.minecraft.world.entity.vehicle.boat.AbstractBoat; import net.minecraft.world.level.material.FluidState; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.Component; diff --git a/src/main/resources/assets/wakes/shaders/gui_hsv.fsh b/src/main/resources/assets/wakes/shaders/gui_hsv.fsh index 751f551..3fe9af0 100644 --- a/src/main/resources/assets/wakes/shaders/gui_hsv.fsh +++ b/src/main/resources/assets/wakes/shaders/gui_hsv.fsh @@ -7,7 +7,6 @@ layout(std140) uniform DynamicTransforms { vec4 ColorModulator; vec3 ModelOffset; mat4 TextureMat; - float LineWidth; }; uniform sampler2D Sampler0; diff --git a/src/main/resources/wakes.accesswidener b/src/main/resources/wakes.accesswidener index aed4318..a3ec847 100644 --- a/src/main/resources/wakes.accesswidener +++ b/src/main/resources/wakes.accesswidener @@ -1,13 +1,14 @@ accessWidener v1 named -accessible field net/minecraft/world/entity/vehicle/AbstractBoat paddlePositions [F - -accessible field net/minecraft/world/entity/vehicle/AbstractBoat PADDLE_SPEED F +accessible field net/minecraft/world/entity/vehicle/boat/AbstractBoat PADDLE_SPEED F +accessible field net/minecraft/world/entity/vehicle/boat/AbstractBoat paddlePositions [F accessible field net/minecraft/client/model/geom/ModelPart$Cube polygons [Lnet/minecraft/client/model/geom/ModelPart$Polygon; - -accessible method net/minecraft/client/gui/components/AbstractSliderButton getSprite ()Lnet/minecraft/resources/ResourceLocation; -accessible method net/minecraft/client/gui/components/AbstractSliderButton getHandleSprite ()Lnet/minecraft/resources/ResourceLocation; +accessible method net/minecraft/client/gui/components/AbstractSliderButton getSprite ()Lnet/minecraft/resources/Identifier; +accessible method net/minecraft/client/gui/components/AbstractSliderButton getHandleSprite ()Lnet/minecraft/resources/Identifier; accessible method net/minecraft/client/gui/components/AbstractWidget onDrag (Lnet/minecraft/client/input/MouseButtonEvent;DD)V extendable method net/minecraft/client/gui/components/EditBox onValueChange (Ljava/lang/String;)V + +accessible field net/minecraft/client/multiplayer/ClientLevel skyFlashTime I + diff --git a/src/main/resources/wakes.mixins.json b/src/main/resources/wakes.mixins.json index 6f9242a..1b480b0 100644 --- a/src/main/resources/wakes.mixins.json +++ b/src/main/resources/wakes.mixins.json @@ -3,10 +3,11 @@ "package": "com.goby56.wakes.mixin", "compatibilityLevel": "JAVA_17", "client": [ - "TameableTeleportMixin", - "WakeSpawnerMixin", "LightmapTextureManagerMixin", - "LilyPadFallMixin" + "LilyPadFallMixin", + "TameableTeleportMixin", + "WakeRendererCleanup", + "WakeSpawnerMixin" ], "injectors": { "defaultRequire": 1