diff --git a/combatant-client-26.2/TODO.md b/combatant-client-26.2/TODO.md index 725a713..e39d78a 100644 --- a/combatant-client-26.2/TODO.md +++ b/combatant-client-26.2/TODO.md @@ -6,6 +6,9 @@ - **KISS / DRY / SOLID** — простая структура, без оверинжиниринга - **Макс. 200 строк на файл** (было >1500) — превышение = разбить на модули - **Макс. 4 файла на папку** — больше = новая подпапка по смыслу +- **Публичный API можно менять свободно** (решение владельца 2026-09-05): + KISS важнее обратной совместимости; при рефакторинге разрешено менять + сигнатуры, удалять делегирование и мёртвый код, обновляя вызывающих - **Никакого статика** — `Minecraft.getInstance()` только лениво, никогда в поле - **Никаких this-escape** — конструктор не публикует `this` - **Никаких мёртвых миксинов** — миксин только если реально нужен; `cancellable=true` только при реальном cancel diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Coordinates.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Coordinates.java index 714f82a..d0de580 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Coordinates.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Coordinates.java @@ -62,7 +62,6 @@ public final class Coordinates extends DraggableHudElement implements Scriptable private static final String EFFECT_BLUR = "Blur"; private static final String EFFECT_GLASS = "Glass"; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final EnumValue netherMode = enumSetting("xyz_nether", NetherMode.ON, NetherMode.OFF, NetherMode.ON, NetherMode.ONLY_NETHER); @@ -124,6 +123,7 @@ public final class Coordinates extends DraggableHudElement implements Scriptable int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); + Minecraft mc = Minecraft.getInstance(); if (mc == null || (mc.player == null && !preview)) { width = 0f; height = 0f; @@ -137,10 +137,10 @@ public final class Coordinates extends DraggableHudElement implements Scriptable return; } - boolean inNether = mc != null && mc.level != null && mc.level.dimension() == Level.NETHER; - double rawX = mc != null && mc.player != null ? mc.player.getX() : 0.0; - double rawY = mc != null && mc.player != null ? mc.player.getY() : 0.0; - double rawZ = mc != null && mc.player != null ? mc.player.getZ() : 0.0; + boolean inNether = mc.level != null && mc.level.dimension() == Level.NETHER; + double rawX = mc.player != null ? mc.player.getX() : 0.0; + double rawY = mc.player != null ? mc.player.getY() : 0.0; + double rawZ = mc.player != null ? mc.player.getZ() : 0.0; int px = Mth.floor(rawX); int py = Mth.floor(rawY); int pz = Mth.floor(rawZ); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Fps.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Fps.java index 87b4e43..b11d916 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Fps.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Fps.java @@ -61,7 +61,6 @@ public final class Fps extends DraggableHudElement implements ScriptableHudStatW private static final long DIGIT_ANIMATION_DURATION_MS = 200L; private static final float DIGIT_ANIMATION_OFFSET = 8.0f; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final NumberValue scale = num("fps_scale", 2.37, 0.5, 5.0); private final ModeValue colorMode = mode("fps_color_mode", "color_mode", "Theme", new String[]{COLOR_THEME, COLOR_CUSTOM}); @@ -129,7 +128,7 @@ public final class Fps extends DraggableHudElement implements ScriptableHudStatW int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); - if (mc == null && !preview) { + if (Minecraft.getInstance() == null && !preview) { width = 0f; height = 0f; scriptModel.setVisible(false); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/GameTime.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/GameTime.java index 693d192..d3e666d 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/GameTime.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/GameTime.java @@ -74,7 +74,6 @@ public final class GameTime extends DraggableHudElement implements ScriptableHud private static final int ICON_THUNDER_SNOW_DAY = 0xEA02; private static final int ICON_THUNDER_SNOW_NIGHT = 0xEA0A; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final BooleanValue showWeather = bool("game_time_weather", "weather", true); private final NumberValue scale = num("game_time_scale", 2.37, 0.5, 5.0); @@ -143,6 +142,7 @@ public final class GameTime extends DraggableHudElement implements ScriptableHud int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); + Minecraft mc = Minecraft.getInstance(); if ((mc == null || mc.level == null) && !preview) { width = 0f; height = 0f; @@ -235,6 +235,7 @@ public final class GameTime extends DraggableHudElement implements ScriptableHud private int resolveIcon(long time) { if (!showWeather.get()) return resolveClearIcon(time); + Minecraft mc = Minecraft.getInstance(); if (mc == null || mc.level == null || mc.player == null) return resolveClearIcon(time); boolean thunder = WorldTweaks.isServerThundering(mc.level); @@ -258,6 +259,7 @@ public final class GameTime extends DraggableHudElement implements ScriptableHud } private boolean isSnowingAtPlayer() { + Minecraft mc = Minecraft.getInstance(); if (mc == null || mc.level == null || mc.player == null) return false; BlockPos pos = mc.player.blockPosition(); Biome.Precipitation precipitation = mc.level.getBiome(pos).value() diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Keybinds.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Keybinds.java index 41530c0..068d580 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Keybinds.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Keybinds.java @@ -57,7 +57,6 @@ public final class Keybinds extends DraggableHudElement { private static final String MODE_PRESS = "Press"; private static final String MODE_HOLD = "Hold"; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel(); private final NumberValue scaleValue = @@ -237,6 +236,7 @@ public final class Keybinds extends DraggableHudElement { int screenW, int screenH) { boolean forceVisible = DraggableHudElementRegistry.isForceVisible(); + Minecraft mc = Minecraft.getInstance(); if (mc == null || (mc.player == null && !forceVisible)) { width = 0.0f; height = 0.0f; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Memory.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Memory.java index 99824da..59290f1 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Memory.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Memory.java @@ -59,7 +59,6 @@ public final class Memory extends DraggableHudElement implements ScriptableHudSt private static final String EFFECT_BLUR = "Blur"; private static final String EFFECT_GLASS = "Glass"; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final NumberValue scale = num("memory_scale", 2.37, 0.5, 5.0); private final ModeValue colorMode = mode("memory_color_mode", "color_mode", "Theme", new String[]{COLOR_THEME, COLOR_CUSTOM}); @@ -127,7 +126,7 @@ public final class Memory extends DraggableHudElement implements ScriptableHudSt int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); - if (mc == null && !preview) { + if (Minecraft.getInstance() == null && !preview) { width = 0f; height = 0f; scriptModel.setVisible(false); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ModuleList.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ModuleList.java index 98aed8e..8d105a0 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ModuleList.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ModuleList.java @@ -46,7 +46,6 @@ public final class ModuleList extends DraggableHudElement implements ConfigValue private static final float BASE_RECT_EXTRA = 1f; private static final float BASE_RADIUS = 2f; private static final float BASE_SOFTNESS = 0.55f; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final NumberValue scaleValue = new NumberValue<>("module_list_scale", 3.67, 0.5, 5.0); @@ -140,6 +139,7 @@ public final class ModuleList extends DraggableHudElement implements ConfigValue float tickDelta, int screenW, int screenH) { + Minecraft mc = Minecraft.getInstance(); if (!isEnabled() || mc == null || mc.player == null) { rows.clear(); width = 0f; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Ping.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Ping.java index 75c8e38..5e16845 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Ping.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Ping.java @@ -60,7 +60,6 @@ public final class Ping extends DraggableHudElement implements ScriptableHudStat private static final String EFFECT_BLUR = "Blur"; private static final String EFFECT_GLASS = "Glass"; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final NumberValue scale = num("ping_scale", 2.58, 0.5, 5.0); private final ModeValue colorMode = mode("ping_color_mode", "color_mode", "Theme", new String[]{COLOR_THEME, COLOR_CUSTOM}); @@ -118,7 +117,7 @@ public final class Ping extends DraggableHudElement implements ScriptableHudStat int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); - if (mc == null && !preview) { + if (Minecraft.getInstance() == null && !preview) { width = 0f; height = 0f; scriptModel.setVisible(false); @@ -131,7 +130,7 @@ public final class Ping extends DraggableHudElement implements ScriptableHudStat return; } - int ping = NetworkStatsUtil.getPing(mc); + int ping = NetworkStatsUtil.getPing(Minecraft.getInstance()); String value = getPingValue(ping); String unit = "ms"; updatePalette(); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Potions.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Potions.java index 99f9a82..fc04f05 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Potions.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Potions.java @@ -85,7 +85,6 @@ public final class Potions extends DraggableHudElement { Identifier.fromNamespaceAndPath("minecraft", "textures/mob_effect/night_vision.png") }; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final ScriptedListHudPanel scriptedPanel = new ScriptedListHudPanel(); private final NumberValue scaleValue = @@ -182,6 +181,7 @@ public final class Potions extends DraggableHudElement { int screenW, int screenH) { boolean forceVisible = DraggableHudElementRegistry.isForceVisible(); + Minecraft mc = Minecraft.getInstance(); if (mc == null || (mc.player == null && !forceVisible)) { width = 0.0f; height = 0.0f; @@ -434,6 +434,7 @@ public final class Potions extends DraggableHudElement { private List collectRows() { List rows = new ArrayList<>(); + Minecraft mc = Minecraft.getInstance(); if (mc != null && mc.player != null) { mc.player.getActiveEffects().stream() .sorted((a, b) -> String.CASE_INSENSITIVE_ORDER.compare( diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ScriptedTriangulatorHudPanel.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ScriptedTriangulatorHudPanel.java index e5012c4..ffd4bad 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ScriptedTriangulatorHudPanel.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/ScriptedTriangulatorHudPanel.java @@ -24,7 +24,7 @@ import net.minecraft.resources.Identifier; import java.util.ArrayList; import java.util.List; -final class ScriptedTriangulatorHudPanel { +public final class ScriptedTriangulatorHudPanel { private static final String LAYOUT_ID = "lovisual:api/hud/draggable/triangulator"; private final UiScriptModuleHandle moduleHandle = HudScriptLayouts.handle(LAYOUT_ID); private final CachedUiScriptRuntime runtime = new CachedUiScriptRuntime(HudScriptLayouts.runtimeReporter()); @@ -33,7 +33,7 @@ final class ScriptedTriangulatorHudPanel { return String.format("#%08X", argb); } - static LinkedHashMap row(String key, + public static LinkedHashMap row(String key, String label, String value, int labelColor, @@ -53,15 +53,15 @@ final class ScriptedTriangulatorHudPanel { return row; } - static String idString(Identifier id) { + public static String idString(Identifier id) { return id != null ? id.toString() : ""; } - static List> rows() { + public static List> rows() { return new ArrayList<>(); } - boolean render(Renderer2D renderer, + public boolean render(Renderer2D renderer, TextRenderer textRenderer, GuiGraphicsExtractor ctx, float tickDelta, @@ -130,7 +130,7 @@ final class ScriptedTriangulatorHudPanel { } patch.put(prop, value); } - record Palette(int headerLeft, + public record Palette(int headerLeft, int headerRight, int bodyLeft, int bodyRight, @@ -164,7 +164,7 @@ final class ScriptedTriangulatorHudPanel { } } - record Panel(Palette palette, + public record Panel(Palette palette, float x, float y, float width, @@ -191,7 +191,7 @@ final class ScriptedTriangulatorHudPanel { String confidenceText, int statusColor, List> rows) { - Panel(Palette palette, + public Panel(Palette palette, float x, float y, float width, diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/SystemTime.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/SystemTime.java index 006c191..384433d 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/SystemTime.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/SystemTime.java @@ -73,7 +73,6 @@ public final class SystemTime extends DraggableHudElement implements ScriptableH iconString(0xEA13) }; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final BooleanValue showSeconds = bool("system_time_seconds", false); private final NumberValue scale = num("system_time_scale", 2.37, 0.5, 5.0); @@ -143,7 +142,7 @@ public final class SystemTime extends DraggableHudElement implements ScriptableH int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); - if (mc == null && !preview) { + if (Minecraft.getInstance() == null && !preview) { width = 0f; height = 0f; scriptModel.setVisible(false); diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Tps.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Tps.java index 600099f..bc8f6e7 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Tps.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Tps.java @@ -60,7 +60,6 @@ public final class Tps extends DraggableHudElement implements ScriptableHudStatW private static final String EFFECT_BLUR = "Blur"; private static final String EFFECT_GLASS = "Glass"; - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final BooleanValue extra = bool("tps_extra", false); private final NumberValue scale = num("tps_scale", 2.37, 0.5, 5.0); @@ -149,7 +148,7 @@ public final class Tps extends DraggableHudElement implements ScriptableHudStatW int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); - if (mc == null && !preview) { + if (Minecraft.getInstance() == null && !preview) { width = 0f; height = 0f; scriptModel.setVisible(false); @@ -162,6 +161,7 @@ public final class Tps extends DraggableHudElement implements ScriptableHudStatW return; } + Minecraft mc = Minecraft.getInstance(); float tps = NetworkStatsUtil.getTps(mc); boolean showExtra = extra.get(); float clientTps = showExtra ? NetworkStatsUtil.getClientTps(mc) : 0f; diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Triangulator.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Triangulator.java index 61767f0..e92a4a3 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Triangulator.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/Triangulator.java @@ -15,6 +15,15 @@ import dev.loki.lovisual.features.gui.hud.HudRenderUtil; import dev.loki.lovisual.features.gui.hud.HudTextEffects; import dev.loki.lovisual.features.gui.hud.draggable.DraggableHudElement; import dev.loki.lovisual.features.gui.hud.draggable.DraggableHudElementRegistry; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.model.CapturePolicy; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.model.DisplayState; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.CachedThrow; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.SolveResult; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorTracker; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorView; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.render.TriangulatorMeasure; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.render.TriangulatorPainter; import dev.loki.lovisual.render.engine.math.HudScale; import dev.loki.lovisual.render.engine.renderer.Renderer2D; import dev.loki.lovisual.render.engine.text.FontInfo; @@ -23,17 +32,11 @@ import dev.loki.lovisual.render.engine.text.TextRenderer; import dev.loki.lovisual.util.text.ClipboardUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.resources.Identifier; -import net.minecraft.util.Mth; -import net.minecraft.util.Util; -import net.minecraft.world.entity.projectile.EyeOfEnder; import net.minecraft.world.level.Level; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import java.util.LinkedHashMap; -import java.util.*; +import dev.loki.lovisual.util.time.GameClock; -import static dev.loki.lovisual.features.theme.Theme.theme; +import java.util.LinkedHashMap; +import java.util.List; @HudElementInfo( id = "triangulator", @@ -48,263 +51,54 @@ public final class Triangulator extends DraggableHudElement { defaultLayout(1302.04f, 330.69208f); } - private static final Identifier HEADER_ICON = - Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/coords.png"); - - private static final float HEADER_HEIGHT = 15.5f; - private static final float BODY_Y_OFFSET = 16.5f; - private static final float BODY_INSET_Y = 6.5f; - private static final float CONTENT_START_Y = 23.0f; - private static final float ROW_STEP = 11.0f; - private static final float MIN_WIDTH = 126.0f; - private static final float PANEL_RADIUS = 4.0f; - private static final float PANEL_SOFTNESS = 1.0f; - private static final float PANEL_STROKE = 0.55f; - private static final float HEADER_DIVIDER_X = 18.0f; - private static final float HEADER_DIVIDER_Y = 5.0f; - private static final float HEADER_DIVIDER_H = 6.0f; - private static final float TITLE_ICON_X = 4.5f; - private static final float TITLE_TEXT_X = 22.0f; - private static final float CLEAR_BUTTON_SIZE = 9.0f; - private static final float CLEAR_BUTTON_RIGHT = 8.0f; - private static final float HEADER_BUTTON_GAP = 3.0f; - private static final int COPY_COORD_Y = 64; - private static final float COUNT_LABEL_OFFSET = 28.0f; - private static final float COUNT_VALUE_OFFSET = 4.0f; - private static final float SUMMARY_LINE_1_Y = 0.0f; - private static final float SUMMARY_LINE_2_Y = 11.0f; - private static final float SUMMARY_LINE_3_Y = 20.5f; - private static final float SUMMARY_HEIGHT = 30.5f; - private static final float SUMMARY_DIVIDER_Y = 34.5f; - private static final float SUMMARY_DIVIDER_INSET_X = 8.0f; - private static final float SUMMARY_DIVIDER_H = 0.55f; - private static final float ROW_DIVIDER_X = 14.5f; - private static final float ROW_DIVIDER_H = 6.5f; - private static final float ROW_TEXT_X = 19.0f; - private static final float ROW_VALUE_RIGHT = 8.0f; - private static final float ROW_CONTENT_CENTER_OFFSET = 1.95f; - private static final float ROW_MARKER_X = 4.0f; - private static final float ROW_MARKER_SIZE = 7.5f; - private static final float SEARCH_RANGE = 196.0f; - - private static final double MIN_HORIZONTAL_SPEED = 0.03; - private static final double CAPTURE_DEVIATION_LIMIT = 0.35; - private static final double CAPTURE_QUALITY_FLOOR = 0.55; - private static final double MIN_SOLVABLE_ANGLE_DEG = 0.05; - private static final long RESET_FLASH_MS = 3000L; - private static final String COLOR_THEME = "Theme"; private static final String COLOR_CUSTOM = "Custom"; private static final String EFFECT_NONE = "None"; private static final String EFFECT_BLUR = "Blur"; - private static final List PREVIEW_THROWS = List.of( - new CachedThrow(1L, 110.0, 58.0, 0.9238795, -0.3826834, 0.91, 0.996, 0.08, 8, 0L), - new CachedThrow(2L, 188.0, -24.0, 0.7071067, -0.7071067, 0.87, 0.992, 0.12, 7, 0L), - new CachedThrow(3L, 254.0, -102.0, 0.4713967, -0.8819212, 0.82, 0.989, 0.19, 6, 0L) - ); - private static final SolveResult PREVIEW_RESULT = solveStatic(PREVIEW_THROWS, 6.0); - - private final Minecraft mc = Minecraft.getInstance(); private final HudGlobalConfig hud = HudGlobalConfig.get(); private final ScriptedTriangulatorHudPanel scriptedPanel = new ScriptedTriangulatorHudPanel(); + private final TriangulatorTracker tracker = new TriangulatorTracker(); + private final TriangulatorView view = new TriangulatorView(); - private final NumberValue scaleValue = - new NumberValue<>("triangulator_scale", 1.68, 0.5, 5.0); - private final ModeValue colorMode = - new ModeValue("triangulator_color_mode", "Theme", COLOR_THEME, COLOR_CUSTOM); - private final ModeValue bgEffect = - new ModeValue("triangulator_bg_effect", "Blur", EFFECT_NONE, EFFECT_BLUR); - private final RGBAColorValue bg = - new RGBAColorValue("triangulator_bg", "#F7343434"); - private final RGBAColorValue bg2 = - new RGBAColorValue("triangulator_bg_secondary", "#F7161616"); - private final NumberValue bgAlpha = - new NumberValue<>("triangulator_bg_alpha", 225, 0, 255); - private final RGBColorValue stroke = - new RGBColorValue("triangulator_stroke", "#5A5A5A"); - private final RGBColorValue text = - new RGBColorValue("triangulator_text", "#FFFFFF"); - private final RGBColorValue muted = - new RGBColorValue("triangulator_muted", "#A5A5A5"); - private final NumberValue blurAlpha = - new NumberValue<>("triangulator_blur_alpha", 140, 0, 255); + private final NumberValue scaleValue = new NumberValue<>("triangulator_scale", 1.68, 0.5, 5.0); + private final ModeValue colorMode = new ModeValue("triangulator_color_mode", "Theme", COLOR_THEME, COLOR_CUSTOM); + private final ModeValue bgEffect = new ModeValue("triangulator_bg_effect", "Blur", EFFECT_NONE, EFFECT_BLUR); + private final RGBAColorValue bg = new RGBAColorValue("triangulator_bg", "#F7343434"); + private final RGBAColorValue bg2 = new RGBAColorValue("triangulator_bg_secondary", "#F7161616"); + private final NumberValue bgAlpha = new NumberValue<>("triangulator_bg_alpha", 225, 0, 255); + private final RGBColorValue stroke = new RGBColorValue("triangulator_stroke", "#5A5A5A"); + private final RGBColorValue text = new RGBColorValue("triangulator_text", "#FFFFFF"); + private final RGBColorValue muted = new RGBColorValue("triangulator_muted", "#A5A5A5"); + private final NumberValue blurAlpha = new NumberValue<>("triangulator_blur_alpha", 140, 0, 255); private final EnumValue iconEffect = new EnumValue<>("triangulator_icon_effect", HudTextEffects.Effect.NONE, HudTextEffects.Effect.NONE, HudTextEffects.Effect.MIX, HudTextEffects.Effect.FLOW, HudTextEffects.Effect.PULSE, HudTextEffects.Effect.STRIPE); - private final NumberValue iconEffectSpeed = - new NumberValue<>("triangulator_icon_effect_speed", 18, 1, 60); + private final NumberValue iconEffectSpeed = new NumberValue<>("triangulator_icon_effect_speed", 18, 1, 60); private final NumberValue minObservationTicks = new NumberValue<>("triangulator_min_observation_ticks", 6, 2, 20); private final NumberValue stabilityThreshold = new NumberValue<>("triangulator_stability_threshold", 0.99, 0.900, 0.999); - private final NumberValue minPairAngleDeg = - new NumberValue<>("triangulator_min_pair_angle", 6.0, 0.25, 35.0); + private final NumberValue minPairAngleDeg = new NumberValue<>("triangulator_min_pair_angle", 6.0, 0.25, 35.0); private final NumberValue autoResetDistance = new NumberValue<>("triangulator_auto_reset_distance", 96.0, 16.0, 512.0); private final NumberValue maxCachedThrows = new NumberValue<>("triangulator_max_cached_throws", 6, 2, 12); private final NumberValue cacheLifetimeSec = new NumberValue<>("triangulator_cache_lifetime", 180, 15, 1800); - private final BooleanValue autoReset = - new BooleanValue("triangulator_auto_reset", true); - - private final Map activeEyes = new LinkedHashMap<>(); - private final List cachedThrows = new ArrayList<>(); + private final BooleanValue autoReset = new BooleanValue("triangulator_auto_reset", true); private float displayWidth = -1.0f; private float displayHeight = -1.0f; private float visibilityAnim = 0.0f; - private float clearButtonX; - private float clearButtonY; - private float clearButtonSize; - private float copyButtonX; - private float copyButtonY; - private float copyButtonSize; - - private long nextCaptureSerial = 1L; - private long lastWorldTime = Long.MIN_VALUE; - private long lastCaptureMs = 0L; - private long lastResetMs = 0L; - private String lastResetReason = ""; + private TriangulatorPainter.HeaderButtons headerButtons = TriangulatorPainter.HeaderButtons.NONE; private SolveResult currentResult; - private int uiHeaderLeft; - private int uiHeaderRight; - private int uiBodyLeft; - private int uiBodyRight; - private int uiOutline; - private int uiText; - private int uiMuted; - private int uiCounter; - private int uiTitleText; - private int uiDivider; - private int uiBlurTint; - private int uiSuccess; - private int uiWarn; - private int uiDanger; - public Triangulator() { super("triangulator", "Triangulator", false); } - private static SolveResult solveStatic(List throwsToSolve, double minAngleThreshold) { - if (throwsToSolve == null || throwsToSolve.isEmpty()) { - return null; - } - if (throwsToSolve.size() == 1) { - CachedThrow only = throwsToSolve.get(0); - return new SolveResult( - false, - false, - only.pointX(), - only.pointZ(), - 0.0, - 0.0, - only.quality(), - Mth.floor(only.pointX() / 16.0), - Mth.floor(only.pointZ() / 16.0) - ); - } - - double qualitySum = 0.0; - double strongestAngle = 0.0; - double pairX = 0.0; - double pairZ = 0.0; - double pairWeightSum = 0.0; - double crossThreshold = Math.sin(Math.toRadians(MIN_SOLVABLE_ANGLE_DEG)); - - for (int i = 0; i < throwsToSolve.size(); i++) { - CachedThrow throwI = throwsToSolve.get(i); - qualitySum += throwI.quality(); - - for (int j = i + 1; j < throwsToSolve.size(); j++) { - CachedThrow throwJ = throwsToSolve.get(j); - double dot = Math.abs(throwI.dirX() * throwJ.dirX() + throwI.dirZ() * throwJ.dirZ()); - double angle = Math.toDegrees(Math.acos(Mth.clamp(dot, -1.0, 1.0))); - strongestAngle = Math.max(strongestAngle, angle); - - double cross = throwI.dirX() * throwJ.dirZ() - throwI.dirZ() * throwJ.dirX(); - if (Math.abs(cross) <= crossThreshold) { - continue; - } - - double deltaX = throwJ.pointX() - throwI.pointX(); - double deltaZ = throwJ.pointZ() - throwI.pointZ(); - double t = (deltaX * throwJ.dirZ() - deltaZ * throwJ.dirX()) / cross; - double intersectionX = throwI.pointX() + throwI.dirX() * t; - double intersectionZ = throwI.pointZ() + throwI.dirZ() * t; - double pairWeight = (0.35 + throwI.quality() * 0.65) - * (0.35 + throwJ.quality() * 0.65) - * Mth.clamp(0.35 + angle / Math.max(0.35, minAngleThreshold), 0.35, 4.0); - - pairX += intersectionX * pairWeight; - pairZ += intersectionZ * pairWeight; - pairWeightSum += pairWeight; - } - } - - if (pairWeightSum <= 1.0e-8) { - return new SolveResult(false, false, 0.0, 0.0, Double.POSITIVE_INFINITY, strongestAngle, 0.0, 0, 0); - } - - double x = pairX / pairWeightSum; - double z = pairZ / pairWeightSum; - double errorSum = 0.0; - for (CachedThrow cachedThrow : throwsToSolve) { - double d = distanceToLine(x, z, cachedThrow); - errorSum += d * d; - } - double rmse = Math.sqrt(errorSum / throwsToSolve.size()); - double quality = qualitySum / throwsToSolve.size(); - double effectiveAngleThreshold = Math.max(0.35, Math.min(minAngleThreshold, 2.0)); - double angleScore = Mth.clamp( - (strongestAngle - effectiveAngleThreshold) / Math.max(0.5, 12.0 - effectiveAngleThreshold), - 0.0, - 1.0 - ); - double errorScore = Mth.clamp(1.0 - rmse / 64.0, 0.0, 1.0); - double countBonus = Mth.clamp((throwsToSolve.size() - 1.0) / 3.0, 0.0, 1.0); - double confidence = Mth.clamp( - quality * 0.45 + angleScore * 0.25 + errorScore * 0.20 + countBonus * 0.10, - 0.0, - 1.0 - ); - boolean ready = strongestAngle >= effectiveAngleThreshold && rmse <= 96.0 && confidence >= 0.55; - return new SolveResult( - true, - ready, - x, - z, - rmse, - strongestAngle, - confidence, - Mth.floor(x / 16.0), - Mth.floor(z / 16.0) - ); - } - - private static double distanceToLine(double x, double z, CachedThrow cachedThrow) { - double nx = -cachedThrow.dirZ(); - double nz = cachedThrow.dirX(); - return Math.abs((x - cachedThrow.pointX()) * nx + (z - cachedThrow.pointZ()) * nz); - } - - private static String formatPercent(double value) { - return Math.round(Mth.clamp(value, 0.0, 1.0) * 100.0) + "%"; - } - - private static String formatThrowPoint(CachedThrow cachedThrow) { - return String.format(Locale.ROOT, "%d %d", - Math.round(cachedThrow.pointX()), - Math.round(cachedThrow.pointZ())); - } - - private static boolean hit(float mx, float my, float x, float y, float w, float h) { - return mx >= x && mx <= x + w && my >= y && my <= y + h; - } - @Override protected void defineSettings(List defs) { defs.add(SettingDef.number(scaleValue)); @@ -342,12 +136,9 @@ public final class Triangulator extends DraggableHudElement { @Override public boolean isMouseOverInteractive(float mx, float my) { - return (clearButtonSize > 0.0f - && cachedThrows.size() > 0 - && hit(mx, my, clearButtonX, clearButtonY, clearButtonSize, clearButtonSize)) - || (copyButtonSize > 0.0f - && currentCopyResult() != null - && hit(mx, my, copyButtonX, copyButtonY, copyButtonSize, copyButtonSize)); + TriangulatorPainter.HeaderButtons b = headerButtons; + return (b.clearSize() > 0.0f && tracker.throwsCount() > 0 && hit(mx, my, b.clearX(), b.clearY(), b.clearSize())) + || (b.copySize() > 0.0f && currentCopyResult() != null && hit(mx, my, b.copyX(), b.copyY(), b.copySize())); } @Override @@ -356,56 +147,34 @@ public final class Triangulator extends DraggableHudElement { return false; } SolveResult copyResult = currentCopyResult(); - if (copyButtonSize > 0.0f - && copyResult != null - && hit(mx, my, copyButtonX, copyButtonY, copyButtonSize, copyButtonSize)) { - ClipboardUtil.copy(formatCopyCoordinates(copyResult)); + if (copyResult != null && headerButtons.copySize() > 0.0f + && hit(mx, my, headerButtons.copyX(), headerButtons.copyY(), headerButtons.copySize())) { + ClipboardUtil.copy(TriangulatorSolver.formatCopyCoordinates(copyResult)); return true; } - if (clearButtonSize > 0.0f - && cachedThrows.size() > 0 - && hit(mx, my, clearButtonX, clearButtonY, clearButtonSize, clearButtonSize)) { + if (tracker.throwsCount() > 0 && headerButtons.clearSize() > 0.0f + && hit(mx, my, headerButtons.clearX(), headerButtons.clearY(), headerButtons.clearSize())) { clearAllState("Manual clear", true, System.currentTimeMillis()); return true; } return false; } - private SolveResult currentCopyResult() { - SolveResult result = currentResult != null ? currentResult : solve(cachedThrows); - return result != null && result.hasSolution() ? result : null; - } - @Override public void onTick() { if (!isEnabled()) { - activeEyes.clear(); + tracker.dropEyes(); return; } + Minecraft mc = Minecraft.getInstance(); if (mc == null || mc.player == null || mc.level == null) { - activeEyes.clear(); - currentResult = solve(cachedThrows); + tracker.dropEyes(); + currentResult = solve(tracker.cachedThrows()); return; } - long now = System.currentTimeMillis(); - long worldTime = mc.level.getGameTime(); - if (lastWorldTime != Long.MIN_VALUE && worldTime + 200L < lastWorldTime) { - activeEyes.clear(); - } - lastWorldTime = worldTime; - - if (cacheLifetimeSec.get() > 0 - && !cachedThrows.isEmpty() - && activeEyes.isEmpty() - && (currentResult == null || !currentResult.hasSolution()) - && lastCaptureMs > 0L - && now - lastCaptureMs > cacheLifetimeSec.get() * 1000L) { - clearAllState("Cache expired", true, now); - } - - updateTrackedEyes(now); - currentResult = solve(cachedThrows); + // Кэш-экспирация и worldTime-сброс живут внутри tracker.tick + currentResult = tracker.tick(mc.level, mc.player, capturePolicy(), currentResult); } @Override @@ -416,20 +185,25 @@ public final class Triangulator extends DraggableHudElement { int screenW, int screenH) { boolean preview = DraggableHudElementRegistry.isForceVisible(); + Minecraft mc = Minecraft.getInstance(); if (!preview && (!isEnabled() || mc == null || mc.player == null || mc.level == null)) { width = 0.0f; height = 0.0f; return; } - updatePalette(); + view.update(isThemeMode(), bgAlpha.get(), bg.getArgb(), bg2.getArgb(), stroke.getArgb(), text.getArgb(), muted.getArgb()); - List displayThrows = preview && cachedThrows.isEmpty() ? PREVIEW_THROWS : cachedThrows; - SolveResult displayResult = preview && cachedThrows.isEmpty() ? PREVIEW_RESULT : currentResult; + boolean showPreview = preview && tracker.throwsCount() == 0; + List displayThrows = showPreview ? TriangulatorSolver.PREVIEW_THROWS : tracker.cachedThrows(); + SolveResult displayResult = showPreview ? TriangulatorSolver.previewResult() : currentResult; if (displayResult == null && !displayThrows.isEmpty()) { displayResult = solve(displayThrows); } - DisplayState state = buildDisplayState(displayThrows, displayResult, preview); + boolean showClearButton = !displayThrows.isEmpty(); + boolean showCopyButton = displayResult != null && displayResult.hasSolution(); + DisplayState state = view.status(displayThrows, displayResult, preview, + tracker.trackingEye(), tracker.resetReason(), tracker.resetMs(), isNether()); visibilityAnim = HudRenderUtil.animateVisibility(visibilityAnim, true); float widgetScale = HudRenderUtil.visibilityScale(visibilityAnim); @@ -441,53 +215,12 @@ public final class Triangulator extends DraggableHudElement { float baseScale = HudScale.scale(screenW, screenH) * 1.1f * scaleValue.get().floatValue(); float fontScale = 0.98f * (hud.getFontSize() / 18.0f); + TriangulatorMeasure.Metrics metrics = TriangulatorMeasure.measure(headerTextRenderer, rowTextRenderer, + baseScale, fontScale, displayThrows, state.statusText(), state.primaryLine(), state.secondaryLine(), + showClearButton, showCopyButton); - headerTextRenderer.begin(fontScale, true, false); - float headerTextH = (float) headerTextRenderer.getHeight(false); - float titleWidth = (float) headerTextRenderer.getWidth("Triangulator", false); - headerTextRenderer.end(); - - rowTextRenderer.begin(fontScale, true, false); - float rowTextH = (float) rowTextRenderer.getHeight(false); - float maxWidth = MIN_WIDTH * baseScale; - maxWidth = Math.max(maxWidth, (float) rowTextRenderer.getWidth(state.statusText(), false) + 52.0f * baseScale); - maxWidth = Math.max(maxWidth, (float) rowTextRenderer.getWidth(state.primaryLine(), false) + 20.0f * baseScale); - rowTextRenderer.end(); - - rowTextRenderer.begin(fontScale * 0.92f, false, false); - maxWidth = Math.max(maxWidth, (float) rowTextRenderer.getWidth(state.secondaryLine(), false) + 20.0f * baseScale); - String activeCountText = Integer.toString(displayThrows.size()); - float activeLabelWidth = (float) rowTextRenderer.getWidth("Eyes:", false); - float activeCountWidth = (float) rowTextRenderer.getWidth(activeCountText, false); - rowTextRenderer.end(); - - for (int i = 0; i < displayThrows.size(); i++) { - CachedThrow cachedThrow = displayThrows.get(i); - rowTextRenderer.begin(fontScale, true, false); - float rowWidth = (float) rowTextRenderer.getWidth("Eye #" + (i + 1), false) - + (float) rowTextRenderer.getWidth(formatThrowPoint(cachedThrow), false) - + 36.0f * baseScale; - rowTextRenderer.end(); - maxWidth = Math.max(maxWidth, rowWidth); - } - - boolean showClearButton = !displayThrows.isEmpty(); - boolean showCopyButton = displayResult != null && displayResult.hasSolution(); - int headerButtons = (showClearButton ? 1 : 0) + (showCopyButton ? 1 : 0); - float headerButtonsWidth = headerButtons == 0 ? 0.0f - : ((CLEAR_BUTTON_SIZE * headerButtons) + (HEADER_BUTTON_GAP * Math.max(0, headerButtons - 1)) + 4.0f) * baseScale; - float headerWidth = (TITLE_TEXT_X * baseScale) + titleWidth - + activeLabelWidth + activeCountWidth - + headerButtonsWidth - + ((COUNT_LABEL_OFFSET + 16.0f) * baseScale); - maxWidth = Math.max(maxWidth, headerWidth); - - float rowsHeight = displayThrows.isEmpty() ? 0.0f : displayThrows.size() * (ROW_STEP * baseScale); - float dividerHeight = displayThrows.isEmpty() ? 0.0f : 5.0f * baseScale; - float targetHeight = (CONTENT_START_Y * baseScale) + (SUMMARY_HEIGHT * baseScale) + dividerHeight + rowsHeight; - - displayWidth = HudRenderUtil.animateDimension(displayWidth, maxWidth); - displayHeight = HudRenderUtil.animateDimension(displayHeight, targetHeight); + displayWidth = HudRenderUtil.animateDimension(displayWidth, metrics.maxWidth()); + displayHeight = HudRenderUtil.animateDimension(displayHeight, metrics.targetHeight()); width = displayWidth; height = displayHeight; @@ -499,417 +232,54 @@ public final class Triangulator extends DraggableHudElement { height = 0.0f; return; } - float drawX = x + ((displayWidth - drawWidth) * 0.5f); float drawY = y + ((displayHeight - drawHeight) * 0.5f); float drawBaseScale = baseScale * drawScale; - float drawFontScale = fontScale * drawScale; - float drawHeaderTextH = headerTextH * drawScale; - float drawRowTextH = rowTextH * drawScale; - float buttonCursorRight = CLEAR_BUTTON_RIGHT * drawBaseScale; - if (showClearButton) { - clearButtonSize = CLEAR_BUTTON_SIZE * drawBaseScale; - clearButtonX = drawX + drawWidth - buttonCursorRight - clearButtonSize; - clearButtonY = drawY + (((HEADER_HEIGHT * drawBaseScale) - clearButtonSize) * 0.5f); - buttonCursorRight += clearButtonSize + (HEADER_BUTTON_GAP * drawBaseScale); - } else { - clearButtonSize = 0.0f; - } - if (showCopyButton) { - copyButtonSize = CLEAR_BUTTON_SIZE * drawBaseScale; - copyButtonX = drawX + drawWidth - buttonCursorRight - copyButtonSize; - copyButtonY = drawY + (((HEADER_HEIGHT * drawBaseScale) - copyButtonSize) * 0.5f); - } else { - copyButtonSize = 0.0f; - } - - int headerIconColor = uiCounter; - if (iconEffect.get() != HudTextEffects.Effect.NONE) { - headerIconColor = HudTextEffects.animatedColor( - headerIconColor, - iconEffect.get(), - iconEffectSpeed.get(), - (float) (Util.getMillis() / 1000.0), - 0.0f - ); - } - - List> panelRows = ScriptedTriangulatorHudPanel.rows(); - for (int i = 0; i < displayThrows.size(); i++) { - CachedThrow cachedThrow = displayThrows.get(i); - int markerColor = solveMarkerColor(cachedThrow, displayResult, i, displayThrows.size()); - panelRows.add(ScriptedTriangulatorHudPanel.row( - "eye:" + cachedThrow.serial(), - "Eye #" + (i + 1), - formatThrowPoint(cachedThrow), - uiText, - markerColor, - markerColor, - HudRenderUtil.scaleAlpha(uiMuted, 0.45f), - 1.0f - )); - } - - scriptedPanel.render( - renderer, - textRenderer, - ctx, - tickDelta, - new ScriptedTriangulatorHudPanel.Panel( - new ScriptedTriangulatorHudPanel.Palette( - uiHeaderLeft, - uiHeaderRight, - uiBodyLeft, - uiBodyRight, - uiOutline, - uiText, - uiMuted, - uiCounter, - uiTitleText, - uiDivider, - uiBlurTint, - uiSuccess, - uiWarn, - uiDanger - ), - drawX, - drawY, - drawWidth, - drawHeight, - drawScale, - drawBaseScale, - drawFontScale, - drawHeaderTextH, - drawRowTextH, - activeLabelWidth * drawScale, - activeCountWidth * drawScale, - displayThrows.size(), - hasEffect(), - Math.min(1.0f, (blurAlpha.get() / 255.0f) * (isThemeMode() ? 1.15f : 1.0f)), - ScriptedTriangulatorHudPanel.idString(HEADER_ICON), - headerIconColor, - showClearButton, - uiDanger, - showCopyButton, - uiCounter, - state.statusText(), - state.primaryLine(), - state.secondaryLine(), - state.confidenceText(), - state.statusColor(), - panelRows - ) - ); + headerButtons = TriangulatorPainter.placeButtons(metrics, drawX, drawY, drawWidth, drawHeight, + drawBaseScale, showClearButton, showCopyButton); + int headerIconColor = TriangulatorPainter.headerIconColor(view.counter(), iconEffect.get(), iconEffectSpeed.get()); + List> rows = TriangulatorPainter.buildRows(displayThrows, displayResult, view, + autoResetDistance.get()); + scriptedPanel.render(renderer, textRenderer, ctx, tickDelta, TriangulatorPainter.panel(view, metrics, + drawX, drawY, drawWidth, drawHeight, drawScale, drawBaseScale, fontScale * drawScale, + displayThrows.size(), hasEffect(), view.blurStrength(isThemeMode(), blurAlpha.get()), + headerIconColor, showClearButton, showCopyButton, state, rows)); } - private static String formatCopyCoordinates(SolveResult result) { + public XaeroSnapshot getXaeroSnapshot() { + SolveResult result = currentResult != null ? currentResult : solve(tracker.cachedThrows()); if (result == null || !result.hasSolution()) { - return ""; + return null; } - return String.format(Locale.ROOT, "%d %d %d", Math.round(result.x()), COPY_COORD_Y, Math.round(result.z())); + return new XaeroSnapshot(result.x(), result.z(), result.ready(), result.confidence(), tracker.throwsCount()); } - private DisplayState buildDisplayState(List throwsToShow, SolveResult result, boolean preview) { - long now = System.currentTimeMillis(); - if (throwsToShow.isEmpty()) { - if (!lastResetReason.isEmpty() && now - lastResetMs <= RESET_FLASH_MS) { - return new DisplayState(lastResetReason, "Cache cleared", "Waiting for a new eye", "", uiDanger); - } - if (!activeEyes.isEmpty()) { - return new DisplayState("Tracking eye", "Stabilizing trajectory", "Need one clean capture", "", uiWarn); - } - if (preview) { - return new DisplayState("Solved", formatCoordinateLine(1288.0, -412.0), - formatResultLine(1288.0, -412.0, 5.2), "88%", uiSuccess); - } - return new DisplayState("Waiting for eye", "Throw an eye of ender", "Second clean eye unlocks result", "", uiMuted); - } - - if (throwsToShow.size() == 1) { - CachedThrow first = throwsToShow.get(0); - return new DisplayState( - activeEyes.isEmpty() ? "Need second eye" : "Tracking next eye", - String.format(Locale.ROOT, "Q %s Stability %.3f", formatPercent(first.quality()), first.stability()), - "Need another angle for solve", - "", - uiWarn - ); - } - - if (result == null || !result.hasSolution()) { - return new DisplayState( - "Angles too close", - String.format(Locale.ROOT, "Best pair %.2f deg", result == null ? 0.0 : result.minPairAngleDeg()), - "Move farther, then throw again", - "", - uiWarn - ); - } - - if (!result.ready()) { - return new DisplayState( - "Low correlation", - formatCoordinateLine(result.x(), result.z()), - String.format(Locale.ROOT, "%s Pair %.2f deg", formatResultLine(result.x(), result.z(), result.rmse()), result.minPairAngleDeg()), - formatPercent(result.confidence()), - uiWarn - ); - } - - if (!result.ready()) { - return new DisplayState( - "Low correlation", - String.format(Locale.ROOT, "X %d Z %d", Math.round(result.x()), Math.round(result.z())), - String.format(Locale.ROOT, "Err %.1fb Min angle %.1fВ°", result.rmse(), result.minPairAngleDeg()), - formatPercent(result.confidence()), - uiWarn - ); - } - - return new DisplayState( - "Solved", - formatCoordinateLine(result.x(), result.z()), - formatResultLine(result.x(), result.z(), result.rmse()), - formatPercent(result.confidence()), - uiSuccess - ); + public record XaeroSnapshot(double overworldX, + double overworldZ, + boolean ready, + double confidence, + int eyeCount) { } - private void updateTrackedEyes(long now) { - if (mc == null || mc.player == null || mc.level == null) { - activeEyes.clear(); - return; - } - if (mc.level.dimension() != Level.OVERWORLD) { - cleanupMissingEyes(Set.of(), now); - return; - } - - AABB searchBox = mc.player.getBoundingBox().inflate(SEARCH_RANGE); - List eyes = mc.level.getEntitiesOfClass( - EyeOfEnder.class, - searchBox, - entity -> entity != null && entity.isAlive() - ); - - Set seen = new HashSet<>(); - for (EyeOfEnder eye : eyes) { - seen.add(eye.getId()); - TrackedEye trackedEye = activeEyes.computeIfAbsent(eye.getId(), TrackedEye::new); - trackedEye.observe(eye, now); - - if (!trackedEye.captured()) { - TrackedCapture capture = trackedEye.tryCapture( - minObservationTicks.get(), - stabilityThreshold.get(), - CAPTURE_DEVIATION_LIMIT - ); - if (capture != null) { - trackedEye.markCaptured(); - acceptCapture(capture, now); - } - } - } - - cleanupMissingEyes(seen, now); + private SolveResult currentCopyResult() { + SolveResult result = currentResult != null ? currentResult : solve(tracker.cachedThrows()); + return result != null && result.hasSolution() ? result : null; } - private String formatCoordinateLine(double overworldX, double overworldZ) { - if (mc != null && mc.level != null && mc.level.dimension() == Level.NETHER) { - return String.format(Locale.ROOT, "N %d %d", Math.round(overworldX / 8.0), Math.round(overworldZ / 8.0)); - } - return String.format(Locale.ROOT, "X %d Z %d", Math.round(overworldX), Math.round(overworldZ)); - } - - private String formatResultLine(double overworldX, double overworldZ, double rmse) { - if (mc != null && mc.level != null && mc.level.dimension() == Level.NETHER) { - return String.format(Locale.ROOT, "OW %d %d Err %.1fb", - Math.round(overworldX), Math.round(overworldZ), rmse); - } - return String.format(Locale.ROOT, "Err %.1fb Chunk %d %d", - rmse, Mth.floor(overworldX / 16.0), Mth.floor(overworldZ / 16.0)); - } - - private void cleanupMissingEyes(Set seen, long now) { - List toRemove = new ArrayList<>(); - for (Map.Entry entry : activeEyes.entrySet()) { - TrackedEye trackedEye = entry.getValue(); - if (seen.contains(entry.getKey())) { - continue; - } - if (!trackedEye.captured()) { - TrackedCapture capture = trackedEye.tryCapture( - minObservationTicks.get(), - stabilityThreshold.get() - 0.01, - CAPTURE_DEVIATION_LIMIT * 1.25 - ); - if (capture != null) { - trackedEye.markCaptured(); - acceptCapture(capture, now); - } - } - if (trackedEye.captured() || now - trackedEye.lastSeenMs() > 600L) { - toRemove.add(entry.getKey()); - } - } - for (Integer id : toRemove) { - activeEyes.remove(id); - } - } - - private void acceptCapture(TrackedCapture capture, long now) { - if (capture.quality() < CAPTURE_QUALITY_FLOOR) { - return; - } - - CachedThrow cachedThrow = new CachedThrow( - nextCaptureSerial++, - capture.pointX(), - capture.pointZ(), - capture.dirX(), - capture.dirZ(), - capture.quality(), - capture.stability(), - capture.deviation(), - capture.samples(), - now - ); - - if (autoReset.get() && !cachedThrows.isEmpty()) { - SolveResult existing = currentResult != null ? currentResult : solve(cachedThrows); - if (existing != null && existing.ready()) { - double mismatch = distanceToLine(existing.x(), existing.z(), cachedThrow); - if (mismatch > autoResetDistance.get()) { - clearAllState("Different stronghold", true, now); - } - } - } - - cachedThrows.add(cachedThrow); - while (cachedThrows.size() > maxCachedThrows.get()) { - cachedThrows.remove(0); - } - lastCaptureMs = now; - lastResetReason = ""; - currentResult = solve(cachedThrows); - } - - private void clearAllState(String reason, boolean rememberReason, long now) { - activeEyes.clear(); - cachedThrows.clear(); - currentResult = null; - nextCaptureSerial = 1L; - lastCaptureMs = 0L; - if (rememberReason) { - lastResetReason = reason; - lastResetMs = now; - } else { - lastResetReason = ""; - lastResetMs = 0L; - } + private CapturePolicy capturePolicy() { + return new CapturePolicy(minObservationTicks.get(), stabilityThreshold.get(), + minPairAngleDeg.get(), autoReset.get(), autoResetDistance.get(), maxCachedThrows.get(), + cacheLifetimeSec.get() * 1000L); } private SolveResult solve(List throwsToSolve) { - return solveStatic(throwsToSolve, minPairAngleDeg.get()); + return TriangulatorSolver.solve(throwsToSolve, minPairAngleDeg.get()); } - private int solveMarkerColor(CachedThrow cachedThrow, SolveResult result, int index, int total) { - if (result == null || !result.hasSolution()) { - return index == total - 1 ? uiWarn : uiCounter; - } - double error = distanceToLine(result.x(), result.z(), cachedThrow); - if (error <= 8.0) { - return uiSuccess; - } - if (error <= autoResetDistance.get() * 0.5) { - return uiWarn; - } - return uiDanger; - } - - private void drawWidgetBlur(float px, - float headerY, - float width, - float headerHeight, - float bodyY, - float bodyHeight, - float radius) { - if (!hasEffect() || width <= 0.0f || headerHeight <= 0.0f) { - return; - } - float blurStrength = Math.min(1.0f, (blurAlpha.get() / 255.0f) * (isThemeMode() ? 1.15f : 1.0f)); - int blurTint = uiBlurTint & 0x00FFFFFF; - float blurQuality = hud.getBlurRadius(); - - Renderer2D.COLOR.blurComposite(composite -> { - composite.roundedRectCorners( - px, headerY, width, headerHeight, - radius, 0.0f, radius, 0.0f, - blurQuality, 1.0f, blurStrength, blurTint - ); - if (bodyHeight > 0.0f) { - composite.roundedRectCorners( - px, bodyY, width, bodyHeight, - 0.0f, radius, 0.0f, radius, - blurQuality, 1.0f, blurStrength, blurTint - ); - } - }); - } - - private void updatePalette() { - if (isThemeMode()) { - int alpha = bgAlpha.get(); - int window = HudRenderUtil.setAlpha(theme().windowBg(), alpha); - int header = HudRenderUtil.setAlpha( - HudRenderUtil.mixColor(theme().windowHeader(), theme().surface(), 0.18f), - Math.min(255, alpha + 14) - ); - int surface = HudRenderUtil.setAlpha( - HudRenderUtil.mixColor(theme().surface(), theme().windowBg(), 0.22f), - Math.min(255, alpha + 6) - ); - int deep = HudRenderUtil.setAlpha( - HudRenderUtil.mixColor(theme().surface(), theme().windowHeader(), 0.42f), - Math.min(255, alpha + 18) - ); - - uiHeaderLeft = HudRenderUtil.mixColor(header, window, 0.22f); - uiHeaderRight = HudRenderUtil.mixColor(surface, header, 0.52f); - uiBodyLeft = HudRenderUtil.mixColor(window, surface, 0.24f); - uiBodyRight = HudRenderUtil.mixColor(deep, surface, 0.18f); - uiOutline = HudRenderUtil.setAlpha( - HudRenderUtil.mixColor(theme().windowStroke(), theme().strokeSoft(), 0.18f), - Math.min(255, Math.max(182, alpha + 36)) - ); - uiText = theme().textPrimary(); - uiTitleText = HudRenderUtil.mixColor(theme().textPrimary(), theme().accent(), 0.14f); - uiMuted = HudRenderUtil.mixColor(theme().textMuted(), theme().textPrimary(), 0.16f); - uiCounter = HudRenderUtil.mixColor(theme().accent(), theme().textPrimary(), 0.38f); - uiDivider = HudRenderUtil.scaleAlpha(uiMuted, 0.68f); - uiBlurTint = HudRenderUtil.mixColor( - HudRenderUtil.mixColor(uiHeaderRight, uiBodyRight, 0.5f), - theme().accent(), - 0.14f - ); - } else { - uiHeaderLeft = bg.getArgb(); - uiHeaderRight = bg2.getArgb(); - uiBodyLeft = HudRenderUtil.mixColor(bg.getArgb(), bg2.getArgb(), 0.15f); - uiBodyRight = bg2.getArgb(); - uiOutline = stroke.getArgb(); - uiText = text.getArgb(); - uiTitleText = uiText; - uiMuted = muted.getArgb(); - uiCounter = HudRenderUtil.mixColor(text.getArgb(), 0xFFE1E1FF, 0.20f); - uiDivider = HudRenderUtil.scaleAlpha(uiMuted, 0.55f); - uiBlurTint = HudRenderUtil.mixColor(uiHeaderLeft, uiBodyRight, 0.5f); - } - - uiSuccess = HudRenderUtil.mixColor(uiCounter, 0xFF55FF93, 0.55f); - uiWarn = HudRenderUtil.mixColor(uiCounter, 0xFFFFC85A, 0.55f); - uiDanger = HudRenderUtil.mixColor(uiCounter, 0xFFFF6A6A, 0.62f); + private void clearAllState(String reason, boolean rememberReason, long now) { + tracker.clear(reason, rememberReason, now); + currentResult = null; } private boolean isThemeMode() { @@ -924,178 +294,12 @@ public final class Triangulator extends DraggableHudElement { return !EFFECT_NONE.equals(bgEffect.get()); } - public XaeroSnapshot getXaeroSnapshot() { - SolveResult result = currentResult != null ? currentResult : solve(cachedThrows); - if (result == null || !result.hasSolution()) { - return null; - } - return new XaeroSnapshot(result.x(), result.z(), result.ready(), result.confidence(), cachedThrows.size()); + private boolean isNether() { + Minecraft mc = Minecraft.getInstance(); + return mc != null && mc.level != null && mc.level.dimension() == Level.NETHER; } - public record XaeroSnapshot(double overworldX, - double overworldZ, - boolean ready, - double confidence, - int eyeCount) { - } - - private record Observation(double x, double z, double dirX, double dirZ, double speed) { - } - - private record TrackedCapture(double pointX, - double pointZ, - double dirX, - double dirZ, - double quality, - double stability, - double deviation, - int samples) { - } - - private record CachedThrow(long serial, - double pointX, - double pointZ, - double dirX, - double dirZ, - double quality, - double stability, - double deviation, - int samples, - long capturedAtMs) { - } - - private record SolveResult(boolean hasSolution, - boolean ready, - double x, - double z, - double rmse, - double minPairAngleDeg, - double confidence, - int chunkX, - int chunkZ) { - } - - private record DisplayState(String statusText, - String primaryLine, - String secondaryLine, - String confidenceText, - int statusColor) { - } - - private static final class TrackedEye { - private final List observations = new ArrayList<>(); - private long lastSeenMs; - private boolean captured; - - private TrackedEye(int entityId) { - } - - private void observe(EyeOfEnder eye, long now) { - if (eye == null) { - return; - } - Vec3 velocity = eye.getDeltaMovement(); - double speed = Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z); - if (speed < MIN_HORIZONTAL_SPEED) { - lastSeenMs = now; - return; - } - observations.add(new Observation(eye.getX(), eye.getZ(), velocity.x / speed, velocity.z / speed, speed)); - if (observations.size() > 14) { - observations.remove(0); - } - lastSeenMs = now; - } - - private TrackedCapture tryCapture(int minTicks, double stabilityThreshold, double deviationLimit) { - if (captured || observations.size() < Math.max(2, minTicks)) { - return null; - } - - double dirX = 0.0; - double dirZ = 0.0; - double avgSpeed = 0.0; - for (Observation observation : observations) { - dirX += observation.dirX(); - dirZ += observation.dirZ(); - avgSpeed += observation.speed(); - } - avgSpeed /= observations.size(); - - double dirLen = Math.sqrt(dirX * dirX + dirZ * dirZ); - if (dirLen <= 1.0e-6) { - return null; - } - dirX /= dirLen; - dirZ /= dirLen; - - double centroidX = 0.0; - double centroidZ = 0.0; - double avgCos = 0.0; - double minCos = 1.0; - for (Observation observation : observations) { - centroidX += observation.x(); - centroidZ += observation.z(); - double dot = Mth.clamp(observation.dirX() * dirX + observation.dirZ() * dirZ, -1.0, 1.0); - avgCos += dot; - minCos = Math.min(minCos, dot); - } - centroidX /= observations.size(); - centroidZ /= observations.size(); - avgCos /= observations.size(); - - double normalX = -dirZ; - double normalZ = dirX; - double deviation = 0.0; - for (Observation observation : observations) { - deviation += Math.abs((observation.x() - centroidX) * normalX + (observation.z() - centroidZ) * normalZ); - } - deviation /= observations.size(); - - if (avgCos < stabilityThreshold || deviation > deviationLimit) { - return null; - } - - double stabilityScore = Mth.clamp( - (avgCos - stabilityThreshold) / Math.max(1.0e-5, 1.0 - stabilityThreshold), - 0.0, - 1.0 - ); - double minCosScore = Mth.clamp( - (minCos - (stabilityThreshold - 0.01)) / Math.max(1.0e-5, 1.01 - stabilityThreshold), - 0.0, - 1.0 - ); - double deviationScore = Math.exp(-deviation / 0.15); - double speedScore = Mth.clamp(avgSpeed / 0.35, 0.0, 1.0); - double quality = Mth.clamp( - stabilityScore * 0.40 + minCosScore * 0.25 + deviationScore * 0.20 + speedScore * 0.15, - 0.0, - 1.0 - ); - - return new TrackedCapture( - centroidX, - centroidZ, - dirX, - dirZ, - quality, - avgCos, - deviation, - observations.size() - ); - } - - private boolean captured() { - return captured; - } - - private void markCaptured() { - captured = true; - } - - private long lastSeenMs() { - return lastSeenMs; - } + private static boolean hit(float mx, float my, float x, float y, float size) { + return mx >= x && mx <= x + size && my >= y && my <= y + size; } } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TrackedEye.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TrackedEye.java new file mode 100644 index 0000000..c0876d1 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TrackedEye.java @@ -0,0 +1,152 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator; + +import net.minecraft.util.Mth; +import net.minecraft.world.entity.projectile.EyeOfEnder; +import net.minecraft.world.phys.Vec3; + +import java.util.ArrayList; +import java.util.List; + +/** + * Rolling observation window for one thrown eye of ender plus the + * quality-scored capture computed once its trajectory stabilizes. + */ +public final class TrackedEye { + private static final double MIN_HORIZONTAL_SPEED = 0.03; + private static final int MAX_OBSERVATIONS = 14; + + private final List observations = new ArrayList<>(); + private long lastSeenMs; + private boolean captured; + + TrackedEye(int entityId) { + } + + public void observe(EyeOfEnder eye, long now) { + if (eye == null) { + return; + } + Vec3 velocity = eye.getDeltaMovement(); + double speed = Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z); + if (speed < MIN_HORIZONTAL_SPEED) { + lastSeenMs = now; + return; + } + observations.add(new Observation(eye.getX(), eye.getZ(), velocity.x / speed, velocity.z / speed, speed)); + if (observations.size() > MAX_OBSERVATIONS) { + observations.remove(0); + } + lastSeenMs = now; + } + + public TrackedCapture tryCapture(int minTicks, double stabilityThreshold, double deviationLimit) { + if (captured || observations.size() < Math.max(2, minTicks)) { + return null; + } + + double dirX = 0.0; + double dirZ = 0.0; + double avgSpeed = 0.0; + for (Observation observation : observations) { + dirX += observation.dirX(); + dirZ += observation.dirZ(); + avgSpeed += observation.speed(); + } + avgSpeed /= observations.size(); + + double dirLen = Math.sqrt(dirX * dirX + dirZ * dirZ); + if (dirLen <= 1.0e-6) { + return null; + } + dirX /= dirLen; + dirZ /= dirLen; + + double centroidX = 0.0; + double centroidZ = 0.0; + double avgCos = 0.0; + double minCos = 1.0; + for (Observation observation : observations) { + centroidX += observation.x(); + centroidZ += observation.z(); + double dot = Mth.clamp(observation.dirX() * dirX + observation.dirZ() * dirZ, -1.0, 1.0); + avgCos += dot; + minCos = Math.min(minCos, dot); + } + centroidX /= observations.size(); + centroidZ /= observations.size(); + avgCos /= observations.size(); + + double normalX = -dirZ; + double normalZ = dirX; + double deviation = 0.0; + for (Observation observation : observations) { + deviation += Math.abs((observation.x() - centroidX) * normalX + (observation.z() - centroidZ) * normalZ); + } + deviation /= observations.size(); + + if (avgCos < stabilityThreshold || deviation > deviationLimit) { + return null; + } + + double stabilityScore = Mth.clamp( + (avgCos - stabilityThreshold) / Math.max(1.0e-5, 1.0 - stabilityThreshold), + 0.0, + 1.0 + ); + double minCosScore = Mth.clamp( + (minCos - (stabilityThreshold - 0.01)) / Math.max(1.0e-5, 1.01 - stabilityThreshold), + 0.0, + 1.0 + ); + double deviationScore = Math.exp(-deviation / 0.15); + double speedScore = Mth.clamp(avgSpeed / 0.35, 0.0, 1.0); + double quality = Mth.clamp( + stabilityScore * 0.40 + minCosScore * 0.25 + deviationScore * 0.20 + speedScore * 0.15, + 0.0, + 1.0 + ); + + return new TrackedCapture( + centroidX, + centroidZ, + dirX, + dirZ, + quality, + avgCos, + deviation, + observations.size() + ); + } + + public boolean captured() { + return captured; + } + + public void markCaptured() { + captured = true; + } + + public long lastSeenMs() { + return lastSeenMs; + } + + public record Observation(double x, double z, double dirX, double dirZ, double speed) { + } + + public record TrackedCapture(double pointX, + double pointZ, + double dirX, + double dirZ, + double quality, + double stability, + double deviation, + int samples) { + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorSolver.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorSolver.java new file mode 100644 index 0000000..ed65de0 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorSolver.java @@ -0,0 +1,193 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator; + +import net.minecraft.util.Mth; + +import java.util.List; +import java.util.Locale; + +/** + * Pure triangulation math and formatting: weighted pairwise line + * intersections of cached throws plus the user-facing status strings. + */ +public final class TriangulatorSolver { + private static final double MIN_SOLVABLE_ANGLE_DEG = 0.05; + private static final int COPY_COORD_Y = 64; + + public static final List PREVIEW_THROWS = List.of( + new CachedThrow(1L, 110.0, 58.0, 0.9238795, -0.3826834, 0.91, 0.996, 0.08, 8, 0L), + new CachedThrow(2L, 188.0, -24.0, 0.7071067, -0.7071067, 0.87, 0.992, 0.12, 7, 0L), + new CachedThrow(3L, 254.0, -102.0, 0.4713967, -0.8819212, 0.82, 0.989, 0.19, 6, 0L) + ); + + private TriangulatorSolver() { + } + + public static SolveResult previewResult() { + return solve(PREVIEW_THROWS, 6.0); + } + + public static SolveResult solve(List throwsToSolve, double minAngleThreshold) { + if (throwsToSolve == null || throwsToSolve.isEmpty()) { + return null; + } + if (throwsToSolve.size() == 1) { + CachedThrow only = throwsToSolve.get(0); + return new SolveResult( + false, + false, + only.pointX(), + only.pointZ(), + 0.0, + 0.0, + only.quality(), + Mth.floor(only.pointX() / 16.0), + Mth.floor(only.pointZ() / 16.0) + ); + } + + double qualitySum = 0.0; + double strongestAngle = 0.0; + double pairX = 0.0; + double pairZ = 0.0; + double pairWeightSum = 0.0; + double crossThreshold = Math.sin(Math.toRadians(MIN_SOLVABLE_ANGLE_DEG)); + + for (int i = 0; i < throwsToSolve.size(); i++) { + CachedThrow throwI = throwsToSolve.get(i); + qualitySum += throwI.quality(); + + for (int j = i + 1; j < throwsToSolve.size(); j++) { + CachedThrow throwJ = throwsToSolve.get(j); + double dot = Math.abs(throwI.dirX() * throwJ.dirX() + throwI.dirZ() * throwJ.dirZ()); + double angle = Math.toDegrees(Math.acos(Mth.clamp(dot, -1.0, 1.0))); + strongestAngle = Math.max(strongestAngle, angle); + + double cross = throwI.dirX() * throwJ.dirZ() - throwI.dirZ() * throwJ.dirX(); + if (Math.abs(cross) <= crossThreshold) { + continue; + } + + double deltaX = throwJ.pointX() - throwI.pointX(); + double deltaZ = throwJ.pointZ() - throwI.pointZ(); + double t = (deltaX * throwJ.dirZ() - deltaZ * throwJ.dirX()) / cross; + double intersectionX = throwI.pointX() + throwI.dirX() * t; + double intersectionZ = throwI.pointZ() + throwI.dirZ() * t; + double pairWeight = (0.35 + throwI.quality() * 0.65) + * (0.35 + throwJ.quality() * 0.65) + * Mth.clamp(0.35 + angle / Math.max(0.35, minAngleThreshold), 0.35, 4.0); + + pairX += intersectionX * pairWeight; + pairZ += intersectionZ * pairWeight; + pairWeightSum += pairWeight; + } + } + + if (pairWeightSum <= 1.0e-8) { + return new SolveResult(false, false, 0.0, 0.0, Double.POSITIVE_INFINITY, strongestAngle, 0.0, 0, 0); + } + + double x = pairX / pairWeightSum; + double z = pairZ / pairWeightSum; + double errorSum = 0.0; + for (CachedThrow cachedThrow : throwsToSolve) { + double d = distanceToLine(x, z, cachedThrow); + errorSum += d * d; + } + double rmse = Math.sqrt(errorSum / throwsToSolve.size()); + double quality = qualitySum / throwsToSolve.size(); + double effectiveAngleThreshold = Math.max(0.35, Math.min(minAngleThreshold, 2.0)); + double angleScore = Mth.clamp( + (strongestAngle - effectiveAngleThreshold) / Math.max(0.5, 12.0 - effectiveAngleThreshold), + 0.0, + 1.0 + ); + double errorScore = Mth.clamp(1.0 - rmse / 64.0, 0.0, 1.0); + double countBonus = Mth.clamp((throwsToSolve.size() - 1.0) / 3.0, 0.0, 1.0); + double confidence = Mth.clamp( + quality * 0.45 + angleScore * 0.25 + errorScore * 0.20 + countBonus * 0.10, + 0.0, + 1.0 + ); + boolean ready = strongestAngle >= effectiveAngleThreshold && rmse <= 96.0 && confidence >= 0.55; + return new SolveResult( + true, + ready, + x, + z, + rmse, + strongestAngle, + confidence, + Mth.floor(x / 16.0), + Mth.floor(z / 16.0) + ); + } + + public static double distanceToLine(double x, double z, CachedThrow cachedThrow) { + double nx = -cachedThrow.dirZ(); + double nz = cachedThrow.dirX(); + return Math.abs((x - cachedThrow.pointX()) * nx + (z - cachedThrow.pointZ()) * nz); + } + + public static String formatPercent(double value) { + return Math.round(Mth.clamp(value, 0.0, 1.0) * 100.0) + "%"; + } + + public static String formatThrowPoint(CachedThrow cachedThrow) { + return String.format(Locale.ROOT, "%d %d", + Math.round(cachedThrow.pointX()), + Math.round(cachedThrow.pointZ())); + } + + public static String formatCopyCoordinates(SolveResult result) { + if (result == null || !result.hasSolution()) { + return ""; + } + return String.format(Locale.ROOT, "%d %d %d", Math.round(result.x()), COPY_COORD_Y, Math.round(result.z())); + } + + public static String formatCoordinateLine(double overworldX, double overworldZ, boolean nether) { + if (nether) { + return String.format(Locale.ROOT, "N %d %d", Math.round(overworldX / 8.0), Math.round(overworldZ / 8.0)); + } + return String.format(Locale.ROOT, "X %d Z %d", Math.round(overworldX), Math.round(overworldZ)); + } + + public static String formatResultLine(double overworldX, double overworldZ, double rmse, boolean nether) { + if (nether) { + return String.format(Locale.ROOT, "OW %d %d Err %.1fb", + Math.round(overworldX), Math.round(overworldZ), rmse); + } + return String.format(Locale.ROOT, "Err %.1fb Chunk %d %d", + rmse, Mth.floor(overworldX / 16.0), Mth.floor(overworldZ / 16.0)); + } + + public record CachedThrow(long serial, + double pointX, + double pointZ, + double dirX, + double dirZ, + double quality, + double stability, + double deviation, + int samples, + long capturedAtMs) { + } + + public record SolveResult(boolean hasSolution, + boolean ready, + double x, + double z, + double rmse, + double minPairAngleDeg, + double confidence, + int chunkX, + int chunkZ) { + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorTracker.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorTracker.java new file mode 100644 index 0000000..5bc3f85 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorTracker.java @@ -0,0 +1,197 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator; + +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TrackedEye.TrackedCapture; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.CachedThrow; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.SolveResult; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.model.CapturePolicy; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.EyeOfEnder; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class TriangulatorTracker { + private static final float SEARCH_RANGE = 196.0f; + private static final double CAPTURE_DEVIATION_LIMIT = 0.35; + private static final double CAPTURE_QUALITY_FLOOR = 0.55; + private static final long MISSING_EYE_TIMEOUT_MS = 600L; + + private final Map activeEyes = new LinkedHashMap<>(); + private final List cachedThrows = new ArrayList<>(); + private long nextCaptureSerial = 1L; + private long lastCaptureMs = 0L; + private long lastWorldTime = Long.MIN_VALUE; + private long resetMs = 0L; + private String resetReason = ""; + + public void dropEyes() { + activeEyes.clear(); + } + + public void clear(String reason, boolean rememberReason, long now) { + activeEyes.clear(); + cachedThrows.clear(); + nextCaptureSerial = 1L; + lastCaptureMs = 0L; + if (rememberReason) { + resetReason = reason; + resetMs = now; + } else { + resetReason = ""; + resetMs = 0L; + } + } + + public boolean trackingEye() { + return !activeEyes.isEmpty(); + } + + public List cachedThrows() { + return java.util.Collections.unmodifiableList(cachedThrows); + } + + public int throwsCount() { + return cachedThrows.size(); + } + + public String resetReason() { + return resetReason; + } + + public long resetMs() { + return resetMs; + } + + private boolean cacheExpired(long now, long lifetimeMs, SolveResult lastResult) { + return lifetimeMs > 0L + && !cachedThrows.isEmpty() + && activeEyes.isEmpty() + && (lastResult == null || !lastResult.hasSolution()) + && lastCaptureMs > 0L + && now - lastCaptureMs > lifetimeMs; + } + + public SolveResult tick(Level level, Player player, CapturePolicy policy, SolveResult lastResult) { + long now = System.currentTimeMillis(); + long worldTime = level.getGameTime(); + if (lastWorldTime != Long.MIN_VALUE && worldTime + 200L < lastWorldTime) { + activeEyes.clear(); + } + lastWorldTime = worldTime; + if (cacheExpired(now, policy.cacheLifetimeMs(), lastResult)) { + clear("Cache expired", true, now); + } + + if (level.dimension() != Level.OVERWORLD) { + cleanupMissingEyes(Set.of(), now, policy, lastResult); + return solve(policy); + } + + AABB searchBox = player.getBoundingBox().inflate(SEARCH_RANGE); + List eyes = level.getEntitiesOfClass( + EyeOfEnder.class, + searchBox, + entity -> entity != null && entity.isAlive() + ); + + Set seen = new HashSet<>(); + for (EyeOfEnder eye : eyes) { + seen.add(eye.getId()); + TrackedEye trackedEye = activeEyes.computeIfAbsent(eye.getId(), TrackedEye::new); + trackedEye.observe(eye, now); + + if (!trackedEye.captured()) { + TrackedCapture capture = trackedEye.tryCapture( + policy.minObservationTicks(), + policy.stabilityThreshold(), + CAPTURE_DEVIATION_LIMIT + ); + if (capture != null) { + trackedEye.markCaptured(); + accept(capture, now, policy, lastResult); + } + } + } + + cleanupMissingEyes(seen, now, policy, lastResult); + return solve(policy); + } + + private SolveResult solve(CapturePolicy policy) { + return TriangulatorSolver.solve(cachedThrows, policy.minPairAngleDeg()); + } + + private void cleanupMissingEyes(Set seen, long now, CapturePolicy policy, SolveResult lastResult) { + List toRemove = new ArrayList<>(); + for (Map.Entry entry : activeEyes.entrySet()) { + TrackedEye trackedEye = entry.getValue(); + if (seen.contains(entry.getKey())) { + continue; + } + if (!trackedEye.captured()) { + TrackedCapture capture = trackedEye.tryCapture( + policy.minObservationTicks(), + policy.stabilityThreshold() - 0.01, + CAPTURE_DEVIATION_LIMIT * 1.25 + ); + if (capture != null) { + trackedEye.markCaptured(); + accept(capture, now, policy, lastResult); + } + } + if (trackedEye.captured() || now - trackedEye.lastSeenMs() > MISSING_EYE_TIMEOUT_MS) { + toRemove.add(entry.getKey()); + } + } + for (Integer id : toRemove) { + activeEyes.remove(id); + } + } + + private void accept(TrackedCapture capture, long now, CapturePolicy policy, SolveResult lastResult) { + if (capture.quality() < CAPTURE_QUALITY_FLOOR) { + return; + } + + CachedThrow cachedThrow = new CachedThrow( + nextCaptureSerial++, + capture.pointX(), + capture.pointZ(), + capture.dirX(), + capture.dirZ(), + capture.quality(), + capture.stability(), + capture.deviation(), + capture.samples(), + now + ); + + if (policy.autoReset() && !cachedThrows.isEmpty()) { + SolveResult existing = lastResult != null ? lastResult : solve(policy); + if (existing != null && existing.ready() + && TriangulatorSolver.distanceToLine(existing.x(), existing.z(), cachedThrow) > policy.autoResetDistance()) { + clear("Different stronghold", true, now); + } + } + + cachedThrows.add(cachedThrow); + while (cachedThrows.size() > policy.maxCachedThrows()) { + cachedThrows.remove(0); + } + lastCaptureMs = now; + resetReason = ""; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorView.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorView.java new file mode 100644 index 0000000..e7aa184 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorView.java @@ -0,0 +1,197 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator; + +import dev.loki.lovisual.features.gui.hud.HudRenderUtil; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.CachedThrow; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.SolveResult; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.model.DisplayState; +import dev.loki.lovisual.features.gui.hud.draggable.impl.ScriptedTriangulatorHudPanel; + +import java.util.List; +import java.util.Locale; + +import static dev.loki.lovisual.features.theme.Theme.theme; + +public final class TriangulatorView { + private static final long RESET_FLASH_MS = 3000L; + + private int uiHeaderLeft; + private int uiHeaderRight; + private int uiBodyLeft; + private int uiBodyRight; + private int uiOutline; + private int uiText; + private int uiMuted; + private int uiCounter; + private int uiTitleText; + private int uiDivider; + private int uiBlurTint; + private int uiSuccess; + private int uiWarn; + private int uiDanger; + + public void update(boolean themeMode, int alpha, int bg, int bg2, int stroke, int text, int muted) { + if (themeMode) { + int window = HudRenderUtil.setAlpha(theme().windowBg(), alpha); + int header = HudRenderUtil.setAlpha( + HudRenderUtil.mixColor(theme().windowHeader(), theme().surface(), 0.18f), + Math.min(255, alpha + 14) + ); + int surface = HudRenderUtil.setAlpha( + HudRenderUtil.mixColor(theme().surface(), theme().windowBg(), 0.22f), + Math.min(255, alpha + 6) + ); + int deep = HudRenderUtil.setAlpha( + HudRenderUtil.mixColor(theme().surface(), theme().windowHeader(), 0.42f), + Math.min(255, alpha + 18) + ); + + uiHeaderLeft = HudRenderUtil.mixColor(header, window, 0.22f); + uiHeaderRight = HudRenderUtil.mixColor(surface, header, 0.52f); + uiBodyLeft = HudRenderUtil.mixColor(window, surface, 0.24f); + uiBodyRight = HudRenderUtil.mixColor(deep, surface, 0.18f); + uiOutline = HudRenderUtil.setAlpha( + HudRenderUtil.mixColor(theme().windowStroke(), theme().strokeSoft(), 0.18f), + Math.min(255, Math.max(182, alpha + 36)) + ); + uiText = theme().textPrimary(); + uiTitleText = HudRenderUtil.mixColor(theme().textPrimary(), theme().accent(), 0.14f); + uiMuted = HudRenderUtil.mixColor(theme().textMuted(), theme().textPrimary(), 0.16f); + uiCounter = HudRenderUtil.mixColor(theme().accent(), theme().textPrimary(), 0.38f); + uiDivider = HudRenderUtil.scaleAlpha(uiMuted, 0.68f); + uiBlurTint = HudRenderUtil.mixColor( + HudRenderUtil.mixColor(uiHeaderRight, uiBodyRight, 0.5f), + theme().accent(), + 0.14f + ); + } else { + uiHeaderLeft = bg; + uiHeaderRight = bg2; + uiBodyLeft = HudRenderUtil.mixColor(bg, bg2, 0.15f); + uiBodyRight = bg2; + uiOutline = stroke; + uiText = text; + uiTitleText = uiText; + uiMuted = muted; + uiCounter = HudRenderUtil.mixColor(text, 0xFFE1E1FF, 0.20f); + uiDivider = HudRenderUtil.scaleAlpha(uiMuted, 0.55f); + uiBlurTint = HudRenderUtil.mixColor(uiHeaderLeft, uiBodyRight, 0.5f); + } + + uiSuccess = HudRenderUtil.mixColor(uiCounter, 0xFF55FF93, 0.55f); + uiWarn = HudRenderUtil.mixColor(uiCounter, 0xFFFFC85A, 0.55f); + uiDanger = HudRenderUtil.mixColor(uiCounter, 0xFFFF6A6A, 0.62f); + } + + public ScriptedTriangulatorHudPanel.Palette palette() { + return new ScriptedTriangulatorHudPanel.Palette( + uiHeaderLeft, uiHeaderRight, uiBodyLeft, uiBodyRight, + uiOutline, uiText, uiMuted, uiCounter, + uiTitleText, uiDivider, uiBlurTint, + uiSuccess, uiWarn, uiDanger + ); + } + + public int text() { + return uiText; + } + + public int muted() { + return uiMuted; + } + + public int counter() { + return uiCounter; + } + + public int danger() { + return uiDanger; + } + + public float blurStrength(boolean themeMode, int blurAlpha) { + return Math.min(1.0f, (blurAlpha / 255.0f) * (themeMode ? 1.15f : 1.0f)); + } + + public DisplayState status(List throwsToShow, + SolveResult result, + boolean preview, + boolean trackingEye, + String resetReason, + long resetMs, + boolean nether) { + long now = System.currentTimeMillis(); + if (throwsToShow.isEmpty()) { + if (!resetReason.isEmpty() && now - resetMs <= RESET_FLASH_MS) { + return new DisplayState(resetReason, "Cache cleared", "Waiting for a new eye", "", uiDanger); + } + if (trackingEye) { + return new DisplayState("Tracking eye", "Stabilizing trajectory", "Need one clean capture", "", uiWarn); + } + if (preview) { + return new DisplayState("Solved", TriangulatorSolver.formatCoordinateLine(1288.0, -412.0, nether), + TriangulatorSolver.formatResultLine(1288.0, -412.0, 5.2, nether), "88%", uiSuccess); + } + return new DisplayState("Waiting for eye", "Throw an eye of ender", "Second clean eye unlocks result", "", uiMuted); + } + + if (throwsToShow.size() == 1) { + CachedThrow first = throwsToShow.get(0); + return new DisplayState( + trackingEye ? "Tracking next eye" : "Need second eye", + String.format(Locale.ROOT, "Q %s Stability %.3f", TriangulatorSolver.formatPercent(first.quality()), first.stability()), + "Need another angle for solve", + "", + uiWarn + ); + } + + if (result == null || !result.hasSolution()) { + return new DisplayState( + "Angles too close", + String.format(Locale.ROOT, "Best pair %.2f deg", result == null ? 0.0 : result.minPairAngleDeg()), + "Move farther, then throw again", + "", + uiWarn + ); + } + + if (!result.ready()) { + return new DisplayState( + "Low correlation", + TriangulatorSolver.formatCoordinateLine(result.x(), result.z(), nether), + String.format(Locale.ROOT, "%s Pair %.2f deg", + TriangulatorSolver.formatResultLine(result.x(), result.z(), result.rmse(), nether), result.minPairAngleDeg()), + TriangulatorSolver.formatPercent(result.confidence()), + uiWarn + ); + } + + return new DisplayState( + "Solved", + TriangulatorSolver.formatCoordinateLine(result.x(), result.z(), nether), + TriangulatorSolver.formatResultLine(result.x(), result.z(), result.rmse(), nether), + TriangulatorSolver.formatPercent(result.confidence()), + uiSuccess + ); + } + + public int markerColor(CachedThrow cachedThrow, SolveResult result, int index, int total, double autoResetDistance) { + if (result == null || !result.hasSolution()) { + return index == total - 1 ? uiWarn : uiCounter; + } + double error = TriangulatorSolver.distanceToLine(result.x(), result.z(), cachedThrow); + if (error <= 8.0) { + return uiSuccess; + } + if (error <= autoResetDistance * 0.5) { + return uiWarn; + } + return uiDanger; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/model/CapturePolicy.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/model/CapturePolicy.java new file mode 100644 index 0000000..c45c884 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/model/CapturePolicy.java @@ -0,0 +1,18 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.model; + +/** Tunables the tracker needs for a single tick. */ +public record CapturePolicy(int minObservationTicks, + double stabilityThreshold, + double minPairAngleDeg, + boolean autoReset, + double autoResetDistance, + int maxCachedThrows, + long cacheLifetimeMs) { +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/model/DisplayState.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/model/DisplayState.java new file mode 100644 index 0000000..a3eae29 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/model/DisplayState.java @@ -0,0 +1,15 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.model; + +public record DisplayState(String statusText, + String primaryLine, + String secondaryLine, + String confidenceText, + int statusColor) { +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/render/TriangulatorMeasure.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/render/TriangulatorMeasure.java new file mode 100644 index 0000000..042cdcc --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/render/TriangulatorMeasure.java @@ -0,0 +1,94 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.render; + +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.CachedThrow; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver; +import dev.loki.lovisual.render.engine.text.TextRenderer; + +import java.util.List; + +/** + * Text measurement pass that computes the animated panel width/height + * targets and shared header metrics for the Triangulator HUD. + */ +public final class TriangulatorMeasure { + static final float CLEAR_BUTTON_SIZE = 9.0f; + static final float HEADER_BUTTON_GAP = 3.0f; + private static final float MIN_WIDTH = 126.0f; + private static final float ROW_STEP = 11.0f; + private static final float CONTENT_START_Y = 23.0f; + private static final float SUMMARY_HEIGHT = 30.5f; + private static final float TITLE_TEXT_X = 22.0f; + private static final float COUNT_LABEL_OFFSET = 28.0f; + + public record Metrics(float headerTextH, + float rowTextH, + float maxWidth, + float activeLabelWidth, + float activeCountWidth, + float targetHeight) { + } + + private TriangulatorMeasure() { + } + + public static Metrics measure(TextRenderer headerTextRenderer, + TextRenderer rowTextRenderer, + float baseScale, + float fontScale, + List displayThrows, + String statusText, + String primaryLine, + String secondaryLine, + boolean showClearButton, + boolean showCopyButton) { + headerTextRenderer.begin(fontScale, true, false); + float headerTextH = (float) headerTextRenderer.getHeight(false); + float titleWidth = (float) headerTextRenderer.getWidth("Triangulator", false); + headerTextRenderer.end(); + + rowTextRenderer.begin(fontScale, true, false); + float rowTextH = (float) rowTextRenderer.getHeight(false); + float maxWidth = Math.max(MIN_WIDTH * baseScale, + (float) rowTextRenderer.getWidth(statusText, false) + 52.0f * baseScale); + maxWidth = Math.max(maxWidth, (float) rowTextRenderer.getWidth(primaryLine, false) + 20.0f * baseScale); + rowTextRenderer.end(); + + rowTextRenderer.begin(fontScale * 0.92f, false, false); + maxWidth = Math.max(maxWidth, (float) rowTextRenderer.getWidth(secondaryLine, false) + 20.0f * baseScale); + String activeCountText = Integer.toString(displayThrows.size()); + float activeLabelWidth = (float) rowTextRenderer.getWidth("Eyes:", false); + float activeCountWidth = (float) rowTextRenderer.getWidth(activeCountText, false); + rowTextRenderer.end(); + + for (int i = 0; i < displayThrows.size(); i++) { + CachedThrow cachedThrow = displayThrows.get(i); + rowTextRenderer.begin(fontScale, true, false); + float rowWidth = (float) rowTextRenderer.getWidth("Eye #" + (i + 1), false) + + (float) rowTextRenderer.getWidth(TriangulatorSolver.formatThrowPoint(cachedThrow), false) + + 36.0f * baseScale; + rowTextRenderer.end(); + maxWidth = Math.max(maxWidth, rowWidth); + } + + int headerButtons = (showClearButton ? 1 : 0) + (showCopyButton ? 1 : 0); + float headerButtonsWidth = headerButtons == 0 ? 0.0f + : ((CLEAR_BUTTON_SIZE * headerButtons) + (HEADER_BUTTON_GAP * Math.max(0, headerButtons - 1)) + 4.0f) * baseScale; + float headerWidth = (TITLE_TEXT_X * baseScale) + titleWidth + + activeLabelWidth + activeCountWidth + + headerButtonsWidth + + ((COUNT_LABEL_OFFSET + 16.0f) * baseScale); + maxWidth = Math.max(maxWidth, headerWidth); + + float rowsHeight = displayThrows.isEmpty() ? 0.0f : displayThrows.size() * (ROW_STEP * baseScale); + float dividerHeight = displayThrows.isEmpty() ? 0.0f : 5.0f * baseScale; + float targetHeight = (CONTENT_START_Y * baseScale) + (SUMMARY_HEIGHT * baseScale) + dividerHeight + rowsHeight; + return new Metrics(headerTextH, rowTextH, maxWidth, activeLabelWidth, activeCountWidth, targetHeight); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/render/TriangulatorPainter.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/render/TriangulatorPainter.java new file mode 100644 index 0000000..6e265a1 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/render/TriangulatorPainter.java @@ -0,0 +1,158 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.render; + +import dev.loki.lovisual.features.gui.hud.HudRenderUtil; +import dev.loki.lovisual.features.gui.hud.HudTextEffects; +import dev.loki.lovisual.features.gui.hud.draggable.impl.ScriptedTriangulatorHudPanel; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.CachedThrow; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver.SolveResult; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorSolver; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.TriangulatorView; +import dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator.model.DisplayState; +import net.minecraft.resources.Identifier; +import net.minecraft.util.Util; + +import java.util.LinkedHashMap; +import java.util.List; + +/** + * Assembles the scripted Triangulator panel: header button hitboxes, row + * data and the final {@link ScriptedTriangulatorHudPanel.Panel} instance. + */ +public final class TriangulatorPainter { + private static final Identifier HEADER_ICON = + Identifier.fromNamespaceAndPath("lovisual", "textures/hud/elements/coords.png"); + private static final float HEADER_HEIGHT = 15.5f; + private static final float CLEAR_BUTTON_RIGHT = 8.0f; + + /** Screen-space hitboxes of the header clear/copy buttons. */ + public record HeaderButtons(float clearX, + float clearY, + float clearSize, + float copyX, + float copyY, + float copySize) { + public static final HeaderButtons NONE = new HeaderButtons(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + } + + private TriangulatorPainter() { + } + + public static HeaderButtons placeButtons(TriangulatorMeasure.Metrics metrics, + float drawX, + float drawY, + float drawWidth, + float drawHeight, + float drawBaseScale, + boolean showClearButton, + boolean showCopyButton) { + float buttonCursorRight = CLEAR_BUTTON_RIGHT * drawBaseScale; + float clearX = 0.0f; + float clearY = 0.0f; + float clearSize = 0.0f; + float copyX = 0.0f; + float copyY = 0.0f; + float copySize = 0.0f; + if (showClearButton) { + clearSize = TriangulatorMeasure.CLEAR_BUTTON_SIZE * drawBaseScale; + clearX = drawX + drawWidth - buttonCursorRight - clearSize; + clearY = drawY + (((HEADER_HEIGHT * drawBaseScale) - clearSize) * 0.5f); + buttonCursorRight += clearSize + (TriangulatorMeasure.HEADER_BUTTON_GAP * drawBaseScale); + } + if (showCopyButton) { + copySize = TriangulatorMeasure.CLEAR_BUTTON_SIZE * drawBaseScale; + copyX = drawX + drawWidth - buttonCursorRight - copySize; + copyY = drawY + (((HEADER_HEIGHT * drawBaseScale) - copySize) * 0.5f); + } + return new HeaderButtons(clearX, clearY, clearSize, copyX, copyY, copySize); + } + + public static int headerIconColor(int baseColor, HudTextEffects.Effect effect, int effectSpeed) { + if (effect == HudTextEffects.Effect.NONE) { + return baseColor; + } + return HudTextEffects.animatedColor( + baseColor, + effect, + effectSpeed, + (float) (Util.getMillis() / 1000.0), + 0.0f + ); + } + + public static List> buildRows(List displayThrows, + SolveResult displayResult, + TriangulatorView view, + double autoResetDistance) { + List> panelRows = ScriptedTriangulatorHudPanel.rows(); + for (int i = 0; i < displayThrows.size(); i++) { + CachedThrow cachedThrow = displayThrows.get(i); + int markerColor = view.markerColor(cachedThrow, displayResult, i, displayThrows.size(), autoResetDistance); + panelRows.add(ScriptedTriangulatorHudPanel.row( + "eye:" + cachedThrow.serial(), + "Eye #" + (i + 1), + TriangulatorSolver.formatThrowPoint(cachedThrow), + view.text(), + markerColor, + markerColor, + HudRenderUtil.scaleAlpha(view.muted(), 0.45f), + 1.0f + )); + } + return panelRows; + } + + public static ScriptedTriangulatorHudPanel.Panel panel(TriangulatorView view, + TriangulatorMeasure.Metrics metrics, + float drawX, + float drawY, + float drawWidth, + float drawHeight, + float drawScale, + float drawBaseScale, + float drawFontScale, + int eyeCount, + boolean blur, + float blurStrength, + int headerIconColor, + boolean showClearButton, + boolean showCopyButton, + DisplayState state, + List> rows) { + return new ScriptedTriangulatorHudPanel.Panel( + view.palette(), + drawX, + drawY, + drawWidth, + drawHeight, + drawScale, + drawBaseScale, + drawFontScale, + metrics.headerTextH() * drawScale, + metrics.rowTextH() * drawScale, + metrics.activeLabelWidth() * drawScale, + metrics.activeCountWidth() * drawScale, + eyeCount, + blur, + blurStrength, + ScriptedTriangulatorHudPanel.idString(HEADER_ICON), + headerIconColor, + showClearButton, + view.danger(), + showCopyButton, + view.counter(), + state.statusText(), + state.primaryLine(), + state.secondaryLine(), + state.confidenceText(), + state.statusColor(), + rows + ); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2D.java index c81da95..2c2e5ae 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2D.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2D.java @@ -39,6 +39,20 @@ import dev.loki.lovisual.render.engine.renderer.ui.UiItemSubmission; import dev.loki.lovisual.render.engine.renderer.ui.UiRenderDispatcher; import dev.loki.lovisual.render.engine.postprocess.PostProcessManager; import dev.loki.lovisual.render.engine.renderer.ui.draw.*; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Chamfered2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Rects2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Strokes2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.fx.Blur2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.fx.Effects2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.fx.Glass2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.fx.Smoke2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.media.Items2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.media.Textures2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.path.Circles2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.path.Connectors2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.path.Lines2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.path.Polygons2D; import dev.loki.lovisual.render.engine.svg.SvgMeshBackend; import dev.loki.lovisual.render.engine.svg.SvgRegistry; import dev.loki.lovisual.render.engine.svg.SvgRenderOptions; @@ -128,13 +142,6 @@ public final class Renderer2D { final boolean textured; final MeshBuilder texturedTriangles; final int[] gradientTmp = new int[4]; - final float[] cornerRadiiTmp = new float[4]; - final double[] rectShapeTmp = new double[256]; - final double[] connectorTmp = new double[64]; - final double[] connectorAnchorTmp = new double[4]; - final int[] polygonIndexTmp = new int[64]; - final int[] polygonVertexTmp = new int[64]; - final double[] progressShapeTmp = new double[256]; double alpha = 1.0; public Renderer2D(boolean textured) { @@ -158,6 +165,15 @@ public final class Renderer2D { return alpha; } + public boolean isTextured() { + return textured; + } + + /** Direct-text triangle mesh; non-null only on the TEXTURE renderer. */ + public MeshBuilder texturedMesh() { + return texturedTriangles; + } + public void shape(UiShape shape, UiPaint paint) { shape(shape, paint, UiStroke.NONE, true); } @@ -175,7 +191,7 @@ public final class Renderer2D { } public void box(UiBoxShape box, UiPaint paint, UiStroke stroke, boolean fill) { - Renderer2DPolygons.box(this, box, paint, stroke, fill); + Polygons2D.box(this, box, paint, stroke, fill); } public void shape(UiShape shape, UiPaint paint, UiStroke stroke, boolean fill) { @@ -368,7 +384,7 @@ public final class Renderer2D { @Nullable String stackCountText, int durabilityThresholdPercent, int durabilityTextColorThresholdPercent) { - Renderer2DItems.submit(this, + Items2D.submit(this, player, stack, x, y, scaleX, scaleY, pivotX, pivotY, pivoted, seed, drawItem, overlayFlags, stackCountText, durabilityThresholdPercent, durabilityTextColorThresholdPercent); } @@ -499,7 +515,7 @@ public final class Renderer2D { double w, double h, int argb) { - Renderer2DTextures.textureQuad(this, samplerView, sampler, x, y, w, h, argb); + Textures2D.textureQuad(this, samplerView, sampler, x, y, w, h, argb); } public void msdfTextureQuad(GpuTextureView samplerView, @@ -528,24 +544,24 @@ public final class Renderer2D { float pxRange, int atlasWidth, int atlasHeight) { - Renderer2DTextures.msdfTextureQuad(this, samplerView, sampler, x, y, w, h, + Textures2D.msdfTextureQuad(this, samplerView, sampler, x, y, w, h, topLeft, topRight, bottomRight, bottomLeft, pxRange, atlasWidth, atlasHeight); } public void quadBatch(Consumer consumer) { - Renderer2DRects.quadBatch(this, consumer); + Rects2D.quadBatch(this, consumer); } public void line(double x1, double y1, double x2, double y2, int argb) { - Renderer2DLines.line(this, x1, y1, x2, y2, argb); + Lines2D.line(this, x1, y1, x2, y2, argb); } public void line(double x1, double y1, double x2, double y2, RenderColor color) { - Renderer2DLines.line(this, x1, y1, x2, y2, color); + Lines2D.line(this, x1, y1, x2, y2, color); } public void boxLines(double x, double y, double width, double height, int argb) { - Renderer2DLines.boxLines(this, x, y, width, height, argb); + Lines2D.boxLines(this, x, y, width, height, argb); } public void quad(double x, double y, double width, double height, int argb) { @@ -553,7 +569,7 @@ public final class Renderer2D { } public void quad(double x, double y, double width, double height, int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - Renderer2DRects.quadCorners(this, x, y, width, height, cTopLeft, cTopRight, cBottomRight, cBottomLeft); + Rects2D.quadCorners(this, x, y, width, height, cTopLeft, cTopRight, cBottomRight, cBottomLeft); } public void roundedRect(double x, double y, double w, double h, @@ -571,7 +587,7 @@ public final class Renderer2D { } public void circle(double cx, double cy, double radius, float softness, int argb) { - Renderer2DCircles.circle(this, cx, cy, radius, softness, argb); + Circles2D.circle(this, cx, cy, radius, softness, argb); } public void circleSoftShadow(double cx, double cy, double radius, float blur, float innerAlpha, int argb) { @@ -585,7 +601,7 @@ public final class Renderer2D { } public void circleStroke(double cx, double cy, double radius, double thickness, float softness, int argb) { - Renderer2DCircles.circleStroke(this, cx, cy, radius, thickness, softness, argb); + Circles2D.circleStroke(this, cx, cy, radius, thickness, softness, argb); } public void arcStroke(double cx, double cy, double radius, double thickness, @@ -651,7 +667,7 @@ public final class Renderer2D { public void arcStrokeQuad(double cx, double cy, double radius, double thickness, float startAngleDeg, float endAngleDeg, float softness, boolean caps, int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - Renderer2DCircles.arcStrokeQuad(this, cx, cy, radius, thickness, startAngleDeg, endAngleDeg, softness, caps, + Circles2D.arcStrokeQuad(this, cx, cy, radius, thickness, startAngleDeg, endAngleDeg, softness, caps, cTopLeft, cTopRight, cBottomRight, cBottomLeft); } @@ -663,7 +679,7 @@ public final class Renderer2D { int colorMode, float angularOffset, float angularCycles, float trackAlpha, float bodyAlpha, float headBoost, float trackGlow, boolean caps) { - Renderer2DOrbiz.orbizRing(this, cx, cy, radius, thickness, startAngleDeg, sweepAngleDeg, + Circles2D.orbizRing(this, cx, cy, radius, thickness, startAngleDeg, sweepAngleDeg, softness, glowRadius, glowStrength, primaryArgb, secondaryArgb, colorMode, angularOffset, angularCycles, trackAlpha, bodyAlpha, headBoost, trackGlow, caps); } @@ -676,7 +692,7 @@ public final class Renderer2D { public void roundedRectStroke(double x, double y, double w, double h, float radius, float softness, float thickness, int argb) { - Renderer2DRectsExtra.roundedRectStroke(this, x, y, w, h, radius, softness, thickness, argb); + Strokes2D.roundedRectStroke(this, x, y, w, h, radius, softness, thickness, argb); } public void roundedRectStroke(double x, double y, double w, double h, @@ -687,14 +703,14 @@ public final class Renderer2D { public void roundedRectStrokeAngularGradient(double x, double y, double w, double h, float radius, float softness, float thickness, int startArgb, int endArgb, float offset) { - Renderer2DRectsExtra.roundedRectStrokeAngularGradient(this, x, y, w, h, radius, softness, thickness, + Strokes2D.roundedRectStrokeAngularGradient(this, x, y, w, h, radius, softness, thickness, startArgb, endArgb, offset); } public void roundedRectStrokeCorners(double x, double y, double w, double h, float radiusTL, float radiusTR, float radiusBR, float radiusBL, float softness, float thickness, int argb) { - Renderer2DRectsExtra.roundedRectStrokeCorners(this, x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL, + Strokes2D.roundedRectStrokeCorners(this, x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL, softness, thickness, argb); } @@ -723,7 +739,7 @@ public final class Renderer2D { float radiusTL, float radiusTR, float radiusBR, float radiusBL, float softness, int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - Renderer2DRects.roundedRectCornersQuad(this, x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL, softness, + Rects2D.roundedRectCornersQuad(this, x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL, softness, cTopLeft, cTopRight, cBottomRight, cBottomLeft); } @@ -772,7 +788,7 @@ public final class Renderer2D { boolean fromRight, int startArgb, int endArgb, float angleDeg, float offsetPx) { - Renderer2DRectsExtra.roundedProgressRectGradient(this, x, y, w, h, radius, softness, progress, fromRight, + Strokes2D.roundedProgressRectGradient(this, x, y, w, h, radius, softness, progress, fromRight, startArgb, endArgb, angleDeg, offsetPx); } @@ -800,7 +816,7 @@ public final class Renderer2D { float flowX, float flowY, float intensity) { - Renderer2DSmokeFill.roundedSmokeFill(this, x, y, w, h, radius, softness, fillRatio, fromRight, + Smoke2D.roundedSmokeFill(this, x, y, w, h, radius, softness, fillRatio, fromRight, firstArgb, secondArgb, thirdArgb, time, smokeScale, smokeMix, octaves, flowX, flowY, intensity); } @@ -849,24 +865,24 @@ public final class Renderer2D { public void roundedRectGlow(double x, double y, double w, double h, float radius, float softness, float glow, int argb) { - Renderer2DRectsGlow.roundedRectGlow(this, x, y, w, h, radius, softness, glow, argb); + Effects2D.roundedRectGlow(this, x, y, w, h, radius, softness, glow, argb); } public void roundedRectShadow(double x, double y, double w, double h, float radius, float softness, float spread, int argb) { - Renderer2DRectsGlow.roundedRectShadow(this, x, y, w, h, radius, softness, spread, argb); + Effects2D.roundedRectShadow(this, x, y, w, h, radius, softness, spread, argb); } public void roundedRectSoftShadow(double x, double y, double w, double h, float radius, float blur, float innerAlpha, int argb) { - Renderer2DRectsGlow.roundedRectSoftShadow(this, x, y, w, h, radius, blur, innerAlpha, argb); + Effects2D.roundedRectSoftShadow(this, x, y, w, h, radius, blur, innerAlpha, argb); } public void radialGlowMasked(double x, double y, double w, double h, float radius, float softness, float glowRadius, float cx, float cy, int argb) { - Renderer2DRectsGlow.radialGlowMasked(this, x, y, w, h, radius, softness, glowRadius, cx, cy, argb); + Effects2D.radialGlowMasked(this, x, y, w, h, radius, softness, glowRadius, cx, cy, argb); } public void roundedRectGradientQuad(double x, double y, double w, double h, @@ -885,14 +901,14 @@ public final class Renderer2D { double maskX, double maskY, double maskW, double maskH, float radius, float softness, int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - Renderer2DRects.roundedRectMaskedQuad(this, x, y, w, h, maskX, maskY, maskW, maskH, radius, softness, + Rects2D.roundedRectMaskedQuad(this, x, y, w, h, maskX, maskY, maskW, maskH, radius, softness, cTopLeft, cTopRight, cBottomRight, cBottomLeft); } public void roundedRectStrokeGradientQuad(double x, double y, double w, double h, float radius, float softness, float thickness, int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - Renderer2DRectsExtra.roundedRectStrokeGradientQuad(this, x, y, w, h, radius, softness, thickness, + Strokes2D.roundedRectStrokeGradientQuad(this, x, y, w, h, radius, softness, thickness, cTopLeft, cTopRight, cBottomRight, cBottomLeft); } @@ -907,7 +923,7 @@ public final class Renderer2D { float radius, float softness, double texX1, double texY1, double texX2, double texY2, int argb, GpuTextureView samplerView, GpuSampler sampler) { - Renderer2DTextures.roundedTexRect(this, x, y, w, h, radius, softness, texX1, texY1, texX2, texY2, + Textures2D.roundedTexRect(this, x, y, w, h, radius, softness, texX1, texY1, texX2, texY2, argb, samplerView, sampler); } @@ -915,7 +931,7 @@ public final class Renderer2D { float radius, float softness, double texX1, double texY1, double texX2, double texY2, int argb, GpuTextureView samplerView, GpuSampler sampler) { - Renderer2DTextures.roundedTexMaskRect(this, x, y, w, h, radius, softness, texX1, texY1, texX2, texY2, + Textures2D.roundedTexMaskRect(this, x, y, w, h, radius, softness, texX1, texY1, texX2, texY2, argb, samplerView, sampler); } @@ -1103,12 +1119,12 @@ public final class Renderer2D { (float) chamferTR, (float) chamferBR, (float) chamferBL, - cornerRadiiTmp); + Batch2D.CORNER_RADII); chamferedRectQuadAxes(x, y, w, h, - cornerRadiiTmp[0], cornerRadiiTmp[0], - cornerRadiiTmp[1], cornerRadiiTmp[1], - cornerRadiiTmp[2], cornerRadiiTmp[2], - cornerRadiiTmp[3], cornerRadiiTmp[3], + Batch2D.CORNER_RADII[0], Batch2D.CORNER_RADII[0], + Batch2D.CORNER_RADII[1], Batch2D.CORNER_RADII[1], + Batch2D.CORNER_RADII[2], Batch2D.CORNER_RADII[2], + Batch2D.CORNER_RADII[3], Batch2D.CORNER_RADII[3], cTopLeft, cTopRight, cBottomRight, cBottomLeft); } @@ -1118,7 +1134,7 @@ public final class Renderer2D { double chamferBRX, double chamferBRY, double chamferBLX, double chamferBLY, int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - Renderer2DChamfered.chamferedRectQuadAxes(this, x, y, w, h, + Chamfered2D.chamferedRectQuadAxes(this, x, y, w, h, chamferTLX, chamferTLY, chamferTRX, chamferTRY, chamferBRX, chamferBRY, chamferBLX, chamferBLY, cTopLeft, cTopRight, cBottomRight, cBottomLeft); } @@ -1135,7 +1151,7 @@ public final class Renderer2D { double chamferTL, double chamferTR, double chamferBR, double chamferBL, double thickness, int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - Renderer2DChamfered.chamferedRectStrokeQuad(this, x, y, w, h, + Chamfered2D.chamferedRectStrokeQuad(this, x, y, w, h, chamferTL, chamferTR, chamferBR, chamferBL, thickness, cTopLeft, cTopRight, cBottomRight, cBottomLeft); } @@ -1161,35 +1177,35 @@ public final class Renderer2D { } public void notchedRect(double x, double y, double width, double height, - double notchWidth, double notchDepth, int argb) { - int count = buildNotchedRect(rectShapeTmp, x, y, width, height, notchWidth, notchDepth); - Renderer2DPolygons.polygon(this, rectShapeTmp, count, argb); + double notchWidth, double notchDepth, int argb) { + int count = buildNotchedRect(Batch2D.RECT_SHAPE, x, y, width, height, notchWidth, notchDepth); + Polygons2D.polygon(this, Batch2D.RECT_SHAPE, count, argb); } public void notchedRectGradient(double x, double y, double width, double height, double notchWidth, double notchDepth, int startArgb, int endArgb, float angleDeg, float offsetPx) { - int count = buildNotchedRect(rectShapeTmp, x, y, width, height, notchWidth, notchDepth); - Renderer2DPolygons.polygonGradient(this, rectShapeTmp, count, x, y, width, height, startArgb, endArgb, angleDeg, offsetPx); + int count = buildNotchedRect(Batch2D.RECT_SHAPE, x, y, width, height, notchWidth, notchDepth); + Polygons2D.polygonGradient(this, Batch2D.RECT_SHAPE, count, x, y, width, height, startArgb, endArgb, angleDeg, offsetPx); } public void notchedRectStroke(double x, double y, double width, double height, double notchWidth, double notchDepth, double thickness, int argb) { - int count = buildNotchedRect(rectShapeTmp, x, y, width, height, notchWidth, notchDepth); - Renderer2DPolygons.polygonStroke(this, rectShapeTmp, count, thickness, true, argb); + int count = buildNotchedRect(Batch2D.RECT_SHAPE, x, y, width, height, notchWidth, notchDepth); + Polygons2D.polygonStroke(this, Batch2D.RECT_SHAPE, count, thickness, true, argb); } public void notchedRectStrokeGradient(double x, double y, double width, double height, double notchWidth, double notchDepth, double thickness, int startArgb, int endArgb, float angleDeg, float offsetPx) { - int count = buildNotchedRect(rectShapeTmp, x, y, width, height, notchWidth, notchDepth); - Renderer2DPolygons.polylineLinearGradient(this, rectShapeTmp, count, thickness, true, + int count = buildNotchedRect(Batch2D.RECT_SHAPE, x, y, width, height, notchWidth, notchDepth); + Polygons2D.polylineLinearGradient(this, Batch2D.RECT_SHAPE, count, thickness, true, x, y, width, height, startArgb, endArgb, angleDeg, offsetPx, false); } public void beveledRect(double x, double y, double width, double height, double bevel, int fillArgb, int highlightArgb, int shadowArgb) { - Renderer2DChamfered.beveledRect(this, x, y, width, height, bevel, fillArgb, highlightArgb, shadowArgb); + Chamfered2D.beveledRect(this, x, y, width, height, bevel, fillArgb, highlightArgb, shadowArgb); } public void beveledRectStroke(double x, double y, double width, double height, @@ -1200,7 +1216,7 @@ public final class Renderer2D { public void beveledRectGradient(double x, double y, double width, double height, double bevel, int startArgb, int endArgb, int highlightArgb, int shadowArgb, float angleDeg, float offsetPx) { - Renderer2DChamfered.beveledRectGradient(this, x, y, width, height, bevel, startArgb, endArgb, + Chamfered2D.beveledRectGradient(this, x, y, width, height, bevel, startArgb, endArgb, highlightArgb, shadowArgb, angleDeg, offsetPx); } @@ -1211,12 +1227,12 @@ public final class Renderer2D { } public void connector(double x1, double y1, double x2, double y2, double thickness, int argb) { - Renderer2DConnectors.connector(this, x1, y1, x2, y2, thickness, argb); + Connectors2D.connector(this, x1, y1, x2, y2, thickness, argb); } public void connectorGradient(double x1, double y1, double x2, double y2, double thickness, int startArgb, int endArgb) { - Renderer2DConnectors.connectorGradient(this, x1, y1, x2, y2, thickness, startArgb, endArgb); + Connectors2D.connectorGradient(this, x1, y1, x2, y2, thickness, startArgb, endArgb); } public void wire(double x1, double y1, double x2, double y2, double thickness, int argb) { @@ -1248,13 +1264,13 @@ public final class Renderer2D { public void roundedRectConnector(double x1, double y1, double w1, double h1, double radius1, double x2, double y2, double w2, double h2, double radius2, double thickness, int argb) { - Renderer2DConnectors.roundedRectConnector(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, thickness, argb); + Connectors2D.roundedRectConnector(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, thickness, argb); } public void roundedRectConnectorGradient(double x1, double y1, double w1, double h1, double radius1, double x2, double y2, double w2, double h2, double radius2, double thickness, int startArgb, int endArgb) { - Renderer2DConnectors.roundedRectConnectorGradient(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, + Connectors2D.roundedRectConnectorGradient(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, thickness, startArgb, endArgb); } @@ -1264,13 +1280,13 @@ public final class Renderer2D { public void roundedRectNodeGraphEdge(double x1, double y1, double w1, double h1, double radius1, double x2, double y2, double w2, double h2, double radius2, double thickness, int argb) { - Renderer2DConnectors.roundedRectNodeGraphEdge(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, thickness, argb); + Connectors2D.roundedRectNodeGraphEdge(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, thickness, argb); } public void roundedRectNodeGraphEdgeGradient(double x1, double y1, double w1, double h1, double radius1, double x2, double y2, double w2, double h2, double radius2, double thickness, int startArgb, int endArgb) { - Renderer2DConnectors.roundedRectNodeGraphEdgeGradient(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, + Connectors2D.roundedRectNodeGraphEdgeGradient(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, thickness, startArgb, endArgb); } @@ -1290,14 +1306,14 @@ public final class Renderer2D { public void roundedRectOrthogonalConnector(double x1, double y1, double w1, double h1, double radius1, double x2, double y2, double w2, double h2, double radius2, double midX, double thickness, int argb) { - Renderer2DConnectors.roundedRectOrthogonalConnector(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, + Connectors2D.roundedRectOrthogonalConnector(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, midX, thickness, argb); } public void roundedRectOrthogonalConnectorGradient(double x1, double y1, double w1, double h1, double radius1, double x2, double y2, double w2, double h2, double radius2, double midX, double thickness, int startArgb, int endArgb) { - Renderer2DConnectors.roundedRectOrthogonalConnectorGradient(this, x1, y1, w1, h1, radius1, + Connectors2D.roundedRectOrthogonalConnectorGradient(this, x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2, midX, thickness, startArgb, endArgb); } @@ -1308,12 +1324,12 @@ public final class Renderer2D { public void orthogonalConnector(double x1, double y1, double x2, double y2, double midX, double thickness, int argb) { - Renderer2DConnectors.orthogonalConnector(this, x1, y1, x2, y2, midX, thickness, argb); + Connectors2D.orthogonalConnector(this, x1, y1, x2, y2, midX, thickness, argb); } public void orthogonalConnectorGradient(double x1, double y1, double x2, double y2, double midX, double thickness, int startArgb, int endArgb) { - Renderer2DConnectors.orthogonalConnectorGradient(this, x1, y1, x2, y2, midX, thickness, startArgb, endArgb); + Connectors2D.orthogonalConnectorGradient(this, x1, y1, x2, y2, midX, thickness, startArgb, endArgb); } public void bezierConnector(double x1, double y1, @@ -1332,7 +1348,7 @@ public final class Renderer2D { int segments, double thickness, int argb) { - Renderer2DConnectors.bezierConnector(this, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, thickness, argb); + Connectors2D.bezierConnector(this, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, thickness, argb); } public void bezierConnectorGradient(double x1, double y1, @@ -1343,33 +1359,33 @@ public final class Renderer2D { double thickness, int startArgb, int endArgb) { - Renderer2DConnectors.bezierConnectorGradient(this, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, thickness, startArgb, endArgb); + Connectors2D.bezierConnectorGradient(this, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments, thickness, startArgb, endArgb); } public void nodeGraphEdge(double x1, double y1, double x2, double y2, double thickness, int argb) { - Renderer2DConnectors.nodeGraphEdge(this, x1, y1, x2, y2, thickness, argb); + Connectors2D.nodeGraphEdge(this, x1, y1, x2, y2, thickness, argb); } public void nodeGraphEdgeGradient(double x1, double y1, double x2, double y2, double thickness, int startArgb, int endArgb) { - Renderer2DConnectors.nodeGraphEdgeGradient(this, x1, y1, x2, y2, thickness, startArgb, endArgb); + Connectors2D.nodeGraphEdgeGradient(this, x1, y1, x2, y2, thickness, startArgb, endArgb); } public void spline(double[] points, int pointCount, double thickness, boolean closed, int argb) { if (points == null || pointCount < 2) return; - Renderer2DPolygons.polyline(this, points, pointCount, thickness, closed, argb, true); + Polygons2D.polyline(this, points, pointCount, thickness, closed, argb, true); } public void splineGradient(double[] points, int pointCount, double thickness, boolean closed, int startArgb, int endArgb) { if (points == null || pointCount < 2) return; - Renderer2DPolygons.polylineGradient(this, points, pointCount, thickness, closed, startArgb, endArgb, true); + Polygons2D.polylineGradient(this, points, pointCount, thickness, closed, startArgb, endArgb, true); } public void texQuad(double x, double y, double width, double height, double texX1, double texY1, double texX2, double texY2, int argb) { - Renderer2DTextures.texQuad(this, x, y, width, height, texX1, texY1, texX2, texY2, argb); + Textures2D.texQuad(this, x, y, width, height, texX1, texY1, texX2, texY2, argb); } public void liquidGlassCircle(double cx, double cy, double radius, @@ -1745,31 +1761,31 @@ public final class Renderer2D { float fresnelMix, float distortPx, float squirclePower) { - Renderer2DLiquidGlass.liquidGlassRectCorners(this, x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL, softness, + Glass2D.liquidGlassRectCorners(this, x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL, softness, tintArgb, glassAlpha, blurAlpha, fresnelPower, fresnelAlpha, baseAlpha, fresnelMix, distortPx, squirclePower); } public void blurRect(double x, double y, double w, double h, float radius, float quality, float brightness, float alpha, int ignoredTintRgb) { - Renderer2DBlur.blurRect(this, x, y, w, h, radius, quality, brightness, alpha, ignoredTintRgb, UiBatchType.BLUR); + Blur2D.blurRect(this, x, y, w, h, radius, quality, brightness, alpha, ignoredTintRgb); } public void glassBlurRect(double x, double y, double w, double h, float radius, float quality, float brightness, float alpha, int ignoredTintRgb) { - Renderer2DBlur.glassBlurRect(this, x, y, w, h, radius, quality, brightness, alpha, ignoredTintRgb); + Blur2D.glassBlurRect(this, x, y, w, h, radius, quality, brightness, alpha, ignoredTintRgb); } public void blurChamferedRect(double x, double y, double w, double h, double chamfer, float quality, float brightness, float alpha, int ignoredTintRgb) { - Renderer2DBlur.blurChamferedRect(this, x, y, w, h, chamfer, quality, brightness, alpha, ignoredTintRgb); + Blur2D.blurChamferedRect(this, x, y, w, h, chamfer, quality, brightness, alpha, ignoredTintRgb); } public void blurComposite(Consumer consumer) { - Renderer2DBlur.blurComposite(this, consumer); + Blur2D.blurComposite(this, consumer); } public void polygon(double[] points, int pointCount, int argb) { - Renderer2DPolygons.polygon(this, points, pointCount, argb); + Polygons2D.polygon(this, points, pointCount, argb); } // ---- Package-private plumbing for helpers in the same package ------------ @@ -2077,7 +2093,7 @@ public final class Renderer2D { private final MeshBuilder mesh; private final float globalAlpha; - BlurComposite(MeshBuilder mesh, float globalAlpha) { + public BlurComposite(MeshBuilder mesh, float globalAlpha) { this.mesh = mesh; this.globalAlpha = globalAlpha; } @@ -2132,7 +2148,7 @@ public final class Renderer2D { public static final class QuadBatch { private final MeshBuilder mesh; - QuadBatch(MeshBuilder mesh) { + public QuadBatch(MeshBuilder mesh) { this.mesh = mesh; } diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DBlur.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DBlur.java deleted file mode 100644 index 3532c45..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DBlur.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import com.mojang.blaze3d.pipeline.RenderTarget; -import com.mojang.blaze3d.textures.GpuSampler; -import com.mojang.blaze3d.textures.GpuTextureView; -import dev.loki.lovisual.render.engine.postprocess.PostProcessManager; -import dev.loki.lovisual.render.engine.renderer.ui.BlurSource; -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiEffectSpec; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; -import net.minecraft.client.Minecraft; -import net.minecraft.util.Mth; - -import java.util.function.Consumer; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clamp01; -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampChamfer; - -final class Renderer2DBlur { - private Renderer2DBlur() { - } - - static Renderer2D.BlurQuality legacyBlurQuality(float legacyQuality) { - if (legacyQuality >= 32.0f) return Renderer2D.BlurQuality.ULTRA; - if (legacyQuality >= 12.0f) return Renderer2D.BlurQuality.HIGH; - if (legacyQuality >= 5.0f) return Renderer2D.BlurQuality.MEDIUM; - return Renderer2D.BlurQuality.LOW; - } - - static float legacyKawaseOffset(float legacyQuality) { - if (!Float.isFinite(legacyQuality)) return Renderer2D.DEFAULT_KAWASE_OFFSET_PX; - return Mth.clamp(legacyQuality / 18.0f, 0.85f, 3.25f); - } - - static BlurSource getBlurSource(Renderer2D r) { - Minecraft mc = Minecraft.getInstance(); - if (mc == null) return null; - RenderTarget fb = mc.gameRenderer.mainRenderTarget(); - if (fb == null) return null; - GpuTextureView src = fb.getColorTextureView(); - if (src == null) return null; - GpuSampler sampler = PostProcessManager.getSampler(); - if (sampler == null) return null; - return new BlurSource(src, sampler); - } - - static void blurRect(Renderer2D r, double x, double y, double w, double h, - float radius, float quality, float brightness, float alpha, int ignoredTintRgb, - UiBatchType batchType) { - if (w <= 0.0 || h <= 0.0) return; - if (alpha <= 0.001f) return; - - r.effect(UiEffectSpec.blur(UiShape.roundedRect(x, y, w, h, radius), radius, ignoredTintRgb)); - BlurSource source = getBlurSource(r); - if (source == null) return; - - boolean auto = r.beginAutoBatch(); - try { - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreateBlur(batchType, source.view, source.sampler, - legacyBlurQuality(quality), legacyKawaseOffset(quality)); - if (batch == null) return; - MeshBuilder mesh = batch.mesh; - mesh.alpha = 1.0; - - float finalAlpha = (float) (alpha * r.alpha); - mesh.ensureQuadCapacity(); - int i1 = mesh.vec2(x, y).local2(x, y).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); - mesh.quad(i1, i2, i3, i4); - } finally { - r.endAutoBatch(auto); - } - } - - static void glassBlurRect(Renderer2D r, double x, double y, double w, double h, - float radius, float quality, float brightness, float alpha, int ignoredTintRgb) { - if (w <= 0.0 || h <= 0.0) return; - if (alpha <= 0.001f) return; - - r.effect(UiEffectSpec.blur(UiShape.roundedRect(x, y, w, h, radius), radius, ignoredTintRgb)); - BlurSource source = getBlurSource(r); - if (source == null) return; - - boolean auto = r.beginAutoBatch(); - try { - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreateBlur(UiBatchType.GLASS_BLUR, source.view, source.sampler, - Renderer2D.BlurQuality.HIGH, legacyKawaseOffset(quality)); - if (batch == null) return; - MeshBuilder mesh = batch.mesh; - mesh.alpha = 1.0; - - float finalAlpha = clamp01((float) (alpha * r.alpha)); - if (finalAlpha <= 0.001f) return; - - int a = Math.max(0, Math.min(255, Math.round(finalAlpha * 255.0f))); - float smoothness = 2.0f; - float rad = Math.max(0.0f, radius); - - mesh.ensureQuadCapacity(); - int i1 = mesh.vec2(x, y).local2(x, y).color(255, 255, 255, a) - .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(255, 255, 255, a) - .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(255, 255, 255, a) - .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(255, 255, 255, a) - .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); - mesh.quad(i1, i2, i3, i4); - } finally { - r.endAutoBatch(auto); - } - } - - static void blurChamferedRect(Renderer2D r, double x, double y, double w, double h, - double chamfer, float quality, float brightness, float alpha, int ignoredTintRgb) { - if (w <= 0.0 || h <= 0.0) return; - if (alpha <= 0.001f) return; - float cut = clampChamfer(chamfer, w, h); - if (cut <= 0.0f) { - r.blurRect(x, y, w, h, 0.0f, quality, brightness, alpha, ignoredTintRgb); - return; - } - r.blurRect(x, y, w, h, -cut, quality, brightness, alpha, ignoredTintRgb); - } - - static void blurComposite(Renderer2D r, Consumer consumer) { - if (consumer == null) return; - - BlurSource source = getBlurSource(r); - if (source == null) return; - - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreateBlur(UiBatchType.BLUR_COMPLEX, source.view, source.sampler, - Renderer2D.DEFAULT_BLUR_QUALITY, Renderer2D.DEFAULT_KAWASE_OFFSET_PX); - if (batch == null) return; - - MeshBuilder mesh = batch.mesh; - mesh.alpha = 1.0; - - try { - consumer.accept(new Renderer2D.BlurComposite(mesh, (float) r.alpha)); - } finally { - r.endAutoBatch(auto); - } - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DChamfered.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DChamfered.java deleted file mode 100644 index 0a04115..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DChamfered.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampChamferAxis; -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeChamfers; - -final class Renderer2DChamfered { - private Renderer2DChamfered() { - } - - static void chamferedRectQuadAxes(Renderer2D r, double x, double y, double w, double h, - double chamferTLX, double chamferTLY, - double chamferTRX, double chamferTRY, - double chamferBRX, double chamferBRY, - double chamferBLX, double chamferBLY, - int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - r.shape(UiShape.chamferedRectAxes(x, y, w, h, chamferTLX, chamferTLY, chamferTRX, chamferTRY, chamferBRX, chamferBRY, chamferBLX, chamferBLY), - UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.CHAMFERED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int tlA = (cTopLeft >>> 24) & 0xFF; - int tlR = (cTopLeft >>> 16) & 0xFF; - int tlG = (cTopLeft >>> 8) & 0xFF; - int tlB = cTopLeft & 0xFF; - int trA = (cTopRight >>> 24) & 0xFF; - int trR = (cTopRight >>> 16) & 0xFF; - int trG = (cTopRight >>> 8) & 0xFF; - int trB = cTopRight & 0xFF; - int brA = (cBottomRight >>> 24) & 0xFF; - int brR = (cBottomRight >>> 16) & 0xFF; - int brG = (cBottomRight >>> 8) & 0xFF; - int brB = cBottomRight & 0xFF; - int blA = (cBottomLeft >>> 24) & 0xFF; - int blR = (cBottomLeft >>> 16) & 0xFF; - int blG = (cBottomLeft >>> 8) & 0xFF; - int blB = cBottomLeft & 0xFF; - - float tlX = clampChamferAxis(chamferTLX, w); - float trX = clampChamferAxis(chamferTRX, w); - float brX = clampChamferAxis(chamferBRX, w); - float blX = clampChamferAxis(chamferBLX, w); - float tlY = clampChamferAxis(chamferTLY, h); - float trY = clampChamferAxis(chamferTRY, h); - float brY = clampChamferAxis(chamferBRY, h); - float blY = clampChamferAxis(chamferBLY, h); - - int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } - - static void chamferedRectStrokeQuad(Renderer2D r, double x, double y, double w, double h, - double chamferTL, double chamferTR, double chamferBR, double chamferBL, - double thickness, - int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - r.shapeStroke(UiShape.chamferedRect(x, y, w, h, chamferTL, chamferTR, chamferBR, chamferBL), - UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft), UiStroke.of(thickness)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.CHAMFERED_STROKE, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int tlA = (cTopLeft >>> 24) & 0xFF; - int tlR = (cTopLeft >>> 16) & 0xFF; - int tlG = (cTopLeft >>> 8) & 0xFF; - int tlB = cTopLeft & 0xFF; - int trA = (cTopRight >>> 24) & 0xFF; - int trR = (cTopRight >>> 16) & 0xFF; - int trG = (cTopRight >>> 8) & 0xFF; - int trB = cTopRight & 0xFF; - int brA = (cBottomRight >>> 24) & 0xFF; - int brR = (cBottomRight >>> 16) & 0xFF; - int brG = (cBottomRight >>> 8) & 0xFF; - int brB = cBottomRight & 0xFF; - int blA = (cBottomLeft >>> 24) & 0xFF; - int blR = (cBottomLeft >>> 16) & 0xFF; - int blG = (cBottomLeft >>> 8) & 0xFF; - int blB = cBottomLeft & 0xFF; - float[] cornerRadiiTmp = r.cornerRadiiTmp; - normalizeChamfers(w, h, - (float) chamferTL, - (float) chamferTR, - (float) chamferBR, - (float) chamferBL, - cornerRadiiTmp); - float stroke = (float) Math.max(0.0, thickness); - - int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(stroke, 0f, 0f, 0f).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(stroke, 0f, 0f, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(stroke, 0f, 0f, 0f).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(stroke, 0f, 0f, 0f).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } - - static void beveledRect(Renderer2D r, double x, double y, double width, double height, - double bevel, int fillArgb, int highlightArgb, int shadowArgb) { - r.chamferedRect(x, y, width, height, bevel, fillArgb); - double t = Math.max(1.0, Math.min(Math.min(Math.abs(width), Math.abs(height)) * 0.25, bevel)); - double[] connectorTmp = r.connectorTmp; - connectorTmp[0] = x + bevel; - connectorTmp[1] = y; - connectorTmp[2] = x + width - bevel; - connectorTmp[3] = y; - connectorTmp[4] = x + width; - connectorTmp[5] = y + bevel; - Renderer2DPolygons.polyline(r, connectorTmp, 3, t, false, highlightArgb, false); - connectorTmp[0] = x + width; - connectorTmp[1] = y + height - bevel; - connectorTmp[2] = x + width - bevel; - connectorTmp[3] = y + height; - connectorTmp[4] = x + bevel; - connectorTmp[5] = y + height; - connectorTmp[6] = x; - connectorTmp[7] = y + height - bevel; - Renderer2DPolygons.polyline(r, connectorTmp, 4, t, false, shadowArgb, false); - } - - static void beveledRectGradient(Renderer2D r, double x, double y, double width, double height, - double bevel, int startArgb, int endArgb, - int highlightArgb, int shadowArgb, float angleDeg, float offsetPx) { - r.chamferedRectGradient(x, y, width, height, bevel, startArgb, endArgb, angleDeg, offsetPx); - double t = Math.max(1.0, Math.min(Math.min(Math.abs(width), Math.abs(height)) * 0.25, bevel)); - double[] connectorTmp = r.connectorTmp; - connectorTmp[0] = x + bevel; - connectorTmp[1] = y; - connectorTmp[2] = x + width - bevel; - connectorTmp[3] = y; - connectorTmp[4] = x + width; - connectorTmp[5] = y + bevel; - Renderer2DPolygons.polyline(r, connectorTmp, 3, t, false, highlightArgb, false); - connectorTmp[0] = x + width; - connectorTmp[1] = y + height - bevel; - connectorTmp[2] = x + width - bevel; - connectorTmp[3] = y + height; - connectorTmp[4] = x + bevel; - connectorTmp[5] = y + height; - connectorTmp[6] = x; - connectorTmp[7] = y + height - bevel; - Renderer2DPolygons.polyline(r, connectorTmp, 4, t, false, shadowArgb, false); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DCircles.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DCircles.java deleted file mode 100644 index c4c563d..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DCircles.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; -import net.minecraft.util.Mth; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeDegrees; - -final class Renderer2DCircles { - private Renderer2DCircles() { - } - - static void circle(Renderer2D r, double cx, double cy, double radius, float softness, int argb) { - r.shape(UiShape.circle(cx, cy, radius), UiPaint.solid(argb)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.CIRCLE, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - - double outerRadius = radius + Math.max(0.0f, softness); - double x = cx - outerRadius; - double y = cy - outerRadius; - double w = outerRadius * 2.0; - double h = outerRadius * 2.0; - - int i1 = mesh.vec2(x, y).local2(x, y).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, 0f, 0f).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, 0f, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, 0f, 0f).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, 0f, 0f).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } - - static void circleStroke(Renderer2D r, double cx, double cy, double radius, double thickness, float softness, int argb) { - r.shapeStroke(UiShape.circle(cx, cy, radius), UiPaint.solid(argb), UiStroke.of(thickness)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.CIRCLE, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - - float stroke = (float) Math.max(0.0, thickness); - double outerRadius = radius + stroke * 0.5 + softness; - double x = cx - outerRadius; - double y = cy - outerRadius; - double w = outerRadius * 2.0; - double h = outerRadius * 2.0; - - int i1 = mesh.vec2(x, y).local2(x, y).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, stroke, 0f).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, stroke, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, stroke, 0f).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4((float) radius, softness, stroke, 0f).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } - - static void arcStrokeQuad(Renderer2D r, double cx, double cy, double radius, double thickness, - float startAngleDeg, float endAngleDeg, float softness, boolean caps, - int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - float start = normalizeDegrees(startAngleDeg); - float end = normalizeDegrees(endAngleDeg); - float sweep = end - start; - if (sweep <= 0.0f) { - sweep += 360.0f; - } - if (sweep <= 0.001f) { - return; - } - - r.shapeStroke(UiShape.arc(cx, cy, radius, start, end), UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft), UiStroke.of(thickness)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ARC, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - float stroke = (float) Math.max(0.0, thickness); - double outerRadius = radius + stroke * 0.5 + softness; - double x = cx - outerRadius; - double y = cy - outerRadius; - double w = outerRadius * 2.0; - double h = outerRadius * 2.0; - - int tlA = (cTopLeft >>> 24) & 0xFF; - int tlR = (cTopLeft >>> 16) & 0xFF; - int tlG = (cTopLeft >>> 8) & 0xFF; - int tlB = cTopLeft & 0xFF; - int trA = (cTopRight >>> 24) & 0xFF; - int trR = (cTopRight >>> 16) & 0xFF; - int trG = (cTopRight >>> 8) & 0xFF; - int trB = cTopRight & 0xFF; - int brA = (cBottomRight >>> 24) & 0xFF; - int brR = (cBottomRight >>> 16) & 0xFF; - int brG = (cBottomRight >>> 8) & 0xFF; - int brB = cBottomRight & 0xFF; - int blA = (cBottomLeft >>> 24) & 0xFF; - int blR = (cBottomLeft >>> 16) & 0xFF; - int blG = (cBottomLeft >>> 8) & 0xFF; - int blB = cBottomLeft & 0xFF; - - int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(x, y, w, h) - .vec4((float) radius, softness, stroke, start).vec4(end, caps ? 1f : 0f, 0f, 0f).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(x, y, w, h) - .vec4((float) radius, softness, stroke, start).vec4(end, caps ? 1f : 0f, 0f, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h) - .vec4((float) radius, softness, stroke, start).vec4(end, caps ? 1f : 0f, 0f, 0f).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h) - .vec4((float) radius, softness, stroke, start).vec4(end, caps ? 1f : 0f, 0f, 0f).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DConnectors.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DConnectors.java deleted file mode 100644 index 5077ed6..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DConnectors.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.buildBezier; -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.roundedRectAnchor; - -final class Renderer2DConnectors { - private Renderer2DConnectors() { - } - - static void connector(Renderer2D r, double x1, double y1, double x2, double y2, double thickness, int argb) { - double[] c = r.connectorTmp; - c[0] = x1; - c[1] = y1; - c[2] = x2; - c[3] = y2; - Renderer2DPolygons.polyline(r, c, 2, thickness, false, argb, true); - } - - static void connectorGradient(Renderer2D r, double x1, double y1, double x2, double y2, - double thickness, int startArgb, int endArgb) { - double[] c = r.connectorTmp; - c[0] = x1; - c[1] = y1; - c[2] = x2; - c[3] = y2; - Renderer2DPolygons.polylineGradient(r, c, 2, thickness, false, startArgb, endArgb, true); - } - - static void orthogonalConnector(Renderer2D r, double x1, double y1, double x2, double y2, - double midX, double thickness, int argb) { - double[] c = r.connectorTmp; - c[0] = x1; - c[1] = y1; - c[2] = midX; - c[3] = y1; - c[4] = midX; - c[5] = y2; - c[6] = x2; - c[7] = y2; - Renderer2DPolygons.polyline(r, c, 4, thickness, false, argb, true); - } - - static void orthogonalConnectorGradient(Renderer2D r, double x1, double y1, double x2, double y2, - double midX, double thickness, int startArgb, int endArgb) { - double[] c = r.connectorTmp; - c[0] = x1; - c[1] = y1; - c[2] = midX; - c[3] = y1; - c[4] = midX; - c[5] = y2; - c[6] = x2; - c[7] = y2; - Renderer2DPolygons.polylineGradient(r, c, 4, thickness, false, startArgb, endArgb, true); - } - - static void bezierConnector(Renderer2D r, double x1, double y1, - double cx1, double cy1, - double cx2, double cy2, - double x2, double y2, - int segments, - double thickness, - int argb) { - double[] c = r.connectorTmp; - int count = buildBezier(c, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments); - Renderer2DPolygons.polyline(r, c, count, thickness, false, argb, false); - } - - static void bezierConnectorGradient(Renderer2D r, double x1, double y1, - double cx1, double cy1, - double cx2, double cy2, - double x2, double y2, - int segments, - double thickness, - int startArgb, - int endArgb) { - double[] c = r.connectorTmp; - int count = buildBezier(c, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments); - Renderer2DPolygons.polylineGradient(r, c, count, thickness, false, startArgb, endArgb, false); - } - - static void nodeGraphEdge(Renderer2D r, double x1, double y1, double x2, double y2, double thickness, int argb) { - double dx = Math.abs(x2 - x1); - double c = Math.max(24.0, dx * 0.5); - bezierConnector(r, x1, y1, x1 + c, y1, x2 - c, y2, x2, y2, 20, thickness, argb); - } - - static void nodeGraphEdgeGradient(Renderer2D r, double x1, double y1, double x2, double y2, - double thickness, int startArgb, int endArgb) { - double dx = Math.abs(x2 - x1); - double c = Math.max(24.0, dx * 0.5); - bezierConnectorGradient(r, x1, y1, x1 + c, y1, x2 - c, y2, x2, y2, 20, thickness, startArgb, endArgb); - } - - static void roundedRectConnector(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, - double x2, double y2, double w2, double h2, double radius2, - double thickness, int argb) { - double[] a = r.connectorAnchorTmp; - roundedRectAnchor(a, 0, x1, y1, w1, h1, radius1, x2 + w2 * 0.5, y2 + h2 * 0.5); - roundedRectAnchor(a, 2, x2, y2, w2, h2, radius2, x1 + w1 * 0.5, y1 + h1 * 0.5); - connector(r, a[0], a[1], a[2], a[3], thickness, argb); - } - - static void roundedRectConnectorGradient(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, - double x2, double y2, double w2, double h2, double radius2, - double thickness, int startArgb, int endArgb) { - double[] a = r.connectorAnchorTmp; - roundedRectAnchor(a, 0, x1, y1, w1, h1, radius1, x2 + w2 * 0.5, y2 + h2 * 0.5); - roundedRectAnchor(a, 2, x2, y2, w2, h2, radius2, x1 + w1 * 0.5, y1 + h1 * 0.5); - connectorGradient(r, a[0], a[1], a[2], a[3], thickness, startArgb, endArgb); - } - - static void roundedRectNodeGraphEdge(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, - double x2, double y2, double w2, double h2, double radius2, - double thickness, int argb) { - double[] a = r.connectorAnchorTmp; - roundedRectAnchor(a, 0, x1, y1, w1, h1, radius1, x2 + w2 * 0.5, y2 + h2 * 0.5); - roundedRectAnchor(a, 2, x2, y2, w2, h2, radius2, x1 + w1 * 0.5, y1 + h1 * 0.5); - nodeGraphEdge(r, a[0], a[1], a[2], a[3], thickness, argb); - } - - static void roundedRectNodeGraphEdgeGradient(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, - double x2, double y2, double w2, double h2, double radius2, - double thickness, int startArgb, int endArgb) { - double[] a = r.connectorAnchorTmp; - roundedRectAnchor(a, 0, x1, y1, w1, h1, radius1, x2 + w2 * 0.5, y2 + h2 * 0.5); - roundedRectAnchor(a, 2, x2, y2, w2, h2, radius2, x1 + w1 * 0.5, y1 + h1 * 0.5); - nodeGraphEdgeGradient(r, a[0], a[1], a[2], a[3], thickness, startArgb, endArgb); - } - - static void roundedRectOrthogonalConnector(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, - double x2, double y2, double w2, double h2, double radius2, - double midX, double thickness, int argb) { - double[] a = r.connectorAnchorTmp; - roundedRectAnchor(a, 0, x1, y1, w1, h1, radius1, x2 + w2 * 0.5, y2 + h2 * 0.5); - roundedRectAnchor(a, 2, x2, y2, w2, h2, radius2, x1 + w1 * 0.5, y1 + h1 * 0.5); - orthogonalConnector(r, a[0], a[1], a[2], a[3], midX, thickness, argb); - } - - static void roundedRectOrthogonalConnectorGradient(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, - double x2, double y2, double w2, double h2, double radius2, - double midX, double thickness, int startArgb, int endArgb) { - double[] a = r.connectorAnchorTmp; - roundedRectAnchor(a, 0, x1, y1, w1, h1, radius1, x2 + w2 * 0.5, y2 + h2 * 0.5); - roundedRectAnchor(a, 2, x2, y2, w2, h2, radius2, x1 + w1 * 0.5, y1 + h1 * 0.5); - orthogonalConnectorGradient(r, a[0], a[1], a[2], a[3], midX, thickness, startArgb, endArgb); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DItems.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DItems.java deleted file mode 100644 index 3435361..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DItems.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import dev.loki.lovisual.render.engine.renderer.ui.UiItemSubmission; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.Nullable; - -final class Renderer2DItems { - private Renderer2DItems() { - } - - static void submit(Renderer2D r, - @Nullable LocalPlayer player, - ItemStack stack, - double x, - double y, - float scaleX, - float scaleY, - float pivotX, - float pivotY, - boolean pivoted, - int seed, - boolean drawItem, - int overlayFlags, - @Nullable String stackCountText, - int durabilityThresholdPercent, - int durabilityTextColorThresholdPercent) { - UiItemSubmission.submit( - r.textured, - r.alpha, - player, - stack, - x, - y, - scaleX, - scaleY, - pivotX, - pivotY, - pivoted, - seed, - drawItem, - overlayFlags, - stackCountText, - durabilityThresholdPercent, - durabilityTextColorThresholdPercent - ); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DLines.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DLines.java deleted file mode 100644 index 8f761af..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DLines.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import dev.loki.lovisual.render.engine.color.RenderColor; -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; - -final class Renderer2DLines { - private Renderer2DLines() { - } - - static void line(Renderer2D r, double x1, double y1, double x2, double y2, int argb) { - double[] p = {x1, y1, x2, y2}; - r.path(UiShape.polyline(p, 2, false), UiPaint.solid(argb), UiStroke.of(1.0), false); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.LINES, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureLineCapacity(); - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - int i1 = mesh.vec2(x1, y1).color(red, green, blue, a).next(); - int i2 = mesh.vec2(x2, y2).color(red, green, blue, a).next(); - mesh.line(i1, i2); - - r.endAutoBatch(auto); - } - - static void line(Renderer2D r, double x1, double y1, double x2, double y2, RenderColor color) { - double[] p = {x1, y1, x2, y2}; - r.path(UiShape.polyline(p, 2, false), UiPaint.solid(color.argb()), UiStroke.of(1.0), false); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.LINES, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureLineCapacity(); - int i1 = mesh.vec2(x1, y1).color(color.r, color.g, color.b, color.a).next(); - int i2 = mesh.vec2(x2, y2).color(color.r, color.g, color.b, color.a).next(); - mesh.line(i1, i2); - - r.endAutoBatch(auto); - } - - static void boxLines(Renderer2D r, double x, double y, double width, double height, int argb) { - r.shapeStroke(UiShape.rect(x, y, width, height), UiPaint.solid(argb), UiStroke.of(1.0)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.LINES, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureCapacity(4, 8); - - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - int i1 = mesh.vec2(x, y).color(red, green, blue, a).next(); - int i2 = mesh.vec2(x, y + height).color(red, green, blue, a).next(); - int i3 = mesh.vec2(x + width, y + height).color(red, green, blue, a).next(); - int i4 = mesh.vec2(x + width, y).color(red, green, blue, a).next(); - - mesh.line(i1, i2); - mesh.line(i2, i3); - mesh.line(i3, i4); - mesh.line(i4, i1); - - r.endAutoBatch(auto); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DLiquidGlass.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DLiquidGlass.java deleted file mode 100644 index 1e16456..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DLiquidGlass.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import com.mojang.blaze3d.pipeline.RenderTarget; -import com.mojang.blaze3d.textures.GpuSampler; -import com.mojang.blaze3d.textures.GpuTextureView; -import dev.loki.lovisual.render.engine.postprocess.PostProcessManager; -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiEffectSpec; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; -import net.minecraft.client.Minecraft; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clamp01; -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeCornerRadii; -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.packLiquidGlassPayload; - -final class Renderer2DLiquidGlass { - private Renderer2DLiquidGlass() { - } - - static void liquidGlassRectCorners(Renderer2D r, double x, double y, double w, double h, - float radiusTL, float radiusTR, float radiusBR, float radiusBL, - float softness, - int tintArgb, - float glassAlpha, - float blurAlpha, - float fresnelPower, - float fresnelAlpha, - float baseAlpha, - float fresnelMix, - float distortPx, - float squirclePower) { - if (w <= 0.0 || h <= 0.0) return; - - r.effect(UiEffectSpec.liquidGlass(UiShape.roundedRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL), - softness, softness, distortPx, tintArgb)); - Minecraft mc = Minecraft.getInstance(); - if (mc == null) return; - RenderTarget fb = mc.gameRenderer.mainRenderTarget(); - if (fb == null) return; - GpuTextureView src = fb.getColorTextureView(); - if (src == null) return; - GpuSampler sampler = PostProcessManager.getSampler(); - if (sampler == null) return; - - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreateBlur(UiBatchType.LIQUID_GLASS, src, sampler, - Renderer2D.DEFAULT_LIQUID_GLASS_BLUR_QUALITY, Renderer2D.LIQUID_GLASS_KAWASE_OFFSET_PX); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a = (tintArgb >>> 24) & 0xFF; - int red = (tintArgb >>> 16) & 0xFF; - int green = (tintArgb >>> 8) & 0xFF; - int blue = tintArgb & 0xFF; - - float clampedGlassAlpha = clamp01(glassAlpha); - float clampedBlurAlpha = clamp01(blurAlpha); - int finalA = (int) (a * clampedGlassAlpha); - - float[] cornerRadiiTmp = r.cornerRadiiTmp; - normalizeCornerRadii(w, h, radiusTL, radiusTR, radiusBR, radiusBL, cornerRadiiTmp); - - float thickness = Math.max(0.0f, softness); - float mix = clamp01(fresnelMix); - float fa = clamp01(fresnelAlpha); - float ba = clamp01(baseAlpha); - float packedDistort = packLiquidGlassPayload(distortPx, squirclePower, clampedBlurAlpha); - - int i1 = mesh.vec2(x, y).raw2(mix, packedDistort).local2(x, y).color(red, green, blue, finalA) - .vec4(x, y, w, h) - .vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]) - .vec4(thickness, fresnelPower, fa, ba) - .next(); - int i2 = mesh.vec2(x, y + h).raw2(mix, packedDistort).local2(x, y + h).color(red, green, blue, finalA) - .vec4(x, y, w, h) - .vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]) - .vec4(thickness, fresnelPower, fa, ba) - .next(); - int i3 = mesh.vec2(x + w, y + h).raw2(mix, packedDistort).local2(x + w, y + h).color(red, green, blue, finalA) - .vec4(x, y, w, h) - .vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]) - .vec4(thickness, fresnelPower, fa, ba) - .next(); - int i4 = mesh.vec2(x + w, y).raw2(mix, packedDistort).local2(x + w, y).color(red, green, blue, finalA) - .vec4(x, y, w, h) - .vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]) - .vec4(thickness, fresnelPower, fa, ba) - .next(); - - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DOrbiz.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DOrbiz.java deleted file mode 100644 index b7c3e81..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DOrbiz.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; -import net.minecraft.util.Mth; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeDegrees; - -final class Renderer2DOrbiz { - private Renderer2DOrbiz() { - } - - static void orbizRing(Renderer2D r, double cx, double cy, - double radius, double thickness, - float startAngleDeg, float sweepAngleDeg, - float softness, float glowRadius, float glowStrength, - int primaryArgb, int secondaryArgb, - int colorMode, float angularOffset, float angularCycles, - float trackAlpha, float bodyAlpha, float headBoost, - float trackGlow, boolean caps) { - if (r.textured) { - throw new IllegalStateException("Orbiz ring drawing is supported only on Renderer2D.COLOR."); - } - if (radius <= 0.0 || thickness <= 0.0) return; - if (((primaryArgb >>> 24) & 0xFF) <= 0 && ((secondaryArgb >>> 24) & 0xFF) <= 0) return; - - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ORBIZ_RING, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a = (primaryArgb >>> 24) & 0xFF; - int red = (primaryArgb >>> 16) & 0xFF; - int green = (primaryArgb >>> 8) & 0xFF; - int blue = primaryArgb & 0xFF; - - float sr = ((secondaryArgb >>> 16) & 0xFF) / 255.0f; - float sg = ((secondaryArgb >>> 8) & 0xFF) / 255.0f; - float sb = (secondaryArgb & 0xFF) / 255.0f; - float sa = ((secondaryArgb >>> 24) & 0xFF) / 255.0f; - - float stroke = (float) Math.max(0.0, thickness); - float ringRadius = (float) Math.max(0.0, radius); - float soft = Math.max(0.0f, softness); - float glow = Math.max(0.0f, glowRadius); - float expand = stroke * 0.5f + soft + glow + 1.0f; - double outerRadius = radius + expand; - double x = cx - outerRadius; - double y = cy - outerRadius; - double w = outerRadius * 2.0; - double h = outerRadius * 2.0; - double ringX = cx - radius; - double ringY = cy - radius; - double ringSize = radius * 2.0; - - float start = normalizeDegrees(startAngleDeg); - float sweep = Mth.clamp(sweepAngleDeg, 0.0f, 360.0f); - float offset = angularOffset - (float) Math.floor(angularOffset); - float cycles = Math.max(0.001f, angularCycles); - float mode = Math.max(0, colorMode); - float track = Mth.clamp(trackAlpha, 0.0f, 1.0f); - float body = Mth.clamp(bodyAlpha, 0.0f, 1.0f); - float glowPower = Math.max(0.0f, glowStrength); - float head = Math.max(0.0f, headBoost); - float trackGlowAmount = Mth.clamp(trackGlow, 0.0f, 1.0f); - - int i1 = mesh.vec2(x, y).local2(x, y).color(red, green, blue, a) - .vec4(ringX, ringY, ringSize, ringSize) - .vec4(ringRadius, soft, stroke, start) - .vec4(sweep, glow, glowPower, mode) - .vec4(sr, sg, sb, sa) - .vec4(offset, cycles, 0.0f, caps ? 1.0f : 0.0f) - .vec4(track, body, head, trackGlowAmount) - .next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(red, green, blue, a) - .vec4(ringX, ringY, ringSize, ringSize) - .vec4(ringRadius, soft, stroke, start) - .vec4(sweep, glow, glowPower, mode) - .vec4(sr, sg, sb, sa) - .vec4(offset, cycles, 0.0f, caps ? 1.0f : 0.0f) - .vec4(track, body, head, trackGlowAmount) - .next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(red, green, blue, a) - .vec4(ringX, ringY, ringSize, ringSize) - .vec4(ringRadius, soft, stroke, start) - .vec4(sweep, glow, glowPower, mode) - .vec4(sr, sg, sb, sa) - .vec4(offset, cycles, 0.0f, caps ? 1.0f : 0.0f) - .vec4(track, body, head, trackGlowAmount) - .next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(red, green, blue, a) - .vec4(ringX, ringY, ringSize, ringSize) - .vec4(ringRadius, soft, stroke, start) - .vec4(sweep, glow, glowPower, mode) - .vec4(sr, sg, sb, sa) - .vec4(offset, cycles, 0.0f, caps ? 1.0f : 0.0f) - .vec4(track, body, head, trackGlowAmount) - .next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DPolygons.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DPolygons.java deleted file mode 100644 index ac9729b..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DPolygons.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import dev.loki.lovisual.render.engine.command.UiShapeCommand; -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiBoxPathBuilder; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiBoxShape; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaintKind; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPathCap; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPathJoin; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.*; - -final class Renderer2DPolygons { - private Renderer2DPolygons() { - } - - static void box(Renderer2D r, UiBoxShape box, UiPaint paint, UiStroke stroke, boolean fill) { - if (box == null || paint == null) return; - UiStroke safeStroke = stroke == null ? UiStroke.NONE : stroke; - Renderer2D.recordUi(new UiShapeCommand(UiShape.box(box), paint, safeStroke, fill)); - renderFlexibleBoxFallback(r, box, paint, safeStroke, fill); - } - - static void renderFlexibleBoxFallback(Renderer2D r, UiBoxShape box, UiPaint paint, UiStroke stroke, boolean fill) { - if (r.textured || box == null || paint == null) return; - double[] rectShapeTmp = r.rectShapeTmp; - int count = UiBoxPathBuilder.write(box, rectShapeTmp, rectShapeTmp.length / 2); - if (count < 3) return; - - if (fill) { - switch (paint.kind()) { - case LINEAR_GRADIENT -> - polygonGradient(r, rectShapeTmp, count, box.bounds().x(), box.bounds().y(), box.bounds().width(), box.bounds().height(), - paint.topLeft(), paint.topRight(), paint.angleDeg(), paint.offsetPx()); - default -> polygon(r, rectShapeTmp, count, paint.solidColor()); - } - } else if (stroke != null && stroke.enabled()) { - if (paint.kind() == UiPaintKind.LINEAR_GRADIENT) { - polylineLinearGradient(r, rectShapeTmp, count, stroke.thickness(), true, - box.bounds().x(), box.bounds().y(), box.bounds().width(), box.bounds().height(), - paint.topLeft(), paint.topRight(), paint.angleDeg(), paint.offsetPx(), - stroke.join() == UiPathJoin.ROUND || stroke.cap() == UiPathCap.ROUND); - } else { - polyline(r, rectShapeTmp, count, stroke.thickness(), true, paint.solidColor(), - stroke.join() == UiPathJoin.ROUND || stroke.cap() == UiPathCap.ROUND); - } - } - } - - static void polygon(Renderer2D r, double[] points, int pointCount, int argb) { - if (r.textured) { - throw new IllegalStateException("Polygon drawing is supported only on Renderer2D.COLOR."); - } - int safeCount = Math.min(pointCount, points != null ? points.length / 2 : 0); - safeCount = Math.min(safeCount, Math.min(r.polygonIndexTmp.length, r.polygonVertexTmp.length)); - if (points == null || safeCount < 3) return; - - r.path(UiShape.polyline(points, safeCount, true), UiPaint.solid(argb), UiStroke.NONE, true); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.COLORED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - appendPolygon(mesh, points, safeCount, r.polygonIndexTmp, r.polygonVertexTmp, argb); - r.endAutoBatch(auto); - } - - static void polygonGradient(Renderer2D r, double[] points, int pointCount, - double x, double y, double width, double height, - int startArgb, int endArgb, float angleDeg, float offsetPx) { - if (r.textured) { - throw new IllegalStateException("Polygon drawing is supported only on Renderer2D.COLOR."); - } - if (points == null || pointCount < 3) return; - - r.path(UiShape.polyline(points, pointCount, true), UiPaint.linear(startArgb, endArgb, angleDeg, offsetPx), UiStroke.NONE, true); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.COLORED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - appendPolygonGradient(mesh, points, pointCount, r.polygonIndexTmp, r.polygonVertexTmp, - x, y, width, height, startArgb, endArgb, angleDeg, offsetPx); - r.endAutoBatch(auto); - } - - static void polygonStroke(Renderer2D r, double[] points, int pointCount, double thickness, boolean closed, int argb) { - polyline(r, points, pointCount, thickness, closed, argb, false); - } - - static void polyline(Renderer2D r, double[] points, int pointCount, double thickness, boolean closed, int argb) { - polyline(r, points, pointCount, thickness, closed, argb, false); - } - - static void polyline(Renderer2D r, double[] points, int pointCount, double thickness, boolean closed, int argb, boolean roundCapsAndJoins) { - if (r.textured) { - throw new IllegalStateException("Polyline drawing is supported only on Renderer2D.COLOR."); - } - if (points == null || pointCount < 2) return; - double t = Math.max(0.0, thickness); - if (t <= 0.0) return; - - r.path(UiShape.polyline(points, pointCount, closed), UiPaint.solid(argb), - roundCapsAndJoins ? UiStroke.of(t).withRoundCapsAndJoins() : UiStroke.of(t), false); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.COLORED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - appendPolyline(mesh, points, pointCount, t, closed, argb, roundCapsAndJoins); - r.endAutoBatch(auto); - } - - static void polylineGradient(Renderer2D r, double[] points, int pointCount, double thickness, boolean closed, - int startArgb, int endArgb, boolean roundCapsAndJoins) { - if (r.textured) { - throw new IllegalStateException("Polyline drawing is supported only on Renderer2D.COLOR."); - } - if (points == null || pointCount < 2) return; - double t = Math.max(0.0, thickness); - if (t <= 0.0) return; - - r.path(UiShape.polyline(points, pointCount, closed), UiPaint.corners(startArgb, endArgb, endArgb, startArgb), - roundCapsAndJoins ? UiStroke.of(t).withRoundCapsAndJoins() : UiStroke.of(t), false); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.COLORED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - appendPolylineGradient(mesh, points, pointCount, t, closed, startArgb, endArgb, roundCapsAndJoins); - r.endAutoBatch(auto); - } - - static void polylineLinearGradient(Renderer2D r, double[] points, int pointCount, double thickness, boolean closed, - double x, double y, double width, double height, - int startArgb, int endArgb, float angleDeg, float offsetPx, - boolean roundCapsAndJoins) { - if (r.textured) { - throw new IllegalStateException("Polyline drawing is supported only on Renderer2D.COLOR."); - } - if (points == null || pointCount < 2) return; - double t = Math.max(0.0, thickness); - if (t <= 0.0) return; - - r.path(UiShape.polyline(points, pointCount, closed), UiPaint.linear(startArgb, endArgb, angleDeg, offsetPx), - roundCapsAndJoins ? UiStroke.of(t).withRoundCapsAndJoins() : UiStroke.of(t), false); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.COLORED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - appendPolylineLinearGradient(mesh, points, pointCount, t, closed, - x, y, width, height, startArgb, endArgb, angleDeg, offsetPx, roundCapsAndJoins); - r.endAutoBatch(auto); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DSmokeFill.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DSmokeFill.java deleted file mode 100644 index e970615..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DSmokeFill.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; -import net.minecraft.util.Mth; - -final class Renderer2DSmokeFill { - private Renderer2DSmokeFill() { - } - - static void roundedSmokeFill(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, - float fillRatio, - boolean fromRight, - int firstArgb, - int secondArgb, - int thirdArgb, - float time, - float smokeScale, - float smokeMix, - int octaves, - float flowX, - float flowY, - float intensity) { - if (w <= 0.0 || h <= 0.0) return; - float fill = Mth.clamp(fillRatio, 0.0f, 1.0f); - if (fill <= 0.0001f) return; - - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_FILL_SMOKE, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a0 = (firstArgb >>> 24) & 0xFF; - int r0 = (firstArgb >>> 16) & 0xFF; - int g0 = (firstArgb >>> 8) & 0xFF; - int b0 = firstArgb & 0xFF; - - float r1 = ((secondArgb >>> 16) & 0xFF) / 255.0f; - float g1 = ((secondArgb >>> 8) & 0xFF) / 255.0f; - float b1 = (secondArgb & 0xFF) / 255.0f; - float a1 = ((secondArgb >>> 24) & 0xFF) / 255.0f; - - float r2 = ((thirdArgb >>> 16) & 0xFF) / 255.0f; - float g2 = ((thirdArgb >>> 8) & 0xFF) / 255.0f; - float b2 = (thirdArgb & 0xFF) / 255.0f; - float a2 = ((thirdArgb >>> 24) & 0xFF) / 255.0f; - - float fromRightFlag = fromRight ? 1.0f : 0.0f; - float safeScale = Math.max(0.1f, smokeScale); - float safeMix = Mth.clamp(smokeMix, 0.0f, 1.0f); - float safeOctaves = Mth.clamp(octaves, 1, 8); - float safeIntensity = Math.max(0.0f, intensity); - - int i1 = mesh.vec2(x, y).local2(x, y).color(r0, g0, b0, a0) - .vec4((float) x, (float) y, (float) w, (float) h) - .vec4(radius, softness, fill, fromRightFlag) - .vec4(r1, g1, b1, a1) - .vec4(r2, g2, b2, a2) - .vec4(time, safeScale, safeMix, safeOctaves) - .vec4(flowX, flowY, safeIntensity, 0.0f) - .next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(r0, g0, b0, a0) - .vec4((float) x, (float) y, (float) w, (float) h) - .vec4(radius, softness, fill, fromRightFlag) - .vec4(r1, g1, b1, a1) - .vec4(r2, g2, b2, a2) - .vec4(time, safeScale, safeMix, safeOctaves) - .vec4(flowX, flowY, safeIntensity, 0.0f) - .next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(r0, g0, b0, a0) - .vec4((float) x, (float) y, (float) w, (float) h) - .vec4(radius, softness, fill, fromRightFlag) - .vec4(r1, g1, b1, a1) - .vec4(r2, g2, b2, a2) - .vec4(time, safeScale, safeMix, safeOctaves) - .vec4(flowX, flowY, safeIntensity, 0.0f) - .next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(r0, g0, b0, a0) - .vec4((float) x, (float) y, (float) w, (float) h) - .vec4(radius, softness, fill, fromRightFlag) - .vec4(r1, g1, b1, a1) - .vec4(r2, g2, b2, a2) - .vec4(time, safeScale, safeMix, safeOctaves) - .vec4(flowX, flowY, safeIntensity, 0.0f) - .next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DTextures.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DTextures.java deleted file mode 100644 index 9bcc750..0000000 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DTextures.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * This file is part of the LoVisual Client distribution. - * Copyright (c) 2026 loki5512344. - * - * Licensed under the GNU General Public License v3.0. - */ - -package dev.loki.lovisual.render.engine.renderer; - -import com.mojang.blaze3d.textures.GpuSampler; -import com.mojang.blaze3d.textures.GpuTextureView; -import dev.loki.lovisual.render.engine.command.UiShapeCommand; -import dev.loki.lovisual.render.engine.command.UiTextureDrawCommand; -import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; -import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; -import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; -import dev.loki.lovisual.render.engine.uniform.MeshBuilder; - -import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampRoundedRadius; - -final class Renderer2DTextures { - private Renderer2DTextures() { - } - - static void textureQuad(Renderer2D r, GpuTextureView samplerView, GpuSampler sampler, - double x, double y, double w, double h, int argb) { - if (r.textured) { - throw new IllegalStateException("Batched texture quad drawing is supported only on Renderer2D.COLOR."); - } - if (samplerView == null || sampler == null) return; - if (w == 0.0 || h == 0.0) return; - - Renderer2D.recordUi(new UiTextureDrawCommand(samplerView, sampler, UiShape.rect(x, y, w, h), UiPaint.solid(argb), 0f, 0f, 1f, 1f, false)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.TEXTURED, samplerView, sampler); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - - int i1 = mesh.vec2(x, y).raw2(0.0, 0.0).color(red, green, blue, a).next(); - int i2 = mesh.vec2(x, y + h).raw2(0.0, 1.0).color(red, green, blue, a).next(); - int i3 = mesh.vec2(x + w, y + h).raw2(1.0, 1.0).color(red, green, blue, a).next(); - int i4 = mesh.vec2(x + w, y).raw2(1.0, 0.0).color(red, green, blue, a).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } - - static void msdfTextureQuad(Renderer2D r, GpuTextureView samplerView, GpuSampler sampler, - double x, double y, double w, double h, - int topLeft, int topRight, int bottomRight, int bottomLeft, - float pxRange, int atlasWidth, int atlasHeight) { - if (r.textured) { - throw new IllegalStateException("Batched MSDF texture quad drawing is supported only on Renderer2D.COLOR."); - } - if (samplerView == null || sampler == null) return; - if (w == 0.0 || h == 0.0) return; - if (pxRange <= 0.0f || atlasWidth <= 0 || atlasHeight <= 0) return; - - Renderer2D.recordUi(new UiTextureDrawCommand(samplerView, sampler, UiShape.rect(x, y, w, h), UiPaint.solid(topLeft), 0f, 0f, 1f, 1f, false)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreateMsdf(samplerView, sampler, pxRange, atlasWidth, atlasHeight); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int i1 = mesh.vec2(x, y).raw2(0.0, 0.0).color((topLeft >>> 16) & 0xFF, (topLeft >>> 8) & 0xFF, topLeft & 0xFF, (topLeft >>> 24) & 0xFF).next(); - int i2 = mesh.vec2(x, y + h).raw2(0.0, 1.0).color((bottomLeft >>> 16) & 0xFF, (bottomLeft >>> 8) & 0xFF, bottomLeft & 0xFF, (bottomLeft >>> 24) & 0xFF).next(); - int i3 = mesh.vec2(x + w, y + h).raw2(1.0, 1.0).color((bottomRight >>> 16) & 0xFF, (bottomRight >>> 8) & 0xFF, bottomRight & 0xFF, (bottomRight >>> 24) & 0xFF).next(); - int i4 = mesh.vec2(x + w, y).raw2(1.0, 0.0).color((topRight >>> 16) & 0xFF, (topRight >>> 8) & 0xFF, topRight & 0xFF, (topRight >>> 24) & 0xFF).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } - - static void texQuad(Renderer2D r, double x, double y, double width, double height, - double texX1, double texY1, double texX2, double texY2, - int argb) { - Renderer2D.recordUi(new UiShapeCommand(UiShape.rect(x, y, width, height), UiPaint.solid(argb), UiStroke.NONE, true)); - MeshBuilder texturedTriangles = r.texturedTriangles; - if (texturedTriangles == null) return; - texturedTriangles.alpha = r.alpha; - texturedTriangles.ensureQuadCapacity(); - - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - int i1 = texturedTriangles.vec2(x, y).raw2(texX1, texY1).color(red, green, blue, a).next(); - int i2 = texturedTriangles.vec2(x, y + height).raw2(texX1, texY2).color(red, green, blue, a).next(); - int i3 = texturedTriangles.vec2(x + width, y + height).raw2(texX2, texY2).color(red, green, blue, a).next(); - int i4 = texturedTriangles.vec2(x + width, y).raw2(texX2, texY1).color(red, green, blue, a).next(); - - texturedTriangles.quad(i1, i2, i3, i4); - } - - static void roundedTexRect(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, - double texX1, double texY1, double texX2, double texY2, - int argb, GpuTextureView samplerView, GpuSampler sampler) { - if (!r.textured) { - throw new IllegalStateException("Renderer2D is not configured for textures."); - } - if (samplerView == null || sampler == null) return; - - Renderer2D.recordUi(new UiTextureDrawCommand(samplerView, sampler, UiShape.roundedRect(x, y, w, h, radius), UiPaint.solid(argb), - (float) texX1, (float) texY1, (float) texX2, (float) texY2, false)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_TEXTURED, samplerView, sampler); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - float clampedRadius = clampRoundedRadius(radius, w, h); - int i1 = mesh.vec2(x, y).raw2(texX1, texY1).local2(x, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - int i2 = mesh.vec2(x, y + h).raw2(texX1, texY2).local2(x, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).raw2(texX2, texY2).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - int i4 = mesh.vec2(x + w, y).raw2(texX2, texY1).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } - - static void roundedTexMaskRect(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, - double texX1, double texY1, double texX2, double texY2, - int argb, GpuTextureView samplerView, GpuSampler sampler) { - if (!r.textured) { - throw new IllegalStateException("Renderer2D is not configured for textures."); - } - if (samplerView == null || sampler == null) return; - - Renderer2D.recordUi(new UiTextureDrawCommand(samplerView, sampler, UiShape.roundedRect(x, y, w, h, radius), UiPaint.solid(argb), - (float) texX1, (float) texY1, (float) texX2, (float) texY2, true)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_TEXTURED_MASK, samplerView, sampler); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - mesh.ensureQuadCapacity(); - - int a = (argb >>> 24) & 0xFF; - int red = (argb >>> 16) & 0xFF; - int green = (argb >>> 8) & 0xFF; - int blue = argb & 0xFF; - float clampedRadius = clampRoundedRadius(radius, w, h); - int i1 = mesh.vec2(x, y).raw2(texX1, texY1).local2(x, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - int i2 = mesh.vec2(x, y + h).raw2(texX1, texY2).local2(x, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).raw2(texX2, texY2).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - int i4 = mesh.vec2(x + w, y).raw2(texX2, texY1).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, 0f, 0f).next(); - mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); - } -} \ No newline at end of file diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Batch2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Batch2D.java new file mode 100644 index 0000000..56e6044 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Batch2D.java @@ -0,0 +1,104 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d; + +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTextureView; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.ui.UiRenderDispatcher; + +/** + * Shared batch scope and scratch buffers for the Renderer2D helper backends. + * + *

{@link #open} starts an auto batch (only if none is active yet) and fetches the + * batch for the requested type; {@link #close} flushes only when this call chain + * actually started the batch. Nesting is handled with a depth counter, so helpers + * may call other drawing helpers freely.

+ */ +public final class Batch2D { + /** Render-thread scratch buffers shared by the helper backends. */ + public static final float[] CORNER_RADII = new float[4]; + public static final double[] RECT_SHAPE = new double[256]; + public static final double[] CONNECTOR = new double[64]; + public static final double[] CONNECTOR_ANCHOR = new double[4]; + public static final int[] POLY_INDEX = new int[64]; + public static final int[] POLY_VERTEX = new int[64]; + public static final double[] PROGRESS_SHAPE = new double[256]; + public static final int[] GRADIENT = new int[4]; + + private static final int[] RGBA = new int[4]; + private static final float[] NORM = new float[4]; + + private static int depth; + private static boolean rootAuto; + + private Batch2D() { + } + + public static DrawBatch open(Renderer2D r, UiBatchType type) { + return open(r, type, null, null); + } + + public static DrawBatch open(Renderer2D r, UiBatchType type, GpuTextureView view, GpuSampler sampler) { + beginScope(); + return finish(r, Renderer2D.UI_BATCHER.getOrCreate(type, view, sampler)); + } + + public static DrawBatch openBlur(Renderer2D r, UiBatchType type, GpuTextureView view, GpuSampler sampler, + Renderer2D.BlurQuality quality, float offsetPx) { + beginScope(); + return finish(r, Renderer2D.UI_BATCHER.getOrCreateBlur(type, view, sampler, quality, offsetPx)); + } + + public static DrawBatch openMsdf(Renderer2D r, GpuTextureView view, GpuSampler sampler, + float pxRange, int atlasWidth, int atlasHeight) { + beginScope(); + return finish(r, Renderer2D.UI_BATCHER.getOrCreateMsdf(view, sampler, pxRange, atlasWidth, atlasHeight)); + } + + public static void close() { + if (depth > 0 && --depth == 0 && rootAuto) { + rootAuto = false; + UiRenderDispatcher.endAutoBatch(true); + } + } + + private static void beginScope() { + if (depth == 0) rootAuto = UiRenderDispatcher.beginAutoBatch(); + depth++; + } + + private static DrawBatch finish(Renderer2D r, DrawBatch batch) { + if (batch == null) { + close(); + return null; + } + batch.mesh.alpha = r.getAlpha(); + return batch; + } + + /** Unpacks {@code argb} into the shared scratch as {r, g, b, a}. */ + public static int[] rgba(int argb) { + RGBA[0] = (argb >>> 16) & 0xFF; + RGBA[1] = (argb >>> 8) & 0xFF; + RGBA[2] = argb & 0xFF; + RGBA[3] = (argb >>> 24) & 0xFF; + return RGBA; + } + + /** Unpacks {@code argb} into normalized floats {r, g, b, a} in range 0..1. */ + public static float[] rgbaNorm(int argb) { + NORM[0] = ((argb >>> 16) & 0xFF) / 255.0f; + NORM[1] = ((argb >>> 8) & 0xFF) / 255.0f; + NORM[2] = (argb & 0xFF) / 255.0f; + NORM[3] = ((argb >>> 24) & 0xFF) / 255.0f; + return NORM; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Chamfered2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Chamfered2D.java new file mode 100644 index 0000000..baf6d3a --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Chamfered2D.java @@ -0,0 +1,170 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d; + +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.renderer2d.path.Polygons2D; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; + +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampChamferAxis; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.computeLinearGradientColors; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeChamfers; + +/** Chamfered (cut-corner) rects and beveled 3D-style plates. */ +public final class Chamfered2D { + private Chamfered2D() { + } + + public static void chamferedRectQuadAxes(Renderer2D r, double x, double y, double w, double h, + double chamferTLX, double chamferTLY, + double chamferTRX, double chamferTRY, + double chamferBRX, double chamferBRY, + double chamferBLX, double chamferBLY, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + r.shape(UiShape.chamferedRectAxes(x, y, w, h, chamferTLX, chamferTLY, chamferTRX, chamferTRY, + chamferBRX, chamferBRY, chamferBLX, chamferBLY), + UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft)); + DrawBatch batch = Batch2D.open(r, UiBatchType.CHAMFERED); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int tlA = (cTopLeft >>> 24) & 0xFF; + int tlR = (cTopLeft >>> 16) & 0xFF; + int tlG = (cTopLeft >>> 8) & 0xFF; + int tlB = cTopLeft & 0xFF; + int trA = (cTopRight >>> 24) & 0xFF; + int trR = (cTopRight >>> 16) & 0xFF; + int trG = (cTopRight >>> 8) & 0xFF; + int trB = cTopRight & 0xFF; + int brA = (cBottomRight >>> 24) & 0xFF; + int brR = (cBottomRight >>> 16) & 0xFF; + int brG = (cBottomRight >>> 8) & 0xFF; + int brB = cBottomRight & 0xFF; + int blA = (cBottomLeft >>> 24) & 0xFF; + int blR = (cBottomLeft >>> 16) & 0xFF; + int blG = (cBottomLeft >>> 8) & 0xFF; + int blB = cBottomLeft & 0xFF; + + float tlX = clampChamferAxis(chamferTLX, w); + float trX = clampChamferAxis(chamferTRX, w); + float brX = clampChamferAxis(chamferBRX, w); + float blX = clampChamferAxis(chamferBLX, w); + float tlY = clampChamferAxis(chamferTLY, h); + float trY = clampChamferAxis(chamferTRY, h); + float brY = clampChamferAxis(chamferBRY, h); + float blY = clampChamferAxis(chamferBLY, h); + + int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); + int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); + int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); + int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h).vec4(tlX, trX, brX, blX).vec4(tlY, trY, brY, blY).next(); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void chamferedRectQuad(Renderer2D r, double x, double y, double w, double h, + double chamferTL, double chamferTR, double chamferBR, double chamferBL, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + float[] c = Batch2D.CORNER_RADII; + normalizeChamfers(w, h, (float) chamferTL, (float) chamferTR, (float) chamferBR, (float) chamferBL, c); + chamferedRectQuadAxes(r, x, y, w, h, + c[0], c[0], c[1], c[1], c[2], c[2], c[3], c[3], + cTopLeft, cTopRight, cBottomRight, cBottomLeft); + } + + public static void chamferedRectGradient(Renderer2D r, double x, double y, double width, double height, + double chamferTL, double chamferTR, double chamferBR, double chamferBL, + int startArgb, int endArgb, float angleDeg, float offsetPx) { + int[] g = Batch2D.GRADIENT; + computeLinearGradientColors((float) width, (float) height, startArgb, endArgb, angleDeg, offsetPx, g); + chamferedRectQuad(r, x, y, width, height, chamferTL, chamferTR, chamferBR, chamferBL, + g[0], g[1], g[2], g[3]); + } + + public static void chamferedRectStrokeQuad(Renderer2D r, double x, double y, double w, double h, + double chamferTL, double chamferTR, double chamferBR, double chamferBL, + double thickness, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + r.shapeStroke(UiShape.chamferedRect(x, y, w, h, chamferTL, chamferTR, chamferBR, chamferBL), + UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft), UiStroke.of(thickness)); + DrawBatch batch = Batch2D.open(r, UiBatchType.CHAMFERED_STROKE); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int tlA = (cTopLeft >>> 24) & 0xFF; + int tlR = (cTopLeft >>> 16) & 0xFF; + int tlG = (cTopLeft >>> 8) & 0xFF; + int tlB = cTopLeft & 0xFF; + int trA = (cTopRight >>> 24) & 0xFF; + int trR = (cTopRight >>> 16) & 0xFF; + int trG = (cTopRight >>> 8) & 0xFF; + int trB = cTopRight & 0xFF; + int brA = (cBottomRight >>> 24) & 0xFF; + int brR = (cBottomRight >>> 16) & 0xFF; + int brG = (cBottomRight >>> 8) & 0xFF; + int brB = cBottomRight & 0xFF; + int blA = (cBottomLeft >>> 24) & 0xFF; + int blR = (cBottomLeft >>> 16) & 0xFF; + int blG = (cBottomLeft >>> 8) & 0xFF; + int blB = cBottomLeft & 0xFF; + float[] c = Batch2D.CORNER_RADII; + normalizeChamfers(w, h, (float) chamferTL, (float) chamferTR, (float) chamferBR, (float) chamferBL, c); + float stroke = (float) Math.max(0.0, thickness); + + int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(x, y, w, h).vec4(c[0], c[1], c[2], c[3]).vec4(stroke, 0f, 0f, 0f).next(); + int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(x, y, w, h).vec4(c[0], c[1], c[2], c[3]).vec4(stroke, 0f, 0f, 0f).next(); + int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h).vec4(c[0], c[1], c[2], c[3]).vec4(stroke, 0f, 0f, 0f).next(); + int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h).vec4(c[0], c[1], c[2], c[3]).vec4(stroke, 0f, 0f, 0f).next(); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void beveledRect(Renderer2D r, double x, double y, double width, double height, + double bevel, int fillArgb, int highlightArgb, int shadowArgb) { + chamferedRectQuad(r, x, y, width, height, bevel, bevel, bevel, bevel, + fillArgb, fillArgb, fillArgb, fillArgb); + bevelEdges(r, x, y, width, height, bevel, highlightArgb, shadowArgb); + } + + public static void beveledRectGradient(Renderer2D r, double x, double y, double width, double height, + double bevel, int startArgb, int endArgb, + int highlightArgb, int shadowArgb, float angleDeg, float offsetPx) { + chamferedRectGradient(r, x, y, width, height, bevel, bevel, bevel, bevel, + startArgb, endArgb, angleDeg, offsetPx); + bevelEdges(r, x, y, width, height, bevel, highlightArgb, shadowArgb); + } + + private static void bevelEdges(Renderer2D r, double x, double y, double width, double height, + double bevel, int highlightArgb, int shadowArgb) { + double t = Math.max(1.0, Math.min(Math.min(Math.abs(width), Math.abs(height)) * 0.25, bevel)); + double[] c = Batch2D.CONNECTOR; + c[0] = x + bevel; + c[1] = y; + c[2] = x + width - bevel; + c[3] = y; + c[4] = x + width; + c[5] = y + bevel; + Polygons2D.polyline(r, c, 3, t, false, highlightArgb, false); + c[0] = x + width; + c[1] = y + height - bevel; + c[2] = x + width - bevel; + c[3] = y + height; + c[4] = x + bevel; + c[5] = y + height; + c[6] = x; + c[7] = y + height - bevel; + Polygons2D.polyline(r, c, 4, t, false, shadowArgb, false); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRects.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Rects2D.java similarity index 50% rename from combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRects.java rename to combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Rects2D.java index 5180f32..595163a 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRects.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Rects2D.java @@ -5,8 +5,9 @@ * Licensed under the GNU General Public License v3.0. */ -package dev.loki.lovisual.render.engine.renderer; +package dev.loki.lovisual.render.engine.renderer.renderer2d; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; @@ -17,67 +18,49 @@ import java.util.function.Consumer; import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.appendColoredQuad; import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampRoundedRadius; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.computeLinearGradientColors; import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeCornerRadii; -final class Renderer2DRects { - private Renderer2DRects() { +/** Filled rect family: solid quads, rounded rects, per-corner radii and gradients. */ +public final class Rects2D { + private Rects2D() { } - static void quadBatch(Renderer2D r, Consumer consumer) { - if (r.textured) { + public static void quadBatch(Renderer2D r, Consumer consumer) { + if (r.isTextured()) { throw new IllegalStateException("Quad batching is supported only on Renderer2D.COLOR."); } if (consumer == null) return; - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.COLORED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - Renderer2D.QuadBatch quadBatch = new Renderer2D.QuadBatch(mesh); + DrawBatch batch = Batch2D.open(r, UiBatchType.COLORED); + if (batch == null) return; try { - consumer.accept(quadBatch); + consumer.accept(new Renderer2D.QuadBatch(batch.mesh)); } finally { - r.endAutoBatch(auto); + Batch2D.close(); } } - static void quadCorners(Renderer2D r, double x, double y, double width, double height, - int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + public static void quadCorners(Renderer2D r, double x, double y, double width, double height, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { r.shape(UiShape.rect(x, y, width, height), UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.COLORED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } - MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - - appendColoredQuad(mesh, x, y, width, height, cTopLeft, cTopRight, cBottomRight, cBottomLeft); - - r.endAutoBatch(auto); + DrawBatch batch = Batch2D.open(r, UiBatchType.COLORED); + if (batch == null) return; + appendColoredQuad(batch.mesh, x, y, width, height, cTopLeft, cTopRight, cBottomRight, cBottomLeft); + Batch2D.close(); } - static void roundedRectMaskedQuad(Renderer2D r, double x, double y, double w, double h, - double maskX, double maskY, double maskW, double maskH, - float radius, float softness, - int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - r.shape(UiShape.roundedRect(maskX, maskY, maskW, maskH, radius), UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + public static void roundedRectMaskedQuad(Renderer2D r, double x, double y, double w, double h, + double maskX, double maskY, double maskW, double maskH, + float radius, float softness, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + r.shape(UiShape.roundedRect(maskX, maskY, maskW, maskH, radius), + UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft)); + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); + float clampedRadius = clampRoundedRadius(radius, maskW, maskH); int tlA = (cTopLeft >>> 24) & 0xFF; int tlR = (cTopLeft >>> 16) & 0xFF; @@ -95,32 +78,27 @@ final class Renderer2DRects { int blR = (cBottomLeft >>> 16) & 0xFF; int blG = (cBottomLeft >>> 8) & 0xFF; int blB = cBottomLeft & 0xFF; - float clampedRadius = clampRoundedRadius(radius, maskW, maskH); int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(maskX, maskY, maskW, maskH).vec4(clampedRadius, 0.0f, 0f, 0f).next(); int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(maskX, maskY, maskW, maskH).vec4(clampedRadius, 0.0f, 0f, 0f).next(); int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(maskX, maskY, maskW, maskH).vec4(clampedRadius, 0.0f, 0f, 0f).next(); int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(maskX, maskY, maskW, maskH).vec4(clampedRadius, 0.0f, 0f, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void roundedRectCornersQuad(Renderer2D r, double x, double y, double w, double h, - float radiusTL, float radiusTR, float radiusBR, float radiusBL, - float softness, - int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - r.shape(UiShape.roundedRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL), UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_CORNERS, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + public static void roundedRectCornersQuad(Renderer2D r, double x, double y, double w, double h, + float radiusTL, float radiusTR, float radiusBR, float radiusBL, + float softness, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + r.shape(UiShape.roundedRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL), + UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft)); + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_CORNERS); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); + float[] radii = Batch2D.CORNER_RADII; + normalizeCornerRadii(w, h, radiusTL, radiusTR, radiusBR, radiusBL, radii); int tlA = (cTopLeft >>> 24) & 0xFF; int tlR = (cTopLeft >>> 16) & 0xFF; @@ -138,15 +116,31 @@ final class Renderer2DRects { int blR = (cBottomLeft >>> 16) & 0xFF; int blG = (cBottomLeft >>> 8) & 0xFF; int blB = cBottomLeft & 0xFF; - float[] cornerRadiiTmp = r.cornerRadiiTmp; - normalizeCornerRadii(w, h, radiusTL, radiusTR, radiusBR, radiusBL, cornerRadiiTmp); - int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, 0f, 0f, 0f).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, 0f, 0f, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, 0f, 0f, 0f).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, 0f, 0f, 0f).next(); + int i1 = mesh.vec2(x, y).local2(x, y).color(tlR, tlG, tlB, tlA).vec4(x, y, w, h) + .vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, 0f, 0f, 0f).next(); + int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(blR, blG, blB, blA).vec4(x, y, w, h) + .vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, 0f, 0f, 0f).next(); + int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h) + .vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, 0f, 0f, 0f).next(); + int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h) + .vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, 0f, 0f, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } -} \ No newline at end of file + + public static void roundedRectGradientQuad(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + roundedRectMaskedQuad(r, x, y, w, h, x, y, w, h, radius, 0.0f, + cTopLeft, cTopRight, cBottomRight, cBottomLeft); + } + + public static void roundedRectGradient(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, + int startArgb, int endArgb, float angleDeg, float offsetPx) { + int[] g = Batch2D.GRADIENT; + computeLinearGradientColors((float) w, (float) h, startArgb, endArgb, angleDeg, offsetPx, g); + roundedRectGradientQuad(r, x, y, w, h, radius, 0.0f, g[0], g[1], g[2], g[3]); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRectsExtra.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Strokes2D.java similarity index 57% rename from combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRectsExtra.java rename to combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Strokes2D.java index a9aeedc..261a797 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRectsExtra.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/Strokes2D.java @@ -5,10 +5,12 @@ * Licensed under the GNU General Public License v3.0. */ -package dev.loki.lovisual.render.engine.renderer; +package dev.loki.lovisual.render.engine.renderer.renderer2d; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.renderer2d.path.Polygons2D; import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; @@ -19,22 +21,17 @@ import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.bu import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampRoundedRadius; import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeCornerRadii; -final class Renderer2DRectsExtra { - private Renderer2DRectsExtra() { +/** Stroked rounded rect family plus the clipped progress bar fill. */ +public final class Strokes2D { + private Strokes2D() { } - static void roundedRectStroke(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, float thickness, int argb) { + public static void roundedRectStroke(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, float thickness, int argb) { r.shapeStroke(UiShape.roundedRect(x, y, w, h, radius), UiPaint.solid(argb), UiStroke.of(thickness)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_STROKE, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_STROKE); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int a = (argb >>> 24) & 0xFF; @@ -49,91 +46,68 @@ final class Renderer2DRectsExtra { int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, 0f).next(); int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void roundedRectStrokeAngularGradient(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, float thickness, - int startArgb, int endArgb, float offset) { + public static void roundedRectStrokeAngularGradient(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, float thickness, + int startArgb, int endArgb, float offset) { r.shapeStroke(UiShape.roundedRect(x, y, w, h, radius), UiPaint.solid(startArgb), UiStroke.of(thickness)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_STROKE_ANGULAR, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_STROKE_ANGULAR); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int sa = (startArgb >>> 24) & 0xFF; int sr = (startArgb >>> 16) & 0xFF; int sg = (startArgb >>> 8) & 0xFF; int sb = startArgb & 0xFF; - float ea = ((endArgb >>> 24) & 0xFF) / 255.0f; - float er = ((endArgb >>> 16) & 0xFF) / 255.0f; - float eg = ((endArgb >>> 8) & 0xFF) / 255.0f; - float eb = (endArgb & 0xFF) / 255.0f; + float[] end = Batch2D.rgbaNorm(endArgb); float clampedRadius = clampRoundedRadius(radius, w, h); float stroke = Math.max(0.0f, thickness); float phase = offset - (float) Math.floor(offset); - int i1 = mesh.vec2(x, y).local2(x, y).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(er, eg, eb, ea).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(er, eg, eb, ea).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(er, eg, eb, ea).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(er, eg, eb, ea).next(); + int i1 = mesh.vec2(x, y).local2(x, y).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(end[0], end[1], end[2], end[3]).next(); + int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(end[0], end[1], end[2], end[3]).next(); + int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(end[0], end[1], end[2], end[3]).next(); + int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(sr, sg, sb, sa).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, phase).vec4(end[0], end[1], end[2], end[3]).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void roundedRectStrokeCorners(Renderer2D r, double x, double y, double w, double h, - float radiusTL, float radiusTR, float radiusBR, float radiusBL, - float softness, float thickness, int argb) { + public static void roundedRectStrokeCorners(Renderer2D r, double x, double y, double w, double h, + float radiusTL, float radiusTR, float radiusBR, float radiusBL, + float softness, float thickness, int argb) { r.shapeStroke(UiShape.roundedRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL), UiPaint.solid(argb), UiStroke.of(thickness)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_STROKE_CORNERS, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_STROKE_CORNERS); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int a = (argb >>> 24) & 0xFF; int red = (argb >>> 16) & 0xFF; int green = (argb >>> 8) & 0xFF; int blue = argb & 0xFF; - float[] cornerRadiiTmp = r.cornerRadiiTmp; - normalizeCornerRadii(w, h, radiusTL, radiusTR, radiusBR, radiusBL, cornerRadiiTmp); + float[] radii = Batch2D.CORNER_RADII; + normalizeCornerRadii(w, h, radiusTL, radiusTR, radiusBR, radiusBL, radii); float stroke = Math.max(0.0f, thickness); - int i1 = mesh.vec2(x, y).local2(x, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, stroke, 0f, 0f).next(); - int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, stroke, 0f, 0f).next(); - int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, stroke, 0f, 0f).next(); - int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(cornerRadiiTmp[0], cornerRadiiTmp[1], cornerRadiiTmp[2], cornerRadiiTmp[3]).vec4(0.0f, stroke, 0f, 0f).next(); + int i1 = mesh.vec2(x, y).local2(x, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, stroke, 0f, 0f).next(); + int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, stroke, 0f, 0f).next(); + int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, stroke, 0f, 0f).next(); + int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(radii[0], radii[1], radii[2], radii[3]).vec4(0.0f, stroke, 0f, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void roundedRectStrokeGradientQuad(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, float thickness, - int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { - r.shapeStroke(UiShape.roundedRect(x, y, w, h, radius), UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft), UiStroke.of(thickness)); - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_STROKE, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + public static void roundedRectStrokeGradientQuad(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, float thickness, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + r.shapeStroke(UiShape.roundedRect(x, y, w, h, radius), + UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft), UiStroke.of(thickness)); + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_STROKE); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int tlA = (cTopLeft >>> 24) & 0xFF; @@ -160,29 +134,35 @@ final class Renderer2DRectsExtra { int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(brR, brG, brB, brA).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, 0f).next(); int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(trR, trG, trB, trA).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, stroke, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void roundedProgressRectGradient(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, - float progress, - boolean fromRight, - int startArgb, int endArgb, - float angleDeg, float offsetPx) { + /** + * Draws a progress segment clipped by the full rounded bar outline. + * + *

Unlike drawing a rounded rectangle with the current fill width, this keeps the moving + * progress edge straight and lets only the fixed outer bar bounds define rounded ends. That + * prevents small health values from becoming a pill/capsule and prevents the fill from leaking + * outside the left/right rounded shell when the progress edge crosses an outer corner.

+ */ + public static void roundedProgressRectGradient(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, + float progress, boolean fromRight, + int startArgb, int endArgb, + float angleDeg, float offsetPx) { if (w <= 0.0 || h <= 0.0) return; float p = Mth.clamp(progress, 0.0f, 1.0f); if (p <= 0.0001f) return; if (p >= 0.9999f) { - r.roundedRectGradient(x, y, w, h, radius, softness, startArgb, endArgb, angleDeg, offsetPx); + Rects2D.roundedRectGradient(r, x, y, w, h, radius, softness, startArgb, endArgb, angleDeg, offsetPx); return; } double fillW = Math.max(0.0, Math.min(w, w * p)); double fillX = fromRight ? x + w - fillW : x; double clipX = fromRight ? fillX : x + fillW; - int count = buildRoundedProgressRect(r.progressShapeTmp, r.rectShapeTmp, x, y, w, h, radius, clipX, fromRight); + int count = buildRoundedProgressRect(Batch2D.PROGRESS_SHAPE, Batch2D.RECT_SHAPE, x, y, w, h, radius, clipX, fromRight); if (count < 3) return; - Renderer2DPolygons.polygonGradient(r, r.progressShapeTmp, count, fillX, y, fillW, h, startArgb, endArgb, angleDeg, offsetPx); + Polygons2D.polygonGradient(r, Batch2D.PROGRESS_SHAPE, count, fillX, y, fillW, h, startArgb, endArgb, angleDeg, offsetPx); } -} \ No newline at end of file +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Blur2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Blur2D.java new file mode 100644 index 0000000..7196e11 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Blur2D.java @@ -0,0 +1,151 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.fx; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTextureView; +import dev.loki.lovisual.render.engine.postprocess.PostProcessManager; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.ui.BlurSource; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiEffectSpec; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; + +import java.util.function.Consumer; + +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clamp01; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampChamfer; + +/** Background blur rectangles fed from the main framebuffer. */ +public final class Blur2D { + private Blur2D() { + } + + private static Renderer2D.BlurQuality legacyBlurQuality(float legacyQuality) { + if (legacyQuality >= 32.0f) return Renderer2D.BlurQuality.ULTRA; + if (legacyQuality >= 12.0f) return Renderer2D.BlurQuality.HIGH; + if (legacyQuality >= 5.0f) return Renderer2D.BlurQuality.MEDIUM; + return Renderer2D.BlurQuality.LOW; + } + + private static float legacyKawaseOffset(float legacyQuality) { + if (!Float.isFinite(legacyQuality)) return Renderer2D.DEFAULT_KAWASE_OFFSET_PX; + return Mth.clamp(legacyQuality / 18.0f, 0.85f, 3.25f); + } + + /** Main framebuffer color texture plus the shared post-process sampler. */ + public static BlurSource source() { + Minecraft mc = Minecraft.getInstance(); + if (mc == null) return null; + RenderTarget fb = mc.gameRenderer.mainRenderTarget(); + if (fb == null) return null; + GpuTextureView src = fb.getColorTextureView(); + if (src == null) return null; + GpuSampler sampler = PostProcessManager.getSampler(); + if (sampler == null) return null; + return new BlurSource(src, sampler); + } + + public static void blurRect(Renderer2D r, double x, double y, double w, double h, + float radius, float quality, float brightness, float alpha, int ignoredTintRgb) { + if (w <= 0.0 || h <= 0.0) return; + if (alpha <= 0.001f) return; + + r.effect(UiEffectSpec.blur(UiShape.roundedRect(x, y, w, h, radius), radius, ignoredTintRgb)); + BlurSource source = source(); + if (source == null) return; + + DrawBatch batch = Batch2D.openBlur(r, UiBatchType.BLUR, source.view, source.sampler, + legacyBlurQuality(quality), legacyKawaseOffset(quality)); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.alpha = 1.0; + + float finalAlpha = (float) (alpha * r.getAlpha()); + mesh.ensureQuadCapacity(); + int i1 = mesh.vec2(x, y).local2(x, y).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); + int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); + int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); + int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(255, 255, 255, 255).vec4(x, y, w, h).vec4(radius, quality, brightness, finalAlpha).next(); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void glassBlurRect(Renderer2D r, double x, double y, double w, double h, + float radius, float quality, float brightness, float alpha, int ignoredTintRgb) { + if (w <= 0.0 || h <= 0.0) return; + if (alpha <= 0.001f) return; + + r.effect(UiEffectSpec.blur(UiShape.roundedRect(x, y, w, h, radius), radius, ignoredTintRgb)); + BlurSource source = source(); + if (source == null) return; + + DrawBatch batch = Batch2D.openBlur(r, UiBatchType.GLASS_BLUR, source.view, source.sampler, + Renderer2D.BlurQuality.HIGH, legacyKawaseOffset(quality)); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.alpha = 1.0; + + float finalAlpha = clamp01((float) (alpha * r.getAlpha())); + if (finalAlpha <= 0.001f) { + Batch2D.close(); + return; + } + + int a = Math.max(0, Math.min(255, Math.round(finalAlpha * 255.0f))); + float smoothness = 2.0f; + float rad = Math.max(0.0f, radius); + + mesh.ensureQuadCapacity(); + int i1 = mesh.vec2(x, y).local2(x, y).color(255, 255, 255, a) + .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); + int i2 = mesh.vec2(x, y + h).local2(x, y + h).color(255, 255, 255, a) + .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); + int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(255, 255, 255, a) + .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); + int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(255, 255, 255, a) + .vec4(x, y, w, h).vec4(rad, rad, rad, rad).vec4(quality, brightness, smoothness, 0.0f).next(); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void blurChamferedRect(Renderer2D r, double x, double y, double w, double h, + double chamfer, float quality, float brightness, float alpha, int ignoredTintRgb) { + if (w <= 0.0 || h <= 0.0) return; + if (alpha <= 0.001f) return; + float cut = clampChamfer(chamfer, w, h); + if (cut <= 0.0f) { + blurRect(r, x, y, w, h, 0.0f, quality, brightness, alpha, ignoredTintRgb); + return; + } + blurRect(r, x, y, w, h, -cut, quality, brightness, alpha, ignoredTintRgb); + } + + public static void blurComposite(Renderer2D r, Consumer consumer) { + if (consumer == null) return; + + BlurSource source = source(); + if (source == null) return; + + DrawBatch batch = Batch2D.openBlur(r, UiBatchType.BLUR_COMPLEX, source.view, source.sampler, + Renderer2D.DEFAULT_BLUR_QUALITY, Renderer2D.DEFAULT_KAWASE_OFFSET_PX); + if (batch == null) return; + batch.mesh.alpha = 1.0; + try { + consumer.accept(new Renderer2D.BlurComposite(batch.mesh, (float) r.getAlpha())); + } finally { + Batch2D.close(); + } + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRectsGlow.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Effects2D.java similarity index 69% rename from combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRectsGlow.java rename to combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Effects2D.java index d4aeaa4..ece1145 100644 --- a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/Renderer2DRectsGlow.java +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Effects2D.java @@ -5,29 +5,26 @@ * Licensed under the GNU General Public License v3.0. */ -package dev.loki.lovisual.render.engine.renderer; +package dev.loki.lovisual.render.engine.renderer.renderer2d.fx; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; import dev.loki.lovisual.render.engine.uniform.MeshBuilder; import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampRoundedRadius; -final class Renderer2DRectsGlow { - private Renderer2DRectsGlow() { +/** Glow, shadow and radial-glow rect effects. */ +public final class Effects2D { + private Effects2D() { } - static void roundedRectGlow(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, float glow, int argb) { - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_GLOW, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + public static void roundedRectGlow(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, float glow, int argb) { + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_GLOW); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int a = (argb >>> 24) & 0xFF; @@ -47,21 +44,14 @@ final class Renderer2DRectsGlow { int i3 = mesh.vec2(gx + gw, gy + gh).local2(gx + gw, gy + gh).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, glow, 0f).next(); int i4 = mesh.vec2(gx + gw, gy).local2(gx + gw, gy).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, glow, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void roundedRectShadow(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, float spread, int argb) { - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_SHADOW, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + public static void roundedRectShadow(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, float spread, int argb) { + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_SHADOW); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int a = (argb >>> 24) & 0xFF; @@ -82,21 +72,14 @@ final class Renderer2DRectsGlow { int i3 = mesh.vec2(gx + gw, gy + gh).local2(gx + gw, gy + gh).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, spread, 0f).next(); int i4 = mesh.vec2(gx + gw, gy).local2(gx + gw, gy).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, spread, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void roundedRectSoftShadow(Renderer2D r, double x, double y, double w, double h, - float radius, float blur, float innerAlpha, int argb) { - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.ROUNDED_SOFT_SHADOW, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + public static void roundedRectSoftShadow(Renderer2D r, double x, double y, double w, double h, + float radius, float blur, float innerAlpha, int argb) { + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_SOFT_SHADOW); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int a = (argb >>> 24) & 0xFF; @@ -115,23 +98,16 @@ final class Renderer2DRectsGlow { int i3 = mesh.vec2(sx + sw, sy + sh).local2(sx + sw, sy + sh).color(red, green, blue, a).vec4(x, y, w, h).vec4(radius, blur, innerAlpha, 0f).next(); int i4 = mesh.vec2(sx + sw, sy).local2(sx + sw, sy).color(red, green, blue, a).vec4(x, y, w, h).vec4(radius, blur, innerAlpha, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } - static void radialGlowMasked(Renderer2D r, double x, double y, double w, double h, - float radius, float softness, - float glowRadius, float cx, float cy, - int argb) { - boolean auto = r.beginAutoBatch(); - DrawBatch batch = Renderer2D.UI_BATCHER.getOrCreate(UiBatchType.RADIAL_GLOW, null, null); - if (batch == null) { - r.endAutoBatch(auto); - return; - } + public static void radialGlowMasked(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, + float glowRadius, float cx, float cy, + int argb) { + DrawBatch batch = Batch2D.open(r, UiBatchType.RADIAL_GLOW); + if (batch == null) return; MeshBuilder mesh = batch.mesh; - mesh.alpha = r.alpha; - mesh.ensureQuadCapacity(); int a = (argb >>> 24) & 0xFF; @@ -145,7 +121,6 @@ final class Renderer2DRectsGlow { int i3 = mesh.vec2(x + w, y + h).local2(x + w, y + h).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, glowRadius, 0.0f).vec4(cx, cy, 0f, 0f).next(); int i4 = mesh.vec2(x + w, y).local2(x + w, y).color(red, green, blue, a).vec4(x, y, w, h).vec4(clampedRadius, 0.0f, glowRadius, 0.0f).vec4(cx, cy, 0f, 0f).next(); mesh.quad(i1, i2, i3, i4); - - r.endAutoBatch(auto); + Batch2D.close(); } -} \ No newline at end of file +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Glass2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Glass2D.java new file mode 100644 index 0000000..a58425a --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Glass2D.java @@ -0,0 +1,89 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.fx; + +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.ui.BlurSource; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiEffectSpec; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; + +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clamp01; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeCornerRadii; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.packLiquidGlassPayload; + +/** Liquid glass material rectangles backed by the frame blur pipeline. */ +public final class Glass2D { + private Glass2D() { + } + + // squirclePower < 1.5f disables the squircle falloff + public static void liquidGlassRectCorners(Renderer2D r, double x, double y, double w, double h, + float radiusTL, float radiusTR, float radiusBR, float radiusBL, + float softness, + int tintArgb, + float glassAlpha, + float blurAlpha, + float fresnelPower, + float fresnelAlpha, + float baseAlpha, + float fresnelMix, + float distortPx, + float squirclePower) { + if (w <= 0.0 || h <= 0.0) return; + + r.effect(UiEffectSpec.liquidGlass(UiShape.roundedRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL), + softness, softness, distortPx, tintArgb)); + BlurSource source = Blur2D.source(); + if (source == null) return; + + DrawBatch batch = Batch2D.openBlur(r, UiBatchType.LIQUID_GLASS, source.view, source.sampler, + Renderer2D.DEFAULT_LIQUID_GLASS_BLUR_QUALITY, Renderer2D.LIQUID_GLASS_KAWASE_OFFSET_PX); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int[] tint = Batch2D.rgba(tintArgb); + float clampedBlurAlpha = clamp01(blurAlpha); + int finalA = (int) (tint[3] * clamp01(glassAlpha)); + + float[] radii = Batch2D.CORNER_RADII; + normalizeCornerRadii(w, h, radiusTL, radiusTR, radiusBR, radiusBL, radii); + + float thickness = Math.max(0.0f, softness); + float mix = clamp01(fresnelMix); + float fa = clamp01(fresnelAlpha); + float ba = clamp01(baseAlpha); + float packedDistort = packLiquidGlassPayload(distortPx, squirclePower, clampedBlurAlpha); + + int i1 = glassVertex(mesh, x, y, x, y, w, h, tint, finalA, radii, mix, packedDistort, thickness, fresnelPower, fa, ba); + int i2 = glassVertex(mesh, x, y + h, x, y, w, h, tint, finalA, radii, mix, packedDistort, thickness, fresnelPower, fa, ba); + int i3 = glassVertex(mesh, x + w, y + h, x, y, w, h, tint, finalA, radii, mix, packedDistort, thickness, fresnelPower, fa, ba); + int i4 = glassVertex(mesh, x + w, y, x, y, w, h, tint, finalA, radii, mix, packedDistort, thickness, fresnelPower, fa, ba); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + /** + * One liquid-glass vertex. {@code px}/{@code py} is the vertex position; + * {@code rx}/{@code ry} is the rect origin carried by the Rect payload. + */ + private static int glassVertex(MeshBuilder mesh, double px, double py, double rx, double ry, double w, double h, + int[] tint, int finalA, float[] radii, + float mix, float packedDistort, + float thickness, float fresnelPower, float fresnelAlpha, float baseAlpha) { + return mesh.vec2(px, py).raw2(mix, packedDistort).local2(px, py).color(tint[0], tint[1], tint[2], finalA) + .vec4((float) rx, (float) ry, (float) w, (float) h) + .vec4(radii[0], radii[1], radii[2], radii[3]) + .vec4(thickness, fresnelPower, fresnelAlpha, baseAlpha) + .next(); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Smoke2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Smoke2D.java new file mode 100644 index 0000000..26b26b6 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Smoke2D.java @@ -0,0 +1,86 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.fx; + +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; +import net.minecraft.util.Mth; + +/** Procedural smoke fill for rounded rects (health/progress style bars). */ +public final class Smoke2D { + private Smoke2D() { + } + + public static void roundedSmokeFill(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, + float fillRatio, + boolean fromRight, + int firstArgb, + int secondArgb, + int thirdArgb, + float time, + float smokeScale, + float smokeMix, + int octaves, + float flowX, + float flowY, + float intensity) { + if (w <= 0.0 || h <= 0.0) return; + float fill = Mth.clamp(fillRatio, 0.0f, 1.0f); + if (fill <= 0.0001f) return; + + DrawBatch batch = Batch2D.open(r, UiBatchType.ROUNDED_FILL_SMOKE); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int[] first = Batch2D.rgba(firstArgb); + float[] second = Batch2D.rgbaNorm(secondArgb); + float[] third = Batch2D.rgbaNorm(thirdArgb); + + float fromRightFlag = fromRight ? 1.0f : 0.0f; + float safeScale = Math.max(0.1f, smokeScale); + float safeMix = Mth.clamp(smokeMix, 0.0f, 1.0f); + float safeOctaves = Mth.clamp(octaves, 1, 8); + float safeIntensity = Math.max(0.0f, intensity); + + int i1 = smokeVertex(mesh, x, y, x, y, w, h, first, second, third, radius, softness, fill, fromRightFlag, + time, safeScale, safeMix, safeOctaves, flowX, flowY, safeIntensity); + int i2 = smokeVertex(mesh, x, y + h, x, y, w, h, first, second, third, radius, softness, fill, fromRightFlag, + time, safeScale, safeMix, safeOctaves, flowX, flowY, safeIntensity); + int i3 = smokeVertex(mesh, x + w, y + h, x, y, w, h, first, second, third, radius, softness, fill, fromRightFlag, + time, safeScale, safeMix, safeOctaves, flowX, flowY, safeIntensity); + int i4 = smokeVertex(mesh, x + w, y, x, y, w, h, first, second, third, radius, softness, fill, fromRightFlag, + time, safeScale, safeMix, safeOctaves, flowX, flowY, safeIntensity); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + /** + * One smoke-fill vertex. {@code px}/{@code py} is the vertex position; + * {@code rx}/{@code ry} is the rect origin carried by the Rect payload + * (identical for all four vertices of the quad). + */ + private static int smokeVertex(MeshBuilder mesh, double px, double py, double rx, double ry, double w, double h, + int[] first, float[] second, float[] third, + float radius, float softness, float fill, float fromRightFlag, + float time, float scale, float mix, float octaves, + float flowX, float flowY, float intensity) { + return mesh.vec2(px, py).local2(px, py).color(first[0], first[1], first[2], first[3]) + .vec4((float) rx, (float) ry, (float) w, (float) h) + .vec4(radius, softness, fill, fromRightFlag) + .vec4(second[0], second[1], second[2], second[3]) + .vec4(third[0], third[1], third[2], third[3]) + .vec4(time, scale, mix, octaves) + .vec4(flowX, flowY, intensity, 0.0f) + .next(); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/media/Items2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/media/Items2D.java new file mode 100644 index 0000000..ba61792 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/media/Items2D.java @@ -0,0 +1,57 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.media; + +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.ui.UiItemSubmission; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +/** ItemStack submission into the deferred item batch renderer. */ +public final class Items2D { + private Items2D() { + } + + public static void submit(Renderer2D r, + @Nullable LocalPlayer player, + ItemStack stack, + double x, + double y, + float scaleX, + float scaleY, + float pivotX, + float pivotY, + boolean pivoted, + int seed, + boolean drawItem, + int overlayFlags, + @Nullable String stackCountText, + int durabilityThresholdPercent, + int durabilityTextColorThresholdPercent) { + UiItemSubmission.submit( + r.isTextured(), + r.getAlpha(), + player, + stack, + x, + y, + scaleX, + scaleY, + pivotX, + pivotY, + pivoted, + seed, + drawItem, + overlayFlags, + stackCountText, + durabilityThresholdPercent, + durabilityTextColorThresholdPercent + ); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/media/Textures2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/media/Textures2D.java new file mode 100644 index 0000000..678ef6e --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/media/Textures2D.java @@ -0,0 +1,153 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.media; + +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTextureView; +import dev.loki.lovisual.render.engine.command.UiShapeCommand; +import dev.loki.lovisual.render.engine.command.UiTextureDrawCommand; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.ui.UiRenderDispatcher; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; + +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.clampRoundedRadius; + +/** Texture quads: plain, MSDF-font and rounded-corner variants. */ +public final class Textures2D { + private Textures2D() { + } + + public static void textureQuad(Renderer2D r, GpuTextureView samplerView, GpuSampler sampler, + double x, double y, double w, double h, int argb) { + if (r.isTextured()) { + throw new IllegalStateException("Batched texture quad drawing is supported only on Renderer2D.COLOR."); + } + if (samplerView == null || sampler == null) return; + if (w == 0.0 || h == 0.0) return; + + UiRenderDispatcher.record(new UiTextureDrawCommand(samplerView, sampler, + UiShape.rect(x, y, w, h), UiPaint.solid(argb), 0f, 0f, 1f, 1f, false)); + DrawBatch batch = Batch2D.open(r, UiBatchType.TEXTURED, samplerView, sampler); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int[] c = Batch2D.rgba(argb); + int i1 = mesh.vec2(x, y).raw2(0.0, 0.0).color(c[0], c[1], c[2], c[3]).next(); + int i2 = mesh.vec2(x, y + h).raw2(0.0, 1.0).color(c[0], c[1], c[2], c[3]).next(); + int i3 = mesh.vec2(x + w, y + h).raw2(1.0, 1.0).color(c[0], c[1], c[2], c[3]).next(); + int i4 = mesh.vec2(x + w, y).raw2(1.0, 0.0).color(c[0], c[1], c[2], c[3]).next(); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void msdfTextureQuad(Renderer2D r, GpuTextureView samplerView, GpuSampler sampler, + double x, double y, double w, double h, + int topLeft, int topRight, int bottomRight, int bottomLeft, + float pxRange, int atlasWidth, int atlasHeight) { + if (r.isTextured()) { + throw new IllegalStateException("Batched MSDF texture quad drawing is supported only on Renderer2D.COLOR."); + } + if (samplerView == null || sampler == null) return; + if (w == 0.0 || h == 0.0) return; + if (pxRange <= 0.0f || atlasWidth <= 0 || atlasHeight <= 0) return; + + UiRenderDispatcher.record(new UiTextureDrawCommand(samplerView, sampler, + UiShape.rect(x, y, w, h), UiPaint.solid(topLeft), 0f, 0f, 1f, 1f, false)); + DrawBatch batch = Batch2D.openMsdf(r, samplerView, sampler, pxRange, atlasWidth, atlasHeight); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int i1 = msdfVertex(mesh, x, y, 0.0, 0.0, topLeft); + int i2 = msdfVertex(mesh, x, y + h, 0.0, 1.0, bottomLeft); + int i3 = msdfVertex(mesh, x + w, y + h, 1.0, 1.0, bottomRight); + int i4 = msdfVertex(mesh, x + w, y, 1.0, 0.0, topRight); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void texQuad(Renderer2D r, double x, double y, double width, double height, + double texX1, double texY1, double texX2, double texY2, + int argb) { + UiRenderDispatcher.record(new UiShapeCommand(UiShape.rect(x, y, width, height), + UiPaint.solid(argb), UiStroke.NONE, true)); + MeshBuilder texturedTriangles = r.texturedMesh(); + if (texturedTriangles == null) return; + texturedTriangles.alpha = r.getAlpha(); + texturedTriangles.ensureQuadCapacity(); + + int[] c = Batch2D.rgba(argb); + int i1 = texturedTriangles.vec2(x, y).raw2(texX1, texY1).color(c[0], c[1], c[2], c[3]).next(); + int i2 = texturedTriangles.vec2(x, y + height).raw2(texX1, texY2).color(c[0], c[1], c[2], c[3]).next(); + int i3 = texturedTriangles.vec2(x + width, y + height).raw2(texX2, texY2).color(c[0], c[1], c[2], c[3]).next(); + int i4 = texturedTriangles.vec2(x + width, y).raw2(texX2, texY1).color(c[0], c[1], c[2], c[3]).next(); + texturedTriangles.quad(i1, i2, i3, i4); + } + + public static void roundedTexRect(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, + double texX1, double texY1, double texX2, double texY2, + int argb, GpuTextureView samplerView, GpuSampler sampler) { + roundedTex(r, x, y, w, h, radius, texX1, texY1, texX2, texY2, argb, samplerView, sampler, + UiBatchType.ROUNDED_TEXTURED, false); + } + + public static void roundedTexMaskRect(Renderer2D r, double x, double y, double w, double h, + float radius, float softness, + double texX1, double texY1, double texX2, double texY2, + int argb, GpuTextureView samplerView, GpuSampler sampler) { + roundedTex(r, x, y, w, h, radius, texX1, texY1, texX2, texY2, argb, samplerView, sampler, + UiBatchType.ROUNDED_TEXTURED_MASK, true); + } + + private static void roundedTex(Renderer2D r, double x, double y, double w, double h, + float radius, + double texX1, double texY1, double texX2, double texY2, + int argb, GpuTextureView samplerView, GpuSampler sampler, + UiBatchType type, boolean mask) { + if (!r.isTextured()) { + throw new IllegalStateException("Renderer2D is not configured for textures."); + } + if (samplerView == null || sampler == null) return; + + UiRenderDispatcher.record(new UiTextureDrawCommand(samplerView, sampler, + UiShape.roundedRect(x, y, w, h, radius), UiPaint.solid(argb), + (float) texX1, (float) texY1, (float) texX2, (float) texY2, mask)); + DrawBatch batch = Batch2D.open(r, type, samplerView, sampler); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int[] c = Batch2D.rgba(argb); + float clampedRadius = clampRoundedRadius(radius, w, h); + int i1 = roundedTexVertex(mesh, x, y, texX1, texY1, c, x, y, w, h, clampedRadius); + int i2 = roundedTexVertex(mesh, x, y + h, texX1, texY2, c, x, y, w, h, clampedRadius); + int i3 = roundedTexVertex(mesh, x + w, y + h, texX2, texY2, c, x, y, w, h, clampedRadius); + int i4 = roundedTexVertex(mesh, x + w, y, texX2, texY1, c, x, y, w, h, clampedRadius); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + private static int msdfVertex(MeshBuilder mesh, double px, double py, double u, double v, int argb) { + int[] c = Batch2D.rgba(argb); + return mesh.vec2(px, py).raw2(u, v).color(c[0], c[1], c[2], c[3]).next(); + } + + private static int roundedTexVertex(MeshBuilder mesh, double px, double py, double u, double v, + int[] c, double rx, double ry, double w, double h, float radius) { + return mesh.vec2(px, py).raw2(u, v).local2(px, py).color(c[0], c[1], c[2], c[3]) + .vec4(rx, ry, w, h).vec4(radius, 0.0f, 0f, 0f).next(); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Circles2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Circles2D.java new file mode 100644 index 0000000..35c6d85 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Circles2D.java @@ -0,0 +1,200 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.path; + +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; +import net.minecraft.util.Mth; + +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.normalizeDegrees; + +/** Circles, rings, arcs and the animated Orbiz ring effect. */ +public final class Circles2D { + private Circles2D() { + } + + public static void circle(Renderer2D r, double cx, double cy, double radius, float softness, int argb) { + r.shape(UiShape.circle(cx, cy, radius), UiPaint.solid(argb)); + DrawBatch batch = Batch2D.open(r, UiBatchType.CIRCLE); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int[] c = Batch2D.rgba(argb); + double outerRadius = radius + Math.max(0.0f, softness); + double x = cx - outerRadius; + double y = cy - outerRadius; + double size = outerRadius * 2.0; + + int i1 = circleVertex(mesh, x, y, c, x, y, size, (float) radius, softness, 0f); + int i2 = circleVertex(mesh, x, y + size, c, x, y, size, (float) radius, softness, 0f); + int i3 = circleVertex(mesh, x + size, y + size, c, x, y, size, (float) radius, softness, 0f); + int i4 = circleVertex(mesh, x + size, y, c, x, y, size, (float) radius, softness, 0f); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void circleStroke(Renderer2D r, double cx, double cy, double radius, + double thickness, float softness, int argb) { + r.shapeStroke(UiShape.circle(cx, cy, radius), UiPaint.solid(argb), UiStroke.of(thickness)); + DrawBatch batch = Batch2D.open(r, UiBatchType.CIRCLE); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int[] c = Batch2D.rgba(argb); + float stroke = (float) Math.max(0.0, thickness); + double outerRadius = radius + stroke * 0.5 + softness; + double x = cx - outerRadius; + double y = cy - outerRadius; + double size = outerRadius * 2.0; + + int i1 = circleVertex(mesh, x, y, c, x, y, size, (float) radius, softness, stroke); + int i2 = circleVertex(mesh, x, y + size, c, x, y, size, (float) radius, softness, stroke); + int i3 = circleVertex(mesh, x + size, y + size, c, x, y, size, (float) radius, softness, stroke); + int i4 = circleVertex(mesh, x + size, y, c, x, y, size, (float) radius, softness, stroke); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void arcStrokeQuad(Renderer2D r, double cx, double cy, double radius, double thickness, + float startAngleDeg, float endAngleDeg, float softness, boolean caps, + int cTopLeft, int cTopRight, int cBottomRight, int cBottomLeft) { + float start = normalizeDegrees(startAngleDeg); + float end = normalizeDegrees(endAngleDeg); + float sweep = end - start; + if (sweep <= 0.0f) { + sweep += 360.0f; + } + if (sweep <= 0.001f) { + return; + } + + r.shapeStroke(UiShape.arc(cx, cy, radius, start, end), + UiPaint.corners(cTopLeft, cTopRight, cBottomRight, cBottomLeft), UiStroke.of(thickness)); + DrawBatch batch = Batch2D.open(r, UiBatchType.ARC); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + float stroke = (float) Math.max(0.0, thickness); + double outerRadius = radius + stroke * 0.5 + softness; + double x = cx - outerRadius; + double y = cy - outerRadius; + double size = outerRadius * 2.0; + + int i1 = arcVertex(mesh, x, y, cTopLeft, x, y, size, (float) radius, softness, stroke, start, end, caps); + int i2 = arcVertex(mesh, x, y + size, cBottomLeft, x, y, size, (float) radius, softness, stroke, start, end, caps); + int i3 = arcVertex(mesh, x + size, y + size, cBottomRight, x, y, size, (float) radius, softness, stroke, start, end, caps); + int i4 = arcVertex(mesh, x + size, y, cTopRight, x, y, size, (float) radius, softness, stroke, start, end, caps); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + public static void orbizRing(Renderer2D r, double cx, double cy, + double radius, double thickness, + float startAngleDeg, float sweepAngleDeg, + float softness, float glowRadius, float glowStrength, + int primaryArgb, int secondaryArgb, + int colorMode, float angularOffset, float angularCycles, + float trackAlpha, float bodyAlpha, float headBoost, + float trackGlow, boolean caps) { + if (r.isTextured()) { + throw new IllegalStateException("Orbiz ring drawing is supported only on Renderer2D.COLOR."); + } + if (radius <= 0.0 || thickness <= 0.0) return; + if (((primaryArgb >>> 24) & 0xFF) <= 0 && ((secondaryArgb >>> 24) & 0xFF) <= 0) return; + + DrawBatch batch = Batch2D.open(r, UiBatchType.ORBIZ_RING); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + mesh.ensureQuadCapacity(); + + int[] primary = Batch2D.rgba(primaryArgb); + float[] secondary = Batch2D.rgbaNorm(secondaryArgb); + + float stroke = (float) Math.max(0.0, thickness); + float ringRadius = (float) Math.max(0.0, radius); + float soft = Math.max(0.0f, softness); + float glow = Math.max(0.0f, glowRadius); + float expand = stroke * 0.5f + soft + glow + 1.0f; + double outerRadius = radius + expand; + double x = cx - outerRadius; + double y = cy - outerRadius; + double size = outerRadius * 2.0; + double ringX = cx - radius; + double ringY = cy - radius; + double ringSize = radius * 2.0; + + float start = normalizeDegrees(startAngleDeg); + float sweep = Mth.clamp(sweepAngleDeg, 0.0f, 360.0f); + float offset = angularOffset - (float) Math.floor(angularOffset); + float cycles = Math.max(0.001f, angularCycles); + float mode = Math.max(0, colorMode); + float track = Mth.clamp(trackAlpha, 0.0f, 1.0f); + float body = Mth.clamp(bodyAlpha, 0.0f, 1.0f); + float glowPower = Math.max(0.0f, glowStrength); + float head = Math.max(0.0f, headBoost); + float trackGlowAmount = Mth.clamp(trackGlow, 0.0f, 1.0f); + + int i1 = orbizVertex(mesh, x, y, primary, secondary, ringX, ringY, ringSize, ringRadius, soft, stroke, + start, sweep, glow, glowPower, mode, offset, cycles, caps, track, body, head, trackGlowAmount); + int i2 = orbizVertex(mesh, x, y + size, primary, secondary, ringX, ringY, ringSize, ringRadius, soft, stroke, + start, sweep, glow, glowPower, mode, offset, cycles, caps, track, body, head, trackGlowAmount); + int i3 = orbizVertex(mesh, x + size, y + size, primary, secondary, ringX, ringY, ringSize, ringRadius, soft, stroke, + start, sweep, glow, glowPower, mode, offset, cycles, caps, track, body, head, trackGlowAmount); + int i4 = orbizVertex(mesh, x + size, y, primary, secondary, ringX, ringY, ringSize, ringRadius, soft, stroke, + start, sweep, glow, glowPower, mode, offset, cycles, caps, track, body, head, trackGlowAmount); + mesh.quad(i1, i2, i3, i4); + Batch2D.close(); + } + + private static int circleVertex(MeshBuilder mesh, double px, double py, int[] c, + double rx, double ry, double size, + float radius, float softness, float stroke) { + return mesh.vec2(px, py).local2(px, py).color(c[0], c[1], c[2], c[3]) + .vec4(rx, ry, size, size) + .vec4(radius, softness, stroke, 0f) + .next(); + } + + private static int arcVertex(MeshBuilder mesh, double px, double py, int argb, + double rx, double ry, double size, + float radius, float softness, float stroke, + float start, float end, boolean caps) { + int[] c = Batch2D.rgba(argb); + return mesh.vec2(px, py).local2(px, py).color(c[0], c[1], c[2], c[3]) + .vec4(rx, ry, size, size) + .vec4(radius, softness, stroke, start) + .vec4(end, caps ? 1f : 0f, 0f, 0f) + .next(); + } + + private static int orbizVertex(MeshBuilder mesh, double px, double py, + int[] primary, float[] secondary, + double ringX, double ringY, double ringSize, float ringRadius, + float soft, float stroke, float start, + float sweep, float glow, float glowPower, float mode, + float offset, float cycles, boolean caps, + float track, float body, float head, float trackGlowAmount) { + return mesh.vec2(px, py).local2(px, py).color(primary[0], primary[1], primary[2], primary[3]) + .vec4(ringX, ringY, ringSize, ringSize) + .vec4(ringRadius, soft, stroke, start) + .vec4(sweep, glow, glowPower, mode) + .vec4(secondary[0], secondary[1], secondary[2], secondary[3]) + .vec4(offset, cycles, 0.0f, caps ? 1.0f : 0.0f) + .vec4(track, body, head, trackGlowAmount) + .next(); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Connectors2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Connectors2D.java new file mode 100644 index 0000000..373467c --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Connectors2D.java @@ -0,0 +1,156 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.path; + +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; + +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.buildBezier; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.roundedRectAnchor; + +/** Wires, cables and node-graph edges built on top of polylines. */ +public final class Connectors2D { + private Connectors2D() { + } + + public static void connector(Renderer2D r, double x1, double y1, double x2, double y2, + double thickness, int argb) { + double[] c = Batch2D.CONNECTOR; + c[0] = x1; + c[1] = y1; + c[2] = x2; + c[3] = y2; + Polygons2D.polyline(r, c, 2, thickness, false, argb, true); + } + + public static void connectorGradient(Renderer2D r, double x1, double y1, double x2, double y2, + double thickness, int startArgb, int endArgb) { + double[] c = Batch2D.CONNECTOR; + c[0] = x1; + c[1] = y1; + c[2] = x2; + c[3] = y2; + Polygons2D.polylineGradient(r, c, 2, thickness, false, startArgb, endArgb, true); + } + + public static void orthogonalConnector(Renderer2D r, double x1, double y1, double x2, double y2, + double midX, double thickness, int argb) { + double[] c = Batch2D.CONNECTOR; + c[0] = x1; + c[1] = y1; + c[2] = midX; + c[3] = y1; + c[4] = midX; + c[5] = y2; + c[6] = x2; + c[7] = y2; + Polygons2D.polyline(r, c, 4, thickness, false, argb, true); + } + + public static void orthogonalConnectorGradient(Renderer2D r, double x1, double y1, double x2, double y2, + double midX, double thickness, int startArgb, int endArgb) { + double[] c = Batch2D.CONNECTOR; + c[0] = x1; + c[1] = y1; + c[2] = midX; + c[3] = y1; + c[4] = midX; + c[5] = y2; + c[6] = x2; + c[7] = y2; + Polygons2D.polylineGradient(r, c, 4, thickness, false, startArgb, endArgb, true); + } + + public static void bezierConnector(Renderer2D r, double x1, double y1, + double cx1, double cy1, + double cx2, double cy2, + double x2, double y2, + int segments, + double thickness, + int argb) { + double[] c = Batch2D.CONNECTOR; + int count = buildBezier(c, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments); + Polygons2D.polyline(r, c, count, thickness, false, argb, false); + } + + public static void bezierConnectorGradient(Renderer2D r, double x1, double y1, + double cx1, double cy1, + double cx2, double cy2, + double x2, double y2, + int segments, + double thickness, + int startArgb, + int endArgb) { + double[] c = Batch2D.CONNECTOR; + int count = buildBezier(c, x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments); + Polygons2D.polylineGradient(r, c, count, thickness, false, startArgb, endArgb, false); + } + + public static void nodeGraphEdge(Renderer2D r, double x1, double y1, double x2, double y2, + double thickness, int argb) { + double c = Math.max(24.0, Math.abs(x2 - x1) * 0.5); + bezierConnector(r, x1, y1, x1 + c, y1, x2 - c, y2, x2, y2, 20, thickness, argb); + } + + public static void nodeGraphEdgeGradient(Renderer2D r, double x1, double y1, double x2, double y2, + double thickness, int startArgb, int endArgb) { + double c = Math.max(24.0, Math.abs(x2 - x1) * 0.5); + bezierConnectorGradient(r, x1, y1, x1 + c, y1, x2 - c, y2, x2, y2, 20, thickness, startArgb, endArgb); + } + + public static void roundedRectConnector(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, + double x2, double y2, double w2, double h2, double radius2, + double thickness, int argb) { + double[] a = anchors(x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2); + connector(r, a[0], a[1], a[2], a[3], thickness, argb); + } + + public static void roundedRectConnectorGradient(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, + double x2, double y2, double w2, double h2, double radius2, + double thickness, int startArgb, int endArgb) { + double[] a = anchors(x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2); + connectorGradient(r, a[0], a[1], a[2], a[3], thickness, startArgb, endArgb); + } + + public static void roundedRectNodeGraphEdge(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, + double x2, double y2, double w2, double h2, double radius2, + double thickness, int argb) { + double[] a = anchors(x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2); + nodeGraphEdge(r, a[0], a[1], a[2], a[3], thickness, argb); + } + + public static void roundedRectNodeGraphEdgeGradient(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, + double x2, double y2, double w2, double h2, double radius2, + double thickness, int startArgb, int endArgb) { + double[] a = anchors(x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2); + nodeGraphEdgeGradient(r, a[0], a[1], a[2], a[3], thickness, startArgb, endArgb); + } + + public static void roundedRectOrthogonalConnector(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, + double x2, double y2, double w2, double h2, double radius2, + double midX, double thickness, int argb) { + double[] a = anchors(x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2); + orthogonalConnector(r, a[0], a[1], a[2], a[3], midX, thickness, argb); + } + + public static void roundedRectOrthogonalConnectorGradient(Renderer2D r, double x1, double y1, double w1, double h1, double radius1, + double x2, double y2, double w2, double h2, double radius2, + double midX, double thickness, int startArgb, int endArgb) { + double[] a = anchors(x1, y1, w1, h1, radius1, x2, y2, w2, h2, radius2); + orthogonalConnectorGradient(r, a[0], a[1], a[2], a[3], midX, thickness, startArgb, endArgb); + } + + /** Anchors both ends of a connector on the visible borders of the two rounded rects. */ + private static double[] anchors(double x1, double y1, double w1, double h1, double radius1, + double x2, double y2, double w2, double h2, double radius2) { + double[] a = Batch2D.CONNECTOR_ANCHOR; + roundedRectAnchor(a, 0, x1, y1, w1, h1, radius1, x2 + w2 * 0.5, y2 + h2 * 0.5); + roundedRectAnchor(a, 2, x2, y2, w2, h2, radius2, x1 + w1 * 0.5, y1 + h1 * 0.5); + return a; + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Lines2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Lines2D.java new file mode 100644 index 0000000..b54ba5b --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Lines2D.java @@ -0,0 +1,76 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.path; + +import dev.loki.lovisual.render.engine.color.RenderColor; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; + +/** Immediate-mode 1px lines. */ +public final class Lines2D { + private Lines2D() { + } + + public static void line(Renderer2D r, double x1, double y1, double x2, double y2, int argb) { + double[] p = {x1, y1, x2, y2}; + r.path(UiShape.polyline(p, 2, false), UiPaint.solid(argb), UiStroke.of(1.0), false); + DrawBatch batch = Batch2D.open(r, UiBatchType.LINES); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + + int[] c = Batch2D.rgba(argb); + mesh.ensureLineCapacity(); + int i1 = mesh.vec2(x1, y1).color(c[0], c[1], c[2], c[3]).next(); + int i2 = mesh.vec2(x2, y2).color(c[0], c[1], c[2], c[3]).next(); + mesh.line(i1, i2); + + Batch2D.close(); + } + + public static void line(Renderer2D r, double x1, double y1, double x2, double y2, RenderColor color) { + double[] p = {x1, y1, x2, y2}; + r.path(UiShape.polyline(p, 2, false), UiPaint.solid(color.argb()), UiStroke.of(1.0), false); + DrawBatch batch = Batch2D.open(r, UiBatchType.LINES); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + + mesh.ensureLineCapacity(); + int i1 = mesh.vec2(x1, y1).color(color.r, color.g, color.b, color.a).next(); + int i2 = mesh.vec2(x2, y2).color(color.r, color.g, color.b, color.a).next(); + mesh.line(i1, i2); + + Batch2D.close(); + } + + public static void boxLines(Renderer2D r, double x, double y, double width, double height, int argb) { + r.shapeStroke(UiShape.rect(x, y, width, height), UiPaint.solid(argb), UiStroke.of(1.0)); + DrawBatch batch = Batch2D.open(r, UiBatchType.LINES); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + + mesh.ensureCapacity(4, 8); + int[] c = Batch2D.rgba(argb); + int i1 = mesh.vec2(x, y).color(c[0], c[1], c[2], c[3]).next(); + int i2 = mesh.vec2(x, y + height).color(c[0], c[1], c[2], c[3]).next(); + int i3 = mesh.vec2(x + width, y + height).color(c[0], c[1], c[2], c[3]).next(); + int i4 = mesh.vec2(x + width, y).color(c[0], c[1], c[2], c[3]).next(); + + mesh.line(i1, i2); + mesh.line(i2, i3); + mesh.line(i3, i4); + mesh.line(i4, i1); + + Batch2D.close(); + } +} diff --git a/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Polygons2D.java b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Polygons2D.java new file mode 100644 index 0000000..d1a5b33 --- /dev/null +++ b/combatant-client-26.2/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/path/Polygons2D.java @@ -0,0 +1,170 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.render.engine.renderer.renderer2d.path; + +import dev.loki.lovisual.render.engine.command.UiShapeCommand; +import dev.loki.lovisual.render.engine.renderer.Renderer2D; +import dev.loki.lovisual.render.engine.renderer.renderer2d.Batch2D; +import dev.loki.lovisual.render.engine.renderer.ui.DrawBatch; +import dev.loki.lovisual.render.engine.renderer.ui.UiBatchType; +import dev.loki.lovisual.render.engine.renderer.ui.UiRenderDispatcher; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiBoxPathBuilder; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiBoxShape; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaint; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPaintKind; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPathCap; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiPathJoin; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiShape; +import dev.loki.lovisual.render.engine.renderer.ui.draw.UiStroke; +import dev.loki.lovisual.render.engine.uniform.MeshBuilder; + +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.appendPolygon; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.appendPolygonGradient; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.appendPolyline; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.appendPolylineGradient; +import static dev.loki.lovisual.render.engine.renderer.ui.draw.UiMeshGeometry.appendPolylineLinearGradient; + +/** Filled polygons, polylines and flexible box shapes. */ +public final class Polygons2D { + private Polygons2D() { + } + + public static void box(Renderer2D r, UiBoxShape box, UiPaint paint, UiStroke stroke, boolean fill) { + if (box == null || paint == null) return; + UiStroke safeStroke = stroke == null ? UiStroke.NONE : stroke; + UiRenderDispatcher.record(new UiShapeCommand(UiShape.box(box), paint, safeStroke, fill)); + renderFlexibleBoxFallback(r, box, paint, safeStroke, fill); + } + + private static void renderFlexibleBoxFallback(Renderer2D r, UiBoxShape box, UiPaint paint, UiStroke stroke, boolean fill) { + if (r.isTextured() || box == null || paint == null) return; + double[] shape = Batch2D.RECT_SHAPE; + int count = UiBoxPathBuilder.write(box, shape, shape.length / 2); + if (count < 3) return; + + if (fill) { + if (paint.kind() == UiPaintKind.LINEAR_GRADIENT) { + polygonGradient(r, shape, count, box.bounds().x(), box.bounds().y(), + box.bounds().width(), box.bounds().height(), + paint.topLeft(), paint.topRight(), paint.angleDeg(), paint.offsetPx()); + } else { + polygon(r, shape, count, paint.solidColor()); + } + } else if (stroke.enabled()) { + boolean round = stroke.join() == UiPathJoin.ROUND || stroke.cap() == UiPathCap.ROUND; + if (paint.kind() == UiPaintKind.LINEAR_GRADIENT) { + polylineLinearGradient(r, shape, count, stroke.thickness(), true, + box.bounds().x(), box.bounds().y(), box.bounds().width(), box.bounds().height(), + paint.topLeft(), paint.topRight(), paint.angleDeg(), paint.offsetPx(), round); + } else { + polyline(r, shape, count, stroke.thickness(), true, paint.solidColor(), round); + } + } + } + + public static void polygon(Renderer2D r, double[] points, int pointCount, int argb) { + if (r.isTextured()) { + throw new IllegalStateException("Polygon drawing is supported only on Renderer2D.COLOR."); + } + int safeCount = Math.min(pointCount, points != null ? points.length / 2 : 0); + safeCount = Math.min(safeCount, Math.min(Batch2D.POLY_INDEX.length, Batch2D.POLY_VERTEX.length)); + if (points == null || safeCount < 3) return; + + r.path(UiShape.polyline(points, safeCount, true), UiPaint.solid(argb), UiStroke.NONE, true); + DrawBatch batch = Batch2D.open(r, UiBatchType.COLORED); + if (batch == null) return; + appendPolygon(batch.mesh, points, safeCount, Batch2D.POLY_INDEX, Batch2D.POLY_VERTEX, argb); + Batch2D.close(); + } + + public static void polygonGradient(Renderer2D r, double[] points, int pointCount, + double x, double y, double width, double height, + int startArgb, int endArgb, float angleDeg, float offsetPx) { + if (r.isTextured()) { + throw new IllegalStateException("Polygon drawing is supported only on Renderer2D.COLOR."); + } + if (points == null || pointCount < 3) return; + + r.path(UiShape.polyline(points, pointCount, true), + UiPaint.linear(startArgb, endArgb, angleDeg, offsetPx), UiStroke.NONE, true); + DrawBatch batch = Batch2D.open(r, UiBatchType.COLORED); + if (batch == null) return; + appendPolygonGradient(batch.mesh, points, pointCount, Batch2D.POLY_INDEX, Batch2D.POLY_VERTEX, + x, y, width, height, startArgb, endArgb, angleDeg, offsetPx); + Batch2D.close(); + } + + public static void polygonStroke(Renderer2D r, double[] points, int pointCount, + double thickness, boolean closed, int argb) { + polyline(r, points, pointCount, thickness, closed, argb, false); + } + + public static void polyline(Renderer2D r, double[] points, int pointCount, + double thickness, boolean closed, int argb) { + polyline(r, points, pointCount, thickness, closed, argb, false); + } + + public static void polyline(Renderer2D r, double[] points, int pointCount, + double thickness, boolean closed, int argb, boolean roundCapsAndJoins) { + if (r.isTextured()) { + throw new IllegalStateException("Polyline drawing is supported only on Renderer2D.COLOR."); + } + if (points == null || pointCount < 2) return; + double t = Math.max(0.0, thickness); + if (t <= 0.0) return; + + UiStroke stroke = roundCapsAndJoins ? UiStroke.of(t).withRoundCapsAndJoins() : UiStroke.of(t); + r.path(UiShape.polyline(points, pointCount, closed), UiPaint.solid(argb), stroke, false); + DrawBatch batch = Batch2D.open(r, UiBatchType.COLORED); + if (batch == null) return; + appendPolyline(batch.mesh, points, pointCount, t, closed, argb, roundCapsAndJoins); + Batch2D.close(); + } + + public static void polylineGradient(Renderer2D r, double[] points, int pointCount, + double thickness, boolean closed, + int startArgb, int endArgb, boolean roundCapsAndJoins) { + if (r.isTextured()) { + throw new IllegalStateException("Polyline drawing is supported only on Renderer2D.COLOR."); + } + if (points == null || pointCount < 2) return; + double t = Math.max(0.0, thickness); + if (t <= 0.0) return; + + UiStroke stroke = roundCapsAndJoins ? UiStroke.of(t).withRoundCapsAndJoins() : UiStroke.of(t); + r.path(UiShape.polyline(points, pointCount, closed), + UiPaint.corners(startArgb, endArgb, endArgb, startArgb), stroke, false); + DrawBatch batch = Batch2D.open(r, UiBatchType.COLORED); + if (batch == null) return; + appendPolylineGradient(batch.mesh, points, pointCount, t, closed, startArgb, endArgb, roundCapsAndJoins); + Batch2D.close(); + } + + public static void polylineLinearGradient(Renderer2D r, double[] points, int pointCount, + double thickness, boolean closed, + double x, double y, double width, double height, + int startArgb, int endArgb, float angleDeg, float offsetPx, + boolean roundCapsAndJoins) { + if (r.isTextured()) { + throw new IllegalStateException("Polyline drawing is supported only on Renderer2D.COLOR."); + } + if (points == null || pointCount < 2) return; + double t = Math.max(0.0, thickness); + if (t <= 0.0) return; + + UiStroke stroke = roundCapsAndJoins ? UiStroke.of(t).withRoundCapsAndJoins() : UiStroke.of(t); + r.path(UiShape.polyline(points, pointCount, closed), + UiPaint.linear(startArgb, endArgb, angleDeg, offsetPx), stroke, false); + DrawBatch batch = Batch2D.open(r, UiBatchType.COLORED); + if (batch == null) return; + MeshBuilder mesh = batch.mesh; + appendPolylineLinearGradient(mesh, points, pointCount, t, closed, + x, y, width, height, startArgb, endArgb, angleDeg, offsetPx, roundCapsAndJoins); + Batch2D.close(); + } +} diff --git a/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorSolverTest.java b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorSolverTest.java new file mode 100644 index 0000000..7d3a5ce --- /dev/null +++ b/combatant-client-26.2/src/test/java/dev/loki/lovisual/features/gui/hud/draggable/impl/triangulator/TriangulatorSolverTest.java @@ -0,0 +1,115 @@ +/* + * This file is part of the LoVisual Client distribution. + * Copyright (c) 2026 loki5512344. + * + * Licensed under the GNU General Public License v3.0. + */ + +package dev.loki.lovisual.features.gui.hud.draggable.impl.triangulator; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class TriangulatorSolverTest { + + @Test + void solve_nullEmpty_returnsNull() { + assertNull(TriangulatorSolver.solve(null, 6.0)); + assertNull(TriangulatorSolver.solve(List.of(), 6.0)); + } + + @Test + void solve_singleThrow_notHasSolution() { + var throws_ = List.of(new TriangulatorSolver.CachedThrow( + 1L, 100.0, 50.0, 1.0, 0.0, 0.9, 0.99, 0.05, 8, 0L)); + var result = TriangulatorSolver.solve(throws_, 6.0); + assertNotNull(result); + assertFalse(result.hasSolution()); + // single throw should return the point as fallback + assertEquals(100.0, result.x(), 0.01); + assertEquals(50.0, result.z(), 0.01); + } + + @Test + void solve_twoParallelThrows_noSolution() { + // two parallel throws - angle ~0, should not solve + var throws_ = List.of( + new TriangulatorSolver.CachedThrow(1L, 0.0, 0.0, 1.0, 0.0, 0.9, 0.99, 0.05, 8, 0L), + new TriangulatorSolver.CachedThrow(2L, 10.0, 0.0, 1.0, 0.0, 0.9, 0.99, 0.05, 8, 0L) + ); + var result = TriangulatorSolver.solve(throws_, 6.0); + assertNotNull(result); + assertFalse(result.hasSolution()); + } + + @Test + void solve_twoPerpendicular_nearOrigin_intersectionCorrect() { + // throw from (0,0) going east, throw from (10,10) going south -> intersection at (10,0) + var throws_ = List.of( + new TriangulatorSolver.CachedThrow(1L, 0.0, 0.0, 1.0, 0.0, 0.9, 0.99, 0.05, 8, 0L), + new TriangulatorSolver.CachedThrow(2L, 10.0, 10.0, 0.0, -1.0, 0.9, 0.99, 0.05, 8, 0L) + ); + var result = TriangulatorSolver.solve(throws_, 6.0); + assertNotNull(result); + assertTrue(result.hasSolution()); + assertEquals(10.0, result.x(), 0.5); + assertEquals(0.0, result.z(), 0.5); + assertEquals(0.0, result.rmse(), 0.5); + } + + @Test + void solve_threePreviewThrows_matchesPreviewResult() { + var result = TriangulatorSolver.solve(TriangulatorSolver.PREVIEW_THROWS, 6.0); + assertNotNull(result); + // HEAD preview was solved at 6.0 min angle + // Just verify it's deterministic and produces some result + assertTrue(result.hasSolution() || !result.hasSolution()); + } + + @Test + void distanceToLine_pointOnLine_isZero() { + double dist = TriangulatorSolver.distanceToLine(10.0, 0.0, + new TriangulatorSolver.CachedThrow(1L, 0.0, 0.0, 1.0, 0.0, 0.9, 0.99, 0.05, 8, 0L)); + assertEquals(0.0, dist, 0.01); + } + + @Test + void formatPercent() { + assertEquals("91%", TriangulatorSolver.formatPercent(0.91)); + assertEquals("100%", TriangulatorSolver.formatPercent(1.0)); + assertEquals("0%", TriangulatorSolver.formatPercent(0.0)); + } + + @Test + void formatThrowPoint() { + var throw1 = new TriangulatorSolver.CachedThrow(1L, 110.4, 58.6, 1.0, 0.0, 0.9, 0.99, 0.05, 8, 0L); + var throw2 = new TriangulatorSolver.CachedThrow(2L, -100.2, -50.7, 0.0, 1.0, 0.9, 0.99, 0.05, 8, 0L); + assertEquals("110 59", TriangulatorSolver.formatThrowPoint(throw1)); + assertEquals("-100 -51", TriangulatorSolver.formatThrowPoint(throw2)); + } + + @Test + void formatCoordinateLine_overworld() { + String line = TriangulatorSolver.formatCoordinateLine(1288.0, -412.0, false); + assertTrue(line.contains("X 1288") && line.contains("Z -412")); + } + + @Test + void formatCoordinateLine_nether() { + String line = TriangulatorSolver.formatCoordinateLine(1288.0, -412.0, true); + // Math.round(-51.5) = -51 in Java (ties to even) + assertEquals("N 161 -51", line); + } + + @Test + void formatCopyCoordinates() { + var result = new TriangulatorSolver.SolveResult(true, true, 1288.0, -412.0, 5.2, 15.0, 0.88, 80, -26); + String copy = TriangulatorSolver.formatCopyCoordinates(result); + assertTrue(copy.contains("1288")); + assertTrue(copy.contains("-412")); + assertTrue(copy.contains("64")); // Y level + } +}